[Erp5-report] r45222 luke - in /slapos/trunk/util/slapos.tool.format: ./ src/ src/slapos/ s...

nobody at svn.erp5.org nobody at svn.erp5.org
Fri Apr 8 11:28:20 CEST 2011


Author: luke
Date: Fri Apr  8 11:28:20 2011
New Revision: 45222

URL: http://svn.erp5.org?rev=45222&view=rev
Log:
 - SlapOS node formatter

Added:
    slapos/trunk/util/slapos.tool.format/
    slapos/trunk/util/slapos.tool.format/CHANGES.txt
    slapos/trunk/util/slapos.tool.format/MANIFEST.in
    slapos/trunk/util/slapos.tool.format/README.txt
    slapos/trunk/util/slapos.tool.format/setup.cfg
    slapos/trunk/util/slapos.tool.format/setup.py
    slapos/trunk/util/slapos.tool.format/slapos.xsd
    slapos/trunk/util/slapos.tool.format/src/
    slapos/trunk/util/slapos.tool.format/src/slapos/
    slapos/trunk/util/slapos.tool.format/src/slapos/__init__.py
    slapos/trunk/util/slapos.tool.format/src/slapos/tool/
    slapos/trunk/util/slapos.tool.format/src/slapos/tool/__init__.py
    slapos/trunk/util/slapos.tool.format/src/slapos/tool/format/
    slapos/trunk/util/slapos.tool.format/src/slapos/tool/format/__init__.py

Added: slapos/trunk/util/slapos.tool.format/CHANGES.txt
URL: http://svn.erp5.org/slapos/trunk/util/slapos.tool.format/CHANGES.txt?rev=45222&view=auto
==============================================================================
--- slapos/trunk/util/slapos.tool.format/CHANGES.txt (added)
+++ slapos/trunk/util/slapos.tool.format/CHANGES.txt [utf8] Fri Apr  8 11:28:20 2011
@@ -0,0 +1,2 @@
+1.0 (unreleased)
+----------------

Added: slapos/trunk/util/slapos.tool.format/MANIFEST.in
URL: http://svn.erp5.org/slapos/trunk/util/slapos.tool.format/MANIFEST.in?rev=45222&view=auto
==============================================================================
--- slapos/trunk/util/slapos.tool.format/MANIFEST.in (added)
+++ slapos/trunk/util/slapos.tool.format/MANIFEST.in [utf8] Fri Apr  8 11:28:20 2011
@@ -0,0 +1,2 @@
+include CHANGES.txt
+include slapos.xsd

Added: slapos/trunk/util/slapos.tool.format/README.txt
URL: http://svn.erp5.org/slapos/trunk/util/slapos.tool.format/README.txt?rev=45222&view=auto
==============================================================================
--- slapos/trunk/util/slapos.tool.format/README.txt (added)
+++ slapos/trunk/util/slapos.tool.format/README.txt [utf8] Fri Apr  8 11:28:20 2011
@@ -0,0 +1,29 @@
+SlapOS Formatter
+================
+
+slapformat is an application to prepare SlapOS ready node (machine).
+
+It "formats" the machine by:
+
+ - creating users and groups
+ - creating bridge interface
+ - creating needed tap interfaces
+ - creating needed directories with proper ownership and permissions
+
+In the end special report is generated and information are posted to
+configured SlapOS server.
+
+This program shall be only run by root.
+
+Requirements
+============
+
+Linux with IPv6, bridging and tap interface support.
+
+Binaries:
+
+ * brctl
+ * groupadd
+ * ip
+ * tunctl
+ * useradd

Added: slapos/trunk/util/slapos.tool.format/setup.cfg
URL: http://svn.erp5.org/slapos/trunk/util/slapos.tool.format/setup.cfg?rev=45222&view=auto
==============================================================================
--- slapos/trunk/util/slapos.tool.format/setup.cfg (added)
+++ slapos/trunk/util/slapos.tool.format/setup.cfg [utf8] Fri Apr  8 11:28:20 2011
@@ -0,0 +1,3 @@
+[egg_info]
+tag_build = .dev
+tag_svn_revision = 1

Added: slapos/trunk/util/slapos.tool.format/setup.py
URL: http://svn.erp5.org/slapos/trunk/util/slapos.tool.format/setup.py?rev=45222&view=auto
==============================================================================
--- slapos/trunk/util/slapos.tool.format/setup.py (added)
+++ slapos/trunk/util/slapos.tool.format/setup.py [utf8] Fri Apr  8 11:28:20 2011
@@ -0,0 +1,39 @@
+# -*- coding: utf-8 -*-
+from setuptools import setup, find_packages
+import os
+
+name = 'slapos.tool.format'
+
+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 = '1.0',
+    description = "slapos - partitioning tools for servers",
+    long_description=long_description,
+    license = "GPLv3",
+    keywords = "vifib server partitioning",
+    include_package_data = True,
+    packages = find_packages('src'),
+    package_dir = {'':'src'},
+    namespace_packages = ['slapos'],
+    # slapgos use this to create a clean ouput
+    install_requires = [
+      'netaddr', # to play safely with IPv6 prefixes
+      'netifaces', # to fetch information about network devices
+      'setuptools', # namespace
+      'slapos.slap', # for posting data to vifib master
+      'xml_marshaller', # to generate data
+    ],
+    entry_points = """
+    [console_scripts]
+    slapformat = %s:main
+    """ % name,
+    )

Added: slapos/trunk/util/slapos.tool.format/slapos.xsd
URL: http://svn.erp5.org/slapos/trunk/util/slapos.tool.format/slapos.xsd?rev=45222&view=auto
==============================================================================
--- slapos/trunk/util/slapos.tool.format/slapos.xsd (added)
+++ slapos/trunk/util/slapos.tool.format/slapos.xsd [utf8] Fri Apr  8 11:28:20 2011
@@ -0,0 +1,29 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<xs:schema xmlns:xs="http://www.w3.org/2001/XMLSchema" elementFormDefault="qualified">
+  <xs:element name="marshal">
+    <xs:complexType>
+      <xs:sequence>
+        <xs:element ref="dictionary"/>
+      </xs:sequence>
+    </xs:complexType>
+  </xs:element>
+  <xs:element name="dictionary">
+    <xs:complexType>
+      <xs:choice minOccurs="0" maxOccurs="unbounded">
+        <xs:element ref="dictionary"/>
+        <xs:element ref="list"/>
+        <xs:element ref="string"/>
+      </xs:choice>
+      <xs:attribute name="id" use="required" type="xs:NCName"/>
+    </xs:complexType>
+  </xs:element>
+  <xs:element name="list">
+    <xs:complexType>
+      <xs:choice minOccurs="0" maxOccurs="unbounded">
+        <xs:element ref="dictionary"/>
+      </xs:choice>
+      <xs:attribute name="id" use="required" type="xs:NCName"/>
+    </xs:complexType>
+  </xs:element>
+  <xs:element name="string" type="xs:NCName"/>
+</xs:schema>

Added: slapos/trunk/util/slapos.tool.format/src/slapos/__init__.py
URL: http://svn.erp5.org/slapos/trunk/util/slapos.tool.format/src/slapos/__init__.py?rev=45222&view=auto
==============================================================================
--- slapos/trunk/util/slapos.tool.format/src/slapos/__init__.py (added)
+++ slapos/trunk/util/slapos.tool.format/src/slapos/__init__.py [utf8] Fri Apr  8 11:28:20 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.format/src/slapos/tool/__init__.py
URL: http://svn.erp5.org/slapos/trunk/util/slapos.tool.format/src/slapos/tool/__init__.py?rev=45222&view=auto
==============================================================================
--- slapos/trunk/util/slapos.tool.format/src/slapos/tool/__init__.py (added)
+++ slapos/trunk/util/slapos.tool.format/src/slapos/tool/__init__.py [utf8] Fri Apr  8 11:28:20 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.format/src/slapos/tool/format/__init__.py
URL: http://svn.erp5.org/slapos/trunk/util/slapos.tool.format/src/slapos/tool/format/__init__.py?rev=45222&view=auto
==============================================================================
--- slapos/trunk/util/slapos.tool.format/src/slapos/tool/format/__init__.py (added)
+++ slapos/trunk/util/slapos.tool.format/src/slapos/tool/format/__init__.py [utf8] Fri Apr  8 11:28:20 2011
@@ -0,0 +1,890 @@
+# -*- coding: utf-8 -*-
+##############################################################################
+#
+# Copyright (c) 2010 Vifib SARL and Contributors. All Rights Reserved.
+#
+# WARNING: This program as such is intended to be used by professional
+# programmers who take the whole responsibility of assessing all potential
+# consequences resulting from its eventual inadequacies and bugs
+# End users who are looking for a ready-to-use solution with commercial
+# guarantees and support are strongly adviced to contract a Free Software
+# Service Company
+#
+# This program is Free Software; you can redistribute it and/or
+# modify it under the terms of the GNU General Public License
+# as published by the Free Software Foundation; either version 3
+# of the License, or (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program; if not, write to the Free Software
+# Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.
+#
+##############################################################################
+from optparse import OptionParser, Option
+from xml_marshaller import xml_marshaller
+import ConfigParser
+import grp
+import logging
+import netaddr
+import netifaces
+import os
+import pwd
+import random
+import slapos.slap as slap
+import socket
+import subprocess
+import sys
+import time
+
+REQUIRED_BINARY_LIST = [
+    'brctl',
+    'groupadd',
+    'ip',
+    'tunctl',
+    'useradd',
+    'usermod',
+    ]
+
+class SlapError(Exception):
+  """
+  Slap error
+  """
+  def __init__(self, message):
+    self.msg = message
+
+class UsageError(SlapError):
+  pass
+
+class ExecError(SlapError):
+  pass
+
+def callAndRead(argument_list, raise_on_error=True):
+  popen = subprocess.Popen(argument_list, stdout=subprocess.PIPE,
+      stderr=subprocess.STDOUT)
+  result = popen.communicate()[0]
+  if raise_on_error and popen.returncode != 0:
+    raise ValueError('Issue during invoking %r, result was:\n%s' % (argument_list, result))
+  return popen.returncode, result
+
+def isGlobalScopeAddress(a):
+  """Returns True if a is global scope IP v4/6 address"""
+  ip = netaddr.IPAddress(a)
+  return not ip.is_link_local() and not ip.is_loopback() and \
+      not ip.is_reserved() and ip.is_unicast()
+
+def netmaskToPrefixIPv4(netmask):
+  """Convert string represented netmask to its intiger prefix"""
+  return netaddr.strategy.ipv4.netmask_to_prefix[
+          netaddr.strategy.ipv4.str_to_int(netmask)]
+
+def netmaskToPrefixIPv6(netmask):
+  """Convert string represented netmask to its intiger prefix"""
+  return netaddr.strategy.ipv6.netmask_to_prefix[
+          netaddr.strategy.ipv6.str_to_int(netmask)]
+
+def _getDict(instance):
+  """
+  Serialize an object instance into dictionnaries. List and dict will remains
+  the same, basic type too. But encapsuled object will be returned as dict.
+  Set, collections and other aren't handle for now.
+
+  Args:
+    instance: an object of anytype.
+
+  Returns:
+    A dictionnary if the given object wasn't a list, a list otherwise.
+  """
+  if isinstance(instance, list):
+    return [_getDict(item) for item in instance]
+
+  elif isinstance(instance, dict):
+    result = {}
+    for key in instance.keys():
+      result[key] = _getDict(instance[key])
+    return result
+
+  else:
+    try:
+
+      result = {}
+      for key in instance.__dict__.keys():
+        result[key] = _getDict(instance.__dict__[key])
+      return result
+
+    except AttributeError:
+      return instance
+
+class Error(Exception):
+  "Base class for exceptions in this module."
+  def __str__(self):
+    return self.message
+
+class NoAddressOnBridge(Error):
+  """
+  Exception raised if there's not address on the bridge to construct IPv6
+  address with.
+
+  Attributes:
+    brige: String, the name of the bridge.
+  """
+
+  def __init__(self, bridge):
+    self.message = 'No IPv6 found on bridge %s to construct IPv6 with.' % bridge
+
+class AddressGenerationError(Error):
+  """
+  Exception raised if the generation of an IPv6 based on the preffix obtained
+  from the bridge failed.
+
+  Attributes:
+    addr: String, the invalid address the exception is raised for.
+  """
+  def __init__(self, addr):
+    self.message = 'Generated IPv6 %s seems not to be a valid IP.' % addr
+
+class Computer:
+  "Object representing the computer"
+
+  def __init__(self, reference, bridge=None, addr = None, netmask = None):
+    """
+    Attributes:
+      reference: String, the reference of the computer.
+      bridge: String, if it has one, the name of the computer's bridge.
+    """
+    self.reference = str(reference)
+    self.bridge = bridge
+    self.partition_list = []
+    self.address = addr
+    self.netmask = netmask
+
+  def __getinitargs__(self):
+    return (self.reference, self.bridge)
+
+  def getAddress(self):
+    """
+    Return a list of the bridge address not attribued to any partition, (which
+    are therefore free for the computer itself).
+
+    Returns:
+      False if the bridge isn't available, else the list of the free adresses.
+    """
+    if self.bridge is None:
+      return None
+
+    computer_partition_address_list = []
+    for partition in self.partition_list:
+      for address in partition.address_list:
+        if netaddr.valid_ipv6(address['addr']):
+          computer_partition_address_list.append(address['addr'])
+    # Going through addresses of the computer's bridge interface
+    for address_dict in self.bridge.getGlobalScopeAddressList():
+      # Comparing with computer's partition addresses
+      if address_dict['addr'] not in computer_partition_address_list:
+        return address_dict
+
+    return None
+
+  def send(self, config):
+    """
+    Send a marshalled dictionnary of the computer object serialized via_getDict.
+    """
+
+    slap_instance = slap.slap()
+    connection_dict = {}
+    if config.key_file and config.cert_file:
+      connection_dict.update(
+        key_file=config.key_file,
+        cert_file=config.cert_file)
+    slap_instance.initializeConnection(config.master_url,
+      **connection_dict)
+    slap_computer = slap_instance.registerComputer(self.reference)
+    return slap_computer.updateConfiguration(
+        xml_marshaller.dumps(_getDict(self)))
+
+  def dump(self, path_to_xml):
+    """
+    Dump the computer object to an xml file via xml_marshaller.
+
+    Args:
+      path_to_xml: String, path to the file to load.
+      users: List of User, list of user needed to be add to the dump
+          (even if they are not related to any tap interface).
+    """
+
+    computer_dict = _getDict(self)
+    output_file = open(path_to_xml,'w')
+    output_file.write(xml_marshaller.dumps(computer_dict))
+    output_file.close()
+
+  @classmethod
+  def load(cls, path_to_xml, reference):
+    """
+    Create a computer object from a valid xml file.
+
+    Arg:
+      path_to_xml: String, a path to a valid file containing
+          a valid configuration.
+
+    Return:
+      A Computer object if the path where pointing on a valid
+          file, False otherwise.
+    """
+
+    dumped_dict = xml_marshaller.loads(open(path_to_xml).read())
+
+    # Reconstructing the computer object from the xml
+    computer = Computer(
+        reference = reference,
+        addr = dumped_dict['address'],
+        netmask = dumped_dict['netmask'],
+    )
+
+    for partition_dict in dumped_dict['partition_list']:
+
+      if partition_dict['user']:
+        user = User(partition_dict['user']['name'])
+      else:
+        user = User('root')
+
+      if partition_dict['tap']:
+        tap = Tap(partition_dict['tap']['name'])
+      else:
+        tap = Tap(partition_dict['reference'])
+
+      address_list = partition_dict['address_list']
+
+      partition = Partition(
+          reference = partition_dict['reference'],
+          path = partition_dict['path'],
+          user = user,
+          address_list = address_list,
+          tap = tap,
+      )
+
+      computer.partition_list.append(partition)
+
+    return computer
+
+  def construct(self):
+    """
+    Construct the computer object as it is.
+    """
+    if self.address is not None:
+      self.bridge.addAddr(self.address, self.netmask)
+
+    for path in self.instance_root, self.software_root: 
+      if not os.path.exists(path):
+        os.makedirs(path, 0755)
+      else:
+        os.chmod(path, 0755)
+
+    # own self.software_root by slapsoft
+    slapsoft = User('slapsoft')
+    slapsoft.path = self.software_root
+    slapsoft.create()
+    slapsoft_pw = pwd.getpwnam(slapsoft.name)
+    os.chown(self.software_root, slapsoft_pw.pw_uid, slapsoft_pw.pw_gid)
+    os.chmod(self.software_root, 0755)
+
+    for partition in self.partition_list:
+      # Reconstructing User's
+      partition.path = os.path.join(self.instance_root, partition.reference)
+      partition.user.setPath(partition.path)
+      partition.user.additional_group_list = [slapsoft.name]
+      partition.user.create()
+
+      # Reconstructing Tap
+      if partition.user and partition.user.isAvailable():
+        owner = partition.user
+      else:
+        owner = User('root')
+
+      partition.tap.createWithOwner(owner)
+
+      self.bridge.addTap(partition.tap)
+
+      # Reconstructing partition's directory
+      partition.createPath()
+
+      # Reconstructing partition's address
+      # There should be two addresses on each Computer Partition:
+      #  * global IPv6
+      #  * local IPv4, took from slapformat:ipv4_local_network
+      if len(partition.address_list) == 0:
+        # regenerate
+        partition.address_list.append(self.bridge.addIPv4LocalAddress())
+        partition.address_list.append(self.bridge.addAddr())
+      else:
+        # regenerate list of addresses
+        old_partition_address_list = partition.address_list
+        partition.address_list = []
+        if len(old_partition_address_list) != 2:
+          raise ValueError('There should be exactly 2 stored addresses')
+        if not any([netaddr.valid_ipv6(q['addr']) for q in old_partition_address_list]):
+          raise ValueError('Not valid ipv6 addresses loaded')
+        if not any([netaddr.valid_ipv4(q['addr']) for q in old_partition_address_list]):
+          raise ValueError('Not valid ipv6 addresses loaded')
+        for address in old_partition_address_list:
+          if netaddr.valid_ipv6(address['addr']):
+            partition.address_list.append(self.bridge.addAddr(address['addr'],
+              address['netmask']))
+          elif netaddr.valid_ipv4(address['addr']):
+            partition.address_list.append(self.bridge.addIPv4LocalAddress(address['addr']))
+          else:
+            raise ValueError('Address %r is incorrect' % address['addr'])
+
+class Partition:
+  "Represent a computer partition"
+
+  def __init__(self, reference, path, user, address_list, tap):
+    """
+    Attributes:
+      reference: String, the name of the parition.
+      path: String, the path to the partition folder.
+      user: User, the user linked to this partition.
+      addres_list: List of associated IP addresses.
+      tap: Tap, the tap interface linked to this partition.
+    """
+
+    self.reference = str(reference)
+    self.path = str(path)
+    self.user = user
+    self.address_list = address_list or []
+    self.tap = tap
+
+  def __getinitargs__(self):
+    return (self.reference, self.path, self.user, self.address_list, self.tap)
+
+  def createPath(self):
+    """
+    Create the directory of the partition, assign to the partition user and give
+    it the 750 permission. In case if path exists just modifies it.
+    """
+
+    self.path = os.path.abspath(self.path)
+    owner = self.user if self.user else User('root')
+    if not os.path.exists(self.path):
+      os.mkdir(self.path, 0750)
+    owner_pw = pwd.getpwnam(owner.name)
+    os.chown(self.path, owner_pw.pw_uid, owner_pw.pw_gid)
+    os.chmod(self.path, 0750)
+
+class User:
+  "User: represent and manipulate a user on the system."
+
+  def __init__(self, user_name, additional_group_list=None):
+    """
+    Attibutes:
+        user_name: string, the name of the user, who will have is home in
+    """
+    self.name = str(user_name)
+    self.additional_group_list = additional_group_list
+
+  def __getinitargs__(self):
+    return (self.name,)
+
+  def setPath(self, path):
+    self.path = path
+
+  def create(self):
+    """
+    Create a user on the system who will be named after the self.name with its
+    own group and directory.
+
+    Returns:
+        True: if the user creation went right
+    """
+    try:
+      grp.getgrnam(self.name)
+    except KeyError:
+      callAndRead(['groupadd', self.name])
+
+    user_parameter_list = ['-d', self.path, '-g', self.name]
+    if self.additional_group_list is not None:
+      user_parameter_list.extend(['-G', ','.join(self.additional_group_list)])
+    user_parameter_list.append(self.name)
+    try:
+      pwd.getpwnam(self.name)
+    except KeyError:
+      callAndRead(['useradd'] + user_parameter_list)
+    else:
+      callAndRead(['usermod'] + user_parameter_list)
+
+    return True
+
+  def isAvailable(self):
+    """
+    Determine the availability of a user on the system
+
+    Return:
+        True: if available
+        False: otherwise
+    """
+
+    try:
+      pwd.getpwnam(self.name)
+      return True
+
+    except KeyError:
+      return False
+
+class Tap:
+  "Tap represent a tap interface on the system"
+
+  def __init__(self, tap_name):
+    """
+    Attributes:
+        tap_name: String, the name of the tap interface.
+        user: User, the owner of the tap interface.
+    """
+
+    self.name = str(tap_name)
+
+  def __getinitargs__(self):
+    return (self.name,)
+
+  def createWithOwner(self,owner):
+    """
+    Create a tap interface on the system.
+
+    Return:
+        True: Everything went right.
+    """
+
+    # some systems does not have -p switch for tunctl
+    #callAndRead(['tunctl', '-p', '-t', self.name, '-u', owner.name])
+    check_file = '/sys/devices/virtual/net/%s/owner' % self.name
+    owner_id = None
+    if os.path.exists(check_file):
+      try:
+        owner_id = int(open(check_file).read().strip())
+      except Exception:
+        pass
+    if (owner_id is None) or (owner_id != pwd.getpwnam(owner.name).pw_uid):
+      callAndRead(['tunctl', '-t', self.name, '-u', owner.name])
+    callAndRead(['ip', 'link', 'set', self.name, 'up'])
+
+    return True
+
+class Bridge:
+  "Bridge represent a bridge on the system"
+
+  def __init__(self, name, ipv4_local_network):
+    """
+    Attributes:
+        name: String, the name of the bridge
+    """
+
+    self.name = str(name)
+    self.ipv4_local_network = ipv4_local_network
+
+  def __getinitargs__(self):
+    return (self.name,)
+
+  def getIPv4LocalAddressList(self):
+    """Returns currently configured local IPv4 addresses which are in ipv4_local_network"""
+    if not socket.AF_INET in netifaces.ifaddresses(self.name):
+      return []
+    return [dict(addr=q['addr'], netmask=q['netmask']) for q in
+      netifaces.ifaddresses(self.name)[socket.AF_INET] if netaddr.IPAddress(
+        q['addr'], 4) in netaddr.glob_to_iprange(
+          netaddr.cidr_to_glob(self.ipv4_local_network))]
+
+  def getGlobalScopeAddressList(self):
+    """Returns currently configured global scope IPv6 addresses"""
+    address_list = [q for q in netifaces.ifaddresses(self.name)[socket.AF_INET6]
+        if isGlobalScopeAddress(q['addr'].split('%')[0])]
+    # XXX: Missing implementation of Unique Local IPv6 Unicast Addresses as
+    # deinfed in http://www.rfc-editor.org/rfc/rfc4193.txt
+    # XXX: XXX: XXX: IT IS DISALLOWED TO IMPLEMENT link-local addresses as
+    # Linux and BSD are possibly wrongly implementing it -- it is "too local"
+    # it is impossible to listen or access it on same node
+    # XXX: IT IS DISALLOWED to implement ad hoc solution like inventing node
+    # local addresses or anything which does not exists in RFC!
+    return address_list
+
+  def getInterfaceList(self):
+    """Returns list of interfaces already present on bridge"""
+    interface_list = []
+    returncode, result = callAndRead(['brctl', 'show'])
+    in_bridge = False
+    for line in result.split('\n'):
+      if len(line.split()) > 1:
+        if self.name in line:
+          interface_list.append(line.split()[-1])
+          in_bridge = True
+          continue
+        if in_bridge:
+          break
+      elif in_bridge:
+        if line.strip():
+          interface_list.append(line.strip())
+
+    return interface_list
+
+  def addTap(self, tap):
+    """
+    Add the tap interface tap to the bridge.
+
+    Args:
+      tap: Tap, the tap interface.
+    """
+    if tap.name not in self.getInterfaceList():
+      callAndRead(['brctl', 'addif', self.name, tap.name])
+
+  def _addSystemAddress(self, address, netmask, ipv6=True):
+    """Adds system address to bridge
+    
+    Returns True if address was added succesfully.
+
+    Returns False if there was issue.
+    """
+    if ipv6:
+      address_string = '%s/%s' % (address, netmaskToPrefixIPv6(netmask))
+      af = socket.AF_INET6
+    else:
+      af = socket.AF_INET
+      address_string = '%s/%s' % (address, netmaskToPrefixIPv4(netmask))
+
+    # check if address is already took by any other interface
+    for interface in netifaces.interfaces():
+      if interface != self.name:
+        address_dict = netifaces.ifaddresses(interface)
+        if af in address_dict:
+         if address in [q['addr'].split('%')[0] for q in address_dict[af]]:
+            return False
+
+    if not af in netifaces.ifaddresses(self.name) or not address in [q['addr'].split('%')[0] for q in netifaces.ifaddresses(self.name)[af]]:
+      # add an address
+      callAndRead(['ip', 'addr', 'add', address_string, 'dev', self.name])
+      # wait few moments
+      time.sleep(2)
+    # check existence on interface
+    returncode, result = callAndRead(['ip', 'addr', 'list', self.name])
+    for l in result.split('\n'):
+      if address in l:
+        if 'tentative' in l:
+          # duplicate, remove
+          callAndRead(['ip', 'addr', 'del', address_string, 'dev', self.name])
+          return False
+        # found and clean
+        return True
+    # even when added not found, this is bad...
+    return False
+
+  def _generateRandomIPv4Address(self, netmask):
+    # no addresses found, generate new one
+    # Try 10 times to add address, raise in case if not possible
+    try_num = 10
+    while try_num > 0:
+      addr = random.choice([q for q in netaddr.glob_to_iprange(
+        netaddr.cidr_to_glob(self.ipv4_local_network))]).format()
+      if dict(addr=addr, netmask=netmask) not in self.getIPv4LocalAddressList():
+        # Checking the validity of the IPv6 address
+        if self._addSystemAddress(addr, netmask, False):
+          return dict(addr=addr, netmask=netmask)
+        try_num -= 1
+
+    raise AddressGenerationError(addr)
+
+  def addIPv4LocalAddress(self, addr=None):
+    """Adds local IPv4 address in ipv4_local_network"""
+    netmask = '255.255.255.255'
+    local_address_list = self.getIPv4LocalAddressList()
+    if addr is None:
+      return self._generateRandomIPv4Address(netmask)
+    elif dict(addr=addr, netmask=netmask) not in local_address_list:
+        if self._addSystemAddress(addr, netmask, False):
+          return dict(addr=addr, netmask=netmask)
+        else:
+          return self._generateRandomIPv4Address(netmask)
+    else:
+      # confirmed to be configured
+      return dict(addr=addr, netmask=netmask)
+
+  def addAddr(self, addr = None, netmask = None):
+    """
+    Adds IP address to bridge.
+
+    If addr is specified and exists already on bridge does nothing.
+
+    If addr is specified and does not exists on bridge, tries to add given address.
+    In case if it is not possible (ex. because network changed) calculates new address.
+
+    Args:
+      addr: Wished address to be added to bridge.
+      netmask: Wished netmask to be used.
+
+    Returns:
+      Tuple of (address, netmask).
+
+    Raises:
+      AddressGenerationError: Couldn't construct valid address with existing
+          one's on the bridgei
+      NoAddressOnBridge: There's no address on the bridge to construct
+          an address with.
+    """
+    # Getting one address of the bridge as base of the next addresses
+    bridge_addr_list = self.getGlobalScopeAddressList()
+
+    # No address found
+    if len(bridge_addr_list) == 0:
+      raise NoAddressOnBridge(self.name)
+    address_dict = bridge_addr_list[0]
+
+    if addr is not None:
+      if dict(addr=addr, netmask=netmask) in bridge_addr_list:
+        # confirmed to be configured
+        return dict(addr=addr, netmask=netmask)
+      if netmask == address_dict['netmask']:
+        # same netmask, so there is a chance to add good one
+        bridge_network = netaddr.ip.IPNetwork('%s/%s' % (address_dict['addr'],
+          netmaskToPrefixIPv6(address_dict['netmask'])))
+        requested_network = netaddr.ip.IPNetwork('%s/%s' % (addr, netmaskToPrefixIPv6(netmask)))
+        if bridge_network.network == requested_network.network:
+          # same network, try to add
+          if self._addSystemAddress(addr, netmask):
+            # succeed, return it
+            return dict(addr=addr, netmask=netmask)
+
+    # Try 10 times to add address, raise in case if not possible
+    try_num = 10
+    netmask = address_dict['netmask']
+    while try_num > 0:
+      addr = ':'.join(address_dict['addr'].split(':')[:-1] + ['%x' % random.randint(1, 65000)])
+      socket.inet_pton(socket.AF_INET6, addr)
+      if dict(addr=addr, netmask=netmask) not in self.getGlobalScopeAddressList():
+        # Checking the validity of the IPv6 address
+        if self._addSystemAddress(addr, netmask):
+          return dict(addr=addr, netmask=netmask)
+        try_num -= 1
+
+    raise AddressGenerationError(addr)
+
+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("-x", "--computer_xml",
+             help="Path to file with computer's XML. If does not exists, "
+                  "will be created",
+             default=None,
+             type=str),
+      Option("-n", "--partition_amount",
+             help="Number of partition to add. Default is 0, i.e. the script" \
+                  " won't create any partition if this argument isn't " \
+                  "specified.",
+             default=0,
+             type=int),
+      Option("-b", "--bridge_name",
+             help="Specify name of a bridge to be used.",
+             type=str),
+      Option("-p", "--partition_base_name",
+             help="The base name all the partition created by the script " \
+                  "will have. The complete name will be the concantenation " \
+                  "of the iterator and the base name. The default value " \
+                  "beeing slappart, the first created partition would be " \
+                  "named slappart0.",
+             type=str),
+      Option("-u", "--user_base_name",
+             help="The base name all the user created by the script will " \
+                  "have. The complete name will be the concantenation of " \
+                  "the iterator with the base name. The default value " \
+                  "beeing slapuser, the first created user would be named " \
+                  "slapuser0.",
+             type=str),
+      Option("-t", "--tap_base_name",
+             help="The base of the name all the tap interface created by " \
+                  "the script will have. The complete name will be the " \
+                  "concatenation of the iterator with the base name. The " \
+                  "default value being slaptap, the first tap interface " \
+                  "would be named slaptap0.",
+             type=str),
+      Option("-r", "--instance_root",
+             help="The path to the folder the home of the user would take " \
+                  "place. The path must finish with a slash '/'. The " \
+                  "default value is '/srv/slapgrid/",
+             type=str),
+      Option("-s", "--software_root",
+             help="The path of software, will be owned by slapsoft.",
+             type=str),
+      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."),
+      ])
+
+  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]
+
+def run(config):
+  try:
+    # Define the computer
+    if os.path.exists(config.computer_xml):
+      config.logger.info('Loading previous computer data from %r' % config.computer_xml)
+      computer = Computer.load(config.computer_xml, reference=config.computer_id)
+      # Connect to the bridge interface defined by the configuration
+      computer.bridge = Bridge(config.bridge_name, config.ipv4_local_network)
+    else:
+      # If no pre-existent configuration found, creating a new computer object
+      config.logger.warning('Creating new data computer with id %r' % config.computer_id)
+      computer = Computer(
+        reference=config.computer_id,
+        bridge=Bridge(config.bridge_name, config.ipv4_local_network),
+        addr=None,
+        netmask=None,
+      )
+
+    partition_amount = int(config.partition_amount)
+    existing_partition_amount = len(computer.partition_list)
+    if existing_partition_amount > partition_amount:
+      raise ValueError('Requested amount of computer partitions (%s) is lower '
+          'then already configured (%s), cannot continue' % (partition_amount,
+            len(computer.partition_list)))
+
+    config.logger.info('Adding %s new partitions' %
+        (partition_amount-existing_partition_amount))
+    for nb_iter in range(existing_partition_amount, partition_amount):
+      # add new ones
+      user = User("%s%s" % (config.user_base_name, nb_iter))
+
+      tap = Tap("%s%s" % (config.tap_base_name, nb_iter))
+
+      path = os.path.join(config.instance_root, "%s%s" % (
+                           config.partition_base_name, nb_iter))
+      computer.partition_list.append(
+        Partition(
+          reference="%s%s" % (config.partition_base_name, nb_iter),
+          path=path,
+          user=user,
+          address_list=None,
+          tap=tap,
+          ))
+
+    computer.instance_root = config.instance_root
+    computer.software_root = config.software_root
+    config.logger.info('Updating computer')
+    computer.construct()
+
+    address = computer.getAddress()
+    computer.address = address['addr']
+    computer.netmask = address['netmask']
+
+    # Dumping and sending to the erp5 the current configuration
+    computer.dump(config.computer_xml)
+    config.logger.info('Posting information to %r' % config.master_url)
+    computer.send(config)
+  except:
+    config.logger.exception('Uncatched exception:')
+    raise
+
+class Config:
+  def setConfig(self, option_dict, configuration_file_path):
+    """
+    Set options given by parameters.
+    """
+    self.key_file = None
+    self.cert_file = None
+    # 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 ("slapformat", "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("slapformat")
+    self.logger.setLevel(logging.INFO)
+    if self.console:
+      self.logger.addHandler(logging.StreamHandler())
+
+    # check root
+    if os.getuid() != 0:
+      message = "Root rights are needed"
+      self.logger.error(message)
+      raise UsageError(message)
+
+    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)
+    # Check mandatory options
+    for parameter in ('computer_id', 'instance_root', 'master_url',
+                      'software_root', 'computer_xml'):
+      if not getattr(self, parameter, None):
+        raise UsageError("Parameter '%s' is not defined." % parameter)
+
+    self.logger.info("Started.")
+    if self.verbose:
+      self.logger.setLevel(logging.DEBUG)
+      self.logger.debug("Verbose mode enabled.")
+
+    # Calculate path once
+    self.computer_xml = os.path.abspath(self.computer_xml)
+
+
+def checkRequiredBinary():
+  missing_binary_list = []
+  for b in REQUIRED_BINARY_LIST:
+    try:
+      callAndRead([b])
+    except ValueError:
+      pass
+    except OSError:
+      missing_binary_list.append(b)
+  if missing_binary_list:
+    raise UsageError('Some required binaries are missing or not functional: %s'%
+         ','.join(missing_binary_list))
+
+def main():
+  "Run default configuration."
+  usage = "usage: %s [options] CONFIGURATION_FILE" % sys.argv[0]
+
+  try:
+    # Parse arguments
+    options, configuration_file_path = Parser(usage=usage).check_args()
+    checkRequiredBinary()
+    config = Config()
+    config.setConfig(options, configuration_file_path)
+    run(config)
+  except UsageError, err:
+    print >>sys.stderr, err.msg
+    print >>sys.stderr, "For help use --help"



More information about the Erp5-report mailing list