[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