[Erp5-report] r45223 luke - in /slapos/trunk/util/slapos.tool.proxy: ./ src/ src/slapos/ sr...
nobody at svn.erp5.org
nobody at svn.erp5.org
Fri Apr 8 11:28:44 CEST 2011
Author: luke
Date: Fri Apr 8 11:28:44 2011
New Revision: 45223
URL: http://svn.erp5.org?rev=45223&view=rev
Log:
- minimialistic SlapOS master implementation
Added:
slapos/trunk/util/slapos.tool.proxy/
slapos/trunk/util/slapos.tool.proxy/CHANGES.txt
slapos/trunk/util/slapos.tool.proxy/MANIFEST.in
slapos/trunk/util/slapos.tool.proxy/README.txt
slapos/trunk/util/slapos.tool.proxy/setup.cfg
slapos/trunk/util/slapos.tool.proxy/setup.py
slapos/trunk/util/slapos.tool.proxy/src/
slapos/trunk/util/slapos.tool.proxy/src/slapos/
slapos/trunk/util/slapos.tool.proxy/src/slapos/__init__.py
slapos/trunk/util/slapos.tool.proxy/src/slapos/tool/
slapos/trunk/util/slapos.tool.proxy/src/slapos/tool/__init__.py
slapos/trunk/util/slapos.tool.proxy/src/slapos/tool/proxy/
slapos/trunk/util/slapos.tool.proxy/src/slapos/tool/proxy/__init__.py
slapos/trunk/util/slapos.tool.proxy/src/slapos/tool/proxy/schema.sql
slapos/trunk/util/slapos.tool.proxy/src/slapos/tool/proxy/static/
slapos/trunk/util/slapos.tool.proxy/src/slapos/tool/proxy/templates/
slapos/trunk/util/slapos.tool.proxy/src/slapos/tool/proxy/views.py
Added: slapos/trunk/util/slapos.tool.proxy/CHANGES.txt
URL: http://svn.erp5.org/slapos/trunk/util/slapos.tool.proxy/CHANGES.txt?rev=45223&view=auto
==============================================================================
--- slapos/trunk/util/slapos.tool.proxy/CHANGES.txt (added)
+++ slapos/trunk/util/slapos.tool.proxy/CHANGES.txt [utf8] Fri Apr 8 11:28:44 2011
@@ -0,0 +1,2 @@
+1.0 (unreleased)
+----------------
Added: slapos/trunk/util/slapos.tool.proxy/MANIFEST.in
URL: http://svn.erp5.org/slapos/trunk/util/slapos.tool.proxy/MANIFEST.in?rev=45223&view=auto
==============================================================================
--- slapos/trunk/util/slapos.tool.proxy/MANIFEST.in (added)
+++ slapos/trunk/util/slapos.tool.proxy/MANIFEST.in [utf8] Fri Apr 8 11:28:44 2011
@@ -0,0 +1 @@
+include CHANGES.txt
Added: slapos/trunk/util/slapos.tool.proxy/README.txt
URL: http://svn.erp5.org/slapos/trunk/util/slapos.tool.proxy/README.txt?rev=45223&view=auto
==============================================================================
--- slapos/trunk/util/slapos.tool.proxy/README.txt (added)
+++ slapos/trunk/util/slapos.tool.proxy/README.txt [utf8] Fri Apr 8 11:28:44 2011
@@ -0,0 +1,3 @@
+slapproxy
+=========
+
Added: slapos/trunk/util/slapos.tool.proxy/setup.cfg
URL: http://svn.erp5.org/slapos/trunk/util/slapos.tool.proxy/setup.cfg?rev=45223&view=auto
==============================================================================
--- slapos/trunk/util/slapos.tool.proxy/setup.cfg (added)
+++ slapos/trunk/util/slapos.tool.proxy/setup.cfg [utf8] Fri Apr 8 11:28:44 2011
@@ -0,0 +1,3 @@
+[egg_info]
+tag_build = .dev
+tag_svn_revision = 1
Added: slapos/trunk/util/slapos.tool.proxy/setup.py
URL: http://svn.erp5.org/slapos/trunk/util/slapos.tool.proxy/setup.py?rev=45223&view=auto
==============================================================================
--- slapos/trunk/util/slapos.tool.proxy/setup.py (added)
+++ slapos/trunk/util/slapos.tool.proxy/setup.py [utf8] Fri Apr 8 11:28:44 2011
@@ -0,0 +1,41 @@
+from setuptools import setup, find_packages
+import os
+
+name = "slapos.tool.proxy"
+version = '1.0'
+
+def read(*rnames):
+ return open(os.path.join(os.path.dirname(__file__), *rnames)).read()
+
+long_description=(
+ read('README.txt')
+ + '\n' +
+ read('CHANGES.txt')
+ )
+
+setup(
+ name = name,
+ version = version,
+ description = "slapproxy - the slapos master proxy",
+ long_description=long_description,
+ license = "GPLv3",
+ keywords = "vifib proxy slap",
+ classifiers=[
+ ],
+ packages = find_packages('src'),
+ include_package_data = True,
+ package_dir = {'':'src'},
+ namespace_packages = ['slapos', 'slapos.tool'],
+ install_requires = [
+ 'Flask', # used to create this
+ 'lxml', # needed to play with XML trees
+ 'setuptools', # namespaces
+ 'slapos.slap', # slapgrid uses slap to communicate with vifib
+ 'xml_marshaller', # to unmarshall/marshall python objects to/from XML
+ ],
+ zip_safe=False,
+ entry_points = """
+ [console_scripts]
+ slapproxy = %(name)s:main
+ """ % dict(name=name),
+ )
Added: slapos/trunk/util/slapos.tool.proxy/src/slapos/__init__.py
URL: http://svn.erp5.org/slapos/trunk/util/slapos.tool.proxy/src/slapos/__init__.py?rev=45223&view=auto
==============================================================================
--- slapos/trunk/util/slapos.tool.proxy/src/slapos/__init__.py (added)
+++ slapos/trunk/util/slapos.tool.proxy/src/slapos/__init__.py [utf8] Fri Apr 8 11:28:44 2011
@@ -0,0 +1,7 @@
+# See http://peak.telecommunity.com/DevCenter/setuptools#namespace-packages
+try:
+ __import__('pkg_resources').declare_namespace(__name__)
+except ImportError:
+ from pkgutil import extend_path
+ __path__ = extend_path(__path__, __name__)
+
Added: slapos/trunk/util/slapos.tool.proxy/src/slapos/tool/__init__.py
URL: http://svn.erp5.org/slapos/trunk/util/slapos.tool.proxy/src/slapos/tool/__init__.py?rev=45223&view=auto
==============================================================================
--- slapos/trunk/util/slapos.tool.proxy/src/slapos/tool/__init__.py (added)
+++ slapos/trunk/util/slapos.tool.proxy/src/slapos/tool/__init__.py [utf8] Fri Apr 8 11:28:44 2011
@@ -0,0 +1,7 @@
+# See http://peak.telecommunity.com/DevCenter/setuptools#namespace-packages
+try:
+ __import__('pkg_resources').declare_namespace(__name__)
+except ImportError:
+ from pkgutil import extend_path
+ __path__ = extend_path(__path__, __name__)
+
Added: slapos/trunk/util/slapos.tool.proxy/src/slapos/tool/proxy/__init__.py
URL: http://svn.erp5.org/slapos/trunk/util/slapos.tool.proxy/src/slapos/tool/proxy/__init__.py?rev=45223&view=auto
==============================================================================
--- slapos/trunk/util/slapos.tool.proxy/src/slapos/tool/proxy/__init__.py (added)
+++ slapos/trunk/util/slapos.tool.proxy/src/slapos/tool/proxy/__init__.py [utf8] Fri Apr 8 11:28:44 2011
@@ -0,0 +1,110 @@
+import os
+import sys
+from optparse import OptionParser, Option
+import logging
+import logging.handlers
+import ConfigParser
+
+
+class Parser(OptionParser):
+ """
+ Parse all arguments.
+ """
+ def __init__(self, usage=None, version=None):
+ """
+ Initialize all options possibles.
+ """
+ OptionParser.__init__(self, usage=usage, version=version,
+ option_list=[
+ Option("-l", "--log_file",
+ help="The path to the log file used by the script.",
+ type=str),
+ Option("-v", "--verbose",
+ default=False,
+ action="store_true",
+ help="Verbose output."),
+ Option("-c", "--console",
+ default=False,
+ action="store_true",
+ help="Console output."),
+ Option("-u", "--database-uri",
+ type=str,
+ help="URI for sqlite database"),
+ ])
+
+ def check_args(self):
+ """
+ Check arguments
+ """
+ (options, args) = self.parse_args()
+ if len(args) != 1:
+ self.error("Incorrect number of arguments")
+
+ return options, args[0]
+
+class Config:
+ def setConfig(self, option_dict, configuration_file_path):
+ """
+ Set options given by parameters.
+ """
+ # Set options parameters
+ for option, value in option_dict.__dict__.items():
+ setattr(self, option, value)
+
+ # Load configuration file
+ configuration_parser = ConfigParser.SafeConfigParser()
+ configuration_parser.read(configuration_file_path)
+ # Merges the arguments and configuration
+ for section in ("slapproxy", "slapos"):
+ configuration_dict = dict(configuration_parser.items(section))
+ for key in configuration_dict:
+ if not getattr(self, key, None):
+ setattr(self, key, configuration_dict[key])
+
+ # set up logging
+ self.logger = logging.getLogger("slapproxy")
+ self.logger.setLevel(logging.INFO)
+ if self.console:
+ self.logger.addHandler(logging.StreamHandler())
+
+ if not self.database_uri:
+ raise ValueError('database-uri is required.')
+ if self.log_file:
+ if not os.path.isdir(os.path.dirname(self.log_file)):
+ # fallback to console only if directory for logs does not exists and
+ # continue to run
+ raise ValueError('Please create directory %r to store %r log file' % (
+ os.path.dirname(self.log_file), self.log_file))
+ else:
+ file_handler = logging.FileHandler(self.log_file)
+ file_handler.setFormatter(logging.Formatter("%(asctime)s - %(name)s - %(levelname)s - %(message)s"))
+ self.logger.addHandler(file_handler)
+ self.logger.info('Configured logging to file %r' % self.log_file)
+
+ self.logger.info("Started.")
+ if self.verbose:
+ self.logger.setLevel(logging.DEBUG)
+ self.logger.debug("Verbose mode enabled.")
+
+def run(config):
+ from views import app
+ app.config['computer_id'] = config.computer_id
+ app.config['DATABASE_URI'] = config.database_uri
+ app.run(host=config.host, port=int(config.port), debug=True)
+
+def main():
+ "Run default configuration."
+ usage = "usage: %s [options] CONFIGURATION_FILE" % sys.argv[0]
+
+ try:
+ # Parse arguments
+ config = Config()
+ config.setConfig(*Parser(usage=usage).check_args())
+
+ run(config)
+ return_code = 0
+ except SystemExit, err:
+ # Catch exception raise by optparse
+ return_code = err
+
+ sys.exit(return_code)
Added: slapos/trunk/util/slapos.tool.proxy/src/slapos/tool/proxy/schema.sql
URL: http://svn.erp5.org/slapos/trunk/util/slapos.tool.proxy/src/slapos/tool/proxy/schema.sql?rev=45223&view=auto
==============================================================================
--- slapos/trunk/util/slapos.tool.proxy/src/slapos/tool/proxy/schema.sql (added)
+++ slapos/trunk/util/slapos.tool.proxy/src/slapos/tool/proxy/schema.sql [utf8] Fri Apr 8 11:28:44 2011
@@ -0,0 +1,24 @@
+--version:7
+CREATE TABLE IF NOT EXISTS software%(version)s (url VARCHAR(255) UNIQUE);
+CREATE TABLE IF NOT EXISTS computer%(version)s (
+ address VARCHAR(255),
+ netmask VARCHAR(255),
+ CONSTRAINT uniq PRIMARY KEY (address, netmask));
+
+CREATE TABLE IF NOT EXISTS partition%(version)s (
+ reference VARCHAR(255) UNIQUE,
+ slap_state VARCHAR(255) DEFAULT 'free',
+ software_release VARCHAR(255),
+ xml TEXT,
+ connection_xml TEXT,
+ software_type VARCHAR(255),
+ partition_reference VARCHAR(255),
+ requested_by VARCHAR(255)
+);
+
+CREATE TABLE IF NOT EXISTS partition_network%(version)s (
+ partition_reference VARCHAR(255),
+ reference VARCHAR(255),
+ address VARCHAR(255),
+ netmask VARCHAR(255)
+);
Added: slapos/trunk/util/slapos.tool.proxy/src/slapos/tool/proxy/views.py
URL: http://svn.erp5.org/slapos/trunk/util/slapos.tool.proxy/src/slapos/tool/proxy/views.py?rev=45223&view=auto
==============================================================================
--- slapos/trunk/util/slapos.tool.proxy/src/slapos/tool/proxy/views.py (added)
+++ slapos/trunk/util/slapos.tool.proxy/src/slapos/tool/proxy/views.py [utf8] Fri Apr 8 11:28:44 2011
@@ -0,0 +1,302 @@
+from flask import g, Flask, request, abort
+import xml_marshaller
+from lxml import etree
+from slapos.slap.slap import Computer, ComputerPartition, SoftwareRelease, SoftwareInstance
+import sqlite3
+
+app = Flask(__name__)
+DB_VERSION = app.open_resource('schema.sql').readline().strip().split(':')[1]
+
+class UnauthorizedError(Exception):
+ pass
+
+def xml2dict(xml):
+ result_dict = {}
+ if xml is not None and xml != '':
+ tree = etree.fromstring(xml.encode('utf-8'))
+ for element in tree.iter(tag=etree.Element):
+ if element.tag == 'parameter':
+ key = element.get('id')
+ value = result_dict.get(key, None)
+ if value is not None:
+ value = value + ' ' + element.text
+ else:
+ value = element.text
+ result_dict[key] = value
+ return result_dict
+
+def dict2xml(dictionnary):
+ instance = etree.Element('instance')
+ for parameter_id, parameter_value in dictionnary.iteritems():
+ # cast everything to string
+ parameter_value = str(parameter_value)
+ etree.SubElement(instance, "parameter",
+ attrib={'id':parameter_id}).text = parameter_value
+ return etree.tostring(instance, pretty_print=True,
+ xml_declaration=True, encoding='utf-8')
+
+def partitiondict2partition(partition):
+ slap_partition = ComputerPartition(app.config['computer_id'],
+ partition['reference'])
+ slap_partition._requested_state = 'started'
+ if partition['software_release']:
+ slap_partition._need_modification = 1
+ else:
+ slap_partition._need_modification = 0
+ slap_partition._parameter_dict = xml2dict(partition['xml'])
+ address_list = []
+ for address in execute_db('partition_network', 'SELECT * FROM %s WHERE partition_reference=?', [partition['reference']]):
+ address_list.append((address['reference'], address['address']))
+ slap_partition._parameter_dict['ip_list'] = address_list
+ slap_partition._parameter_dict['slap_software_type'] = partition['software_type']
+ slap_partition._connection_dict = xml2dict(partition['connection_xml'])
+ slap_partition._software_release_document = SoftwareRelease(
+ software_release=partition['software_release'],
+ computer_guid=app.config['computer_id'])
+ return slap_partition
+
+def execute_db(table, query, args=(), one=False):
+ try:
+ cur = g.db.execute(query % (table + DB_VERSION,), args)
+ except:
+ app.logger.error('There was some issue during processing query %r on table %r with args %r' % (query, table, args))
+ raise
+
+ rv = [dict((cur.description[idx][0], value)
+ for idx, value in enumerate(row)) for row in cur.fetchall()]
+ return (rv[0] if rv else None) if one else rv
+
+def connect_db():
+ return sqlite3.connect(app.config['DATABASE_URI'])
+
+ at app.before_request
+def before_request():
+ g.db = connect_db()
+ schema = app.open_resource('schema.sql')
+ schema = schema.read() % dict(version = DB_VERSION)
+ g.db.cursor().executescript(schema)
+ g.db.commit()
+
+ at app.after_request
+def after_request(response):
+ g.db.commit()
+ g.db.close()
+ return response
+
+ at app.route('/getComputerInformation', methods=['GET'])
+def getComputerInformation():
+ computer_id = request.args['computer_id']
+ if app.config['computer_id'] == computer_id:
+ slap_computer = Computer(computer_id)
+ slap_computer._software_release_list = []
+ for sr in execute_db('software', 'select * from %s'):
+ slap_computer._software_release_list.append(SoftwareRelease(
+ software_release=sr['url'], computer_guid=computer_id))
+ slap_computer._computer_partition_list = []
+ for partition in execute_db('partition', 'SELECT * FROM %s'):
+ slap_computer._computer_partition_list.append(partitiondict2partition(
+ partition))
+ return xml_marshaller.xml_marshaller.dumps(slap_computer)
+ else:
+ raise UnauthorizedError, "Only accept request for: %s" % \
+ app.config['computer_id']
+
+ at app.route('/setComputerPartitionConnectionXml', methods=['POST'])
+def setComputerPartitionConnectionXml():
+ computer_id = request.form['computer_id']
+ computer_partition_id = request.form['computer_partition_id']
+ connection_xml = request.form['connection_xml']
+ connection_dict = xml_marshaller.xml_marshaller.loads(
+ connection_xml.encode())
+ connection_xml = dict2xml(connection_dict)
+ query = 'UPDATE %s SET connection_xml=? WHERE reference=?'
+ argument_list = [connection_xml, computer_partition_id.encode()]
+ execute_db('partition', query, argument_list)
+ return 'done'
+
+ at app.route('/buildingSoftwareRelease', methods=['POST'])
+def buildingSoftwareRelease():
+ return 'Ignored'
+
+ at app.route('/availableSoftwareRelease', methods=['POST'])
+def availableSoftwareRelease():
+ computer_id = request.form['computer_id']
+ url = request.form['url']
+ return 'Ignored'
+
+ at app.route('/softwareReleaseError', methods=['POST'])
+def softwareReleaseError():
+ return 'Ignored'
+
+ at app.route('/buildingComputerPartition', methods=['POST'])
+def buildingComputerPartition():
+ computer_id = request.form['computer_id']
+ computer_partition_id = request.form['computer_partition_id']
+ return 'Ignored'
+
+ at app.route('/availableComputerPartition', methods=['POST'])
+def availableComputerPartition():
+ computer_id = request.form['computer_id']
+ computer_partition_id = request.form['computer_partition_id']
+ return 'Ignored'
+
+ at app.route('/softwareInstanceError', methods=['POST'])
+def softwareInstanceError():
+ computer_id = request.form['computer_id']
+ computer_partition_id = request.form['computer_partition_id']
+ error_log = request.form['error_log']
+ return 'Ignored'
+
+ at app.route('/startedComputerPartition', methods=['POST'])
+def startedComputerPartition():
+ computer_id = request.form['computer_id']
+ computer_partition_id = request.form['computer_partition_id']
+ return 'Ignored'
+
+ at app.route('/stoppedComputerPartition', methods=['POST'])
+def stoppedComputerPartition():
+ computer_id = request.form['computer_id']
+ computer_partition_id = request.form['computer_partition_id']
+ return 'Ignored'
+
+ at app.route('/destroyedComputerPartition', methods=['POST'])
+def destroyedComputerPartition():
+ computer_id = request.form['computer_id']
+ computer_partition_id = request.form['computer_partition_id']
+ return 'Ignored'
+
+ at app.route('/requestComputerPartition', methods=['POST'])
+def requestComputerPartition():
+ software_release = request.form['software_release'].encode()
+ # some supported parameters
+ software_type = request.form.get('software_type', 'RootSoftwareInstance'
+ ).encode()
+ partition_reference = request.form.get('partition_reference', '').encode()
+ partition_id = request.form.get('computer_partition_id', '').encode()
+ partition_parameter_kw = request.form.get('partition_parameter_xml', None)
+ if partition_parameter_kw:
+ partition_parameter_kw = xml_marshaller.xml_marshaller.loads(
+ partition_parameter_kw.encode())
+ else:
+ partition_parameter_kw = {}
+ instance_xml = dict2xml(partition_parameter_kw)
+ args = []
+ a = args.append
+ q = 'SELECT * FROM %s WHERE software_release=?'
+ a(software_release)
+ if software_type:
+ q += ' AND software_type=?'
+ a(software_type)
+ if partition_reference:
+ q += ' AND partition_reference=?'
+ a(partition_reference)
+ if partition_id:
+ q += ' AND requested_by=?'
+ a(partition_id)
+ partition = execute_db('partition', q, args, one=True)
+ if partition is None:
+ partition = execute_db('partition',
+ 'SELECT * FROM %s WHERE slap_state="free"', (), one=True)
+ if partition is None:
+ app.logger.warning('No more free computer partition')
+ abort(408)
+ args = []
+ a = args.append
+ q = 'UPDATE %s SET software_release=?, slap_state="busy"'
+ a(software_release)
+ if software_type:
+ q += ' ,software_type=?'
+ a(software_type)
+ if partition_reference:
+ q += ' ,partition_reference=?'
+ a(partition_reference)
+ if partition_id:
+ q += ' ,requested_by=?'
+ a(partition_id)
+ if instance_xml:
+ q+= ' ,xml=?'
+ a(instance_xml)
+ q += ' WHERE reference=?'
+ a(partition['reference'].encode())
+ execute_db('partition', q, args)
+ args = []
+ partition = execute_db('partition', 'SELECT * FROM %s WHERE reference=?',
+ [partition['reference'].encode()], one=True)
+ address_list = []
+ for address in execute_db('partition_network', 'SELECT * FROM %s WHERE partition_reference=?', [partition['reference']]):
+ address_list.append((address['reference'], address['address']))
+ return xml_marshaller.xml_marshaller.dumps(SoftwareInstance(**dict(
+ xml=partition['xml'],
+ connection_xml=partition['connection_xml'],
+ slap_computer_id=app.config['computer_id'],
+ slap_computer_partition_id=partition['reference'],
+ slap_software_release_url=partition['software_release'],
+ slap_server_url='slap_server_url',
+ slap_software_type=partition['software_type'],
+ slave_id_list=[],
+ ip_list=address_list
+ )))
+ abort(408)
+ computer_id = request.form.get('computer_id')
+ computer_partition_id = request.form.get('computer_partition_id')
+ software_type = request.form.get('software_type')
+ partition_reference = request.form.get('partition_reference')
+ shared_xml = request.form.get('shared_xml')
+ partition_parameter_xml = request.form.get('partition_parameter_xml')
+ filter_xml = request.form.get('filter_xml')
+ raise NotImplementedError
+
+ at app.route('/useComputer', methods=['POST'])
+def useComputer():
+ computer_id = request.form['computer_id']
+ use_string = request.form['use_string']
+ return 'Ignored'
+
+ at app.route('/loadComputerConfigurationFromXML', methods=['POST'])
+def loadComputerConfigurationFromXML():
+ xml = request.form['xml']
+ computer_dict = xml_marshaller.xml_marshaller.loads(str(xml))
+ if app.config['computer_id'] == computer_dict['reference']:
+ args = []
+ a = args.append
+ execute_db('computer', 'INSERT OR REPLACE INTO %s values(:address, :netmask)',
+ computer_dict)
+ for partition in computer_dict['partition_list']:
+
+ execute_db('partition', 'INSERT OR IGNORE INTO %s (reference) values(:reference)', partition)
+ execute_db('partition_network', 'DELETE FROM %s WHERE partition_reference = ?', [partition['reference']])
+ for address in partition['address_list']:
+ address['reference'] = partition['tap']['name']
+ address['partition_reference'] = partition['reference']
+ execute_db('partition_network', 'INSERT OR REPLACE INTO %s (reference, partition_reference, address, netmask) values(:reference, :partition_reference, :addr, :netmask)', address)
+
+ return 'done'
+ else:
+ raise UnauthorizedError, "Only accept request for: %s" % \
+ app.config['computer_id']
+
+ at app.route('/registerComputerPartition', methods=['GET'])
+def registerComputerPartition():
+ computer_reference = request.args['computer_reference']
+ computer_partition_reference = request.args['computer_partition_reference']
+ if app.config['computer_id'] == computer_reference:
+ partition = execute_db('partition', 'SELECT * FROM %s WHERE reference=?',
+ [computer_partition_reference.encode()], one=True)
+ if partition is None:
+ raise UnauthorizedError
+ return xml_marshaller.xml_marshaller.dumps(
+ partitiondict2partition(partition))
+ else:
+ raise UnauthorizedError, "Only accept request for: %s" % \
+ app.config['computer_id']
+
+ at app.route('/supplySupply', methods=['POST'])
+def supplySupply():
+ url = request.form['url']
+ computer_id = request.form['computer_id']
+ if app.config['computer_id'] == computer_id:
+ execute_db('software', 'INSERT OR REPLACE INTO %s VALUES(?)', [url])
+ else:
+ raise UnauthorizedError, "Only accept request for: %s" % \
+ app.config['computer_id']
+ return '%r added' % url
More information about the Erp5-report
mailing list