class VirtController(object):

    def __init__(self, read_only=False):

        if not read_only:
            logging.debug("new RW VirtController")
            self.conn = libvirt.open("qemu:///system")
        else:
            logging.debug("new RO VirtController")
            self.conn = libvirt.openReadOnly("qemu:///system")

    #
    # storage pools
    #

    def find_storage_pool(self, path):
        """Search for any storage pool with the same path among all defined
           and active storage pools in Libvirt. If one matches, returns it or
           None if not found.

           :param string path: the absolute path of the storage pool to find
           :exceptions CloubedControllerException:
               * a problem is encountered in libvirt
        """

        try:
            all_pools = self.conn.listStoragePools() + self.conn.listDefinedStoragePools()
            for storage_pool_name in all_pools:

                storage_pool = self.conn.storagePoolLookupByName(storage_pool_name)

                xml = storage_pool.XMLDesc(0)
                dom = parseString(xml)
                cur_path = dom.getElementsByTagName('path')[0].firstChild.data

                if cur_path == path:

                    logging.info("found storage pool {name} with the same path" \
                                     .format(name=storage_pool_name))
                    return storage_pool

            return None

        except libvirt.libvirtError as err:
            raise CloubedControllerException(err)

    def create_storage_pool(self, xml):
        """Create a new storage pool in libvirt based on the XML description in
           the string parameter.

           :param string xml: the XML description of the storage pool to create
           :exceptions CloubedControllerException:
               * a problem is encountered in libvirt
        """

        try:
            self.conn.storagePoolCreateXML(xml, 0)
        except libvirt.libvirtError as err:
            raise CloubedControllerException(err)

    @staticmethod
    def __status_storage_pool(state_code):
        """Returns the name of the status of the StoragePool in Libvirt
           according to its state code

          :param integer xml: the status code
        """

        # Extracted from libvirt API documentation:
        # enum virStoragePoolState {
        #   VIR_STORAGE_POOL_INACTIVE     = 0 Not running
        #   VIR_STORAGE_POOL_BUILDING     = 1 Initializing pool, not available
        #   VIR_STORAGE_POOL_RUNNING      = 2 Running normally
        #   VIR_STORAGE_POOL_DEGRADED     = 3 Running degraded
        #   VIR_STORAGE_POOL_INACCESSIBLE = 4 Running, but not accessible
        #   VIR_STORAGE_POOL_STATE_LAST   = 5
        # }

        states = [ "inactive",
                   "initializing",
                   "active", # voluntarily not 'running' to stay compliant with
                             # 'virsh pool-list' output
                   "degraded",
                   "inaccessible" ]

        if state_code == -1:
            # special value introduced by get_infos() for own Cloubed use when
            # storage pool is not yet defined in Libvirt
            return 'undefined'

        return states[state_code]

    @staticmethod
    def __info_storage_pool(storage_pool):
        """Returns a dict full of key/value string pairs with information about
           the Libvirt storage pool.

           :param libvirt.virStoragePool storage_pool: the storage pool to
               inspect
        """

        infos = {}
        infos['status'] = VirtController.__status_storage_pool(storage_pool.info()[0])
        xml = parseString(storage_pool.XMLDesc(0))
        try:
            element = xml.getElementsByTagName('path').pop()
            infos['path'] = element.childNodes[0].data
        # IndexError exception is passed in order to continue silently
        # if elements are not found in the XML tree
        except IndexError:
            pass
        return infos

    def info_storage_pool(self, path):
        """Returns a dict full of key/value string pairs with information about
           the StoragePool.

           :param string path: the path of the storage pool to look up
           :exceptions CloubedControllerException:
               * a problem is encountered in libvirt
        """

        infos = {}
        storage_pool = self.find_storage_pool(path)
        try:
            if storage_pool is not None:
                infos = VirtController.__info_storage_pool(storage_pool)
            else:
                infos['status'] = VirtController.__status_storage_pool(-1)
        except libvirt.libvirtError as err:
            raise CloubedControllerException(err)
        return infos

    #
    # storage volumes
    #

    def find_storage_volume(self, storage_pool, name):
        """Search for any storage volume with the same name among all defined
           storage volumes within the storage pool in Libvirt. If one matches,
           returns it as libvirt.virStorageVol or None if not found.

           :param StoragePool storage_pool: a reference to the storage pool in
               which the volume should be found
           :param string name: the name of the filename of the storage volume to
               find
           :exceptions CloubedControllerException:
               * a problem is encountered in libvirt
        """

        # type(pool) is libvirt.virStoragePool
        pool = self.find_storage_pool(storage_pool.path)

        try:
            if pool is not None:
                for storage_volume_name in pool.listVolumes():
                    if storage_volume_name == name:
                        return pool.storageVolLookupByName(storage_volume_name)
        except libvirt.libvirtError as err:
            raise CloubedControllerException(err)

        return None

    def create_storage_volume(self, storage_pool, xml):
        """Create a new storage volume in libvirt based on the XML description
           in parameter.

           :param StoragePool storage_pool: a reference to the storage pool in
               which the volume will be created
           :param string xml: the XML description of the storage volume to create
           :exceptions CloubedControllerException:
               * the storage pool in parameter could not be found in libvirt
               * a problem is encountered in libvirt
        """

        # type(pool) is libvirt.virStoragePool
        pool = self.find_storage_pool(storage_pool.path)

        if not pool:
            raise CloubedControllerException("pool {path} not found by "\
                                             "virtualization controller" \
                                             .format(path=storage_pool.path))

        try:
            pool.createXML(xml, 0)
        except libvirt.libvirtError as err:
            raise CloubedControllerException(err)

    @staticmethod
    def __info_storage_volume(storage_volume):
        """Returns a dict with a bunch of infos about a Libvirt storage volume.

           :param libvirt.virStorageVolume storage_volume: the storage volume
               to inspect.
        """
        infos = {}
        infos['status'] = 'active'

        # extract infos out of libvirt XML
        xml = parseString(storage_volume.XMLDesc(0))

        # IndexError exception is passed in order to continue silently
        # if elements are not found in the XML tree

        # path
        try:
            element = xml.getElementsByTagName('path').pop()
            infos['path'] = element.childNodes[0].data
        except IndexError:
            pass

        # capacity/allocation
        try:
            element = xml.getElementsByTagName('capacity').pop()
            capacity = int(element.childNodes[0].data) / 1024**2
            infos['capacity'] = capacity
            element = xml.getElementsByTagName('allocation').pop()
            allocation = int(element.childNodes[0].data) / 1024**2
            infos['allocation'] = allocation
        except IndexError:
            pass
        return infos

    def info_storage_volume(self, storage_pool, name):
        """Returns a dict full of key/value string pairs with information about
           the StorageVolume.

           :param StoragePool storage_pool: a reference to the storage pool in
               which the volume should be found
           :param string name: the name of the filename of the storage volume to
               find
           :exceptions CloubedControllerException:
               * a problem is encountered in libvirt
        """

        infos = {}
        pool_info = self.info_storage_pool(storage_pool.path)
        if pool_info['status'] == 'undefined':
            infos['status'] = "-"
        else:
            storage_volume = self.find_storage_volume(storage_pool, name)
            if storage_volume is not None:
                infos = VirtController.__info_storage_volume(storage_volume)
            else:
                infos['status'] = 'undefined'
        return infos

    #
    # networks
    #

    def find_network(self, name):
        """Search for network with the same name among all defined and active
           networks in Libvirt. If one matches, returns it as libvirt.virNetwork
           or None if not found.

           :param string name: the name of the network to find
           :exceptions CloubedControllerException:
               * a problem is encountered in libvirt
        """

        try:
            networks = self.conn.listNetworks() + self.conn.listDefinedNetworks()
            for network_name in networks:
                if network_name == name:
                    return self.conn.networkLookupByName(network_name)
        except libvirt.libvirtError as err:
            raise CloubedControllerException(err)

        return None

    def create_network(self, xml):
        """Create a new network in libvirt based on the XML description in
           parameter.

           :param string xml: the XML description of the network to create
           :exceptions CloubedControllerException:
               * a problem is encountered in libvirt
        """

        try:
            self.conn.networkCreateXML(xml)
        except libvirt.libvirtError as err:
            raise CloubedControllerException(err)

    @staticmethod
    def __info_network(network):
        """Returns a dict with a bunch of infos about a Libvirt network.

           :param libvirt.virNetwork network: the network to inspect
        """

        infos = {}

        # status name of the Network from Libvirt standpoint
        if network.isActive():
            infos['status'] = 'active'
        else:
            infos['status'] = 'inactive'

        # extract infos out of libvirt XML
        xml = parseString(network.XMLDesc(0))

        # IndexError exception is passed in order to continue silently
        # if elements are not found in the XML tree

        # bridge name
        try:
            element = xml.getElementsByTagName('bridge').pop()
            bridge = element.getAttribute('name')
            infos['bridge'] = bridge
        except IndexError:
            pass

        # current ip/netmask
        try:
            element = xml.getElementsByTagName('ip').pop()
            ip = element.getAttribute('address')
            infos['ip'] = ip
            netmask = element.getAttribute('netmask')
            infos['netmask'] = netmask
        except IndexError:
            pass

        return infos

    def info_network(self, name):
        """Returns a dict with a bunch of infos about a network in Libvirt.

           :param string name: the name of the network to inspect
           :exceptions CloubedControllerException:
               * a problem is encountered in libvirt
        """

        infos = {}

        try:
            network = self.find_network(name)
            if network is not None:
                infos = VirtController.__info_network(network)
            else:
                infos['status'] = 'undefined'
        except libvirt.libvirtError as err:
            raise CloubedControllerException(err)

        return infos

    def info_networks(self):
        """Returns a dict with a bunch of infos about all networks
           in Libvirt.

           :exceptions CloubedControllerException:
               * a problem is encountered in libvirt
        """

        infos = {}

        try:
            networks = self.conn.listNetworks() + self.conn.listDefinedNetworks()
            for network_name in networks:
                network = self.conn.networkLookupByName(network_name)
                infos[network_name] = VirtController.__info_network(network)
        except libvirt.libvirtError as err:
            raise CloubedControllerException(err)

        return infos

    #
    # domains
    #

    def find_domain(self, name):
        """Search for domain with the same name among all defined and active
           domains in Libvirt. If one matches, returns it as libvirt.virDomain
           or None if not found.

           :param string name: the name of the domain to find
           :exceptions CloubedControllerException:
               * a problem is encountered in libvirt
        """

        try:
            # workaround since no way to directly get list of names of all
            # active domains in Libvirt API
            active_domains = [ self.conn.lookupByID(domain_id).name() \
                                   for domain_id in self.conn.listDomainsID() ]

            domains = active_domains + self.conn.listDefinedDomains()
            for domain_name in domains:
                if domain_name == name:
                    return self.conn.lookupByName(domain_name)
        except libvirt.libvirtError as err:
            raise CloubedControllerException(err)

        return None

    def create_domain(self, xml):
        """Create a new domain in libvirt based on the XML description in
           parameter.

           :param string xml: the XML description of the domain to create
           :exceptions CloubedControllerException:
               * a problem is encountered in libvirt
        """

        try:
            self.conn.createXML(xml, 0)
        except libvirt.libvirtError as err:
            raise CloubedControllerException(err)

    def shutdown_domain(self, domain_name):
        """Shutdown the domain in libvirt whose name is in parameter.

           :param string domain_name: the name of the domain to shutdown
           :exceptions CloubedControllerException:
               * a problem is encountered in libvirt
        """

        domain = self.find_domain(domain_name)
        if domain is not None:
            try:
                domain.shutdown()
            except libvirt.libvirtError as err:
                raise CloubedControllerException(err)

    def reboot_domain(self, domain_name):
        """Gracefully reboot the domain in libvirt whose name is in parameter.

           :param string domain_name: the name of the domain to reboot
           :exceptions CloubedControllerException:
               * a problem is encountered in libvirt
        """

        domain = self.find_domain(domain_name)
        if domain is not None:
            try:
                domain.reboot(0)
            except libvirt.libvirtError as err:
                raise CloubedControllerException(err)

    def reset_domain(self, domain_name):
        """Cold-reset the domain in libvirt whose name is in parameter.

           :param string domain_name: the name of the domain to reset
           :exceptions CloubedControllerException:
               * a problem is encountered in libvirt
        """

        domain = self.find_domain(domain_name)
        if domain is not None:
            try:
                domain.reset(0)
            except libvirt.libvirtError as err:
                raise CloubedControllerException(err)

    def suspend_domain(self, domain_name):
        """Suspend-to-RAM (ACPI S3 state) the domain in libvirt whose name is
           in parameter.

           :param string domain_name: the name of the domain to suspend
           :exceptions CloubedControllerException:
               * a problem is encountered in libvirt
        """

        domain = self.find_domain(domain_name)
        if domain is not None:
            try:
                domain.pMSuspendForDuration( \
                         libvirt.VIR_NODE_SUSPEND_TARGET_MEM, 0, 0)
            except libvirt.libvirtError as err:
                raise CloubedControllerException(err)

    def resume_domain(self, domain_name):
        """Resume a previously suspended domain in libvirt whose name is in
           parameter.

           :param string domain_name: the name of the domain to resume
           :exceptions CloubedControllerException:
               * a problem is encountered in libvirt
        """

        domain = self.find_domain(domain_name)
        if domain is not None:
            try:
                domain.pMWakeup(0)
            except libvirt.libvirtError as err:
                raise CloubedControllerException(err)

    @staticmethod
    def __status_domain(state_code):
        """Returns the name of the status of the Domain in Libvirt
           according to its state code.

          :param integer state_code: the status code
        """

        # Extracted from libvirt API documentation:
        # enum virDomainState {
        #   VIR_DOMAIN_NOSTATE     = 0 no state
        #   VIR_DOMAIN_RUNNING     = 1 the domain is running
        #   VIR_DOMAIN_BLOCKED     = 2 the domain is blocked on resource
        #   VIR_DOMAIN_PAUSED      = 3 the domain is paused by user
        #   VIR_DOMAIN_SHUTDOWN    = 4 the domain is being shut down
        #   VIR_DOMAIN_SHUTOFF     = 5 the domain is shut off
        #   VIR_DOMAIN_CRASHED     = 6 the domain is crashed
        #   VIR_DOMAIN_PMSUSPENDED = 7 the domain is suspended by guest power
        #                              management
        #   VIR_DOMAIN_LAST        = 8 NB: this enum value will increase over
        #                              time as new events are added to the
        #                              libvirt API. It reflects the last state
        #                              supported by this version of the libvirt
        #                              API.
        # }

        states = [ "unknown",
                   "running",
                   "blocked",
                   "paused",
                   "shutdown",
                   "shutoff",
                   "crashed",
                   "suspended" ]

        if state_code == -1:
            # special value introduced by get_infos() for own Cloubed use when
            # domain is not yet defined in Libvirt
            return 'undefined'
        return states[state_code]

    @staticmethod
    def __info_domain(domain):
        """Returns a dict with a bunch of infos about a Libvirt domain.

           :param libvirt.virDomain domain: the domain to inspect
        """
        infos = {}
        # get libvirt status
        infos['status'] = VirtController.__status_domain(domain.info()[0])

        # extract infos out of libvirt XML
        xml = parseString(domain.XMLDesc(0))

        # spice port
        try:
            element = xml.getElementsByTagName('graphics').pop()
            port = element.getAttribute('port')
            type = element.getAttribute('type')
            infos['console'] = type
            infos['port'] = str(port)
        # IndexError exception is passed in order to continue silently
        # if elements are not found in the XML tree
        except IndexError:
            pass
        return infos

    def info_domain(self, name):
        """Returns a dict with a bunch of infos about a domain in Libvirt.

           :param string name: the name of the domain to inspect
           :exceptions CloubedControllerException:
               * a problem is encountered in libvirt
        """
        infos = {}
        domain = self.find_domain(name)
        if domain is not None:
            infos = VirtController.__info_domain(domain)
        else:
            infos['status'] = VirtController.__status_domain(-1)
        return infos

    #
    # event management
    #

    @staticmethod
    def event_register():

        libvirt.virEventRegisterDefaultImpl()

    @staticmethod
    def event_run():

        if libvirt is not None: libvirt.virEventRunDefaultImpl()

    def setKeepAlive(self, major, minor):
        """Returns void
        """

        self.conn.setKeepAlive(major, minor)

    def domain_event_register(self, handler):
        """Returns void
        """

        self.conn.domainEventRegisterAny(None,
                                         libvirt.VIR_DOMAIN_EVENT_ID_LIFECYCLE,
                                         handler,
                                         None)
    #
    # Support testing methods
    #

    @staticmethod
    def supports_spice():
        return libvirt.getVersion() >= 8006 class StoragePool:

    """ StoragePool class """

    def __init__(self, tbd, storage_pool_conf):

        self.tbd = tbd
        self.ctl = self.tbd.ctl

        self.name = storage_pool_conf.name
        use_namespace = True # should better be a conf parameter in the future
        if use_namespace:    # logic should moved be in an abstract parent class
            self.libvirt_name = \
                "{user}:{testbed}:{name}" \
                    .format(user = getuser(),
                            testbed = storage_pool_conf.testbed,
                            name = self.name)
        else:
            self.libvirt_name = self.name
        self.path = storage_pool_conf.path

        self._doc = None

    def xml(self):

        """
            Returns the libvirt XML representation of the StoragePool
        """

        self.__init_xml()
        return self._doc

    def toxml(self):

        """
            Returns the libvirt XML representation of the StoragePool as string
        """

        return self.xml().toxml()

    def get_infos(self):
        """Returns a dict full of key/value string pairs with information about
           the StoragePool.
        """
        return self.ctl.info_storage_pool(self.path)

    def get_status(self):
        """Returns the status name of the StoragePool from VirtController
           standpoint extracted out of the dict returned by get_infos().
           This method is called by StorageVolume class.
        """
        return self.get_infos()['status']

    def destroy(self):

        """
            Destroys the StoragePool in libvirt.
        """

        storage_pool = self.ctl.find_storage_pool(self.path)
        if storage_pool is None:
            logging.debug("unable to destroy storage pool {name} since not " \
                          "found in libvirt".format(name=self.name))
            return # do nothing and leave

        if storage_pool.isActive():
            # The storage pool can only by destroyed if it has not any volume,
            # including volume of other testbeds
            nb_vols = storage_pool.numOfVolumes()
            if nb_vols == 0:
                logging.warn("destroying storage pool {name}".format(name=self.name))
                storage_pool.destroy()
            else:
                logging.warn("unable storage pool {name} because it still has " \
                             "{nb} volumes".format(name=self.name, nb=nb_vols))
        else:
            logging.warn("undefining storage pool {name}".format(name=self.name))
            storage_pool.undefine()

    def create(self):

        """
            Creates the StoragePool in libvirt. If it already exists, simply
            link to it.
        """

        storage_pool = self.ctl.find_storage_pool(self.path)

        if storage_pool is not None:
            logging.info("found storage pool {name} with the same path" \
                             .format(name=storage_pool.name()))
            self.libvirt_name = storage_pool.name() # override libvirt name

            # if not active in libvirt (defined but not started), activate it
            # TODO: virStoragePool.isActive() should be called in VirtController
            if not storage_pool.isActive():
                logging.info("storage pool {name} is not active, activating it..." \
                                 .format(name=self.libvirt_name))
                # TODO: virStoragePool.create() should be called in VirtController
                storage_pool.create(0)

        else:
            self.ctl.create_storage_pool(self.toxml())

    def __init_xml(self):

        """
            __init_xml: Generate the libvirt XML representation of the
                        StoragePool
        """

        self._doc = Document()

        # <pool type="dir">
        #   <name>poolname</name>
        #   <target>
        #     <path>/absolute/path</path>
        #   </target>
        # </pool>

        # root node: pool
        element_pool = self._doc.createElement("pool")
        element_pool.setAttribute("type","dir")
        self._doc.appendChild(element_pool)

        # name node
        element_name = self._doc.createElement("name")
        node_name = self._doc.createTextNode(self.libvirt_name)
        element_name.appendChild(node_name)
        element_pool.appendChild(element_name)

        # target node
        element_target = self._doc.createElement("target")
        element_pool.appendChild(element_target)

        # path node
        element_path = self._doc.createElement("path")
        node_path = self._doc.createTextNode(self.path)
        element_path.appendChild(node_path)
        element_target.appendChild(element_path)

    def get_templates_dict(self):

        """ Returns a dictionary with all parameters for the Storage Pool """

        clean_name = clean_string_for_template(self.name)

        return { "storagepool.{name}.path" \
                     .format(name=clean_name) : str(self.path) } class StorageVolume:

    """ StorageVoluem class """

    def __init__(self, tbd, storage_volume_conf):

        self.tbd = tbd
        self.ctl = self.tbd.ctl

        sp_name = storage_volume_conf.storage_pool
        self.storage_pool = self.tbd.get_storage_pool_by_name(sp_name)
        self.name = storage_volume_conf.name
        use_namespace = True # should better be a conf parameter in the future
        if use_namespace:    # logic should moved be in an abstract parent class
            self.libvirt_name = \
                "{user}:{testbed}:{name}" \
                    .format(user = getuser(),
                            testbed = storage_volume_conf.testbed,
                            name = self.name)
        else:
            self.libvirt_name = self.name

        self._size = storage_volume_conf.size
        self._imgtype = storage_volume_conf.format

        self._backing = storage_volume_conf.backing

        self._doc = None

    def __repr__(self):

        return "{name} [{size}GB]".format(name=self.name,
                                          size=self._size)

    def xml(self):

        """
            Returns the libvirt XML representation of the StorageVolume
        """

        self.__init_xml()
        return self._doc

    def toxml(self):

        """
            Returns the libvirt XML representation of the StorageVolume as string
        """

        return self.xml().toxml()

    def getfilename(self):

        """
            getfilename: Returns the file name of the StorageVolume
        """

        return self.libvirt_name + "." + self._imgtype

    def getpath(self):

        """
            getpath: Returns the full absolute path of the StorageVolume
        """

        return os.path.join(self.storage_pool.path, self.getfilename())

    def get_infos(self):
        """Returns a dict full of key/value string pairs with information about
           the StorageVolume.
        """
        return self.ctl.info_storage_volume(self.storage_pool,
                                            self.getfilename())

    def destroy(self):

        """
            Destroys the StorageVolume in libvirt
        """

        storage_volume = self.ctl.find_storage_volume(self.storage_pool,
                                                      self.getfilename())

        if storage_volume is None:
            logging.debug("unable to destroy storage volume {name} since not " \
                          "found in libvirt".format(name=self.name))
            return # do nothing and leave

        logging.warn("destroying storage volume {name}".format(name=self.name))
        storage_volume.delete(0)

    def create(self, overwrite=True):

        """
            create: Creates the StorageVolume in libvirt
        """

        found = False
        sv_name = None

        # delete storage volumes w/ the same name
        storage_volume = self.ctl.find_storage_volume(self.storage_pool,
                                                      self.getfilename())
        found = storage_volume is not None

        if found and overwrite:

            # first delete then re-create the storage volume
            logging.info("deleting storage volume {filename}" \
                             .format(filename=self.getfilename()))
            storage_volume.delete(0)
            self.ctl.create_storage_volume(self.storage_pool,
                                           self.toxml())
        elif not found:
            self.ctl.create_storage_volume(self.storage_pool,
                                           self.toxml())

    def __init_xml(self):

        """
            __init_xml: Generates the libvirt XML representation of the
                        StorageVolume
        """

        self._doc = Document()

        # <volume>
        #   <name>imgname.img</name>
        #   <allocation>0</allocation>
        #   <capacity unit="G">10</capacity>
        #   <target>
        #     <format type='qcow2'/>
        #     <permissions>
        #       <mode>0744</mode>
        #       <label>virt_image_t</label>
        #     </permissions>
        #   </target>
        #   <backingStore>
        #     <path>/home/rpalancher/Documents/git/examples-cloubed/debian/pool/rpalancher:debian:debian-vol-server.qcow2</path>
        #     <permissions>
        #       <mode>0744</mode>
        #       <label>virt_image_t</label>
        #     </permissions>
        #   </backingStore>
        # </volume>

        # root element: volume
        element_volume = self._doc.createElement("volume")
        self._doc.appendChild(element_volume)

        # name element
        element_name = self._doc.createElement("name")
        node_name = self._doc.createTextNode(self.getfilename())
        element_name.appendChild(node_name)
        element_volume.appendChild(element_name)
        
        # allocation element
        element_allocation = self._doc.createElement("allocation")
        node_allocation = self._doc.createTextNode("0")
        element_allocation.appendChild(node_allocation)
        element_volume.appendChild(element_allocation)
        
        # capacity element
        element_capacity = self._doc.createElement("capacity")
        element_capacity.setAttribute("unit", "G") # gigabyte
        node_capacity = self._doc.createTextNode(str(self._size))
        element_capacity.appendChild(node_capacity)
        element_volume.appendChild(element_capacity)

        # target element
        element_target = self._doc.createElement("target")
        element_volume.appendChild(element_target)

        # target/format element
        element_format = self._doc.createElement("format")
        element_format.setAttribute("type", self._imgtype)
        element_target.appendChild(element_format)

        # target/permissions element
        element_permissions = self._doc.createElement("permissions")
        element_target.appendChild(element_permissions)

        # target/permissions/owner element
        #element_owner = self._doc.createElement("owner")
        #node_owner = self._doc.createTextNode("1001")
        #element_owner.appendChild(node_owner)
        #element_permissions.appendChild(element_owner)

        # target/permissions/group element
        #element_group = self._doc.createElement("group")
        #node_group = self._doc.createTextNode("1000")
        #element_group.appendChild(node_group)
        #element_permissions.appendChild(element_group)

        # target/permissions/mode element
        element_mode = self._doc.createElement("mode")
        node_mode = self._doc.createTextNode("0744")
        element_mode.appendChild(node_mode)
        element_permissions.appendChild(element_mode)

        # target/permissions/label element
        element_label = self._doc.createElement("label")
        node_label = self._doc.createTextNode("virt_image_t")
        element_label.appendChild(node_label)
        element_permissions.appendChild(element_label)

        #   <backingStore>
        #     <path>/home/rpalancher/Documents/git/examples-cloubed/debian/pool/rpalancher:debian:debian-vol-server.qcow2</path>
        #     <permissions>
        #       <owner>107</owner>
        #       <group>107</group>
        #       <mode>0744</mode>
        #       <label>virt_image_t</label>
        #     </permissions>
        #   </backingStore>

        if self._backing is not None:

            backing = self.tbd.get_storage_volume_by_name(self._backing)

            # backingStore element
            element_backing = self._doc.createElement("backingStore")
            element_volume.appendChild(element_backing)

            # backingStore/path element
            element_path = self._doc.createElement("path")
            node_path = self._doc.createTextNode(backing.getpath())
            element_path.appendChild(node_path)
            element_backing.appendChild(element_path)

            # backingStore/format element
            element_format = self._doc.createElement("format")
            element_format.setAttribute("type", backing._imgtype)
            element_backing.appendChild(element_format)

            # backingStore/permissions element
            element_permissions = self._doc.createElement("permissions")
            element_backing.appendChild(element_permissions)

            # backingStore/permissions/mode element
            element_mode = self._doc.createElement("mode")
            node_mode = self._doc.createTextNode("0744")
            element_mode.appendChild(node_mode)
            element_permissions.appendChild(element_mode)

            # backingStore/permissions/label element
            element_label = self._doc.createElement("label")
            node_label = self._doc.createTextNode("virt_image_t")
            element_label.appendChild(node_label)
            element_permissions.appendChild(element_label)

    def get_templates_dict(self):

        """
            Returns a dictionary with all parameters of the Storage Volume in
            Configuration
        """

        clean_name = clean_string_for_template(self.name)

        return { "storagevolume.{name}.format"      \
                     .format(name=clean_name) : str(self._imgtype),
                 "storagevolume.{name}.size"        \
                     .format(name=clean_name) : str(self._size),
                 "storagevolume.{name}.storagepool" \
                     .format(name=clean_name) : str(self.storage_pool.name) } class Domain:

    """ Domain class """

    def __init__(self, tbd, domain_conf):

        self.tbd = tbd
        self.ctl = self.tbd.ctl
        self._virtobj = None
        self.name = domain_conf.name

        use_namespace = True # should better be a conf parameter in the future
        if use_namespace:    # logic should moved be in an abstract parent class
            self.libvirt_name = \
                "{user}:{testbed}:{name}" \
                    .format(user = getuser(),
                            testbed = domain_conf.testbed,
                            name = self.name)
        else:
            self.libvirt_name = self.name

        self.vcpu = domain_conf.sockets * domain_conf.cores * domain_conf.threads
        self.sockets = domain_conf.sockets
        self.cores = domain_conf.cores
        self.threads = domain_conf.threads

        self.memory = domain_conf.memory

        self.netifs = []
        # ex: [ 'admin', 'backbone' ]
        for netif in domain_conf.netifs:
            netif = DomainNetif(self.tbd, self.name, netif)
            self.netifs.append(netif)

        self.disks = []
        for disk in domain_conf.disks:
            self.disks.append(DomainDisk(self.tbd, disk))

        self.cdrom = domain_conf.cdrom

        self.virtfs = [ DomainVirtfs(virtfs["source"], virtfs["target"]) \
                        for virtfs in domain_conf.virtfs ]

        self.bootdev = None # defined at boot time
        self.graphics = domain_conf.graphics

        self.templates = []
        for template_conf in domain_conf.template_files:
            self.templates.append(DomainTemplate(template_conf))
        self.template_vars = domain_conf.template_vars

        self._events = []
        self._doc = None

        self._lock = threading.Lock()

    #
    # accessors
    #

    def get_storage_volumes(self):

        """ Returns the list of storage volume of the Domain """

        return [ disk.storage_volume for disk in self.disks ]

    def get_storage_volumes_names(self):

        """ Returns the list of storage volume names of the Domain """

        return [ disk.get_storage_volume_name() for disk in self.disks ]

    def get_networks(self):

        """ Returns the list of network of the Domain """

        return [ netif.network for netif in self.netifs ]

    def get_networks_names(self):

        """ Returns the list of network names of the Domain """

        return [ netif.get_network_name() for netif in self.netifs ]

    def get_infos(self):
        """Returns a dict full of key/value string pairs with information about
           the Domain.
        """
        return self.ctl.info_domain(self.libvirt_name)

    def get_template_by_name(self, template_name):

        """ Returns the DomainTemplate with this name """

        for template in self.templates:
            if template.name == template_name:
                return template

        # template not found therefore raise exception
        raise CloubedException(
                  "template {template} not defined for domain {domain}" \
                      .format(template = template_name,
                              domain = self.name))

    def get_first_host_ip(self):
        """Returns the first host ip addresses found in on all networks
           connected to the domain or None if not found.
        """

        list_ip_hosts = [ dom_netif.network.ip_host \
                          for dom_netif in self.netifs \
                          if dom_netif.network.ip_host \
                             is not None ]
        if len(list_ip_hosts) > 0:
            return list_ip_hosts[0]
        return None

    def xml(self):

        """ Returns the libvirt XML representation of the Domain """

        self.__init_xml()
        return self._doc

    def toxml(self):

        """ Returns the libvirt XML representation of the Domain as string """

        return self.xml().toxml()

    def created(self):

        """ Returns True if the Domain has been created in libvirt """

        return self._virtobj is not None

    def create(self,
               bootdev='hd'):

        """ Creates the Domain """

        domain = self.ctl.find_domain(self.libvirt_name)
        if domain:
            if domain.isActive():
                logging.info("destroying domain {name}" \
                                 .format(name=self.libvirt_name))
                domain.destroy()
            else:
                logging.info("undefining domain {name}" \
                                 .format(name=self.libvirt_name))
                domain.undefine()

        self.bootdev = bootdev

        # create the domain
        self.ctl.create_domain(self.toxml())
        logging.info("domain {domain}: created".format(domain=self.name))

    def shutdown(self):

        """ Shutdown the domain """

        domain = self.ctl.find_domain(self.libvirt_name)
        if domain is None:
            logging.warning("unable to shutdown domain {name} since not " \
                            "found in libvirt".format(name=self.name))
            return
        self.ctl.shutdown_domain(self.libvirt_name)
        logging.info("domain {domain}: shutdown".format(domain=self.name))

    def destroy(self):

        """
            Destroys the Domain in libvirt
        """

        domain = self.ctl.find_domain(self.libvirt_name)
        if domain is None:
            logging.debug("unable to destroy domain {name} since not found " \
                          "in libvirt".format(name=self.name))
            return # do nothing and leave
        if domain.isActive():
            logging.warn("destroying domain {name}".format(name=self.name))
            domain.destroy()
        else:
            logging.warn("undefining domain {name}".format(name=self.name))
            domain.undefine()

    def reboot(self):

        """ Gracefully reboot the domain """

        domain = self.ctl.find_domain(self.libvirt_name)
        if domain is None:
            logging.warning("unable to reboot domain {name} since not " \
                            "found in libvirt".format(name=self.name))
            return
        self.ctl.reboot_domain(self.libvirt_name)
        logging.info("domain {domain}: reboot".format(domain=self.name))

    def reset(self):

        """ Cold-reset the domain """

        domain = self.ctl.find_domain(self.libvirt_name)
        if domain is None:
            logging.warning("unable to reset domain {name} since not " \
                            "found in libvirt".format(name=self.name))
            return
        self.ctl.reset_domain(self.libvirt_name)
        logging.info("domain {domain}: reset".format(domain=self.name))

    def suspend(self):

        """ Suspend-to-RAM (ACPI S3 state) the domain """

        domain = self.ctl.find_domain(self.libvirt_name)
        if domain is None:
            logging.warning("unable to suspend domain {name} since not " \
                            "found in libvirt".format(name=self.name))
            return
        self.ctl.suspend_domain(self.libvirt_name)
        logging.info("domain {domain}: suspend".format(domain=self.name))

    def resume(self):

        """ Resume the domain from suspended state """

        domain = self.ctl.find_domain(self.libvirt_name)
        if domain is None:
            logging.warning("unable to resume domain {name} since not " \
                            "found in libvirt".format(name=self.name))
            return
        self.ctl.resume_domain(self.libvirt_name)
        logging.info("domain {domain}: resume".format(domain=self.name))

    def notify_event(self, event):

        """ notify_event: used to notify a Domain about a DomainEvent """

        logging.debug("domain {domain}: notified with event {event}" \
                          .format(domain=self.name, event=event))
        self._lock.acquire()        
        logging.debug("domain {domain}: lock acquired by notifyEvent" \
                          .format(domain=self.name))
        self._events.append(event)
        self._lock.release()
        logging.debug("domain {domain}: lock released by notifyEvent" \
                          .format(domain=self.name))

    def wait_for_event(self, event):

        """
            wait_for_event: used to wait until the Domain is notified with the
            appropriate DomainEvent
        """

        logging.debug("domain {domain}: entering in wait_for_event loop" \
                      " waiting for event {event}" \
                      .format(domain=self.name,
                              event=event))

        while True:
            self._lock.acquire()        
            #logging.debug("domain {domain}: lock acquired by waitForEvent" \
            #                  .format(domain=self.name))
            for loop_event in self._events:
                if loop_event == event:
                    logging.info("domain {domain}: waited event {event}" \
                                 " found!" \
                                      .format(domain=self.name,
                                              event=loop_event))
                    self._lock.release()
                    return
                else:
                    logging.info("domain {domain}: removing needless event" \
                                 " {event}" \
                                      .format(domain=self.name,
                                              event=loop_event))
                    self._events.remove(loop_event)
            self._lock.release()
            #logging.debug("domain {domain}: lock released by waitForEvent" \
            #                  .format(domain=self.name))
            time.sleep(1)

    def wait_tcp_socket(self, port):
        """Wait infinitely until TCP socket is open on port in parameter on the
           first static IP address of the domain. This method raises a
           CloubedException if the domain has not any static IP address in
           configuration. The port in parameter must be an integer and a valid
           TCP port number (in range 0-65535).
        """

        ips = [ netif.ip for netif in self.netifs \
                         if netif.ip is not None ]
        if len(ips) == 0:
            raise CloubedException("unable to wait for TCP socket since " \
                                   "domain {name} does not have any static " \
                                   "IP address".format(name=self.name))

        sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
        # set socket timeout to 1 second to avoid endless blocking
        # socket.connect() call
        sock.settimeout(1)
        firstip = ips[0]
        while True:
            logging.debug("trying to connect to TCP socket {ip}:{port}" \
                          .format(ip=firstip, port=port))
            try:
                sock.connect((firstip, port))
            except socket.error as msg:
                logging.debug("TCP error: {error}".format(error=msg))
                time.sleep(1)
                continue
            except socket.timeout as msg:
                logging.debug("TCP timeout: {error}".format(error=msg))
                continue
            break

    def __init_xml(self):

        """
            __init_xml: Translate the Domain to the libvirt XML representation
        """

        self._doc = Document()

        # <domain type='kvm'>
        #   <name>test-libvirt</name>
        #   <memory unit='GiB'>2</memory>
        #   <vcpu>2</vcpu>
        #   <cpu mode='host-model'>
        #     <model fallback='allow'/>
        #     <feature policy='optional' name='vmx'/>
        #     <topology sockets='1' cores='2' threads='1'/>
        #   </cpu>
        #   <os>
        #     <type>hvm</type>
        #     <boot dev='network' />
        #   </os>
        #   <clock sync="localtime"/>
        #   <features>
        #     <acpi />
        #   </features>
        #   <on_poweroff>destroy</on_poweroff>
        #   <on_reboot>restart</on_reboot>
        #   <on_crash>restart</on_crash>
        #   <on_lockfailure>poweroff</on_lockfailure>
        #   <devices>
        #     <disk type='file' device='disk'>
        #       <source file='/absolute/path' />
        #       <target dev='sda' bus='virtio' />
        #       <driver name='qemu' type='qcow2' />
        #     </disk>
        #     <disk type='block' device='cdrom'>
        #       <driver name='qemu' type='raw' />
        #       <target dev='hdc' bus='ide' tray='open' />
        #       <readonly />
        #     </disk>
        #     <disk type='file' device='cdrom'>
        #       <source file='/home/user/boot.iso'/>
        #       <target dev='hdc' bus='ide'/>
        #       <readonly/>
        #     </disk>
        #     <filesystem type='mount' accessmode='passthrough'>
        #       <source dir='/export/to/guest'/>
        #       <target dir='/import/from/host'/>
        #       <readonly/>
        #     </filesystem>
        #     <interface type="network">
        #       <source network="network-name"/>
        #       <mac address="00:16:3e:75:40:d5"/>
        #       <model type="virtio"/>
        #     </interface>
        #     <graphics type='sdl'/>
        #     <graphics type='spice' />
        #     <serial type='pty'>
        #       <target port='0'/>
        #     </serial>
        #     <console type='pty'>
        #       <target type='serial' port='0'/>
        #     </console>
        #   </devices>
        # </domain>

        # root: domain
        element_domain = self._doc.createElement("domain")
        element_domain.setAttribute("type", "kvm")
        self._doc.appendChild(element_domain)

        # name 
        element_name = self._doc.createElement("name")
        node_name = self._doc.createTextNode(self.libvirt_name)
        element_name.appendChild(node_name)
        element_domain.appendChild(element_name)
        
        # memory
        element_memory = self._doc.createElement("memory")
        element_memory.setAttribute("unit", "MiB")
        node_memory = self._doc.createTextNode(str(self.memory))
        element_memory.appendChild(node_memory)
        element_domain.appendChild(element_memory)
        
        # vcpu
        element_vcpu = self._doc.createElement("vcpu")
        node_vcpu = self._doc.createTextNode(str(self.vcpu))
        element_vcpu.appendChild(node_vcpu)
        element_domain.appendChild(element_vcpu)

        # cpu
        element_cpu = self._doc.createElement("cpu")
        element_cpu.setAttribute("mode", "host-model")
        element_domain.appendChild(element_cpu)

        # cpu/model
        element_model = self._doc.createElement("model")
        element_model.setAttribute("fallback", "allow")
        element_cpu.appendChild(element_model)

        # cpu/topology
        element_topology = self._doc.createElement("topology")
        element_topology.setAttribute("sockets", str(self.sockets))
        element_topology.setAttribute("cores", str(self.cores))
        element_topology.setAttribute("threads", str(self.threads))
        element_cpu.appendChild(element_topology)

        # os
        element_os = self._doc.createElement("os")
        element_domain.appendChild(element_os)

        # os/type
        element_type = self._doc.createElement("type")
        node_type = self._doc.createTextNode("hvm")
        element_type.appendChild(node_type)
        element_os.appendChild(element_type)

        # os/boot
        if self.bootdev is not None:
            element_boot = self._doc.createElement("boot")
            element_boot.setAttribute("dev", self.bootdev)
            element_os.appendChild(element_boot)

        # clock
        element_clock = self._doc.createElement("clock")
        element_clock.setAttribute("sync", "localtime")
        element_domain.appendChild(element_clock)

        # features
        element_features = self._doc.createElement("features")
        element_domain.appendChild(element_features)

        # features/acpi
        element_acpi = self._doc.createElement("acpi")
        element_features.appendChild(element_acpi)
        
        # on_poweroff
        element_onpoweroff = self._doc.createElement("on_poweroff")
        node_onpoweroff = self._doc.createTextNode("destroy")
        element_onpoweroff.appendChild(node_onpoweroff)
        element_domain.appendChild(element_onpoweroff)

        # on_reboot
        element_onreboot = self._doc.createElement("on_reboot")
        node_onreboot = self._doc.createTextNode("restart")
        element_onreboot.appendChild(node_onreboot)
        element_domain.appendChild(element_onreboot)

        # on_crash
        element_oncrash = self._doc.createElement("on_crash")
        node_oncrash = self._doc.createTextNode("restart")
        element_oncrash.appendChild(node_oncrash)
        element_domain.appendChild(element_oncrash)

        # on_lockfailure
        element_onlockfailure = self._doc.createElement("on_lockfailure")
        node_onlockfailure = self._doc.createTextNode("poweroff")
        element_onlockfailure.appendChild(node_onlockfailure)
        element_domain.appendChild(element_onlockfailure)

        # devices
        element_devices = self._doc.createElement("devices")
        element_domain.appendChild(element_devices)

        # hard disk

        for disk in self.disks:
            # devices/disk
            element_disk = self._doc.createElement("disk")
            element_disk.setAttribute("type", "file")
            element_disk.setAttribute("device", "disk")
            element_devices.appendChild(element_disk)
    
            # devices/disk/source
            element_source = self._doc.createElement("source")
            element_source.setAttribute("file",
                                        disk.storage_volume.getpath())
            element_disk.appendChild(element_source)
    
            # devices/disk/target
            element_target = self._doc.createElement("target")
            element_target.setAttribute("dev", disk.device)
            element_target.setAttribute("bus", disk.bus)
            element_disk.appendChild(element_target)
           
            # devices/disk/driver
            element_driver = self._doc.createElement("driver")
            element_driver.setAttribute("name", "qemu")
            element_driver.setAttribute("type", "qcow2")
            element_disk.appendChild(element_driver)

        # cdrom

        # devices/disk
        element_disk = self._doc.createElement("disk")
        if self.cdrom:
            element_disk.setAttribute("type", "file")
        else:
            element_disk.setAttribute("type", "block")
        element_disk.setAttribute("device", "cdrom")
        element_devices.appendChild(element_disk)

        if self.cdrom:
            # devices/disk/source
            element_source = self._doc.createElement("source")
            element_source.setAttribute("file", self.cdrom)
            element_disk.appendChild(element_source)
        else:
            # devices/disk/driver
            element_driver = self._doc.createElement("driver")
            element_driver.setAttribute("name", "qemu")
            element_driver.setAttribute("type", "raw")
            element_disk.appendChild(element_driver)

        # devices/disk/target
        element_target = self._doc.createElement("target")
        element_target.setAttribute("dev", "hdc")
        element_target.setAttribute("bus", "ide")
        if not self.cdrom:
            element_target.setAttribute("tray", "open")
        element_disk.appendChild(element_target)

        # devices/disk/readonly
        element_readonly = self._doc.createElement("readonly")
        element_disk.appendChild(element_readonly)

        # virtfs

        for fs in self.virtfs:

            # devices/filesystem
            element_fs = self._doc.createElement("filesystem")
            element_fs.setAttribute("type", "mount")
            #element_fs.setAttribute("accessmode", "passthrough")
            element_fs.setAttribute("accessmode", "mapped")
            element_devices.appendChild(element_fs)

            # devices/filesystem/source
            element_source = self._doc.createElement("source")
            element_source.setAttribute("dir", fs.source)
            element_fs.appendChild(element_source)

            # devices/filesystem/target
            element_target = self._doc.createElement("target")
            element_target.setAttribute("dir", fs.target)
            element_fs.appendChild(element_target)

        # netif 

        for netif in self.netifs:

            # devices/interface
            element_interface = self._doc.createElement("interface")
            element_interface.setAttribute("type", "network")
            element_devices.appendChild(element_interface)

            # devices/interface/source
            element_source = self._doc.createElement("source")
            element_source.setAttribute("network",
                                        netif.network.libvirt_name)
            element_interface.appendChild(element_source)

            # devices/interface/target
            #element_target = self._doc.createElement("target")
            #element_target.setAttribute("dev", "")
            #element_interface.appendChild(element_target)

            # devices/interface/mac
            element_mac = self._doc.createElement("mac")
            element_mac.setAttribute("address", netif.mac)
            element_interface.appendChild(element_mac)

            # devices/interface/model
            element_model = self._doc.createElement("model")
            element_model.setAttribute("type", "virtio")
            element_interface.appendChild(element_model)

        # devices/graphics
        if self.graphics:
            element_graphics = self._doc.createElement("graphics")
            element_graphics.setAttribute("type", self.graphics)
            # if spice is used, enable port auto-allocation
            if self.graphics == "spice":
                element_graphics.setAttribute("autoport", "yes")
            element_devices.appendChild(element_graphics)

        # serial console

        # devices/serial
        element_serial = self._doc.createElement("serial")
        element_serial.setAttribute("type", "pty")
        element_devices.appendChild(element_serial)

        # devices/serial/target
        element_target = self._doc.createElement("target")
        element_target.setAttribute("port", "0")
        element_serial.appendChild(element_target)

        # devices/console
        element_console = self._doc.createElement("console")
        element_console.setAttribute("type", "pty")
        element_devices.appendChild(element_console)

        # devices/console/target
        element_target = self._doc.createElement("target")
        element_target.setAttribute("type", "serial")
        element_target.setAttribute("port", "0")
        element_console.appendChild(element_target)

    def get_contextual_templates_dict(self):

        """
            Returns a dictionary with all parameters of the Domain Configuration
            with contextual prefix
        """

        return self.get_templates_dict(prefix="self")

    def get_absolute_templates_dict(self):

        """
            Returns a dictionary with all parameters of the Domain Configuration
            with absolute prefix
        """

        clean_name = clean_string_for_template(self.name)
        return self.get_templates_dict(prefix = \
                                       "domain.{name}".format(name=clean_name))

    def get_templates_dict(self, prefix):

        """
            Returns a dictionary with all parameters of the Domain Configuration
        """

        clean_name = clean_string_for_template(self.name)
        domain_dict = { "{prefix}.name" \
                            .format(prefix=prefix) : str(clean_name),
                        "{prefix}.cpu" \
                            .format(prefix=prefix) : str(self.sockets * self.cores * self.threads),
                        "{prefix}.sockets" \
                            .format(prefix=prefix) : str(self.sockets),
                        "{prefix}.cores" \
                            .format(prefix=prefix) : str(self.cores),
                        "{prefix}.threads" \
                            .format(prefix=prefix) : str(self.threads),
                        "{prefix}.memory" \
                            .format(prefix=prefix) : str(self.memory),
                        "{prefix}.graphics" \
                            .format(prefix=prefix) : str(self.graphics),
                      }

        # add netifs
        for netif in self.netifs:
            network_clean_name = \
                clean_string_for_template(netif.network.name)
            key = "{prefix}.{network}.mac" \
                      .format(prefix=prefix,
                              network=network_clean_name)
            domain_dict[key] = netif.mac
            if netif.ip:
                key = "{prefix}.{network}.ip" \
                          .format(prefix=prefix,
                                  network=network_clean_name)
                domain_dict[key] = str(netif.ip)

        tpl_vars_dict = {}

        for var_key, var_value in list(self.template_vars.items()):
            full_key = "{prefix}.tpl.{var_name}" \
                           .format(prefix=prefix, var_name=var_key)
            tpl_vars_dict[full_key] = str(var_value)

        domain_dict.update(tpl_vars_dict)

        return domain_dict class Network:

    """ Network class """

    def __init__(self, tbd, network_conf):

        self.tbd = tbd
        self.ctl = self.tbd.ctl

        self.name = network_conf.name
        use_namespace = True # should better be a conf parameter in the future
        if use_namespace:    # logic should moved be in an abstract parent class
            self.libvirt_name = \
                "{user}:{testbed}:{name}" \
                    .format(user = getuser(),
                            testbed = network_conf.testbed,
                            name = self.name)
        else:
            self.libvirt_name = self.name

        self._forward_mode = network_conf.forward_mode
        self._bridge_name = network_conf.bridge_name

        self._with_local_settings = False
        self.ip_host = None
        self._netmask = None
        if network_conf.has_local_settings():
            self._with_local_settings = True
            self.ip_host = network_conf.ip_host
            self._netmask = network_conf.netmask

        self._with_dhcp = False
        self._dhcp_range_start = None
        self._dhcp_range_stop = None
        if network_conf.has_dhcp():
            self._with_dhcp = True
            self._dhcp_range_start = network_conf.dhcp_start
            self._dhcp_range_end = network_conf.dhcp_end

        self._domain = network_conf.domain

        self._with_pxe = False
        self._tftproot = None
        self._bootfile = None
        if network_conf.has_pxe():
            self._with_pxe = True
            self._tftproot = network_conf.pxe_tftp_dir
            self._bootfile = network_conf.pxe_boot_file

        # list of statically declared hosts in the network
        self._hosts = []

        self._doc = None

    def xml(self):

        """ Returns the libvirt XML representation of the Network """

        self.__init_xml()
        return self._doc

    def toxml(self):

        """ Returns the libvirt XML representation of the Network as string """

        return self.xml().toxml()

    def get_infos(self):
        """Returns a dict full of key/value string pairs with information about
           the Network.
        """
        return self.ctl.info_network(self.libvirt_name)

    def register_host(self, hostname, mac, ip):

        """ Register a host with a static IP address in DHCP """

        logging.debug("registering host {hostname} in network {network}" \
                          .format(hostname=hostname,
                                  network=self.name))
        self._hosts.append({"hostname": hostname, "mac": mac, "ip": ip})

    def destroy(self):

        """
            Destroys the Network in libvirt
        """

        network = self.ctl.find_network(self.libvirt_name)
        if network is None:
            logging.debug("unable to destroy network {name} since not found " \
                          "in libvirt".format(name=self.name))
            return # do nothing and leave
        if network.isActive():
            logging.warn("destroying network {name}".format(name=self.name))
            network.destroy()
        else:
            logging.warn("undefining network {name}".format(name=self.name))
            network.undefine()

    def __check_conflict(self):
        """It looks over existing active networks in Libvirt in order to detect
           potential conflicting IP settings. If yes, it returns a tuple with
           True and the name of the first conflicting network. Else it returns
           a tuple with False and None.
        """
        infos = self.ctl.info_networks()
        for network_name, network_infos in list(infos.items()):
            if network_name != self.libvirt_name and \
                network_infos['status'] == 'active':
                if net_conflict(self.ip_host, self._netmask,
                               network_infos.get('ip'),
                               network_infos.get('netmask')):
                    return (True, network_name)
        return (False, None)

    def create(self, overwrite=False):
        """Creates the Network in libvirt. First, it searches if the network
           has already been created previously, based on its name. If yes and
           overwrite parameter is True, it will delete the previous Network and
           creates this one instead. If yes and overwrite is False, it does not
           do anything. Else, it creates the networks.
           Before creating the network, it checks if another active Network has
           conflicting IP settings. If yes, a CloubedException is raised.

           :exceptions CloubedException:
               * another active network with conflicting IP settings has been
                 found in Libvirt.
        """

        network = self.ctl.find_network(self.libvirt_name)
        found = network is not None
        create = False

        if found and overwrite:
            if network.isActive():
                logging.info("destroying network {name}".format(name=self.name))
                network.destroy()
            else:
                logging.info("undefining network {name}".format(name=self.name))
                network.undefine()
            create = True
        elif not found:
            create = True

        if create:
            conflict, network_name = self.__check_conflict()
            if conflict:
                raise CloubedException("another network {network} is already " \
                                       "active with conflicting IP settings" \
                                           .format(network=network_name))
            else:
                self.ctl.create_network(self.toxml())

    def __init_xml(self):

        """
            __init_xml: Generates the libvirt XML representation of the Network
        """

        self._doc = Document()

        # Open vSwitch - not used yet in libvirt 0.9.10

        # <network>
        #   <name>ovs-network</name>
        #   <forward mode='bridge'/>
        #   <bridge name='ovsbr0' />
        #   <virtualport type='openvswitch'/>
        #   <portgroup name='green' default='yes'>
        #     <virtualport>
        #       <parameters profileid='green-profile'/>
        #     </virtualport>
        #   </portgroup>
        #   <portgroup name='blue'>
        #     <vlan>
        #       <tag id='2'/>
        #     </vlan>
        #     <virtualport>
        #       <parameters profileid='blue-profile'/>
        #     </virtualport>
        #   </portgroup>
        # </network>

        # Isolated network

        # <network>
        #   <name>private</name>
        #   <bridge name="virbr2" />
        #   <forward mode="nat"/>
        #   <domain name="example.net">
        #   <ip address="192.168.152.1" netmask="255.255.255.0">
        #     <tftp root="/var/lib/tftp" />
        #     <dhcp>
        #       <range start="192.168.152.20" end="192.168.152.254" />
        #       <bootp file='filename' />
        #     </dhcp>
        #   </ip>
        # </network>

        # Pre-existing bridge, with libvirt >= 0.9.4

        # <network>
        #   <name>host-bridge</name>
        #   <forward mode="bridge"/>
        #   <bridge name="br0"/>
        # </network>

        # root element: network
        element_network = self._doc.createElement("network")
        self._doc.appendChild(element_network)

        # name element
        element_name = self._doc.createElement("name")
        node_name = self._doc.createTextNode(self.libvirt_name)
        element_name.appendChild(node_name)
        element_network.appendChild(element_name)

        # bridge element
        element_bridge = self._doc.createElement("bridge")
        if self._bridge_name is not None:
            element_bridge.setAttribute("name", self._bridge_name)
        element_network.appendChild(element_bridge)

        # forward element
        if self._forward_mode is not None:
            element_forward = self._doc.createElement("forward")
            element_forward.setAttribute("mode", self._forward_mode)
            element_network.appendChild(element_forward)

        # domain
        if self._domain:
            element_domain = self._doc.createElement("domain")
            element_domain.setAttribute("name", self._domain)
            element_network.appendChild(element_domain)

        # To avoid unwanted behaviour and conflicts on external LAN, DHCP and
        # PXE cannot be enable on bridge forwording networks
        if self._forward_mode != 'bridge':

            # ip element
            if self._with_local_settings:
                element_ip = self._doc.createElement("ip")
                element_ip.setAttribute("address", self.ip_host)
                element_ip.setAttribute("netmask", self._netmask)
                element_network.appendChild(element_ip)

            # ip/tftp element
            if self._with_pxe:
                element_tftp = self._doc.createElement("tftp")
                element_tftp.setAttribute("root", self._tftproot)
                element_ip.appendChild(element_tftp)

            if self._with_dhcp:
                # ip/dhcp element
                element_dhcp = self._doc.createElement("dhcp")
                element_ip.appendChild(element_dhcp)

                # ip/dhcp/range element
                element_range = self._doc.createElement("range")
                element_range.setAttribute("start", self._dhcp_range_start)
                element_range.setAttribute("end", self._dhcp_range_end)
                element_dhcp.appendChild(element_range)

                # ip/dhcp/bootp
                if self._with_pxe:
                    element_bootp = self._doc.createElement("bootp")
                    element_bootp.setAttribute("file", self._bootfile)
                    element_dhcp.appendChild(element_bootp)

                # ip/dhcp/host
                for host in self._hosts:
                    element_host = self._doc.createElement("host")
                    element_host.setAttribute("mac", host["mac"])
                    element_host.setAttribute("name", host["hostname"])
                    element_host.setAttribute("ip", host["ip"])
                    element_dhcp.appendChild(element_host)

    def get_templates_dict(self):

        """
            Returns a dictionary with all parameters of this Network
            Configuration
        """

        clean_name = clean_string_for_template(self.name)

        tpl_dict = { "network.{name}.forward_mode" \
                         .format(name=clean_name) : str(self._forward_mode),
                     "network.{name}.bridge_name" \
                         .format(name=clean_name) : str(self._bridge_name),
                     "network.{name}.ip_host" \
                         .format(name=clean_name) : str(self.ip_host),
                     "network.{name}.netmask" \
                         .format(name=clean_name) : str(self._netmask),
                     "network.{name}.dhcp_start" \
                         .format(name=clean_name) : str(self._dhcp_range_start),
                     "network.{name}.dhcp_end" \
                         .format(name=clean_name) : str(self._dhcp_range_stop),
                     "network.{name}.pxe_tftp_dir" \
                         .format(name=clean_name) : str(self._tftproot),
                     "network.{name}.pxe_boot_file" \
                         .format(name=clean_name) : str(self._bootfile) }

        # port is hard-coded in HTTPServer class
        if self.ip_host is not None:
            http_server = "http://" + self.ip_host + ":5432"
            tpl_dict["network.{name}.http_server" \
                     .format(name=clean_name)] = http_server

        return tpl_dict class EventManager:

    """ EventManager class """

    tbd = None

    def __init__(self, tbd):

        VirtController.event_register()
        self._stop = threading.Event()

        self._thread = threading.Thread(target=self.run_event_loop,
                                        name="libvirtEventLoop")
        self._thread.setDaemon(True)
        self._thread.start()

        self._ctl = VirtController(read_only=True)
        self._ctl.domain_event_register(EventManager.manage_event)
        self._ctl.setKeepAlive(5, 3)

        EventManager.tbd = tbd

        logging.debug("initialized event manager")

    def terminate(self):

        logging.debug("terminating event manager thread")
        self._stop.set()

    def run_event_loop(self):

        """ run_event_loop: Starts libvirt event loop """

        # In race conditions on python interpreter exit, libvirt sometimes
        # becomes None. So loop conditioner tests if it is still defined in
        # order to avoid  errors.
        while not self._stop.is_set():
            VirtController.event_run()

    @staticmethod
    def manage_event(conn, dom, event_type, event_detail, opaque):

        """ manage_event: handler launched by libvirt in case of event """

        event = DomainEvent(event_type, event_detail)
        logging.debug("event on domain {domain_name}({domain_id}) " \
                      "{event_type} {event_detail}" \
                          .format(domain_name=dom.name(),
                                  domain_id=dom.ID(),
                                  event_type=event.type,
                                  event_detail=event.detail))

        domain = EventManager.tbd.get_domain_by_libvirt_name(dom.name())
        # test if notified event comes from a domain in current testbed
        if domain is None:
            logging.debug("event received for domain {domain} but not found " \
                          "in testbed".format(domain=dom.name()))
        else:
            domain.notify_event(event) class Configuration:

    """ Main Configuration class """

    def __init__(self, loader):

        self._loader = loader
        conf = self._loader.content

        self.testbed = None
        self.__parse_testbed(conf)

        self.storage_pools   = []
        self.storage_volumes = []
        self.networks        = []
        self.domains         = []
        self.__parse_items(conf)

        self.templates = {} # empty dict
        self.__parse_templates(conf)

    def __parse_testbed(self, conf):
        """
            Parses the testbed parameter over the conf dictionary given in
            parameter and raises appropriate exception if a problem is found
        """

        if 'testbed' not in conf:
            raise CloubedConfigurationException(
                      "testbed parameter is missing")

        if type(conf['testbed']) is not str:
            raise CloubedConfigurationException(
                      "format of the testbed parameter is not valid")

        self.testbed = conf['testbed']

    def __parse_items(self, conf):
        """
            Parses all items (storage pools, storage volumes, networks and
            domains) over the conf dictionary given in parameter and raises
            appropriate exception if a problem is found
        """

        # This dict basically contains all infos to parse items in YAML and
        # build according data structures. The format of this dict is:
        # <name of section for the items in YAML> : {
        #        { 'class': <ConfigurationItem class for these items>,
        #          'list': <list to append with the Configuration object> }

        items = { 'storagepools':
                      { 'class': ConfigurationStoragePool,
                        'list': self.storage_pools },
                  'storagevolumes':
                      { 'class': ConfigurationStorageVolume,
                        'list': self.storage_volumes },
                  'networks':
                      { 'class': ConfigurationNetwork,
                        'list': self.networks },
                  'domains':
                      { 'class': ConfigurationDomain,
                        'list': self.domains } }

        optional_items = [ 'storagepools', 'storagevolumes' ]

        # Iterations over the dict. The variables are:
        #   item_section: the name of the section in YAML
        #   meta: the dict with 'class' and 'list' for the section
        #   items: the list of items in the section
        #   item_class: the ConfigurationItem class for the items
        #   item_list: the list to append with build ConfigurationItem objects

        for item_section, meta in list(items.items()):

            item_class = meta['class']
            item_list = meta['list']

            if item_section not in conf:
                if item_section in optional_items:
                    logging.debug("loading defaut {item_section}" \
                                  .format(item_section=item_section))
                    default_item = item_class.default()
                    if default_item is not None:
                        item_list.append(item_class(self, default_item))
                else:
                    raise CloubedConfigurationException(
                          "{item_section} parameter is missing" \
                              .format(item_section=item_section))
            else:
                items = conf[item_section]
                if type(items) is not list:
                    raise CloubedConfigurationException(
                          "format of {item_section} parameter is not valid" \
                             .format(item_section=item_section))

                for item in items:
                    item['testbed'] = self.testbed
                    item_list.append(item_class(self, item))

    def __parse_templates(self, conf):
        """
            Parses the global testbed template variables in the dedicated
            section of the conf dictionary given in parameter and raises
            appropriate exception if a problem is found
        """

        if "templates" in conf:

            templates = conf["templates"]

            if type(templates) is not dict:
                raise CloubedConfigurationException(
                    "format of the templates section is not valid")

            if len(templates) == 0:
                self.templates = {}
            else:
                for variable, value in list(templates.items()):
                    if type(value) is not str:
                        raise CloubedConfigurationException(
                            "format of the value of the global template " \
                            "variable {variable} is not valid" \
                                .format(variable=variable))
                    key = "testbed.{variable}".format(variable=variable)
                    self.templates[key] = value
        else:
            # empty dict by default
            self.templates = {} class ConfigurationLoader:

    """ ConfigurationLoader class """

    def __init__(self, conf_file):

        self.file_path = conf_file

        try:
            yaml_file = open(self.file_path)
        except IOError:
            raise CloubedConfigurationException(
                      "Not able to open file {file_path}" \
                          .format(file_path = self.file_path))

        try:
            self.content = yaml.load(yaml_file, Loader=yaml.FullLoader)
        except yaml.YAMLError as err:
            raise CloubedConfigurationException(
                      "Error while loading {file_path} file (may" \
                      " not be valid YAML content): {error}" \
                          .format(file_path=self.file_path,
                                  error=err))
        yaml_file.close()

        if type(self.content) is not dict:
            raise CloubedConfigurationException(
                      "File {file_path} is not a valid YAML file for Cloubed" \
                          .format(file_path=self.file_path)) class HTTPServer():

    """ HTTPServer class """

    def __init__(self):

        self._port = 5432
        self._handler = http.server.SimpleHTTPRequestHandler
        self._address = None
        self._httpd = None
        self._thread = None

    def launched(self):

        """
           Returns True if the HTTPServer is already launched
        """

        # check both the thread and the httpd server since httpd server could
        # have failed when launched

        return self._thread is not None and self._httpd is not None

    def launch(self, address):

        """
            launch: Creates the daemon thread that will start the HTTP server
        """

        self._address = address
        self._thread = threading.Thread(target=self.threaded_server,
                                        name="ClouBedHTTPServer")
        self._thread.setDaemon(True)
        self._thread.start()

    def terminate(self):

        """
            shutdown the http server in thread
        """
        logging.debug("shutting down http server")
        self._httpd.shutdown()

    def threaded_server(self):

        """
            threaded_server: Thread routine that actually starts the HTTP server
        """

        try:
            self._httpd = socketserver.TCPServer((self._address, self._port),
                                                 self._handler)
            self._httpd.serve_forever()
        except socket.error as e:
            logging.warn("error while launching TCP Server: {err}".format(err=e)) class DomainEvent:

    """ DomainEvent class """

    _domain_event_infos = {
        0: ["DEFINED",     {
            0: "DEFINED_ADDED", # newly created config file
            1: "DEFINED_UPDATED", # changed config file
            2: "DEFINED_LAST" }
           ],
        1: ["UNDEFINED",   {
            0: "UNDEFINED_REMOVED", # deleted the config file
            1: "UNDEFINED_LAST" }
           ],
        2: ["STARTED",     {
            0: "STARTED_BOOTED", # normal startup from boot
            1: "STARTED_MIGRATED", # incoming migration from another host
            2: "STARTED_RESTORED", # restored from a state file
            3: "STARTED_FROM_SNAPSHOT", # restored from snapshot
            4: "STARTED_WAKEUP", # started due to wakeup event
            5: "STARTED_LAST" }
           ],
        3: ["SUSPENDED",   {
            0: "SUSPENDED_PAUSED", # normal suspend due to admin pause
            1: "SUSPENDED_MIGRATED", # suspended for offline migration
            2: "SUSPENDED_IOERROR", # suspended due to a disk I/O error
            3: "SUSPENDED_WATCHDOG", # suspended due to a watchdog firing
            4: "SUSPENDED_RESTORED", # restored from paused state file
            5: "SUSPENDED_FROM_SNAPSHOT", # restored from paused snapshot
            6: "SUSPENDED_API_ERROR", # suspended after failure during libvirt
                                      # API call
            7: "SUSPENDED_LAST" }
           ],
        4: ["RESUMED",     {
            0: "RESUMED_UNPAUSED", # normal resume due to admin unpause
            1: "RESUMED_MIGRATED", # resumed for completion during migration
            2: "RESUMED_FROM_SNAPSHOT", # resumed from snapshot
            3: "RESUMED_LAST" }
           ],
        5: ["STOPPED",     {
            0: "STOPPED_SHUTDOWN", # normal shutdown
            1: "STOPPED_DESTROYED", # forced poweroff from host
            2: "STOPPED_CRASHED", # guest crashed
            3: "STOPPED_MIGRATED", # migrated off to another host
            4: "STOPPED_SAVED", # saved to a state file
            5: "STOPPED_FAILED", # host emulator/mgmt failed
            6: "STOPPED_FROM_SNAPSHOT", # offline snapshot loaded
            7: "STOPPED_LAST" }
           ],
        6: ["SHUTDOWN",    {
            0: "SHUTDOWN_FINISHED", # guest finished shutdown sequence
            1: "SHUTDOWN_LAST" }
           ],
        7: ["PMSUSPENDED", {
            0: "PMSUSPENDED_MEMORY", # guest was PM suspended to memory
            1: "PMSUSPENDED_DISK", # guest was PM suspended tp disk
            2: "PMSUSPENDED_LAST" }
           ],
        8: ["LAST", None ]
    }

    def __init__(self, event_type, event_detail):

        if isinstance(event_type, int) and isinstance(event_detail, int):

            self.type = DomainEvent \
                             ._domain_event_infos[event_type][0]
            self.detail = DomainEvent \
                               ._domain_event_infos[event_type][1][event_detail]

        elif isinstance(event_type, str) and isinstance(event_detail, str):

            self.type = event_type
            self.detail = event_detail

        else:

            return None

    def __str__(self):

        return "{event_type}>{event_detail}" \
                   .format(event_type=self.type, event_detail=self.detail)

    def __eq__(self, other):

        logging.debug("comparing events: {event1} {event2}" \
                          .format(event1=self, event2=other))

        if self.type == other.type \
           and self.detail == other.detail:
            return True

        return False

    @classmethod
    def get_event_info(cls, event_type, event_detail):

        """
            get_event_info: Returns both the type name and the detail of the
            DomainEvent
        """

        return (cls._domain_event_infos[event_type][0],
                cls._domain_event_infos[event_type][1][event_detail]) class CloubedException(Exception):

    """ Base class for exceptions in Cloubed """

    def __init__(self, msg):

        super(CloubedException, self).__init__(msg)
        self.msg = msg

    def __str__(self):

        return "{msg}".format(msg=self.msg)
import sys
import os
import logging
import _thread
from cloubed.VirtController import VirtController
from cloubed.StoragePool import StoragePool
from cloubed.StorageVolume import StorageVolume
from cloubed.Domain import Domain
from cloubed.Network import Network
from cloubed.EventManager import EventManager
from cloubed.conf.Configuration import Configuration
from cloubed.conf.ConfigurationLoader import ConfigurationLoader
from cloubed.HTTPServer import HTTPServer
from cloubed.DomainEvent import DomainEvent
from cloubed.CloubedException import CloubedException
#!/usr/bin/python3
# -*- coding: utf-8 -*-
#
# Copyright 2013-2020 Rémi Palancher
#
# This file is part of Cloubed.
#
# Cloubed is free software: you can redistribute it and/or modify
# it under the terms of the GNU Lesser General Public License as
# published by the Free Software Foundation, either version 3 of
# the License, or (at your option) any later version.
#
# Cloubed 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 Lesser General Public License for more details.
#
# You should have received a copy of the GNU Lesser General Public
# License along with Cloubed.  If not, see
# <http://www.gnu.org/licenses/>.

""" Cloubed """



class Singleton(type):

    """ Singleton metaclass """

    __instances = {}
    __lockObj = _thread.allocate_lock()  # lock object

    def __call__(mcs, *args, **kwargs):
        mcs.__lockObj.acquire()
        try:
            if mcs not in mcs.__instances:
                mcs.__instances[mcs] = super(Singleton, mcs) \
                                           .__call__(*args, **kwargs)
        finally:
            #  Exit from critical section whatever happens
            mcs.__lockObj.release()

        return mcs.__instances[mcs]

    def initialized(mcs):

        return mcs in mcs.__instances

class Cloubed(metaclass=Singleton):

    """ Cloubed main class """

    def __init__(self, conf_loader=None):

        #
        # connection to the hypervisor
        #

        self.ctl = VirtController()
        if self.ctl == None:
            logging.error("Failed to open connection to the hypervisor")
            sys.exit(1)

        #
        # EventManager, None at the beginning. Initialized by
        # self.launch_event_manager() in self.wait_event()
        #
        self._event_manager = None

        #
        # parse configuration file
        #
        if conf_loader:
            self._conf_loader = conf_loader
        else:
            configuration_filename = os.path.join(os.getcwd(), "cloubed.yaml")
            self._conf_loader = ConfigurationLoader(configuration_filename)
        self._conf = Configuration(self._conf_loader)
        self._name = self._conf.testbed
    
        #
        # initialize storage pools
        #    
        self._storage_pools = []
        for storage_pool_conf in self._conf.storage_pools:
            logging.info("initializing storage pool {name}" \
                             .format(name=storage_pool_conf.name))
            self._storage_pools.append(StoragePool(self,
                                                   storage_pool_conf))
    
        #
        # initialize storage volumes
        #
        self._storage_volumes = []
        for storage_volume_conf in self._conf.storage_volumes:
            logging.info("initializing storage volume {name}" \
                             .format(name=storage_volume_conf.name))
            self._storage_volumes.append(StorageVolume(self,
                                                       storage_volume_conf))
    
        #
        # initialize networks
        #
        self._networks = []
        for network_conf in self._conf.networks:
            logging.info("initializing network {name}" \
                             .format(name=network_conf.name))

            self._networks.append(Network(self,