Example #1
0
    def hypervisor_hook_xmpp_authenticated(self,
                                           origin=None,
                                           user_info=None,
                                           arguments=None):
        """
        Triggered when we are authenticated. Initializes everything.
        @type origin: L{TNArchipelEnity}
        @param origin: the origin of the hook
        @type user_info: object
        @param user_info: random user information
        @type arguments: object
        @param arguments: runtime argument
        """

        self.xmpp_authenticated = True
        self.central_keepalive_pubsub = TNPubSubNode(
            self.entity.xmppclient, self.entity.pubsubserver,
            ARCHIPEL_KEEPALIVE_PUBSUB)
        self.central_keepalive_pubsub.recover()
        self.central_keepalive_pubsub.subscribe(
            self.entity.jid, self.handle_central_keepalive_event)
        self.entity.log.info(
            "CENTRALDB: entity %s is now subscribed to events from node %s" %
            (self.entity.jid, ARCHIPEL_KEEPALIVE_PUBSUB))
        self.last_keepalive_heard = datetime.datetime.now()
Example #2
0
 def manage_platform_vm_request(self, origin, user_info, arguments):
     """
     Register to pubsub event node /archipel/platform/requests/in
     and /archipel/platform/requests/out
     @type origin: L{TNArchipelEnity}
     @param origin: the origin of the hook
     @type user_info: object
     @param user_info: random user information
     @type arguments: object
     @param arguments: runtime argument
     """
     nodeVMRequestsInName = "/archipel/platform/requests/in"
     self.entity.log.info("PLATFORMREQ: getting the pubsub node %s" %
                          nodeVMRequestsInName)
     self.pubsub_request_in_node = TNPubSubNode(self.entity.xmppclient,
                                                self.entity.pubsubserver,
                                                nodeVMRequestsInName)
     self.pubsub_request_in_node.recover()
     self.entity.log.info("PLATFORMREQ: node %s recovered." %
                          nodeVMRequestsInName)
     self.pubsub_request_in_node.subscribe(self.entity.jid,
                                           self._handle_request_event)
     self.entity.log.info(
         "PLATFORMREQ: entity %s is now subscribed to events from node %s" %
         (self.entity.jid, nodeVMRequestsInName))
     nodeVMRequestsOutName = "/archipel/platform/requests/out"
     self.entity.log.info("PLATFORMREQ: getting the pubsub node %s" %
                          nodeVMRequestsOutName)
     self.pubsub_request_out_node = TNPubSubNode(self.entity.xmppclient,
                                                 self.entity.pubsubserver,
                                                 nodeVMRequestsOutName)
     self.pubsub_request_out_node.recover()
     self.entity.log.info("PLATFORMREQ: node %s recovered." %
                          nodeVMRequestsOutName)
Example #3
0
 def manage_platform_vm_request(self, origin, user_info, arguments):
     """
     Register to pubsub event node /archipel/platform/requests/in
     and /archipel/platform/requests/out
     @type origin: L{TNArchipelEnity}
     @param origin: the origin of the hook
     @type user_info: object
     @param user_info: random user information
     @type arguments: object
     @param arguments: runtime argument
     """
     nodeVMRequestsInName = "/archipel/platform/requests/in"
     self.entity.log.info("PLATFORMREQ: getting the pubsub node %s" % nodeVMRequestsInName)
     self.pubsub_request_in_node = TNPubSubNode(
         self.entity.xmppclient, self.entity.pubsubserver, nodeVMRequestsInName
     )
     self.pubsub_request_in_node.recover()
     self.entity.log.info("PLATFORMREQ: node %s recovered." % nodeVMRequestsInName)
     self.pubsub_request_in_node.subscribe(self.entity.jid.getStripped(), self._handle_request_event)
     self.entity.log.info(
         "PLATFORMREQ: entity %s is now subscribed to events from node %s" % (self.entity.jid, nodeVMRequestsInName)
     )
     nodeVMRequestsOutName = "/archipel/platform/requests/out"
     self.entity.log.info("PLATFORMREQ: getting the pubsub node %s" % nodeVMRequestsOutName)
     self.pubsub_request_out_node = TNPubSubNode(
         self.entity.xmppclient, self.entity.pubsubserver, nodeVMRequestsOutName
     )
     self.pubsub_request_out_node.recover()
     self.entity.log.info("PLATFORMREQ: node %s recovered." % nodeVMRequestsOutName)
Example #4
0
 def manage_vmparking_node(self, origin, user_info, arguments):
     """
     Register to pubsub event node /archipel/platform/requests/in
     and /archipel/platform/requests/out
     @type origin: L{TNArchipelEnity}
     @param origin: the origin of the hook
     @type user_info: object
     @param user_info: random user information
     @type arguments: object
     @param arguments: runtime argument
     """
     nodeVMParkingName = "/archipel/vmparking"
     self.entity.log.info("VMPARKING: getting the pubsub node %s" %
                          nodeVMParkingName)
     self.pubsub_vmparking = TNPubSubNode(self.entity.xmppclient,
                                          self.entity.pubsubserver,
                                          nodeVMParkingName)
     self.pubsub_vmparking.recover(wait=True)
     self.entity.log.info("VMPARKING: node %s recovered." %
                          nodeVMParkingName)
     self.pubsub_vmparking.subscribe(self.entity.jid,
                                     self._handle_request_event,
                                     wait=True)
     self.entity.log.info(
         "VMPARKING: entity %s is now subscribed to events from node %s" %
         (self.entity.jid, nodeVMParkingName))
Example #5
0
 def recover_pubsubs(self, origin, user_info, arguments):
     """
     Get the global tag pubsub node.
     Arguments here are used to be HOOK compliant see register_hook of L{TNHookableEntity}
     """
     # getting the tags pubsub node
     tagsNodeName = "/archipel/tags"
     self.pubSubNodeTags = TNPubSubNode(self.xmppclient, self.pubsubserver, tagsNodeName)
     if not self.pubSubNodeTags.recover(wait=True):
         Exception("The pubsub node /archipel/tags must have been created. You can use archipel-tagnode tool to create it.")
Example #6
0
    def hook_xmpp_authenticated(self,
                                origin=None,
                                user_info=None,
                                arguments=None):
        """
        Triggered when we are authenticated. Initializes everything.
        @type origin: L{TNArchipelEnity}
        @param origin: the origin of the hook
        @type user_info: object
        @param user_info: random user information
        @type arguments: object
        @param arguments: runtime argument
        """

        self.xmpp_authenticated = True
        status = "%s" % ARCHIPEL_XMPP_SHOW_ONLINE
        self.change_presence(self.xmppstatusshow, status)

        self.is_central_agent = False

        self.central_agent_mode = self.configuration.get(
            "CENTRALAGENT", "centralagent")
        self.ping_hypervisors = self.configuration.getboolean(
            "CENTRALAGENT", "ping_hypervisors")
        # 2 possible modes :
        # - auto          : default mode of operation. If there is no other central agent detected,
        #                   the node is acting as central agent. If 2 central agents are declared
        #                   at the same time, an election is performed.
        # - force         : will always be central agent. centralized model when one hypervisor is always online.
        #                   Warning : be sure there is only 1 otherwise they will fight with each other.
        self.log.debug("CENTRALAGENT: Mode %s" % self.central_agent_mode)
        self.central_keepalive_pubsub = TNPubSubNode(
            self.xmppclient, self.pubsubserver, ARCHIPEL_KEEPALIVE_PUBSUB)
        self.central_keepalive_pubsub.recover()
        self.central_keepalive_pubsub.subscribe(
            self.jid, self.handle_central_keepalive_event)
        self.log.info(
            "CENTRALAGENT: entity %s is now subscribed to events from node %s"
            % (self.jid, ARCHIPEL_KEEPALIVE_PUBSUB))

        self.change_presence("away", "Standby")

        if self.central_agent_mode == "force":

            self.become_central_agent()

        elif self.central_agent_mode == "auto":

            self.last_keepalive_heard = datetime.datetime.now()
            self.last_hyp_check = datetime.datetime.now()
Example #7
0
    def hypervisor_hook_xmpp_authenticated(self, origin=None, user_info=None, arguments=None):
        """
        Triggered when we are authenticated. Initializes everything.
        @type origin: L{TNArchipelEnity}
        @param origin: the origin of the hook
        @type user_info: object
        @param user_info: random user information
        @type arguments: object
        @param arguments: runtime argument
        """

        self.xmpp_authenticated  = True
        self.central_keepalive_pubsub = TNPubSubNode(self.entity.xmppclient, self.entity.pubsubserver, ARCHIPEL_KEEPALIVE_PUBSUB)
        self.central_keepalive_pubsub.recover()
        self.central_keepalive_pubsub.subscribe(self.entity.jid, self.handle_central_keepalive_event)
        self.entity.log.info("CENTRALDB: entity %s is now subscribed to events from node %s" % (self.entity.jid, ARCHIPEL_KEEPALIVE_PUBSUB))
        self.last_keepalive_heard = datetime.datetime.now()
Example #8
0
 def manage_vmparking_node(self, origin, user_info, arguments):
     """
     Register to pubsub event node /archipel/platform/requests/in
     and /archipel/platform/requests/out
     @type origin: L{TNArchipelEnity}
     @param origin: the origin of the hook
     @type user_info: object
     @param user_info: random user information
     @type arguments: object
     @param arguments: runtime argument
     """
     nodeVMParkingName = "/archipel/vmparking"
     self.entity.log.info("VMPARKING: getting the pubsub node %s" % nodeVMParkingName)
     self.pubsub_vmparking = TNPubSubNode(self.entity.xmppclient, self.entity.pubsubserver, nodeVMParkingName)
     self.pubsub_vmparking.recover(wait=True)
     self.entity.log.info("VMPARKING: node %s recovered." % nodeVMParkingName)
     self.pubsub_vmparking.subscribe(self.entity.jid, self._handle_request_event, wait=True)
     self.entity.log.info("VMPARKING: entity %s is now subscribed to events from node %s" % (self.entity.jid, nodeVMParkingName))
Example #9
0
    def hook_xmpp_authenticated(self, origin=None, user_info=None, arguments=None):
        """
        Triggered when we are authenticated. Initializes everything.
        @type origin: L{TNArchipelEnity}
        @param origin: the origin of the hook
        @type user_info: object
        @param user_info: random user information
        @type arguments: object
        @param arguments: runtime argument
        """

        self.xmpp_authenticated  = True
        status = "%s" % ARCHIPEL_XMPP_SHOW_ONLINE
        self.change_presence(self.xmppstatusshow, status)

        self.is_central_agent    = False

        self.central_agent_mode  = self.configuration.get("CENTRALAGENT", "centralagent")
        self.ping_hypervisors    = self.configuration.getboolean("CENTRALAGENT", "ping_hypervisors")
        # 2 possible modes :
        # - auto          : default mode of operation. If there is no other central agent detected,
        #                   the node is acting as central agent. If 2 central agents are declared
        #                   at the same time, an election is performed.
        # - force         : will always be central agent. centralized model when one hypervisor is always online.
        #                   Warning : be sure there is only 1 otherwise they will fight with each other.
        self.log.debug("CENTRALAGENT: Mode %s" % self.central_agent_mode)
        self.central_keepalive_pubsub = TNPubSubNode(self.xmppclient, self.pubsubserver, ARCHIPEL_KEEPALIVE_PUBSUB)
        self.central_keepalive_pubsub.recover()
        self.central_keepalive_pubsub.subscribe(self.jid, self.handle_central_keepalive_event)
        self.log.info("CENTRALAGENT: entity %s is now subscribed to events from node %s" % (self.jid, ARCHIPEL_KEEPALIVE_PUBSUB))

        self.change_presence("away", "Standby")

        if self.central_agent_mode=="force":

            self.become_central_agent()

        elif self.central_agent_mode=="auto":

            self.last_keepalive_heard = datetime.datetime.now()
            self.last_hyp_check = datetime.datetime.now()
Example #10
0
class TNPlatformRequests(TNArchipelPlugin):
    def __init__(self, configuration, entity, entry_point_group):
        """
        Initialize the plugin.
        @type configuration: Configuration object
        @param configuration: the configuration
        @type entity: L{TNArchipelEntity}
        @param entity: the entity that owns the plugin
        @type entry_point_group: string
        @param entry_point_group: the group name of plugin entry_point
        """
        TNArchipelPlugin.__init__(self,
                                  configuration=configuration,
                                  entity=entity,
                                  entry_point_group=entry_point_group)
        self.pubsub_request_in_node = None
        self.pubsub_request_out_node = None
        self.computing_unit = None
        # get eventual computing unit plugin
        self.load_computing_unit()
        # creates permissions
        self.entity.permission_center.create_permission(
            "platform_allocvm",
            "Authorizes user to send cross platform request", True)
        # register to the node vmrequest
        self.entity.register_hook("HOOK_ARCHIPELENTITY_XMPP_AUTHENTICATED",
                                  method=self.manage_platform_vm_request)

    ### Plugin interface

    def register_handlers(self):
        """
        This method will be called by the plugin user when it will be
        necessary to register module for listening to stanza.
        """
        self.entity.xmppclient.RegisterHandler('iq',
                                               self.process_iq,
                                               ns=ARCHIPEL_NS_PLATFORM)

    def unregister_handlers(self):
        """
        Unregister the handlers.
        """
        self.entity.xmppclient.UnregisterHandler('iq',
                                                 self.process_iq,
                                                 ns=ARCHIPEL_NS_PLATFORM)

    @staticmethod
    def plugin_info():
        """
        Return informations about the plugin.
        @rtype: dict
        @return: dictionary contaning plugin informations
        """
        plugin_friendly_name = "Hypervisor Platform Request"
        plugin_identifier = "platformrequest"
        plugin_configuration_section = None
        plugin_configuration_tokens = []
        return {
            "common-name": plugin_friendly_name,
            "identifier": plugin_identifier,
            "configuration-section": plugin_configuration_section,
            "configuration-tokens": plugin_configuration_tokens
        }

    ### Plugin loading

    def load_computing_unit(self):
        """
        Loads the external computing unit.
        """
        for factory_method in iter_entry_points(
                group="archipel.plugin.platform.computingunit",
                name="factory"):
            method = factory_method.load()
            plugin_content = method()
            self.computing_unit = plugin_content["plugin"]
            self.entity.log.info("PLATFORMREQ: loading computing unit %s" %
                                 plugin_content["info"]["common-name"])
            break
        if not self.computing_unit:
            self.computing_unit = TNBasicPlatformScoreComputing()
            self.entity.log.info("PLATFORMREQ: using default computing unit.")

    ### Performs platform actions

    def perform_score_computing(self, request):
        """
        Compute the score for the given request.
        @type request: string
        @param request: the requested action name
        @rtype: float
        @return: the score computed by the computing unit ([0.0, 1.0])
        """
        return self.computing_unit.score(request)

    ### Pubsub management

    def manage_platform_vm_request(self, origin, user_info, arguments):
        """
        Register to pubsub event node /archipel/platform/requests/in
        and /archipel/platform/requests/out
        @type origin: L{TNArchipelEnity}
        @param origin: the origin of the hook
        @type user_info: object
        @param user_info: random user information
        @type arguments: object
        @param arguments: runtime argument
        """
        nodeVMRequestsInName = "/archipel/platform/requests/in"
        self.entity.log.info("PLATFORMREQ: getting the pubsub node %s" %
                             nodeVMRequestsInName)
        self.pubsub_request_in_node = TNPubSubNode(self.entity.xmppclient,
                                                   self.entity.pubsubserver,
                                                   nodeVMRequestsInName)
        self.pubsub_request_in_node.recover()
        self.entity.log.info("PLATFORMREQ: node %s recovered." %
                             nodeVMRequestsInName)
        self.pubsub_request_in_node.subscribe(self.entity.jid,
                                              self._handle_request_event)
        self.entity.log.info(
            "PLATFORMREQ: entity %s is now subscribed to events from node %s" %
            (self.entity.jid, nodeVMRequestsInName))
        nodeVMRequestsOutName = "/archipel/platform/requests/out"
        self.entity.log.info("PLATFORMREQ: getting the pubsub node %s" %
                             nodeVMRequestsOutName)
        self.pubsub_request_out_node = TNPubSubNode(self.entity.xmppclient,
                                                    self.entity.pubsubserver,
                                                    nodeVMRequestsOutName)
        self.pubsub_request_out_node.recover()
        self.entity.log.info("PLATFORMREQ: node %s recovered." %
                             nodeVMRequestsOutName)

    def _handle_request_event(self, event):
        """
        Triggered when a platform wide virtual machine request is received.
        @type event: xmpp.Node
        @param event: the push event
        """
        items = event.getTag("event").getTag("items").getTags("item")
        for item in items:
            try:
                item_publisher = xmpp.JID(item.getAttr("publisher"))
            except Exception as ex:
                self.entity.log.error(
                    "The pubsub node has not 'publisher' tag. This is a bug in ejabberd, not the fault of Archipel. You can find a patch here for ejabberd here https://support.process-one.net/browse/EJAB-1347"
                )
                continue
            if not item_publisher.getStripped() == self.entity.jid.getStripped(
            ):
                try:
                    self.entity.log.info(
                        "PLATFORMREQ: received a platform-wide virtual machine request from %s"
                        % item_publisher)
                    request_uuid = item.getTag("archipel").getAttr("uuid")
                    request_action = item.getTag("archipel").getAttr("action")
                    if not self.entity.permission_center.check_permission(
                            item_publisher.getStripped(),
                            "platform_%s" % request_action):
                        self.entity.log.warning(
                            "User %s have no permission to perform platform action %s"
                            % (item_publisher, request_action))
                        return
                    score = self.perform_score_computing(item)
                    if score:
                        answer_node = xmpp.Node("archipel",
                                                attrs={
                                                    "uuid": request_uuid,
                                                    "score": score
                                                })
                        self.pubsub_request_out_node.add_item(answer_node)
                except Exception as ex:
                    self.entity.log.error(
                        "PLATFORMREQ: seems that request is not valid (%s) %s"
                        % (str(ex), str(item)))

    ### XMPP Management

    def process_iq(self, conn, iq):
        """
        This method is invoked when a ARCHIPEL_NS_PLATFORM IQ is received.
        It understands IQ of type:
            - allocvm
        @type conn: xmpp.Dispatcher
        @param conn: ths instance of the current connection that send the stanza
        @type iq: xmpp.Protocol.Iq
        @param iq: the received IQ
        """
        reply = None
        action = self.entity.check_acp(conn, iq)
        self.entity.check_perm(conn, iq, action, -1, prefix="platform_")
        if action == "allocvm":
            reply = self.iq_allocvm(iq)
        if reply:
            conn.send(reply)
            raise xmpp.protocol.NodeProcessed

    def iq_allocvm(self, iq):
        """
        Alloc a new VM on the hypervisor.
        @type iq: xmpp.Protocol.Iq
        @param iq: the received IQ
        @rtype: xmpp.Protocol.Iq
        @return: a ready to send IQ containing the result of the action
        """
        try:
            reply = iq.buildReply("result")
            self.entity.alloc(requester=iq.getFrom(), requested_name=None)
        except Exception as ex:
            reply = build_error_iq(self, ex, iq)
        return reply
Example #11
0
class TNArchipelCentralAgent (TNArchipelEntity, TNHookableEntity, TNAvatarControllableEntity, TNTaggableEntity):
    """
    This class represents a Central Agent XMPP Capable. This is a XMPP client
    which manages a central database containing all hypervisors and all vms
    in the system, and send regular pings to all hypervisors.
    """

    def __init__(self, jid, password, configuration):
        """
        This is the constructor of the class.
        @type jid: string
        @param jid: the jid of the hypervisor
        @type password: string
        @param password: the password associated to the JID
        """
        TNArchipelEntity.__init__(self, jid, password, configuration, "central-agent")
        self.log.info("Starting Archipel central agent")

        self.xmppserveraddr            = self.jid.getDomain()
        self.entity_type               = "central-agent"
        self.default_avatar            = self.configuration.get("CENTRALAGENT", "central_agent_default_avatar")
        self.libvirt_event_callback_id = None
        self.vcard_infos               = {}

        self.vcard_infos["TITLE"]      = "Central agent"

        self.log.info("Server address defined as %s" % self.xmppserveraddr)

        # start the permission center
        self.permission_db_file = self.configuration.get("CENTRALAGENT", "centralagent_permissions_database_path")
        self.permission_center.start(database_file=self.permission_db_file)
        self.init_permissions()

        # action on auth
        self.register_hook("HOOK_ARCHIPELENTITY_XMPP_AUTHENTICATED", method=self.hook_xmpp_authenticated)
        self.register_hook("HOOK_ARCHIPELENTITY_XMPP_AUTHENTICATED", method=self.manage_vcard_hook)

        # create hooks
        self.create_hook("HOOK_CENTRALAGENT_VM_REGISTERED")
        self.create_hook("HOOK_CENTRALAGENT_VM_UNREGISTERED")
        self.create_hook("HOOK_CENTRALAGENT_HYP_REGISTERED")
        self.create_hook("HOOK_CENTRALAGENT_HYP_UNREGISTERED")


        self.central_agent_jid_val = None

        self.xmpp_authenticated   = False
        self.is_central_agent     = False
        self.salt                 = random.random()
        self.random_wait          = random.random()
        self.database             = sqlite3.connect(self.configuration.get("CENTRALAGENT", "database"), check_same_thread=False)
        self.database.row_factory = sqlite3.Row

        # defining the structure of the keepalive pubsub event
        self.keepalive_event      = xmpp.Node("event",attrs={"type":"keepalive","jid":self.jid})
        self.last_keepalive_heard = datetime.datetime.now()
        self.last_hyp_check       = datetime.datetime.now()
        self.required_stats_xml   = None

        # module inits
        self.initialize_modules('archipel.plugin.core')
        self.initialize_modules('archipel.plugin.centralagent')

        module_platformrequest    = self.configuration.get("MODULES", "platformrequest")

        if module_platformrequest:

            required_stats = self.get_plugin("platformrequest").computing_unit.required_stats
            self.required_stats_xml = xmpp.Node("required_stats")

            for stat in required_stats:

                self.log.debug("CENTRALAGENT: stat : %s" % stat)
                self.required_stats_xml.addChild("stat", attrs=stat)

            self.keepalive_event.addChild(node = self.required_stats_xml)

    ### Utilities

    def init_permissions(self):
        """
        Initialize the permissions.
        """
        TNArchipelEntity.init_permissions(self)

    def register_handlers(self):
        """
        This method will be called by the plugin user when it will be
        necessary to register module for listening to stanza.
        """
        TNArchipelEntity.register_handlers(self)
        self.xmppclient.RegisterHandler('iq', self.process_iq_for_centralagent, ns=ARCHIPEL_NS_CENTRALAGENT)

    def unregister_handlers(self):
        """
        Unregister the handlers.
        """
        TNArchipelEntity.unregister_handlers(self)
        self.xmppclient.UnregisterHandler('iq', self.process_iq_for_centralagent, ns=ARCHIPEL_NS_CENTRALAGENT)

    def process_iq_for_centralagent(self, conn, iq):
        """
        This method is invoked when a ARCHIPEL_NS_CENTRALAGENT IQ is received.
        It understands IQ of type:
            - read_hypervisors
            - read_vms
            - get_existing_vms_instances
            - register_hypervisors
            - register_vms
            - update_vms
            - update_hypervisors
            - unregister_hypervisors
            - unregister_vms
        @type conn: xmpp.Dispatcher
        @param conn: ths instance of the current connection that send the stanza
        @type iq: xmpp.Protocol.Iq
        @param iq: the received IQ
        """
        reply = None
        action = self.check_acp(conn, iq)
        if action == "read_hypervisors":
            reply = self.iq_read_hypervisors(iq)
        elif action == "read_vms":
            reply = self.iq_read_vms(iq)
        elif action == "get_existing_vms_instances":
            reply = self.iq_get_existing_vms_instances(iq)
        elif action == "register_hypervisors":
            reply = self.iq_register_hypervisors(iq)
        elif action == "register_vms":
            reply = self.iq_register_vms(iq)
        elif action == "update_vms":
            reply = self.iq_update_vms(iq)
        elif action == "update_vms_domain":
            reply = self.iq_update_vms_domain(iq)
        elif action == "update_hypervisors":
            reply = self.iq_update_hypervisors(iq)
        elif action == "unregister_hypervisors":
            reply = self.iq_unregister_hypervisors(iq)
        elif action == "unregister_vms":
            reply = self.iq_unregister_vms(iq)
        if reply:
            conn.send(reply)
            raise xmpp.protocol.NodeProcessed

    ### Pubsub management

    def hook_xmpp_authenticated(self, origin=None, user_info=None, arguments=None):
        """
        Triggered when we are authenticated. Initializes everything.
        @type origin: L{TNArchipelEnity}
        @param origin: the origin of the hook
        @type user_info: object
        @param user_info: random user information
        @type arguments: object
        @param arguments: runtime argument
        """

        self.xmpp_authenticated  = True
        status = "%s" % ARCHIPEL_XMPP_SHOW_ONLINE
        self.change_presence(self.xmppstatusshow, status)

        self.is_central_agent    = False

        self.central_agent_mode  = self.configuration.get("CENTRALAGENT", "centralagent")
        self.ping_hypervisors    = self.configuration.getboolean("CENTRALAGENT", "ping_hypervisors")
        # 2 possible modes :
        # - auto          : default mode of operation. If there is no other central agent detected,
        #                   the node is acting as central agent. If 2 central agents are declared
        #                   at the same time, an election is performed.
        # - force         : will always be central agent. centralized model when one hypervisor is always online.
        #                   Warning : be sure there is only 1 otherwise they will fight with each other.
        self.log.debug("CENTRALAGENT: Mode %s" % self.central_agent_mode)
        self.central_keepalive_pubsub = TNPubSubNode(self.xmppclient, self.pubsubserver, ARCHIPEL_KEEPALIVE_PUBSUB)
        self.central_keepalive_pubsub.recover()
        self.central_keepalive_pubsub.subscribe(self.jid, self.handle_central_keepalive_event)
        self.log.info("CENTRALAGENT: entity %s is now subscribed to events from node %s" % (self.jid, ARCHIPEL_KEEPALIVE_PUBSUB))

        self.change_presence("away", "Standby")

        if self.central_agent_mode=="force":

            self.become_central_agent()

        elif self.central_agent_mode=="auto":

            self.last_keepalive_heard = datetime.datetime.now()
            self.last_hyp_check = datetime.datetime.now()

    def become_central_agent(self):
        """
        triggered when becoming active central agent
        """
        self.is_central_agent = True
        self.manage_database()
        initial_keepalive      = xmpp.Node("event",attrs={"type":"keepalive","jid":self.jid})
        initial_keepalive.setAttr("force_update","true")
        initial_keepalive.setAttr("salt",self.salt)
        now = datetime.datetime.now()
        now_string = now.strftime("%Y-%m-%d %H:%M:%S.%f")
        initial_keepalive.setAttr("central_agent_time", now_string)

        if self.required_stats_xml:

            initial_keepalive.addChild(node = self.required_stats_xml)

        self.central_keepalive_pubsub.add_item(initial_keepalive)
        self.log.debug("CENTRALAGENT: initial keepalive sent")
        self.last_keepalive_sent = datetime.datetime.now()
        self.last_hyp_check = datetime.datetime.now()
        self.change_presence("","Active")

    def central_agent_jid(self):
        """
        Returns the jid of the central agent. In case we are a VM, query hypervisor.
        """
        return self.central_agent_jid_val

    def keepalive_event_with_date(self):
        """
        Returns the keepalive event with current date, to send to the pubsub
        so that all ping calculations are based on central agent date.
        """
        now = datetime.datetime.now()
        now_string = now.strftime("%Y-%m-%d %H:%M:%S.%f")
        keepalive_event = self.keepalive_event
        keepalive_event.setAttr("central_agent_time", now_string)

        return keepalive_event


    def handle_central_keepalive_event(self,event):
        """
        Called when the central agents announce themselves.
        @type event: xmpp.Node
        @param event: the pubsub event node
        """
        items = event.getTag("event").getTag("items").getTags("item")

        for item in items:

            central_announcement_event = item.getTag("event")
            event_type                 = central_announcement_event.getAttr("type")

            if event_type == "keepalive":

                old_central_agent_jid = self.central_agent_jid()
                keepalive_jid = xmpp.JID(central_announcement_event.getAttr("jid"))

                if self.is_central_agent and keepalive_jid != self.jid:

                    # detect another central agent
                    self.log.warning("CENTRALAGENT: another central agent detected, performing election")
                    keepalive_salt = float(central_announcement_event.getAttr("salt"))

                    if keepalive_salt > self.salt:
                        self.log.debug("CENTRALAGENT: stepping down")
                        self.change_presence("away","Standby")
                        self.is_central_agent = False
                    else:
                        self.log.debug("CENTRALAGENT: election won")
                        return

                self.central_agent_jid_val = keepalive_jid
                self.last_keepalive_heard  = datetime.datetime.now()

    def iq_read_hypervisors(self,iq):
        """
        Called when the central agent receives a hypervisor read event.
        @type iq: xmpp.Iq
        @param iq: received Iq
        """
        try:
            read_event = iq.getTag("query").getTag("archipel").getTag("event")
            columns = read_event.getAttr("columns")
            where_statement = read_event.getAttr("where_statement")
            reply = iq.buildReply("result")
            entries = self.read_hypervisors(columns, where_statement)
            for entry in self.pack_entries(entries):
                reply.addChild(node = entry)
        except Exception as ex:
            reply = build_error_iq(self, ex, iq, ARCHIPEL_ERROR_CODE_CENTRALAGENT)
        return reply

    def iq_read_vms(self,iq):
        """
        Called when the central agent receives a vms read event.
        @type iq: xmpp.Iq
        @param iq: received Iq
        """
        try:
            read_event = iq.getTag("query").getTag("archipel").getTag("event")
            columns = read_event.getAttr("columns")
            where_statement = read_event.getAttr("where_statement")
            reply = iq.buildReply("result")
            entries = self.read_vms(columns, where_statement)
            for entry in self.pack_entries(entries):
                reply.addChild(node = entry)
        except Exception as ex:
            reply = build_error_iq(self, ex, iq, ARCHIPEL_ERROR_CODE_CENTRALAGENT)
        return reply

    def iq_get_existing_vms_instances(self,iq):
        """
        Called when the central agent receives a request to check if entities
        are already defined elsewhere
        @type iq: xmpp.Iq
        @param iq: received Iq
        """
        try:
            read_event = iq.getTag("query").getTag("archipel").getTag("event")
            entries = self.unpack_entries(iq)
            self.log.debug("CENTRALAGENT: iq_get_existing_vms_instances : iq : %s, entries : %s" % (iq, entries))
            origin_hyp = iq.getFrom()
            reply = iq.buildReply("result")
            entries = self.get_existing_vms_instances(entries, origin_hyp)
            for entry in self.pack_entries(entries):
                reply.addChild(node = entry)
        except Exception as ex:
            reply = build_error_iq(self, ex, iq, ARCHIPEL_ERROR_CODE_CENTRALAGENT)
        return reply

    def iq_register_hypervisors(self,iq):
        """
        Called when the central agent receives a hypervisor registration event.
        @type iq: xmpp.Iq
        @param iq: received Iq
        """
        try:
            reply = iq.buildReply("result")
            entries = self.unpack_entries(iq)
            self.register_hypervisors(entries)
            self.perform_hooks("HOOK_CENTRALAGENT_HYP_REGISTERED", entries)
        except Exception as ex:
            reply = build_error_iq(self, ex, iq, ARCHIPEL_ERROR_CODE_CENTRALAGENT)
        return reply

    def iq_register_vms(self,iq):
        """
        Called when the central agent receives a vms registration event.
        @type iq: xmpp.Iq
        @param iq: received Iq
        """
        try:
            reply = iq.buildReply("result")
            entries = self.unpack_entries(iq)
            self.register_vms(entries)
            self.perform_hooks("HOOK_CENTRALAGENT_VM_REGISTERED", entries)
        except Exception as ex:
            reply = build_error_iq(self, ex, iq, ARCHIPEL_ERROR_CODE_CENTRALAGENT)
        return reply

    def iq_update_vms(self,iq):
        """
        Called when the central agent receives a vms update event.
        @type iq: xmpp.Iq
        @param iq: received Iq
        """
        try:
            reply = iq.buildReply("result")
            entries = self.unpack_entries(iq)
            self.update_vms(entries)
        except Exception as ex:
            reply = build_error_iq(self, ex, iq, ARCHIPEL_ERROR_CODE_CENTRALAGENT)
        return reply

    def iq_update_vms_domain(self,iq):
        """
        Called when the central agent receives a vm domain update event.
        @type iq: xmpp.Iq
        @param iq: received Iq
        """
        try:
            reply = iq.buildReply("result")
            entries = self.unpack_entries(iq)
            entries = self.update_vms_domain(entries)
            for entry in self.pack_entries(entries):
                reply.addChild(node = entry)
        except Exception as ex:
            reply = build_error_iq(self, ex, iq, ARCHIPEL_ERROR_CODE_CENTRALAGENT)
        return reply

    def iq_update_hypervisors(self,iq):
        """
        Called when the central agent receives a vms update event.
        @type iq: xmpp.Iq
        @param iq: received Iq
        """
        try:
            reply = iq.buildReply("result")
            entries = self.unpack_entries(iq)
            self.update_hypervisors(entries)
        except Exception as ex:
            reply = build_error_iq(self, ex, iq, ARCHIPEL_ERROR_CODE_CENTRALAGENT)
        return reply

    def iq_unregister_hypervisors(self,iq):
        """
        Called when the central agent receives a hypervisor unregistration event.
        @type iq: xmpp.Iq
        @param iq: received Iq
        """
        try:
            reply = iq.buildReply("result")
            entries = self.unpack_entries(iq)
            self.unregister_hypervisors(entries)
            self.perform_hooks("HOOK_CENTRALAGENT_HYP_UNREGISTERED", entries)
        except Exception as ex:
            reply = build_error_iq(self, ex, iq, ARCHIPEL_ERROR_CODE_CENTRALAGENT)
        return reply

    def iq_unregister_vms(self,iq):
        """
        Called when the central agent receives a vms registration event.
        @type iq: xmpp.Iq
        @param iq: received Iq
        """
        try:
            reply = iq.buildReply("result")
            in_entries = self.unpack_entries(iq)
            out_entries = self.unregister_vms(in_entries)
            self.perform_hooks("HOOK_CENTRALAGENT_VM_UNREGISTERED", out_entries)
            for entry in self.pack_entries(out_entries):
                reply.addChild(node = entry)
        except Exception as ex:
            reply = build_error_iq(self, ex, iq, ARCHIPEL_ERROR_CODE_CENTRALAGENT)
        return reply

    def read_hypervisors(self, columns, where_statement):
        """
        Reads list of hypervisors in central db.
        """
        read_statement = "select %s from hypervisors" % columns
        if where_statement:
            read_statement += " where %s" % where_statement
        rows = self.database.execute(read_statement)
        ret = []
        for row in rows:
            ret.append({"jid":row[0], "last_seen":row[1], "status":row[2]})
        return ret

    def read_vms(self, columns, where_statement):
        """
        Read list of vms in central db.
        """
        read_statement = "select %s from vms" % columns
        if where_statement:
            read_statement += " where %s" % where_statement
        rows = self.database.execute(read_statement)
        ret = []
        for row in rows:
            if columns == "*":
                ret.append({"uuid":row[0], "parker":row[1], "creation_date":row[2], "domain":row[3], "hypervisor":row[4]})
            else:
                res = {}
                i = 0
                for col in columns.split(","):
                    res[col]=row[i]
                    i+=1
                ret.append(res)
        return ret

    def get_existing_vms_instances(self, entries, origin_hyp):
        """
        Based on a list of vms, and an hypervisor, return list of vms which
        are defined in another, currently running, hypervisor.
        """
        uuids = []
        for entry in entries:
            uuids.append(entry["uuid"])
        read_statement = "select vms.uuid from vms join hypervisors on hypervisors.jid=vms.hypervisor where (vms.uuid='"
        read_statement += "' or vms.uuid='".join(uuids)
        read_statement += "') and hypervisors.jid != '%s'" % origin_hyp
        read_statement += " and hypervisors.status='Online'"

        self.log.debug("CENTRALAGENT: Check if vm uuids %s exist elsewhere " % uuids)
        rows = self.database.execute(read_statement)
        ret = []
        for row in rows:
            ret.append({"uuid":row[0]})
        self.log.debug("CENTRALAGENT: We found %s on %s vms existing on others hypervistors." % (len(ret), len(uuids)))
        return ret

    def read_parked_vms(self, entries):
        """
        Based on a list of vms, and an hypervisor, return list of vms which
        are parked (have no hypervisor, or have a hypervisor which is not online)
        """
        uuids = []
        for entry in entries:
            uuids.append(entry["uuid"])
        read_statement = "select uuid, domain from vms where uuid=('"
        read_statement += "' or vms.uuid='".join(uuids)
        read_statement += "') and hypervisor='None' or hypervisor not in (select jid from hypervisors where status='Online')"
        self.log.debug("CENTRALDB: Get parked vms from database")

        rows = self.database.execute(read_statement)
        ret = []
        for row in rows:
            ret.append({"uuid":row[0], "domain":row[1]})
        self.log.debug("CENTRALDB: We found %s parked vms" % len(ret))
        return ret

    def register_hypervisors(self,entries):
        """
        Register a list of hypervisors into central db.
        @type entries: List
        @param entries: list of hypervisors
        """
        self.db_commit("insert into hypervisors values(:jid, :last_seen, :status, :stat1, :stat2, :stat3)", entries)

    def register_vms(self,entries):
        """
        Register a list of vms into central db.
        @type entries: List
        @param entries: list of vms
        """
        self.db_commit("insert into vms values(:uuid, :parker, :creation_date, :domain, :hypervisor)",entries)

    def update_vms(self,entries):
        """
        Update a list of vms in central db.
        @type entries: List
        @param entries: list of vms
        """
        update_snipplets=[]
        for key,val in entries[0].iteritems():
            if key!="uuid":
                update_snipplets.append("%s=:%s" % (key, key))
        command = "update vms set %s where uuid=:uuid" % (", ".join(update_snipplets))
        self.db_commit(command, entries)

    def update_vms_domain(self,entries):
        """
        Update a list of vms domain in central db.
        Performs extra checks compared to a raw update.
        @type entries: List
        @param entries: list of vms
        """
        results = []

        # we check if vms are really parked
        parked_vms_ret = self.read_parked_vms(entries)
        parked_vms = {}
        for ret in parked_vms_ret:
            parked_vms[ret["uuid"]] = {"domain":xmpp.simplexml.NodeBuilder(data=ret["domain"]).getDom()}
        entries_to_commit = []

        for i in range(len(entries)):
            entry = entries[i]
            error = False
            for key,val in entry.iteritems():
                if key == "domain":
                    new_domain = xmpp.simplexml.NodeBuilder(data=val).getDom()
                if key == "uuid":
                    uuid = val
            if uuid not in parked_vms.keys():
                result = "ERROR: There is no virtual machine parked with uuid %s" % uuid
                error = True
            else:
                old_domain = parked_vms[uuid]["domain"]

                previous_uuid = old_domain.getTag("uuid").getData()
                previous_name = old_domain.getTag("name").getData()
                new_uuid = ""
                new_name = ""
                if new_domain.getTag("uuid"):
                    new_uuid = new_domain.getTag("uuid").getData()
                if new_domain.getTag("name"):
                    new_name = new_domain.getTag("name").getData()

                if not previous_uuid.lower() == new_uuid.lower():
                    result = "ERROR: UUID of new description must be the same (was %s, is %s)" % (previous_uuid, new_uuid)
                    error = True
                if not previous_name.lower() == new_name.lower():
                    result = "ERROR: Name of new description must be the same (was %s, is %s)" % (previous_name, new_name)
                    error = True
                if not new_name or new_name == "":
                    result = "ERROR: Missing name information"
                    error = True
                if not new_uuid or new_uuid == "":
                    result = "ERROR: Missing UUID information"
                    error = True
                if not error:
                    # all checks ok, performing update
                    if new_domain.getTag('description'):
                        new_domain.delChild("description")
                    previous_description = old_domain.getTag("description")
                    self.log.debug("CENTRALAGENT: previous description : %s" % str(previous_description))
                    new_domain.addChild(node=old_domain.getTag("description"))
                    result = "Central database updated with new information"
                    entries_to_commit.append({"uuid": uuid, "domain": str(new_domain)})
                results.append({"result": result, "uuid": uuid, "error": error})

        if len(entries_to_commit) >0 :
            command = "update vms set domain=:domain where uuid=:uuid"
            self.db_commit(command, entries_to_commit)
        return results

    def update_hypervisors(self,entries):
        """
        Update a list of hypervisors in central db.
        @type entries: List
        @param entries: list of vms
        """
        update_snipplets=[]
        for key,val in entries[0].iteritems():
            if key!="jid":
                update_snipplets.append("%s=:%s" % (key, key))
        command = "update hypervisors set %s where jid=:jid" % (", ".join(update_snipplets))
        self.db_commit(command, entries)

    def unregister_hypervisors(self,entries):
        """
        Unregister a list of hypervisors from central db.
        @type entries: List
        @param entries: list of hypervisors
        """
        self.db_commit("delete from hypervisors where jid=:jid, last_seen=:last_seen, status=:status",entries)

    def unregister_vms(self, entries):
        """
        Unregister a list of vms from central db.
        @type entries: List
        @param entries: list of vms
        """
        # first, we extract jid so that the hypervisor can unregister them
        uuids = []
        for entry in entries:
            uuids.append(entry["uuid"])
        where_statement = "uuid = '"
        where_statement += "' or uuid='".join(uuids)
        where_statement += "'"

        # list of vms which have been found in central db, including uuid and jid
        cleaned_entries = self.read_vms("uuid,domain", where_statement)
        for i in range(len(cleaned_entries)):
            domain_xml =  cleaned_entries[i]["domain"]
            if domain_xml != "None":
                domain = xmpp.simplexml.NodeBuilder(data=cleaned_entries[i]["domain"]).getDom()
                cleaned_entries[i]["jid"] = xmpp.JID(domain.getTag("description").getData().split("::::")[0])
                del(cleaned_entries[i]["domain"])


        self.db_commit("delete from vms where uuid=:uuid",cleaned_entries)
        return cleaned_entries

    def unpack_entries(self, iq):
        """
        Unpack the list of entries from iq for database processing.
        @type iq: xmpp.Iq
        @param event: received Iq
        """
        central_database_event = iq.getTag("query").getTag("archipel").getTag("event")
        command = central_database_event.getAttr("command")
        entries=[]
        for entry in central_database_event.getChildren():
            entry_dict={}
            for entry_val in entry.getChildren():
                entry_dict[entry_val.getAttr("key")]=entry_val.getAttr("value")
            entries.append(entry_dict)
        return entries

    def pack_entries(self, entries):
        """
        Pack the list of entries to send to remote entity.
        @rtype: list
        @return: list of xmpp nodes, one per entry
        @type entries: list
        @param entries: list of dict entities
        """
        packed_entries = []
        for entry in entries:
            entryTag = xmpp.Node(tag="entry")
            for key,value in entry.iteritems():
                entryTag.addChild("item",attrs={"key":key,"value":value})
            packed_entries.append(entryTag)
        return packed_entries

    def db_commit(self, command, entries):
        if self.is_central_agent:
            self.log.debug("CENTRALAGENT: commit '%s' with entries %s" % (command, entries) )
            self.database.executemany(command, entries)
            self.database.commit()
        else:
            raise Exception("CENTRALAGENT: we are not central agent")

    def check_hyps(self):
        """
        Check that hypervisors are alive.
        """
        self.log.debug("CENTRALAGENT: Checking hypervisors state")
        now = datetime.datetime.now()
        rows = self.database.execute("select jid,last_seen,status from hypervisors;")
        hypervisor_to_update = []

        for row in rows:
            jid, last_seen, status = row
            last_seen_date = datetime.datetime.strptime(last_seen, "%Y-%m-%d %H:%M:%S.%f")
            if (now - last_seen_date).seconds > ARCHIPEL_CENTRAL_HYP_CHECK_TIMEOUT and status == "Online":
                self.log.warning("CENTRALAGENT: Hypervisor %s timed out" % jid)
                hypervisor_to_update.append({"jid": jid, "status": "Unreachable"})
            elif (now - last_seen_date).seconds <= ARCHIPEL_CENTRAL_HYP_CHECK_TIMEOUT and status == "Unreachable":
                self.log.info("CENTRALAGENT: Hypervisor %s is back up Online" % jid)
                hypervisor_to_update.append({"jid": jid, "status": "Online"})

        if hypervisor_to_update:
            self.update_hypervisors(hypervisor_to_update)

        self.last_hyp_check = datetime.datetime.now()

    ### Database Management

    def manage_database(self):
        """
        Create and / or recover the parking database
        """
        self.database.execute("create table if not exists vms (uuid text unique on conflict replace, parker string, creation_date date, domain string, hypervisor string)")
        self.database.execute("create table if not exists hypervisors (jid text unique on conflict replace, last_seen date, status string, stat1 int, stat2 int, stat3 int)")
        #By default on startup, put everything in the parking. Hypervisors will announce their vms.
        self.database.execute("update vms set hypervisor='None';")
        self.database.commit()

    ### Event loop

    def on_xmpp_loop_tick(self):

        if self.xmpp_authenticated:

            if not self.is_central_agent and self.central_agent_mode=="auto":

                # before becoming a central agent, we wait for timeout period plus a random amount of time
                # to avoid race conditions
                central_agent_timeout = ARCHIPEL_CENTRAL_AGENT_TIMEOUT * ( 1 + self.random_wait )

                if (datetime.datetime.now() - self.last_keepalive_heard).seconds > central_agent_timeout:
                    self.log.info("CENTRALAGENT: has not detected any central agent for the last %s seconds, becoming central agent." % central_agent_timeout)
                    self.become_central_agent()

            elif self.is_central_agent: # we are central agent
                if (datetime.datetime.now() - self.last_keepalive_sent).seconds >= ARCHIPEL_CENTRAL_AGENT_KEEPALIVE:
                    self.central_keepalive_pubsub.add_item(self.keepalive_event_with_date())
                    self.last_keepalive_sent = datetime.datetime.now()

                if self.ping_hypervisors:
                    if (datetime.datetime.now() - self.last_hyp_check).seconds >= ARCHIPEL_CENTRAL_HYP_CHECK_FREQUENCY:
                        self.check_hyps()
Example #12
0
class TNVMParking (TNArchipelPlugin):

    def __init__(self, configuration, entity, entry_point_group):
        """
        Initialize the plugin.
        @type configuration: Configuration object
        @param configuration: the configuration
        @type entity: L{TNArchipelEntity}
        @param entity: the entity that owns the plugin
        @type entry_point_group: string
        @param entry_point_group: the group name of plugin entry_point
        """
        TNArchipelPlugin.__init__(self, configuration=configuration, entity=entity, entry_point_group=entry_point_group)
        self.pubsub_vmparking = None;
        self.inhibit_next_general_push = None

        # creates permissions
        self.entity.permission_center.create_permission("vmparking_park", "Authorizes user to park a virtual machines", False)

        if isinstance(self.entity, TNArchipelHypervisor):
            self.entity.permission_center.create_permission("vmparking_list", "Authorizes user to list virtual machines in parking", False)
            self.entity.permission_center.create_permission("vmparking_unpark", "Authorizes user to unpark a virtual machines", False)
            self.entity.permission_center.create_permission("vmparking_delete", "Authorizes user to delete parked virtual machines", False)
            self.entity.permission_center.create_permission("vmparking_updatexml", "Authorizes user to delete parked virtual machines", False)

        # vocabulary
        if isinstance(self.entity, TNArchipelHypervisor):
            registrar_items = [{    "commands" : ["park"],
                                    "parameters": [{"name": "identifiers", "description": "the UUIDs of the VM to park, separated with comas, with no space"}],
                                    "method": self.message_park_hypervisor,
                                    "permissions": ["vmparking_park"],
                                    "description": "Park the virtual machine with the given UUIDs"},

                                {   "commands" : ["unpark"],
                                    "parameters": [{"name": "identifiers", "description": "UUIDs of the virtual machines or parking tickets, separated by comas, with no space"}],
                                    "method": self.message_unpark,
                                    "permissions": ["vmparking_unpark"],
                                    "description": "Unpark the virtual machine parked with the given identifier"},

                                {    "commands" : ["park list"],
                                     "parameters": [],
                                     "method": self.message_list,
                                     "permissions": ["vmparking_list"],
                                     "description": "List all parked virtual machines" }
                                ]
        elif isinstance(self.entity, TNArchipelVirtualMachine):
            registrar_items = [{    "commands" : ["park"],
                                    "parameters": [],
                                    "method": self.message_park_vm,
                                    "permissions": ["vmparking_park"],
                                    "description": "Park the virtual machine"},
                                ]

        self.entity.add_message_registrar_items(registrar_items)

        # register to the node vmrequest
        if isinstance(self.entity, TNArchipelHypervisor):
            self.entity.register_hook("HOOK_ARCHIPELENTITY_XMPP_AUTHENTICATED", method=self.manage_vmparking_node)


    ### Plugin interface

    def register_handlers(self):
        """
        This method will be called by the plugin user when it will be
        necessary to register module for listening to stanza.
        """
        if isinstance(self.entity, TNArchipelHypervisor):
            self.entity.xmppclient.RegisterHandler('iq', self.process_iq_for_hypervisor, ns=ARCHIPEL_NS_HYPERVISOR_VMPARKING)
        elif isinstance(self.entity, TNArchipelVirtualMachine):
            self.entity.xmppclient.RegisterHandler('iq', self.process_iq_for_vm, ns=ARCHIPEL_NS_VM_VMPARKING)

    def unregister_handlers(self):
        """
        Unregister the handlers.
        """
        if isinstance(self.entity, TNArchipelHypervisor):
            self.entity.xmppclient.UnregisterHandler('iq', self.process_iq_for_hypervisor, ns=ARCHIPEL_NS_HYPERVISOR_VMPARKING)
        elif isinstance(self.entity, TNArchipelVirtualMachine):
            self.entity.xmppclient.UnregisterHandler('iq', self.process_iq_for_vm, ns=ARCHIPEL_NS_VM_VMPARKING)


    @staticmethod
    def plugin_info():
        """
        Return informations about the plugin.
        @rtype: dict
        @return: dictionary contaning plugin informations
        """
        plugin_friendly_name           = "Virtual Machine Parking"
        plugin_identifier              = "vmparking"
        plugin_configuration_section   = None
        plugin_configuration_tokens    = []
        return {    "common-name"               : plugin_friendly_name,
                    "identifier"                : plugin_identifier,
                    "configuration-section"     : plugin_configuration_section,
                    "configuration-tokens"      : plugin_configuration_tokens }



    ### Pubsub management

    def manage_vmparking_node(self, origin, user_info, arguments):
        """
        Register to pubsub event node /archipel/platform/requests/in
        and /archipel/platform/requests/out
        @type origin: L{TNArchipelEnity}
        @param origin: the origin of the hook
        @type user_info: object
        @param user_info: random user information
        @type arguments: object
        @param arguments: runtime argument
        """
        nodeVMParkingName = "/archipel/vmparking"
        self.entity.log.info("VMPARKING: getting the pubsub node %s" % nodeVMParkingName)
        self.pubsub_vmparking = TNPubSubNode(self.entity.xmppclient, self.entity.pubsubserver, nodeVMParkingName)
        self.pubsub_vmparking.recover(wait=True)
        self.entity.log.info("VMPARKING: node %s recovered." % nodeVMParkingName)
        self.pubsub_vmparking.subscribe(self.entity.jid, self._handle_request_event, wait=True)
        self.entity.log.info("VMPARKING: entity %s is now subscribed to events from node %s" % (self.entity.jid, nodeVMParkingName))

    def _handle_request_event(self, event):
        """
        Triggered when a platform wide virtual machine request is received.
        @type event: xmpp.Node
        @param event: the push event
        """
        self.entity.log.debug("VMPARKING: received pubsub event")
        if not self.inhibit_next_general_push:
            self.entity.push_change("vmparking", "external-update")
        self.inhibit_next_general_push = False



    ### Utilities

    def get_ticket_from_uuid(self, uuid):
        """
        parse the parked vm to find the ticket of the given uuid
        @type uuid: String
        @param uuid: the UUID of the vm
        @rtype: String
        @return: pubsub item id
        """
        items = self.pubsub_vmparking.get_items()
        for item in items:
            domain = item.getTag("virtualmachine").getTag("domain")
            if domain.getTag("uuid").getData() == uuid:
                return item.getAttr("id")
        return None

    def is_vm_already_parked(self, uuid):
        """
        Check if vm with given UUID is already parked
        @type uuid: String
        @param uuid: the UUID of the vm
        @rtype: Boolean
        @return: True is vm is already in park
        """
        if self.get_ticket_from_uuid(uuid):
            return True
        return False


    ### Processing function

    def list(self):
        """
        List virtual machines in the park. It returns a dict with the following form:
        [{"info": { "itemid": <PUBSUB-TICKET>,
                    "parker": <JID-OF-PARKER>,
                    "date": <DATE-OF-LAST-UPDATE>},
          "domain": <XML-DOMAIN-NODE>},
          ...
        ]
        @rtype: Array
        @return: listinformations about virtual machines.
        """
        nodes = self.pubsub_vmparking.get_items()
        ret = []
        for node in nodes:
            domain = xmpp.Node(node=node.getTag("virtualmachine").getTag("domain"))
            ret.append({"info":
                            {"itemid": node.getAttr("id"),
                            "parker": node.getTag("virtualmachine").getAttr("parker"),
                            "date": node.getTag("virtualmachine").getAttr("date")},
                        "domain": domain})
        def sorting(a, b):
            return cmp(a["domain"].getTag("name").getData(), b["domain"].getTag("name").getData())
        ret.sort(sorting)
        return ret

    def park(self, uuid, parker_jid, force=False):
        """
        Park a virtual machine
        @type uuid: String
        @param uuid: the UUID of the virtual machine to park
        @type force: Boolean
        @param force: if True, the machine will be destroyed if running
        """
        if self.is_vm_already_parked(uuid):
            raise Exception("VM with UUID %s is already parked" % uuid)

        vm = self.entity.get_vm_by_uuid(uuid)
        if not vm:
            raise Exception("No virtual machine with UUID %s" % uuid)
        if not vm.domain:
            raise Exception("VM with UUID %s cannot be parked because it is not defined" % uuid)
        if not vm.info()["state"] == 5:
            if not force:
                raise Exception("VM with UUID %s cannot be parked because it is running" % uuid)
            else:
                vm.destroy()

        domain = vm.xmldesc(mask_description=False)
        vm_jid = xmpp.JID(domain.getTag("description").getData().split("::::")[0])

        def publish_success(resp):
            if resp.getType() == "result":
                self.entity.soft_free(vm_jid)
                self.inhibit_next_general_push = True
                self.entity.push_change("vmparking", "parked")
                self.entity.log.info("VMPARKING: successfully parked %s" % str(vm_jid))
            else:
                self.inhibit_next_general_push = True
                self.entity.push_change("vmparking", "cannot-park", content_node=resp)
                self.entity.log.error("VMPARKING: cannot park: %s" % str(resp))

        vmparkednode = xmpp.Node(tag="virtualmachine", attrs={  "parker": parker_jid.getStripped(),
                                                                "date": datetime.datetime.now(),
                                                                "origin": self.entity.jid.getStripped().lower()})
        vmparkednode.addChild(node=domain)
        self.pubsub_vmparking.add_item(vmparkednode, callback=publish_success)
        self.entity.log.info("VMPARKING: virtual machine %s as been parked" % uuid)

    def unpark(self, identifier, start=False):
        """
        Unpark virtual machine
        @type identifier: String
        @param identifier: the UUID of a VM or the pubsub ID (parking ticket)
        @type start: Boolean
        @param start: if True, the virtual machine will start after unparking
        """
        ticket = self.get_ticket_from_uuid(identifier)
        if not ticket:
            ticket = identifier
        vm_item = self.pubsub_vmparking.get_item(ticket)
        if not vm_item:
            raise Exception("There is no virtual machine parked with ticket %s" % ticket)

        def retract_success(resp, user_info):
            if resp.getType() == "result":
                domain = vm_item.getTag("virtualmachine").getTag("domain")
                ret = str(domain).replace('xmlns=\"archipel:hypervisor:vmparking\"', '')
                domain = xmpp.simplexml.NodeBuilder(data=ret).getDom()
                vmjid = domain.getTag("description").getData().split("::::")[0]
                vmpass = domain.getTag("description").getData().split("::::")[1]
                vmname = domain.getTag("name").getData()
                vm_thread = self.entity.soft_alloc(xmpp.JID(vmjid), vmname, vmpass, start=False)
                vm = vm_thread.get_instance()
                vm.register_hook("HOOK_ARCHIPELENTITY_XMPP_AUTHENTICATED", method=vm.define_hook, user_info=domain, oneshot=True)
                if start:
                    vm.register_hook("HOOK_ARCHIPELENTITY_XMPP_AUTHENTICATED", method=vm.control_create_hook, oneshot=True)
                vm_thread.start()
                self.inhibit_next_general_push = True
                self.entity.push_change("vmparking", "unparked")
                self.entity.log.info("VMPARKING: successfully unparked %s" % str(vmjid))
            else:
                self.inhibit_next_general_push = True
                self.entity.push_change("vmparking", "cannot-unpark", content_node=resp)
                self.entity.log.error("VMPARKING: cannot unpark: %s" % str(resp))
        self.pubsub_vmparking.remove_item(ticket, callback=retract_success)

    def delete(self, identifier):
        """
        Delete a parked virtual machine
        @type identifier: String
        @param identifier: the UUID of a parked VM or the pubsub ID (parking ticket)
        """
        ticket = self.get_ticket_from_uuid(identifier)
        if not ticket:
            ticket = identifier
        vm_item = self.pubsub_vmparking.get_item(ticket)
        if not vm_item:
            raise Exception("There is no virtual machine parked with ticket %s" % ticket)

        def retract_success(resp, user_info):
            if resp.getType() == "result":
                vmjid = xmpp.JID(vm_item.getTag("virtualmachine").getTag("domain").getTag("description").getData().split("::::")[0])
                vmfolder = "%s/%s" % (self.configuration.get("VIRTUALMACHINE", "vm_base_path"), vmjid.getNode())
                if os.path.exists(vmfolder):
                    shutil.rmtree(vmfolder)
                self.entity.get_plugin("xmppserver").users_unregister([vmjid])
                self.inhibit_next_general_push = True
                self.entity.push_change("vmparking", "deleted")
                self.entity.log.info("VMPARKING: successfully deleted %s from parking" % str(vmjid))
            else:
                self.inhibit_next_general_push = True
                self.entity.push_change("vmparking", "cannot-delete", content_node=resp)
                self.entity.log.error("VMPARKING: cannot delete: %s" % str(resp))
        self.pubsub_vmparking.remove_item(ticket, callback=retract_success)

    def updatexml(self, identifier, domain):
        """
        Update the domain XML of a parked VM
        @type identifier: String
        @param identifier: the pubsub ID (parking ticket) or the VM UUID
        @type domain: xmpp.Node
        @param domain: the new XML description
        """
        ticket = self.get_ticket_from_uuid(identifier)
        if not ticket:
            ticket = identifier
        vm_item = self.pubsub_vmparking.get_item(ticket)
        if not vm_item:
            raise Exception("There is no virtual machine parked with ticket %s" % ticket)

        old_domain = vm_item.getTag("virtualmachine").getTag("domain")
        previous_uuid = old_domain.getTag("uuid").getData()
        previous_name = old_domain.getTag("name").getData()
        new_uuid = domain.getTag("uuid").getData()
        new_name = domain.getTag("name").getData()

        if not previous_uuid.lower() == new_uuid.lower():
            raise Exception("UUID of new description must be the same (was %s, is %s)" % (previous_uuid, new_uuid))
        if not previous_name.lower() == new_name.lower():
            raise Exception("Name of new description must be the same (was %s, is %s)" % (previous_name, new_name))
        if not new_name or new_name == "":
            raise Exception("Missing name information")
        if not new_uuid or new_uuid == "":
            raise Exception("Missing UUID information")

        if domain.getTag('description'):
            domain.delChild("description")
        domain.addChild(node=old_domain.getTag("description"))
        vm_item.getTag("virtualmachine").delChild("domain")
        vm_item.getTag("virtualmachine").addChild(node=domain)

        def publish_success(resp):
            if resp.getType() == "result":
                self.inhibit_next_general_push = True
                self.entity.push_change("vmparking", "updated")
                self.pubsub_vmparking.remove_item(ticket)
                self.entity.log.info("VMPARKING: virtual machine %s as been updated" % new_uuid)
            else:
                self.inhibit_next_general_push = True
                self.entity.push_change("vmparking", "cannot-update", content_node=resp)
                self.entity.log.error("VMPARKING: unable to update item for virtual machine %s: %s" % (new_uuid, resp))
        self.pubsub_vmparking.add_item(vm_item.getTag("virtualmachine"), callback=publish_success)



    ### XMPP Management for hypervisors

    def process_iq_for_hypervisor(self, conn, iq):
        """
        This method is invoked when a ARCHIPEL_NS_HYPERVISOR_VMPARKING IQ is received.
        It understands IQ of type:
            - list
            - park
            - unpark
            - destroy
            - updatexml
        @type conn: xmpp.Dispatcher
        @param conn: ths instance of the current connection that send the stanza
        @type iq: xmpp.Protocol.Iq
        @param iq: the received IQ
        """
        reply = None
        action = self.entity.check_acp(conn, iq)
        self.entity.check_perm(conn, iq, action, -1, prefix="vmparking_")
        if action == "list":
            reply = self.iq_list(iq)
        if action == "park":
            reply = self.iq_park(iq)
        if action == "unpark":
            reply = self.iq_unpark(iq)
        if action == "delete":
            reply = self.iq_delete(iq)
        if action == "updatexml":
            reply = self.iq_updatexml(iq)
        if reply:
            conn.send(reply)
            raise xmpp.protocol.NodeProcessed

    def iq_list(self, iq):
        """
        Return the list of parked virtual machines
        @type iq: xmpp.Protocol.Iq
        @param iq: the received IQ
        @rtype: xmpp.Protocol.Iq
        @return: a ready to send IQ containing the result of the action
        """
        try:
            reply = iq.buildReply("result")
            parked_vms = self.list()
            nodes = []
            for parked_vm in parked_vms:
                vm_node = xmpp.Node("virtualmachine", attrs=parked_vm["info"])
                if parked_vm["domain"].getTag('description'):
                    parked_vm["domain"].delChild("description")
                vm_node.addChild(node=parked_vm["domain"])
                nodes.append(vm_node)
            reply.setQueryPayload(nodes)
        except Exception as ex:
            reply = build_error_iq(self, ex, iq, ARCHIPEL_ERROR_CODE_VMPARK_LIST)
        return reply

    def message_list(self, msg):
        """
        Handle the parking list message.
        @type msg: xmmp.Protocol.Message
        @param msg: the message
        @rtype: string
        @return: the answer
        """
        try:
            tokens = msg.getBody().split()
            if not len(tokens) == 2:
                return "I'm sorry, you use a wrong format. You can type 'help' to get help."
            parked_vms = self.list()
            resp = "Sure! Here is the virtual machines parked:\n"
            for info in parked_vms:
                ticket = info["info"]["itemid"]
                name = info["domain"].getTag("name").getData()
                uuid = info["domain"].getTag("uuid").getData()
                resp = "%s - [%s]: %s (%s)\n" % (resp, ticket, name, uuid)
            return resp
        except Exception as ex:
            return build_error_message(self, ex, msg)

    def iq_park(self, iq):
        """
        Park virtual machine
        @type iq: xmpp.Protocol.Iq
        @param iq: the received IQ
        @rtype: xmpp.Protocol.Iq
        @return: a ready to send IQ containing the result of the action
        """
        try:
            items = iq.getTag("query").getTag("archipel").getTags("item")
            for item in items:
                vm_uuid = item.getAttr("uuid")
                if not vm_uuid:
                    self.entity.log.error("VMPARKING: Unable to park vm: missing 'uuid' element.")
                    raise Exception("You must must set the UUID of the vms you want to park")
                force_destroy = False
                if item.getAttr("force") and item.getAttr("force").lower() in ("yes", "y", "true", "1"):
                    force_destroy = True
                self.park(vm_uuid, iq.getFrom(), force=force_destroy)
            reply = iq.buildReply("result")
        except Exception as ex:
            reply = build_error_iq(self, ex, iq, ARCHIPEL_ERROR_CODE_VMPARK_PARK)
        return reply

    def message_park_hypervisor(self, msg):
        """
        Handle the park message.
        @type msg: xmmp.Protocol.Message
        @param msg: the message
        @rtype: string
        @return: the answer
        """
        try:
            tokens = msg.getBody().split()
            if len(tokens) < 2:
                return "I'm sorry, you use a wrong format. You can type 'help' to get help."
            uuids = tokens[1].split(",")
            for vmuuid in uuids:
                self.park(vmuuid, msg.getFrom())
            if len(uuids) == 1:
                return "Virtual machine is parking."
            else:
                return "Virtual machines are parking."
        except Exception as ex:
            return build_error_message(self, ex, msg)

    def iq_unpark(self, iq):
        """
        Unpark virtual machine
        @type iq: xmpp.Protocol.Iq
        @param iq: the received IQ
        @rtype: xmpp.Protocol.Iq
        @return: a ready to send IQ containing the result of the action
        """
        try:
            reply = iq.buildReply("result")
            items = iq.getTag("query").getTag("archipel").getTags("item")
            for item in items:
                identifier = item.getAttr("identifier")
                autostart = False
                if item.getAttr("start") and item.getAttr("start").lower() in ("yes", "y", "true", "1"):
                    autostart = True
                self.unpark(identifier, start=autostart)
        except Exception as ex:
            reply = build_error_iq(self, ex, iq, ARCHIPEL_ERROR_CODE_VMPARK_UNPARK)
        return reply

    def message_unpark(self, msg):
        """
        Handle the unpark message.
        @type msg: xmmp.Protocol.Message
        @param msg: the message
        @rtype: string
        @return: the answer
        """
        try:
            tokens = msg.getBody().split()
            if len(tokens) < 2:
                return "I'm sorry, you use a wrong format. You can type 'help' to get help."
            itemids = tokens[1].split(",")
            for itemid in itemids:
                self.unpark(itemid)
            if len(itemids) == 1:
                return "Virtual machine is unparking."
            else:
                return "Virtual machines are unparking."
        except Exception as ex:
            return build_error_message(self, ex, msg)

    def iq_delete(self, iq):
        """
        Delete a parked virtual machine
        @type iq: xmpp.Protocol.Iq
        @param iq: the received IQ
        @rtype: xmpp.Protocol.Iq
        @return: a ready to send IQ containing the result of the action
        """
        try:
            reply = iq.buildReply("result")
            items = iq.getTag("query").getTag("archipel").getTags("item")
            for item in items:
                identifier = item.getAttr("identifier")
                self.delete(identifier)
        except Exception as ex:
            reply = build_error_iq(self, ex, iq, ARCHIPEL_ERROR_CODE_VMPARK_DELETE)
        return reply

    def iq_updatexml(self, iq):
        """
        Update the XML description of a parked virtual machine
        @type iq: xmpp.Protocol.Iq
        @param iq: the received IQ
        @rtype: xmpp.Protocol.Iq
        @return: a ready to send IQ containing the result of the action
        """
        try:
            reply = iq.buildReply("result")
            identifier = iq.getTag("query").getTag("archipel").getAttr("identifier")
            domain = iq.getTag("query").getTag("archipel").getTag("domain")
            self.updatexml(identifier, domain)
        except Exception as ex:
            reply = build_error_iq(self, ex, iq, ARCHIPEL_ERROR_CODE_VMPARK_UPDATEXML)
        return reply


    ## XMPP Management for hypervisors

    def process_iq_for_vm(self, conn, iq):
        """
        This method is invoked when a ARCHIPEL_NS_VM_VMPARKING IQ is received.
        It understands IQ of type:
            - park
        @type conn: xmpp.Dispatcher
        @param conn: ths instance of the current connection that send the stanza
        @type iq: xmpp.Protocol.Iq
        @param iq: the received IQ
        """
        reply = None
        action = self.entity.check_acp(conn, iq)
        self.entity.check_perm(conn, iq, action, -1, prefix="vmparking_")
        if action == "park":
            reply = self.iq_park_vm(iq)
        if reply:
            conn.send(reply)
            raise xmpp.protocol.NodeProcessed

    def iq_park_vm(self, iq):
        """
        ask own hypervisor to park the virtual machine
        @type iq: xmpp.Protocol.Iq
        @param iq: the received IQ
        @rtype: xmpp.Protocol.Iq
        @return: a ready to send IQ containing the result of the action
        """
        try:
            reply = iq.buildReply("result")
            force_destroy = False
            force_attr = iq.getTag("query").getTag("archipel").getAttr("force")
            if force_attr and force_attr.lower() in ("yes", "y", "true", "1"):
                force_destroy = True
            self.entity.hypervisor.get_plugin("vmparking").park(self.entity.uuid, iq.getFrom(), force=force_destroy)
        except Exception as ex:
            reply = build_error_iq(self, ex, iq, ARCHIPEL_ERROR_CODE_VMPARK_PARK)
        return reply

    def message_park_vm(self, msg):
        """
        Handle the park message for vm.
        @type msg: xmmp.Protocol.Message
        @param msg: the message
        @rtype: string
        @return: the answer
        """
        try:
            tokens = msg.getBody().split()
            if not len(tokens) == 1:
                return "I'm sorry, you use a wrong format. You can type 'help' to get help."
            self.entity.hypervisor.get_plugin("vmparking").park(self.entity.uuid, msg.getFrom())
            return "I'm parking."
        except Exception as ex:
            return build_error_message(self, ex, msg)
Example #13
0
class TNPlatformRequests (TNArchipelPlugin):

    def __init__(self, configuration, entity, entry_point_group):
        """
        Initialize the plugin.
        @type configuration: Configuration object
        @param configuration: the configuration
        @type entity: L{TNArchipelEntity}
        @param entity: the entity that owns the plugin
        @type entry_point_group: string
        @param entry_point_group: the group name of plugin entry_point
        """
        TNArchipelPlugin.__init__(self, configuration=configuration, entity=entity, entry_point_group=entry_point_group)
        self.pubsub_request_in_node     = None
        self.pubsub_request_out_node    = None
        self.computing_unit             = None
        # get eventual computing unit plugin
        self.load_computing_unit()
        # creates permissions
        self.entity.permission_center.create_permission("platform_allocvm", "Authorizes user to send cross platform request", True)
        # register to the node vmrequest
        self.entity.register_hook("HOOK_ARCHIPELENTITY_XMPP_AUTHENTICATED", method=self.manage_platform_vm_request)


    ### Plugin interface

    def register_handlers(self):
        """
        This method will be called by the plugin user when it will be
        necessary to register module for listening to stanza.
        """
        self.entity.xmppclient.RegisterHandler('iq', self.process_iq, ns=ARCHIPEL_NS_PLATFORM)

    def unregister_handlers(self):
        """
        Unregister the handlers.
        """
        self.entity.xmppclient.UnregisterHandler('iq', self.process_iq, ns=ARCHIPEL_NS_PLATFORM)

    @staticmethod
    def plugin_info():
        """
        Return informations about the plugin.
        @rtype: dict
        @return: dictionary contaning plugin informations
        """
        plugin_friendly_name           = "Hypervisor Platform Request"
        plugin_identifier              = "platformrequest"
        plugin_configuration_section   = None
        plugin_configuration_tokens    = []
        return {    "common-name"               : plugin_friendly_name,
                    "identifier"                : plugin_identifier,
                    "configuration-section"     : plugin_configuration_section,
                    "configuration-tokens"      : plugin_configuration_tokens }


    ### Plugin loading

    def load_computing_unit(self):
        """
        Loads the external computing unit.
        """
        for factory_method in iter_entry_points(group="archipel.plugin.platform.computingunit", name="factory"):
            method              = factory_method.load()
            plugin_content      = method()
            self.computing_unit = plugin_content["plugin"]
            self.entity.log.info("PLATFORMREQ: loading computing unit %s" % plugin_content["info"]["common-name"])
            break
        if not self.computing_unit:
            self.computing_unit = TNBasicPlatformScoreComputing()
            self.entity.log.info("PLATFORMREQ: using default computing unit.")


    ### Performs platform actions

    def perform_score_computing(self, request):
        """
        Compute the score for the given request.
        @type request: string
        @param request: the requested action name
        @rtype: float
        @return: the score computed by the computing unit ([0.0, 1.0])
        """
        return self.computing_unit.score(request)


    ### Pubsub management

    def manage_platform_vm_request(self, origin, user_info, arguments):
        """
        Register to pubsub event node /archipel/platform/requests/in
        and /archipel/platform/requests/out
        @type origin: L{TNArchipelEnity}
        @param origin: the origin of the hook
        @type user_info: object
        @param user_info: random user information
        @type arguments: object
        @param arguments: runtime argument
        """
        nodeVMRequestsInName = "/archipel/platform/requests/in"
        self.entity.log.info("PLATFORMREQ: getting the pubsub node %s" % nodeVMRequestsInName)
        self.pubsub_request_in_node = TNPubSubNode(self.entity.xmppclient, self.entity.pubsubserver, nodeVMRequestsInName)
        self.pubsub_request_in_node.recover()
        self.entity.log.info("PLATFORMREQ: node %s recovered." % nodeVMRequestsInName)
        self.pubsub_request_in_node.subscribe(self.entity.jid, self._handle_request_event)
        self.entity.log.info("PLATFORMREQ: entity %s is now subscribed to events from node %s" % (self.entity.jid, nodeVMRequestsInName))
        nodeVMRequestsOutName = "/archipel/platform/requests/out"
        self.entity.log.info("PLATFORMREQ: getting the pubsub node %s" % nodeVMRequestsOutName)
        self.pubsub_request_out_node = TNPubSubNode(self.entity.xmppclient, self.entity.pubsubserver, nodeVMRequestsOutName)
        self.pubsub_request_out_node.recover()
        self.entity.log.info("PLATFORMREQ: node %s recovered." % nodeVMRequestsOutName)

    def _handle_request_event(self, event):
        """
        Triggered when a platform wide virtual machine request is received.
        @type event: xmpp.Node
        @param event: the push event
        """
        items = event.getTag("event").getTag("items").getTags("item")
        for item in items:
            try:
                item_publisher = xmpp.JID(item.getAttr("publisher"))
            except Exception as ex:
                self.entity.log.error("The pubsub node has not 'publisher' tag. This is a bug in ejabberd, not the fault of Archipel. You can find a patch here for ejabberd here https://support.process-one.net/browse/EJAB-1347")
                continue
            if not item_publisher.getStripped() == self.entity.jid.getStripped():
                try:
                    self.entity.log.info("PLATFORMREQ: received a platform-wide virtual machine request from %s" % item_publisher)
                    request_uuid    = item.getTag("archipel").getAttr("uuid")
                    request_action  = item.getTag("archipel").getAttr("action")
                    if not self.entity.permission_center.check_permission(item_publisher.getStripped(), "platform_%s" % request_action):
                        self.entity.log.warning("User %s have no permission to perform platform action %s" % (item_publisher, request_action))
                        return
                    score = self.perform_score_computing(item)
                    if score:
                        answer_node = xmpp.Node("archipel", attrs={"uuid": request_uuid, "score": score})
                        self.pubsub_request_out_node.add_item(answer_node)
                except Exception as ex:
                    self.entity.log.error("PLATFORMREQ: seems that request is not valid (%s) %s" % (str(ex), str(item)))


    ### XMPP Management

    def process_iq(self, conn, iq):
        """
        This method is invoked when a ARCHIPEL_NS_PLATFORM IQ is received.
        It understands IQ of type:
            - allocvm
        @type conn: xmpp.Dispatcher
        @param conn: ths instance of the current connection that send the stanza
        @type iq: xmpp.Protocol.Iq
        @param iq: the received IQ
        """
        reply = None
        action = self.entity.check_acp(conn, iq)
        self.entity.check_perm(conn, iq, action, -1, prefix="platform_")
        if action == "allocvm":
            reply = self.iq_allocvm(iq)
        if reply:
            conn.send(reply)
            raise xmpp.protocol.NodeProcessed

    def iq_allocvm(self, iq):
        """
        Alloc a new VM on the hypervisor.
        @type iq: xmpp.Protocol.Iq
        @param iq: the received IQ
        @rtype: xmpp.Protocol.Iq
        @return: a ready to send IQ containing the result of the action
        """
        try:
            reply = iq.buildReply("result")
            self.entity.alloc(requester=iq.getFrom(), requested_name=None)
        except Exception as ex:
            reply = build_error_iq(self, ex, iq)
        return reply
Example #14
0
class TNTaggableEntity (object):
    """
    This class allow ArchipelEntity to be taggable.
    """

    def __init__(self, pubsubserver, jid, xmppclient, permission_center, log):
        """
        Initialize the TNTaggableEntity.
        @type pubsubserver: string
        @param pubsubserver: the JID of the pubsub server
        @type jid: string
        @param jid: the JID of the current entity
        @type xmppclient: xmpp.Dispatcher
        @param xmppclient: the entity xmpp client
        @type permission_center: TNPermissionCenter
        @param permission_center: the permission center of the entity
        @type log: TNArchipelLog
        @param log: the logger of the entity
        """
        self.pubSubNodeTags     = None
        self.pubsubserver       = pubsubserver
        self.xmppclient         = xmppclient
        self.permission_center  = permission_center
        self.jid                = jid
        self.log                = log

    ### subclass must implement this

    def check_acp(conn, iq):
        """
        Function that verify if the ACP is valid.
        @type conn: xmpp.Dispatcher
        @param conn: the connection
        @type iq: xmpp.Protocol.Iq
        @param iq: the IQ to check
        @raise Exception: Exception if not implemented
        """
        raise Exception("Subclass of TNTaggableEntity must implement check_acp.")

    def check_perm(self, conn, stanza, action_name, error_code=-1, prefix=""):
        """
        function that verify if the permissions are granted
        @type conn: xmpp.Dispatcher
        @param conn: the connection
        @type stanza: xmpp.Node
        @param stanza: the stanza containing the action
        @type action_name: string
        @param action_name: the action to check
        @type error_code: int
        @param error_code: the error code to return
        @type prefix: string
        @param prefix: the prefix of the action
        @raise Exception: Exception if not implemented
        """
        raise Exception("Subclass of TNTaggableEntity must implement check_perm.")


    ### Pubsub

    def recover_pubsubs(self, origin, user_info, arguments):
        """
        Get the global tag pubsub node.
        Arguments here are used to be HOOK compliant see register_hook of L{TNHookableEntity}
        """
        # getting the tags pubsub node
        tagsNodeName = "/archipel/tags"
        self.pubSubNodeTags = TNPubSubNode(self.xmppclient, self.pubsubserver, tagsNodeName)
        if not self.pubSubNodeTags.recover(wait=True):
            Exception("The pubsub node /archipel/tags must have been created. You can use archipel-tagnode tool to create it.")

    def init_permissions(self):
        """
        Initialize the tag permissions.
        """
        self.permission_center.create_permission("settags", "Authorizes users to modify entity's tags", False)

    def register_handlers(self):
        """
        Initialize the handlers for tags.
        """
        self.xmppclient.RegisterHandler('iq', self.process_tags_iq, ns=ARCHIPEL_NS_TAGS)

    def unregister_handlers(self):
        """
        Unregister the handlers for tags.
        """
        self.xmppclient.UnregisterHandler('iq', self.process_tags_iq, ns=ARCHIPEL_NS_TAGS)

    ### Tags

    def process_tags_iq(self, conn, iq):
        """
        This method is invoked when a ARCHIPEL_NS_TAGS IQ is received.
        It understands IQ of type:
            - settags
        @type conn: xmpp.Dispatcher
        @param conn: ths instance of the current connection that send the stanza
        @type iq: xmpp.Protocol.Iq
        @param iq: the received IQ
        """
        action = self.check_acp(conn, iq)
        self.check_perm(conn, iq, action, -1)
        if action == "settags":
            reply = self.iq_set_tags(iq)
            conn.send(reply)
            raise xmpp.protocol.NodeProcessed

    def set_tags(self, tags):
        """
        Set the tags of the current entity.
        @type tags: string
        @param tags: the string containing tags separated by ';;'
        """
        current_id = None
        for item in self.pubSubNodeTags.get_items():
            if item.getTag("tag") and item.getTag("tag").getAttr("jid") == self.jid.getStripped():
                current_id = item.getAttr("id")
        if current_id:
            self.pubSubNodeTags.remove_item(current_id, callback=self.did_clean_old_tags, user_info=tags)
        else:
            tagNode = xmpp.Node(tag="tag", attrs={"jid": self.jid.getStripped(), "tags": tags})
            self.pubSubNodeTags.add_item(tagNode)

    def did_clean_old_tags(self, resp, user_info):
        """
        Callback called when old tags has been removed if any.
        @raise Exception: Exception if not implemented
        """
        if resp.getType() == "result":
            tagNode = xmpp.Node(tag="tag", attrs={"jid": self.jid.getStripped(), "tags": user_info})
            self.pubSubNodeTags.add_item(tagNode)
        else:
            raise Exception("Tags unable to set tags. answer is: " + str(resp))

    def iq_set_tags(self, iq):
        """
        Set the current tag.
        @type iq: xmpp.Protocol.IQ
        @param iq: the IQ containing the request
        @rtype: xmpp.Protocol.IQ
        @return: the IQ containing the answer
        """
        try:
            reply = iq.buildReply("result")
            tags = iq.getTag("query").getTag("archipel").getAttr("tags")
            self.set_tags(tags)
        except Exception as ex:
            reply = build_error_iq(self, ex, iq, ARCHIPEL_ERROR_CODE_SET_TAGS)
        return reply
Example #15
0
class TNCentralDb(TNArchipelPlugin):
    """
    This contains the necessary interfaces to interact with central agent and central db
    """
    def __init__(self, configuration, entity, entry_point_group):
        """
        Initialize the plugin.
        @type configuration: Configuration object
        @param configuration: the configuration
        @type entity: L{TNArchipelEntity}
        @param entity: the entity that owns the plugin
        @type entry_point_group: string
        @param entry_point_group: the group name of plugin entry_point
        """
        TNArchipelPlugin.__init__(self,
                                  configuration=configuration,
                                  entity=entity,
                                  entry_point_group=entry_point_group)

        if self.entity.__class__.__name__ == "TNArchipelHypervisor":
            self.entity.register_hook(
                "HOOK_ARCHIPELENTITY_XMPP_AUTHENTICATED",
                method=self.hypervisor_hook_xmpp_authenticated)
            self.entity.register_hook("HOOK_HYPERVISOR_WOKE_UP",
                                      method=self.push_vms_in_central_db)
            self.entity.register_hook("HOOK_HYPERVISOR_FREE",
                                      method=self.hook_vm_unregistered)

        if self.entity.__class__.__name__ == "TNArchipelVirtualMachine":
            self.entity.register_hook("HOOK_VM_INITIALIZE",
                                      method=self.hook_missed_vms,
                                      oneshot=True)
            self.entity.register_hook("HOOK_VM_DEFINE",
                                      method=self.hook_vm_event)

        self.central_agent_jid_val = None
        self.last_keepalive_heard = None
        self.keepalive_interval = int(ARCHIPEL_CENTRAL_AGENT_TIMEOUT * 2)
        self.hypervisor_timeout_threshold = int(ARCHIPEL_CENTRAL_AGENT_TIMEOUT)
        self.delayed_tasks = TNTasks(self.entity.log)
        self.vms_to_hook = set()

        self.xmpp_authenticated = False

    # Hooks

    def hook_missed_vms(self, origin=None, user_info=None, arguments=None):
        """
        Called when a vm was not ready on first push_vms_in_central_db.
        """
        if self.entity.hypervisor.get_plugin(
                "centraldb"
        ) and self.entity.uuid in self.entity.hypervisor.get_plugin(
                "centraldb").vms_to_hook:
            self.entity.hypervisor.get_plugin("centraldb").vms_to_hook.remove(
                self.entity.uuid)
            self.entity.log.debug(
                "CENTRALDB: Registering ourself in centraldb as we were not ready the first time."
            )
            self.hook_vm_event()

    def hook_vm_event(self, origin=None, user_info=None, arguments=None):
        """
        Called when a VM definition or change of definition occurs.
        This will advertise definition to the central agent
        """
        if not self.entity.definition:
            return

        xmldesc = self.entity.xmldesc(mask_description=False)
        vm_info = [{
            "uuid": self.entity.uuid,
            "parker": None,
            "creation_date": None,
            "domain": xmldesc,
            "hypervisor": self.entity.hypervisor.jid,
            'name': xmldesc.getTag("name").getData()
        }]
        self.register_vms(vm_info)

    def hook_vm_unregistered(self,
                             origin=None,
                             user_info=None,
                             arguments=None):
        """
        Called when a VM termination occurs.
        This will advertise undefinition to the central agent.
        """
        self.unregister_vms([{"uuid": arguments.uuid}], None)

    # Pubsub management

    def hypervisor_hook_xmpp_authenticated(self,
                                           origin=None,
                                           user_info=None,
                                           arguments=None):
        """
        Triggered when we are authenticated. Initializes everything.
        @type origin: L{TNArchipelEnity}
        @param origin: the origin of the hook
        @type user_info: object
        @param user_info: random user information
        @type arguments: object
        @param arguments: runtime argument
        """

        self.xmpp_authenticated = True
        self.central_keepalive_pubsub = TNPubSubNode(
            self.entity.xmppclient, self.entity.pubsubserver,
            ARCHIPEL_KEEPALIVE_PUBSUB)
        self.central_keepalive_pubsub.recover()
        self.central_keepalive_pubsub.subscribe(
            self.entity.jid, self.handle_central_keepalive_event)
        self.entity.log.info(
            "CENTRALDB: entity %s is now subscribed to events from node %s" %
            (self.entity.jid, ARCHIPEL_KEEPALIVE_PUBSUB))
        self.last_keepalive_heard = datetime.datetime.now()

    def central_agent_jid(self):
        """
        Returns the jid of the central agent. In case we are a VM, query hypervisor.
        """
        if self.entity.__class__.__name__ == "TNArchipelVirtualMachine":
            return self.entity.hypervisor.get_plugin(
                "centraldb").central_agent_jid()

        if not self.central_agent_jid_val:
            return None

        if not self.entity.roster.getItem(
                self.central_agent_jid_val.getStripped()):
            self.entity.log.warning(
                "CENTRALDB: CentralAgent not in my roster, adding it.")
            self.entity.add_jid(self.central_agent_jid_val)

        # if central agent has a status, it's available
        try:
            if self.entity.roster.getStatus(
                    self.central_agent_jid_val.getStripped()):
                return self.central_agent_jid_val
        except:
            pass

        # If presence is not known check if we hit the keepalive threshold timeout
        # This could append when you restart it for example
        # Time interval is not an exact science so let's double it for timetout
        if self.last_keepalive_heard and (
                datetime.datetime.now() - self.last_keepalive_heard
        ).total_seconds() > self.keepalive_interval * 2:
            self.entity.log.error("CENTRALDB: CentralAgent is down.")
            self.central_agent_jid_val = None
            return None
        else:
            self.entity.log.warning(
                "CENTRALDB: Can't get central-agent presence, using keepalive to check it's availability."
            )
            return self.central_agent_jid_val

    def handle_central_keepalive_event(self, event):
        """
        Called when the central agent sends a keepalive.
        @type event: xmpp.Node
        @param event: the pubsub event node
        """
        items = event.getTag("event").getTag("items").getTags("item")

        for item in items:
            central_announcement_event = item.getTag("event")
            event_type = central_announcement_event.getAttr("type")

            if event_type == "keepalive":
                old_central_agent_jid = self.central_agent_jid()
                self.entity.log.debug("CENTRALDB: Keepalive heard : %s " %
                                      str(item))
                keepalive_jid = xmpp.JID(
                    central_announcement_event.getAttr("jid"))

                if central_announcement_event.getAttr("keepalive_interval"):
                    self.keepalive_interval = int(
                        central_announcement_event.getAttr(
                            "keepalive_interval"))

                if central_announcement_event.getAttr(
                        "hypervisor_timeout_threshold"):
                    self.hypervisor_timeout_threshold = int(
                        central_announcement_event.getAttr(
                            "hypervisor_timeout_threshold"))

                self.central_agent_jid_val = keepalive_jid
                self.last_keepalive_heard = datetime.datetime.now()

                self.delayed_tasks.add(
                    (self.hypervisor_timeout_threshold -
                     self.keepalive_interval) * 2 / 3,
                    self.push_statistics_to_centraldb,
                    {'central_announcement_event': central_announcement_event})

                if not old_central_agent_jid:
                    self.delayed_tasks.add(self.keepalive_interval,
                                           self.handle_first_keepalive,
                                           {'keepalive_jid': keepalive_jid})
                elif central_announcement_event.getAttr(
                        "force_update"
                ) == "true" or keepalive_jid != old_central_agent_jid:
                    self.delayed_tasks.add(self.keepalive_interval,
                                           self.push_vms_in_central_db)

    def push_statistics_to_centraldb(self, central_announcement_event):
        """
        each time we hear a keepalive, we push relevant statistics to central db
        """
        # parsing required statistics to be pushed to central agent
        required_statistics = []
        if central_announcement_event.getTag("required_stats"):
            for required_stat in central_announcement_event.getTag(
                    "required_stats").getChildren():
                required_statistics.append({
                    "major":
                    required_stat.getAttr("major"),
                    "minor":
                    required_stat.getAttr("minor")
                })

        stats_results = {"jid": str(self.entity.jid)}
        if len(required_statistics) > 0:
            stat_num = 0
            for stat in required_statistics:
                stat_num += 1
                value = eval(
                    "self.entity.get_plugin('hypervisor_health').collector.stats_%s"
                    % stat["major"])[-1][stat["minor"]]
                stats_results["stat%s" % stat_num] = value

            self.entity.log.debug("CENTRALDB: updating central db with %s" %
                                  stats_results)

        self.update_hypervisors([stats_results])

    def handle_first_keepalive(self, keepalive_jid, callback=None, kwargs={}):
        """
        this is the first keepalive. We query hypervisors that have vm entities somewhere else
        then we trigger method manage_persistence to instantiate vm entities.
        """
        vms_from_local_db = self.entity.get_vms_from_local_db()

        if len(vms_from_local_db) > 0:
            dbCommand = xmpp.Node(tag="event", attrs={"jid": self.entity.jid})

            def _get_existing_vms_instances_callback(conn, packed_vms):
                existing_vms_entities = self.unpack_entries(packed_vms)
                self.entity.manage_persistence(vms_from_local_db,
                                               existing_vms_entities)
                if callback:
                    callback(**kwargs)

            for vm in vms_from_local_db:
                entryTag = xmpp.Node(tag="entry")
                uuid = xmpp.JID(vm["string_jid"]).getNode()
                entryTag.addChild("item", attrs={"key": "uuid", "value": uuid})
                dbCommand.addChild(node=entryTag)

            iq = xmpp.Iq(typ="set",
                         queryNS=ARCHIPEL_NS_CENTRALAGENT,
                         to=keepalive_jid)
            iq.getTag("query").addChild(
                name="archipel",
                attrs={"action": "get_existing_vms_instances"})
            iq.getTag("query").getTag("archipel").addChild(node=dbCommand)
            self.entity.xmppclient.SendAndCallForResponse(
                iq, _get_existing_vms_instances_callback)

        else:
            # update status to Online(0)
            self.entity.manage_persistence()
            if callback:
                callback(**kwargs)

    def push_vms_in_central_db(self,
                               origin=None,
                               user_info=None,
                               arguments=None):
        """
        there is a new central agent, or we just started.
        Consequently, we re-populate central database
        since we are using "on conflict replace" mode of sqlite, inserting an existing uuid will overwrite it.
        """
        vms = []
        for vm, vmprops in self.entity.virtualmachines.iteritems():
            if vmprops.definition:
                vms.append({
                    "uuid": vmprops.uuid,
                    "parker": None,
                    "creation_date": None,
                    "domain": vmprops.definition,
                    "hypervisor": self.entity.jid,
                    "name": vmprops.definition.getTag("name").getData()
                })
            else:
                self.entity.log.debug(
                    "[CENTRALDB] The entity %s looks not ready, postponing it's registration."
                    % vmprops.uuid)
                self.vms_to_hook.add(vmprops.uuid)

        if len(vms) >= 1:
            self.register_vms(vms)

        self.register_hypervisors([{
            "jid": self.entity.jid,
            "status": "Online",
            "last_seen": datetime.datetime.now(),
            "stat1": 0,
            "stat2": 0,
            "stat3": 0
        }])

    # Database Management
    # read commands

    def read_hypervisors(self, columns, where_statement, callback):
        """
        List vm in database.
        @type table: list
        @param table: the list of hypervisors to insert
        """
        self.read_from_db("read_hypervisors", columns, where_statement,
                          callback)

    def read_vms(self, columns, where_statement, callback):
        """
        Registers a list of vms into central database.
        @type table: list
        @param table: the list of vms to insert
        """
        self.read_from_db("read_vms", columns, where_statement, callback)

    # write commands

    def register_hypervisors(self, table):
        """
        Registers a list of hypervisors into central database.
        @type table: list
        @param table: the list of hypervisors to insert
        """
        self.commit_to_db("register_hypervisors", table, None)

    def register_vms(self, table):
        """
        Registers a list of vms into central database.
        @type table: list
        @param table: the list of vms to insert
        """
        self.commit_to_db("register_vms", table, None)

    def unregister_hypervisors(self, table):
        """
        Unregisters a list of hypervisors from central database.
        @type table: list
        @param table: the list of hypervisors to remove
        """
        self.commit_to_db("unregister_hypervisors", table, None)

    def unregister_vms(self, table, callback):
        """
        Unregisters a list of vms from central database.
        @type table: list
        @param table: the list of vms to remove
        @type callback: func
        @para callback: will return  list of vms actually unregistered
        """
        self.commit_to_db("unregister_vms", table, callback)

    def update_vms(self, table):
        """
        Update a set of vms in central database.
        @type table: list
        @param table: the list of vms to update. Must contain the "uuid" attribute as
                      this is the one used for key in the update statement.
        """
        self.commit_to_db("update_vms", table, None)

    def update_vms_domain(self, table, callback):
        """
        Update a set of vms in central database.
        Performs additional checks for domain update when vm is offline.
        @type table: list
        @param table: the list of vms to update. Must contain the "uuid" attribute as
                      this is the one used for key in the update statement.
        """
        self.commit_to_db("update_vms_domain", table, callback)

    def update_hypervisors(self, table):
        """
        Update a set of hypervisors in central database.
        @type table: list
        @param table: the list of hypervisors to update. Must contain the "jid" attribute as
                      this is the one used for key in the update statement.
        """
        self.commit_to_db("update_hypervisors", table, None)

    def commit_to_db(self, action, table, callback):
        """
        Sends a command to active central agent for execution
        @type command: string
        @param command: the sql command to execute
        @type table: table
        @param command: the table of dicts of values associated with the command.
        """
        def commit_to_db_callback(conn, resp):
            if callback:
                unpacked_entries = self.unpack_entries(resp)
                callback(unpacked_entries)

        central_agent_jid = self.central_agent_jid()

        if central_agent_jid:
            dbCommand = xmpp.Node(tag="event", attrs={"jid": self.entity.jid})
            for entry in table:
                entryTag = xmpp.Node(tag="entry")
                for key, value in entry.iteritems():
                    entryTag.addChild("item",
                                      attrs={
                                          "key": key,
                                          "value": value
                                      })

                dbCommand.addChild(node=entryTag)

            iq = xmpp.Iq(typ="set",
                         queryNS=ARCHIPEL_NS_CENTRALAGENT,
                         to=central_agent_jid)
            iq.getTag("query").addChild(name="archipel",
                                        attrs={"action": action})
            iq.getTag("query").getTag("archipel").addChild(node=dbCommand)
            self.entity.log.debug("CENTRALDB [%s]: \n%s" %
                                  (action.upper(), iq))
            self.entity.xmppclient.SendAndCallForResponse(
                iq, commit_to_db_callback)
        else:
            self.entity.log.warning(
                "CENTRALDB: cannot commit to db because we have not detected any central agent"
            )

    def read_from_db(self, action, columns, where_statement, callback):
        """
        Send a select statement to central db.
        @type command: string
        @param command: the sql command to execute
        @type columns: string
        @param columns: the list of database columns to return
        @type where_statement: string
        @param where_statement: for database reads, provides "where" constraint
        """
        def _read_from_db_callback(conn, resp):
            unpacked_entries = self.unpack_entries(resp)
            self.entity.log.debug(
                "CENTRALDB: read %s entries from db response" %
                len(unpacked_entries))
            callback(unpacked_entries)

        central_agent_jid = self.central_agent_jid()

        if central_agent_jid:
            dbCommand = xmpp.Node(tag="event", attrs={"jid": self.entity.jid})
            if where_statement:
                dbCommand.setAttr("where_statement", where_statement)

            if columns:
                dbCommand.setAttr("columns", columns)

            self.entity.log.debug(
                "CENTRALDB: Asking central db for [%s] %s %s" %
                (action.upper(), columns, where_statement))
            iq = xmpp.Iq(typ="set",
                         queryNS=ARCHIPEL_NS_CENTRALAGENT,
                         to=central_agent_jid)
            iq.getTag("query").addChild(name="archipel",
                                        attrs={"action": action})
            iq.getTag("query").getTag("archipel").addChild(node=dbCommand)
            self.entity.xmppclient.SendAndCallForResponse(
                iq, _read_from_db_callback)
        else:
            self.entity.log.warning(
                "CENTRALDB: cannot read from db because we have not detected any central agent"
            )

    def unpack_entries(self, iq):
        """
        Unpack the list of entries from iq for database processing.
        @type iq: xmpp.Iq
        @param event: received Iq
        """
        entries = []

        for entry in iq.getChildren():
            entry_dict = {}
            for entry_val in entry.getChildren():
                if entry_val.getAttr("key"):
                    entry_dict[entry_val.getAttr("key")] = entry_val.getAttr(
                        "value")
            if entry_dict != {}:
                entries.append(entry_dict)
        return entries

    # Plugin information

    @staticmethod
    def plugin_info():
        """
        Return informations about the plugin.
        @rtype: dict
        @return: dictionary contaning plugin informations
        """
        plugin_friendly_name = "Central db"
        plugin_identifier = "centraldb"
        plugin_configuration_section = None
        plugin_configuration_tokens = []
        return {
            "common-name": plugin_friendly_name,
            "identifier": plugin_identifier,
            "configuration-section": plugin_configuration_section,
            "configuration-tokens": plugin_configuration_tokens
        }
Example #16
0
class TNVMParking(TNArchipelPlugin):
    def __init__(self, configuration, entity, entry_point_group):
        """
        Initialize the plugin.
        @type configuration: Configuration object
        @param configuration: the configuration
        @type entity: L{TNArchipelEntity}
        @param entity: the entity that owns the plugin
        @type entry_point_group: string
        @param entry_point_group: the group name of plugin entry_point
        """
        TNArchipelPlugin.__init__(self,
                                  configuration=configuration,
                                  entity=entity,
                                  entry_point_group=entry_point_group)
        self.pubsub_vmparking = None
        self.inhibit_next_general_push = None

        # creates permissions
        self.entity.permission_center.create_permission(
            "vmparking_park", "Authorizes user to park a virtual machines",
            False)

        if isinstance(self.entity, TNArchipelHypervisor):
            self.entity.permission_center.create_permission(
                "vmparking_list",
                "Authorizes user to list virtual machines in parking", False)
            self.entity.permission_center.create_permission(
                "vmparking_unpark",
                "Authorizes user to unpark a virtual machines", False)
            self.entity.permission_center.create_permission(
                "vmparking_delete",
                "Authorizes user to delete parked virtual machines", False)
            self.entity.permission_center.create_permission(
                "vmparking_updatexml",
                "Authorizes user to delete parked virtual machines", False)

        # vocabulary
        if isinstance(self.entity, TNArchipelHypervisor):
            registrar_items = [{
                "commands": ["park"],
                "parameters": [{
                    "name":
                    "identifiers",
                    "description":
                    "the UUIDs of the VM to park, separated with comas, with no space"
                }],
                "method":
                self.message_park_hypervisor,
                "permissions": ["vmparking_park"],
                "description":
                "Park the virtual machine with the given UUIDs"
            }, {
                "commands": ["unpark"],
                "parameters": [{
                    "name":
                    "identifiers",
                    "description":
                    "UUIDs of the virtual machines or parking tickets, separated by comas, with no space"
                }],
                "method":
                self.message_unpark,
                "permissions": ["vmparking_unpark"],
                "description":
                "Unpark the virtual machine parked with the given identifier"
            }, {
                "commands": ["park list"],
                "parameters": [],
                "method":
                self.message_list,
                "permissions": ["vmparking_list"],
                "description":
                "List all parked virtual machines"
            }]
        elif isinstance(self.entity, TNArchipelVirtualMachine):
            registrar_items = [
                {
                    "commands": ["park"],
                    "parameters": [],
                    "method": self.message_park_vm,
                    "permissions": ["vmparking_park"],
                    "description": "Park the virtual machine"
                },
            ]

        self.entity.add_message_registrar_items(registrar_items)

        # register to the node vmrequest
        if isinstance(self.entity, TNArchipelHypervisor):
            self.entity.register_hook("HOOK_ARCHIPELENTITY_XMPP_AUTHENTICATED",
                                      method=self.manage_vmparking_node)

    ### Plugin interface

    def register_handlers(self):
        """
        This method will be called by the plugin user when it will be
        necessary to register module for listening to stanza.
        """
        if isinstance(self.entity, TNArchipelHypervisor):
            self.entity.xmppclient.RegisterHandler(
                'iq',
                self.process_iq_for_hypervisor,
                ns=ARCHIPEL_NS_HYPERVISOR_VMPARKING)
        elif isinstance(self.entity, TNArchipelVirtualMachine):
            self.entity.xmppclient.RegisterHandler('iq',
                                                   self.process_iq_for_vm,
                                                   ns=ARCHIPEL_NS_VM_VMPARKING)

    def unregister_handlers(self):
        """
        Unregister the handlers.
        """
        if isinstance(self.entity, TNArchipelHypervisor):
            self.entity.xmppclient.UnregisterHandler(
                'iq',
                self.process_iq_for_hypervisor,
                ns=ARCHIPEL_NS_HYPERVISOR_VMPARKING)
        elif isinstance(self.entity, TNArchipelVirtualMachine):
            self.entity.xmppclient.UnregisterHandler(
                'iq', self.process_iq_for_vm, ns=ARCHIPEL_NS_VM_VMPARKING)

    @staticmethod
    def plugin_info():
        """
        Return informations about the plugin.
        @rtype: dict
        @return: dictionary contaning plugin informations
        """
        plugin_friendly_name = "Virtual Machine Parking"
        plugin_identifier = "vmparking"
        plugin_configuration_section = None
        plugin_configuration_tokens = []
        return {
            "common-name": plugin_friendly_name,
            "identifier": plugin_identifier,
            "configuration-section": plugin_configuration_section,
            "configuration-tokens": plugin_configuration_tokens
        }

    ### Pubsub management

    def manage_vmparking_node(self, origin, user_info, arguments):
        """
        Register to pubsub event node /archipel/platform/requests/in
        and /archipel/platform/requests/out
        @type origin: L{TNArchipelEnity}
        @param origin: the origin of the hook
        @type user_info: object
        @param user_info: random user information
        @type arguments: object
        @param arguments: runtime argument
        """
        nodeVMParkingName = "/archipel/vmparking"
        self.entity.log.info("VMPARKING: getting the pubsub node %s" %
                             nodeVMParkingName)
        self.pubsub_vmparking = TNPubSubNode(self.entity.xmppclient,
                                             self.entity.pubsubserver,
                                             nodeVMParkingName)
        self.pubsub_vmparking.recover(wait=True)
        self.entity.log.info("VMPARKING: node %s recovered." %
                             nodeVMParkingName)
        self.pubsub_vmparking.subscribe(self.entity.jid,
                                        self._handle_request_event,
                                        wait=True)
        self.entity.log.info(
            "VMPARKING: entity %s is now subscribed to events from node %s" %
            (self.entity.jid, nodeVMParkingName))

    def _handle_request_event(self, event):
        """
        Triggered when a platform wide virtual machine request is received.
        @type event: xmpp.Node
        @param event: the push event
        """
        self.entity.log.debug("VMPARKING: received pubsub event")
        if not self.inhibit_next_general_push:
            self.entity.push_change("vmparking", "external-update")
        self.inhibit_next_general_push = False

    ### Utilities

    def get_ticket_from_uuid(self, uuid):
        """
        parse the parked vm to find the ticket of the given uuid
        @type uuid: String
        @param uuid: the UUID of the vm
        @rtype: String
        @return: pubsub item id
        """
        items = self.pubsub_vmparking.get_items()
        for item in items:
            domain = item.getTag("virtualmachine").getTag("domain")
            if domain.getTag("uuid").getData() == uuid:
                return item.getAttr("id")
        return None

    def is_vm_already_parked(self, uuid):
        """
        Check if vm with given UUID is already parked
        @type uuid: String
        @param uuid: the UUID of the vm
        @rtype: Boolean
        @return: True is vm is already in park
        """
        if self.get_ticket_from_uuid(uuid):
            return True
        return False

    ### Processing function

    def list(self):
        """
        List virtual machines in the park. It returns a dict with the following form:
        [{"info": { "itemid": <PUBSUB-TICKET>,
                    "parker": <JID-OF-PARKER>,
                    "date": <DATE-OF-LAST-UPDATE>},
          "domain": <XML-DOMAIN-NODE>},
          ...
        ]
        @rtype: Array
        @return: listinformations about virtual machines.
        """
        nodes = self.pubsub_vmparking.get_items()
        ret = []
        for node in nodes:
            domain = xmpp.Node(
                node=node.getTag("virtualmachine").getTag("domain"))
            ret.append({
                "info": {
                    "itemid": node.getAttr("id"),
                    "parker": node.getTag("virtualmachine").getAttr("parker"),
                    "date": node.getTag("virtualmachine").getAttr("date")
                },
                "domain": domain
            })

        def sorting(a, b):
            return cmp(a["domain"].getTag("name").getData(),
                       b["domain"].getTag("name").getData())

        ret.sort(sorting)
        return ret

    def park(self, uuid, parker_jid, force=False):
        """
        Park a virtual machine
        @type uuid: String
        @param uuid: the UUID of the virtual machine to park
        @type force: Boolean
        @param force: if True, the machine will be destroyed if running
        """
        if self.is_vm_already_parked(uuid):
            raise Exception("VM with UUID %s is already parked" % uuid)

        vm = self.entity.get_vm_by_uuid(uuid)
        if not vm:
            raise Exception("No virtual machine with UUID %s" % uuid)
        if not vm.domain:
            raise Exception(
                "VM with UUID %s cannot be parked because it is not defined" %
                uuid)
        if not vm.info()["state"] == 5:
            if not force:
                raise Exception(
                    "VM with UUID %s cannot be parked because it is running" %
                    uuid)
            else:
                vm.destroy()

        domain = vm.xmldesc(mask_description=False)
        vm_jid = xmpp.JID(
            domain.getTag("description").getData().split("::::")[0])

        def publish_success(resp):
            if resp.getType() == "result":
                self.entity.soft_free(vm_jid)
                self.inhibit_next_general_push = True
                self.entity.push_change("vmparking", "parked")
                self.entity.log.info("VMPARKING: successfully parked %s" %
                                     str(vm_jid))
            else:
                self.inhibit_next_general_push = True
                self.entity.push_change("vmparking",
                                        "cannot-park",
                                        content_node=resp)
                self.entity.log.error("VMPARKING: cannot park: %s" % str(resp))

        vmparkednode = xmpp.Node(tag="virtualmachine",
                                 attrs={
                                     "parker":
                                     parker_jid.getStripped(),
                                     "date":
                                     datetime.datetime.now(),
                                     "origin":
                                     self.entity.jid.getStripped().lower()
                                 })
        vmparkednode.addChild(node=domain)
        self.pubsub_vmparking.add_item(vmparkednode, callback=publish_success)
        self.entity.log.info("VMPARKING: virtual machine %s as been parked" %
                             uuid)

    def unpark(self, identifier, start=False):
        """
        Unpark virtual machine
        @type identifier: String
        @param identifier: the UUID of a VM or the pubsub ID (parking ticket)
        @type start: Boolean
        @param start: if True, the virtual machine will start after unparking
        """
        ticket = self.get_ticket_from_uuid(identifier)
        if not ticket:
            ticket = identifier
        vm_item = self.pubsub_vmparking.get_item(ticket)
        if not vm_item:
            raise Exception(
                "There is no virtual machine parked with ticket %s" % ticket)

        def retract_success(resp, user_info):
            if resp.getType() == "result":
                domain = vm_item.getTag("virtualmachine").getTag("domain")
                ret = str(domain).replace(
                    'xmlns=\"archipel:hypervisor:vmparking\"', '')
                domain = xmpp.simplexml.NodeBuilder(data=ret).getDom()
                vmjid = domain.getTag("description").getData().split("::::")[0]
                vmpass = domain.getTag("description").getData().split(
                    "::::")[1]
                vmname = domain.getTag("name").getData()
                vm_thread = self.entity.soft_alloc(xmpp.JID(vmjid),
                                                   vmname,
                                                   vmpass,
                                                   start=False)
                vm = vm_thread.get_instance()
                vm.register_hook("HOOK_ARCHIPELENTITY_XMPP_AUTHENTICATED",
                                 method=vm.define_hook,
                                 user_info=domain,
                                 oneshot=True)
                if start:
                    vm.register_hook("HOOK_ARCHIPELENTITY_XMPP_AUTHENTICATED",
                                     method=vm.control_create_hook,
                                     oneshot=True)
                vm_thread.start()
                self.inhibit_next_general_push = True
                self.entity.push_change("vmparking", "unparked")
                self.entity.log.info("VMPARKING: successfully unparked %s" %
                                     str(vmjid))
            else:
                self.inhibit_next_general_push = True
                self.entity.push_change("vmparking",
                                        "cannot-unpark",
                                        content_node=resp)
                self.entity.log.error("VMPARKING: cannot unpark: %s" %
                                      str(resp))

        self.pubsub_vmparking.remove_item(ticket, callback=retract_success)

    def delete(self, identifier):
        """
        Delete a parked virtual machine
        @type identifier: String
        @param identifier: the UUID of a parked VM or the pubsub ID (parking ticket)
        """
        ticket = self.get_ticket_from_uuid(identifier)
        if not ticket:
            ticket = identifier
        vm_item = self.pubsub_vmparking.get_item(ticket)
        if not vm_item:
            raise Exception(
                "There is no virtual machine parked with ticket %s" % ticket)

        def retract_success(resp, user_info):
            if resp.getType() == "result":
                vmjid = xmpp.JID(
                    vm_item.getTag("virtualmachine").getTag("domain").getTag(
                        "description").getData().split("::::")[0])
                vmfolder = "%s/%s" % (self.configuration.get(
                    "VIRTUALMACHINE", "vm_base_path"), vmjid.getNode())
                if os.path.exists(vmfolder):
                    shutil.rmtree(vmfolder)
                self.entity.get_plugin("xmppserver").users_unregister([vmjid])
                self.inhibit_next_general_push = True
                self.entity.push_change("vmparking", "deleted")
                self.entity.log.info(
                    "VMPARKING: successfully deleted %s from parking" %
                    str(vmjid))
            else:
                self.inhibit_next_general_push = True
                self.entity.push_change("vmparking",
                                        "cannot-delete",
                                        content_node=resp)
                self.entity.log.error("VMPARKING: cannot delete: %s" %
                                      str(resp))

        self.pubsub_vmparking.remove_item(ticket, callback=retract_success)

    def updatexml(self, identifier, domain):
        """
        Update the domain XML of a parked VM
        @type identifier: String
        @param identifier: the pubsub ID (parking ticket) or the VM UUID
        @type domain: xmpp.Node
        @param domain: the new XML description
        """
        ticket = self.get_ticket_from_uuid(identifier)
        if not ticket:
            ticket = identifier
        vm_item = self.pubsub_vmparking.get_item(ticket)
        if not vm_item:
            raise Exception(
                "There is no virtual machine parked with ticket %s" % ticket)

        old_domain = vm_item.getTag("virtualmachine").getTag("domain")
        previous_uuid = old_domain.getTag("uuid").getData()
        previous_name = old_domain.getTag("name").getData()
        new_uuid = domain.getTag("uuid").getData()
        new_name = domain.getTag("name").getData()

        if not previous_uuid.lower() == new_uuid.lower():
            raise Exception(
                "UUID of new description must be the same (was %s, is %s)" %
                (previous_uuid, new_uuid))
        if not previous_name.lower() == new_name.lower():
            raise Exception(
                "Name of new description must be the same (was %s, is %s)" %
                (previous_name, new_name))
        if not new_name or new_name == "":
            raise Exception("Missing name information")
        if not new_uuid or new_uuid == "":
            raise Exception("Missing UUID information")

        if domain.getTag('description'):
            domain.delChild("description")
        domain.addChild(node=old_domain.getTag("description"))
        vm_item.getTag("virtualmachine").delChild("domain")
        vm_item.getTag("virtualmachine").addChild(node=domain)

        def publish_success(resp):
            if resp.getType() == "result":
                self.inhibit_next_general_push = True
                self.entity.push_change("vmparking", "updated")
                self.pubsub_vmparking.remove_item(ticket)
                self.entity.log.info(
                    "VMPARKING: virtual machine %s as been updated" % new_uuid)
            else:
                self.inhibit_next_general_push = True
                self.entity.push_change("vmparking",
                                        "cannot-update",
                                        content_node=resp)
                self.entity.log.error(
                    "VMPARKING: unable to update item for virtual machine %s: %s"
                    % (new_uuid, resp))

        self.pubsub_vmparking.add_item(vm_item.getTag("virtualmachine"),
                                       callback=publish_success)

    ### XMPP Management for hypervisors

    def process_iq_for_hypervisor(self, conn, iq):
        """
        This method is invoked when a ARCHIPEL_NS_HYPERVISOR_VMPARKING IQ is received.
        It understands IQ of type:
            - list
            - park
            - unpark
            - destroy
            - updatexml
        @type conn: xmpp.Dispatcher
        @param conn: ths instance of the current connection that send the stanza
        @type iq: xmpp.Protocol.Iq
        @param iq: the received IQ
        """
        reply = None
        action = self.entity.check_acp(conn, iq)
        self.entity.check_perm(conn, iq, action, -1, prefix="vmparking_")
        if action == "list":
            reply = self.iq_list(iq)
        if action == "park":
            reply = self.iq_park(iq)
        if action == "unpark":
            reply = self.iq_unpark(iq)
        if action == "delete":
            reply = self.iq_delete(iq)
        if action == "updatexml":
            reply = self.iq_updatexml(iq)
        if reply:
            conn.send(reply)
            raise xmpp.protocol.NodeProcessed

    def iq_list(self, iq):
        """
        Return the list of parked virtual machines
        @type iq: xmpp.Protocol.Iq
        @param iq: the received IQ
        @rtype: xmpp.Protocol.Iq
        @return: a ready to send IQ containing the result of the action
        """
        try:
            reply = iq.buildReply("result")
            parked_vms = self.list()
            nodes = []
            for parked_vm in parked_vms:
                vm_node = xmpp.Node("virtualmachine", attrs=parked_vm["info"])
                if parked_vm["domain"].getTag('description'):
                    parked_vm["domain"].delChild("description")
                vm_node.addChild(node=parked_vm["domain"])
                nodes.append(vm_node)
            reply.setQueryPayload(nodes)
        except Exception as ex:
            reply = build_error_iq(self, ex, iq,
                                   ARCHIPEL_ERROR_CODE_VMPARK_LIST)
        return reply

    def message_list(self, msg):
        """
        Handle the parking list message.
        @type msg: xmmp.Protocol.Message
        @param msg: the message
        @rtype: string
        @return: the answer
        """
        try:
            tokens = msg.getBody().split()
            if not len(tokens) == 2:
                return "I'm sorry, you use a wrong format. You can type 'help' to get help."
            parked_vms = self.list()
            resp = "Sure! Here is the virtual machines parked:\n"
            for info in parked_vms:
                ticket = info["info"]["itemid"]
                name = info["domain"].getTag("name").getData()
                uuid = info["domain"].getTag("uuid").getData()
                resp = "%s - [%s]: %s (%s)\n" % (resp, ticket, name, uuid)
            return resp
        except Exception as ex:
            return build_error_message(self, ex, msg)

    def iq_park(self, iq):
        """
        Park virtual machine
        @type iq: xmpp.Protocol.Iq
        @param iq: the received IQ
        @rtype: xmpp.Protocol.Iq
        @return: a ready to send IQ containing the result of the action
        """
        try:
            items = iq.getTag("query").getTag("archipel").getTags("item")
            for item in items:
                vm_uuid = item.getAttr("uuid")
                if not vm_uuid:
                    self.entity.log.error(
                        "VMPARKING: Unable to park vm: missing 'uuid' element."
                    )
                    raise Exception(
                        "You must must set the UUID of the vms you want to park"
                    )
                force_destroy = False
                if item.getAttr("force") and item.getAttr("force").lower() in (
                        "yes", "y", "true", "1"):
                    force_destroy = True
                self.park(vm_uuid, iq.getFrom(), force=force_destroy)
            reply = iq.buildReply("result")
        except Exception as ex:
            reply = build_error_iq(self, ex, iq,
                                   ARCHIPEL_ERROR_CODE_VMPARK_PARK)
        return reply

    def message_park_hypervisor(self, msg):
        """
        Handle the park message.
        @type msg: xmmp.Protocol.Message
        @param msg: the message
        @rtype: string
        @return: the answer
        """
        try:
            tokens = msg.getBody().split()
            if len(tokens) < 2:
                return "I'm sorry, you use a wrong format. You can type 'help' to get help."
            uuids = tokens[1].split(",")
            for vmuuid in uuids:
                self.park(vmuuid, msg.getFrom())
            if len(uuids) == 1:
                return "Virtual machine is parking."
            else:
                return "Virtual machines are parking."
        except Exception as ex:
            return build_error_message(self, ex, msg)

    def iq_unpark(self, iq):
        """
        Unpark virtual machine
        @type iq: xmpp.Protocol.Iq
        @param iq: the received IQ
        @rtype: xmpp.Protocol.Iq
        @return: a ready to send IQ containing the result of the action
        """
        try:
            reply = iq.buildReply("result")
            items = iq.getTag("query").getTag("archipel").getTags("item")
            for item in items:
                identifier = item.getAttr("identifier")
                autostart = False
                if item.getAttr("start") and item.getAttr("start").lower() in (
                        "yes", "y", "true", "1"):
                    autostart = True
                self.unpark(identifier, start=autostart)
        except Exception as ex:
            reply = build_error_iq(self, ex, iq,
                                   ARCHIPEL_ERROR_CODE_VMPARK_UNPARK)
        return reply

    def message_unpark(self, msg):
        """
        Handle the unpark message.
        @type msg: xmmp.Protocol.Message
        @param msg: the message
        @rtype: string
        @return: the answer
        """
        try:
            tokens = msg.getBody().split()
            if len(tokens) < 2:
                return "I'm sorry, you use a wrong format. You can type 'help' to get help."
            itemids = tokens[1].split(",")
            for itemid in itemids:
                self.unpark(itemid)
            if len(itemids) == 1:
                return "Virtual machine is unparking."
            else:
                return "Virtual machines are unparking."
        except Exception as ex:
            return build_error_message(self, ex, msg)

    def iq_delete(self, iq):
        """
        Delete a parked virtual machine
        @type iq: xmpp.Protocol.Iq
        @param iq: the received IQ
        @rtype: xmpp.Protocol.Iq
        @return: a ready to send IQ containing the result of the action
        """
        try:
            reply = iq.buildReply("result")
            items = iq.getTag("query").getTag("archipel").getTags("item")
            for item in items:
                identifier = item.getAttr("identifier")
                self.delete(identifier)
        except Exception as ex:
            reply = build_error_iq(self, ex, iq,
                                   ARCHIPEL_ERROR_CODE_VMPARK_DELETE)
        return reply

    def iq_updatexml(self, iq):
        """
        Update the XML description of a parked virtual machine
        @type iq: xmpp.Protocol.Iq
        @param iq: the received IQ
        @rtype: xmpp.Protocol.Iq
        @return: a ready to send IQ containing the result of the action
        """
        try:
            reply = iq.buildReply("result")
            identifier = iq.getTag("query").getTag("archipel").getAttr(
                "identifier")
            domain = iq.getTag("query").getTag("archipel").getTag("domain")
            self.updatexml(identifier, domain)
        except Exception as ex:
            reply = build_error_iq(self, ex, iq,
                                   ARCHIPEL_ERROR_CODE_VMPARK_UPDATEXML)
        return reply

    ## XMPP Management for hypervisors

    def process_iq_for_vm(self, conn, iq):
        """
        This method is invoked when a ARCHIPEL_NS_VM_VMPARKING IQ is received.
        It understands IQ of type:
            - park
        @type conn: xmpp.Dispatcher
        @param conn: ths instance of the current connection that send the stanza
        @type iq: xmpp.Protocol.Iq
        @param iq: the received IQ
        """
        reply = None
        action = self.entity.check_acp(conn, iq)
        self.entity.check_perm(conn, iq, action, -1, prefix="vmparking_")
        if action == "park":
            reply = self.iq_park_vm(iq)
        if reply:
            conn.send(reply)
            raise xmpp.protocol.NodeProcessed

    def iq_park_vm(self, iq):
        """
        ask own hypervisor to park the virtual machine
        @type iq: xmpp.Protocol.Iq
        @param iq: the received IQ
        @rtype: xmpp.Protocol.Iq
        @return: a ready to send IQ containing the result of the action
        """
        try:
            reply = iq.buildReply("result")
            self.entity.hypervisor.get_plugin("vmparking").park(
                self.entity.uuid, iq.getFrom())
        except Exception as ex:
            reply = build_error_iq(self, ex, iq,
                                   ARCHIPEL_ERROR_CODE_VMPARK_PARK)
        return reply

    def message_park_vm(self, msg):
        """
        Handle the park message for vm.
        @type msg: xmmp.Protocol.Message
        @param msg: the message
        @rtype: string
        @return: the answer
        """
        try:
            tokens = msg.getBody().split()
            if not len(tokens) == 1:
                return "I'm sorry, you use a wrong format. You can type 'help' to get help."
            self.entity.hypervisor.get_plugin("vmparking").park(
                self.entity.uuid, msg.getFrom())
            return "I'm parking."
        except Exception as ex:
            return build_error_message(self, ex, msg)
Example #17
0
class TNArchipelCentralAgent(TNArchipelEntity, TNHookableEntity,
                             TNTaggableEntity):
    """
    This class represents a Central Agent XMPP Capable. This is a XMPP client
    which manages a central database containing all hypervisors and all vms
    in the system, and send regular pings to all hypervisors.
    """
    def __init__(self, jid, password, configuration):
        """
        This is the constructor of the class.
        @type jid: string
        @param jid: the jid of the hypervisor
        @type password: string
        @param password: the password associated to the JID
        """
        TNArchipelEntity.__init__(self, jid, password, configuration,
                                  "central-agent")
        self.log.info("Starting Archipel central agent")

        self.xmppserveraddr = self.jid.getDomain()
        self.entity_type = "central-agent"
        self.default_avatar = self.configuration.get(
            "CENTRALAGENT", "central_agent_default_avatar")
        self.libvirt_event_callback_id = None
        self.vcard_infos = {}

        self.vcard_infos["TITLE"] = "Central agent"

        self.log.info("Server address defined as %s" % self.xmppserveraddr)

        # module inits
        self.initialize_modules('archipel.plugin.core')
        self.initialize_modules('archipel.plugin.centralagent')

        # action on auth
        self.register_hook("HOOK_ARCHIPELENTITY_XMPP_AUTHENTICATED",
                           method=self.hook_xmpp_authenticated)

        self.central_agent_jid_val = None

        self.xmpp_authenticated = False
        self.is_central_agent = False
        self.salt = random.random()
        self.random_wait = random.random()
        self.database = sqlite3.connect(self.configuration.get(
            "CENTRALAGENT", "database"),
                                        check_same_thread=False)
        self.database.row_factory = sqlite3.Row

        # defining the structure of the keepalive pubsub event
        self.keepalive_event = xmpp.Node("event",
                                         attrs={
                                             "type": "keepalive",
                                             "jid": self.jid
                                         })
        self.last_keepalive_heard = datetime.datetime.now()
        self.last_hyp_check = datetime.datetime.now()
        self.required_stats_xml = None

        # start the permission center
        self.permission_db_file = self.configuration.get(
            "CENTRALAGENT", "centralagent_permissions_database_path")
        self.permission_center.start(database_file=self.permission_db_file)
        self.init_permissions()

        module_platformrequest = self.configuration.get(
            "MODULES", "platformrequest")

        if module_platformrequest:

            required_stats = self.get_plugin(
                "platformrequest").computing_unit.required_stats
            self.required_stats_xml = xmpp.Node("required_stats")

            for stat in required_stats:

                self.log.debug("CENTRALAGENT: stat : %s" % stat)
                self.required_stats_xml.addChild("stat", attrs=stat)

            self.keepalive_event.addChild(node=self.required_stats_xml)

    ### Utilities

    def register_handlers(self):
        """
        This method will be called by the plugin user when it will be
        necessary to register module for listening to stanza.
        """
        TNArchipelEntity.register_handlers(self)
        self.xmppclient.RegisterHandler('iq',
                                        self.process_iq_for_centralagent,
                                        ns=ARCHIPEL_NS_CENTRALAGENT)

    def unregister_handlers(self):
        """
        Unregister the handlers.
        """
        TNArchipelEntity.unregister_handlers(self)
        self.xmppclient.UnregisterHandler('iq',
                                          self.process_iq_for_centralagent,
                                          ns=ARCHIPEL_NS_CENTRALAGENT)

    def process_iq_for_centralagent(self, conn, iq):
        """
        This method is invoked when a ARCHIPEL_NS_CENTRALAGENT IQ is received.
        It understands IQ of type:
            - read_hypervisors
            - read_vms
            - read_vms_started_elsewhere
            - register_hypervisors
            - register_vms
            - update_vms
            - update_hypervisors
            - unregister_hypervisors
            - unregister_vms
        @type conn: xmpp.Dispatcher
        @param conn: ths instance of the current connection that send the stanza
        @type iq: xmpp.Protocol.Iq
        @param iq: the received IQ
        """
        reply = None
        action = self.check_acp(conn, iq)
        if action == "read_hypervisors":
            reply = self.iq_read_hypervisors(iq)
        elif action == "read_vms":
            reply = self.iq_read_vms(iq)
        elif action == "read_vms_started_elsewhere":
            reply = self.iq_read_vms_started_elsewhere(iq)
        elif action == "register_hypervisors":
            reply = self.iq_register_hypervisors(iq)
        elif action == "register_vms":
            reply = self.iq_register_vms(iq)
        elif action == "update_vms":
            reply = self.iq_update_vms(iq)
        elif action == "update_vms_domain":
            reply = self.iq_update_vms_domain(iq)
        elif action == "update_hypervisors":
            reply = self.iq_update_hypervisors(iq)
        elif action == "unregister_hypervisors":
            reply = self.iq_unregister_hypervisors(iq)
        elif action == "unregister_vms":
            reply = self.iq_unregister_vms(iq)
        if reply:
            self.log.debug("CENTRALAGENT: we got a reply for this iq %s" %
                           reply)
            conn.send(reply)
            self.log.debug("CENTRALAGENT: reply sent")
            raise xmpp.protocol.NodeProcessed

    ### Pubsub management

    def hook_xmpp_authenticated(self,
                                origin=None,
                                user_info=None,
                                arguments=None):
        """
        Triggered when we are authenticated. Initializes everything.
        @type origin: L{TNArchipelEnity}
        @param origin: the origin of the hook
        @type user_info: object
        @param user_info: random user information
        @type arguments: object
        @param arguments: runtime argument
        """

        self.xmpp_authenticated = True
        status = "%s" % ARCHIPEL_XMPP_SHOW_ONLINE
        self.change_presence(self.xmppstatusshow, status)

        self.is_central_agent = False

        self.central_agent_mode = self.configuration.get(
            "CENTRALAGENT", "centralagent")
        self.ping_hypervisors = self.configuration.getboolean(
            "CENTRALAGENT", "ping_hypervisors")
        # 2 possible modes :
        # - auto          : default mode of operation. If there is no other central agent detected,
        #                   the node is acting as central agent. If 2 central agents are declared
        #                   at the same time, an election is performed.
        # - force         : will always be central agent. centralized model when one hypervisor is always online.
        #                   Warning : be sure there is only 1 otherwise they will fight with each other.
        self.log.debug("CENTRALAGENT: Mode %s" % self.central_agent_mode)
        self.central_keepalive_pubsub = TNPubSubNode(
            self.xmppclient, self.pubsubserver, ARCHIPEL_KEEPALIVE_PUBSUB)
        self.central_keepalive_pubsub.recover()
        self.central_keepalive_pubsub.subscribe(
            self.jid, self.handle_central_keepalive_event)
        self.log.info(
            "CENTRALAGENT: entity %s is now subscribed to events from node %s"
            % (self.jid, ARCHIPEL_KEEPALIVE_PUBSUB))

        self.change_presence("away", "Standby")

        if self.central_agent_mode == "force":

            self.become_central_agent()

        elif self.central_agent_mode == "auto":

            self.last_keepalive_heard = datetime.datetime.now()
            self.last_hyp_check = datetime.datetime.now()

    def become_central_agent(self):
        """
        triggered when becoming active central agent
        """
        self.is_central_agent = True
        self.manage_database()
        initial_keepalive = xmpp.Node("event",
                                      attrs={
                                          "type": "keepalive",
                                          "jid": self.jid
                                      })
        initial_keepalive.setAttr("force_update", "true")
        initial_keepalive.setAttr("salt", self.salt)
        now = datetime.datetime.now()
        now_string = now.strftime("%Y-%m-%d %H:%M:%S.%f")
        initial_keepalive.setAttr("central_agent_time", now_string)

        if self.required_stats_xml:

            initial_keepalive.addChild(node=self.required_stats_xml)

        self.central_keepalive_pubsub.add_item(initial_keepalive)
        self.log.debug("CENTRALAGENT: initial keepalive sent")
        self.last_keepalive_sent = datetime.datetime.now()
        self.last_hyp_check = datetime.datetime.now()
        self.change_presence("", "Active")

    def central_agent_jid(self):
        """
        Returns the jid of the central agent. In case we are a VM, query hypervisor.
        """
        return self.central_agent_jid_val

    def keepalive_event_with_date(self):
        """
        Returns the keepalive event with current date, to send to the pubsub
        so that all ping calculations are based on central agent date.
        """
        now = datetime.datetime.now()
        now_string = now.strftime("%Y-%m-%d %H:%M:%S.%f")
        keepalive_event = self.keepalive_event
        keepalive_event.setAttr("central_agent_time", now_string)

        return keepalive_event

    def handle_central_keepalive_event(self, event):
        """
        Called when the central agents announce themselves.
        @type event: xmpp.Node
        @param event: the pubsub event node
        """
        items = event.getTag("event").getTag("items").getTags("item")

        for item in items:

            central_announcement_event = item.getTag("event")
            event_type = central_announcement_event.getAttr("type")

            if event_type == "keepalive":

                old_central_agent_jid = self.central_agent_jid()
                self.log.debug("CENTRALAGENT: Keepalive heard.")
                keepalive_jid = xmpp.JID(
                    central_announcement_event.getAttr("jid"))

                if self.is_central_agent and keepalive_jid != self.jid:

                    # detect another central agent
                    self.log.warning(
                        "CENTRALAGENT: another central agent detected, performing election"
                    )
                    keepalive_salt = float(
                        central_announcement_event.getAttr("salt"))

                    if keepalive_salt > self.salt:

                        self.log.debug("CENTRALAGENT: stepping down")
                        self.change_presence("away", "Standby")
                        self.is_central_agent = False

                    else:

                        self.log.debug("CENTRALAGENT: election won")
                        return

                self.central_agent_jid_val = keepalive_jid
                self.last_keepalive_heard = datetime.datetime.now()

    def iq_read_hypervisors(self, iq):
        """
        Called when the central agent receives a hypervisor read event.
        @type iq: xmpp.Iq
        @param iq: received Iq
        """
        try:
            read_event = iq.getTag("query").getTag("archipel").getTag("event")
            columns = read_event.getAttr("columns")
            where_statement = read_event.getAttr("where_statement")
            reply = iq.buildReply("result")
            entries = self.read_hypervisors(columns, where_statement)
            for entry in self.pack_entries(entries):
                reply.addChild(node=entry)
        except Exception as ex:
            reply = build_error_iq(self, ex, iq,
                                   ARCHIPEL_ERROR_CODE_CENTRALAGENT)
        return reply

    def iq_read_vms(self, iq):
        """
        Called when the central agent receives a vms read event.
        @type iq: xmpp.Iq
        @param iq: received Iq
        """
        try:
            read_event = iq.getTag("query").getTag("archipel").getTag("event")
            columns = read_event.getAttr("columns")
            where_statement = read_event.getAttr("where_statement")
            reply = iq.buildReply("result")
            entries = self.read_vms(columns, where_statement)
            for entry in self.pack_entries(entries):
                reply.addChild(node=entry)
        except Exception as ex:
            reply = build_error_iq(self, ex, iq,
                                   ARCHIPEL_ERROR_CODE_CENTRALAGENT)
        return reply

    def iq_read_vms_started_elsewhere(self, iq):
        """
        Called when the central agent receives a vms started elsewhere read event.
        @type iq: xmpp.Iq
        @param iq: received Iq
        """
        try:
            read_event = iq.getTag("query").getTag("archipel").getTag("event")
            entries = self.unpack_entries(iq)
            self.log.debug(
                "CENTRALAGENT: iq_read_vms_started_elsewhere : iq : %s, entries : %s"
                % (iq, entries))
            origin_hyp = iq.getFrom()
            reply = iq.buildReply("result")
            entries = self.read_vms_started_elsewhere(entries, origin_hyp)
            for entry in self.pack_entries(entries):
                reply.addChild(node=entry)
        except Exception as ex:
            reply = build_error_iq(self, ex, iq,
                                   ARCHIPEL_ERROR_CODE_CENTRALAGENT)
        return reply

    def iq_register_hypervisors(self, iq):
        """
        Called when the central agent receives a hypervisor registration event.
        @type iq: xmpp.Iq
        @param iq: received Iq
        """
        try:
            reply = iq.buildReply("result")
            entries = self.unpack_entries(iq)
            self.register_hypervisors(entries)
        except Exception as ex:
            reply = build_error_iq(self, ex, iq,
                                   ARCHIPEL_ERROR_CODE_CENTRALAGENT)
        return reply

    def iq_register_vms(self, iq):
        """
        Called when the central agent receives a vms registration event.
        @type iq: xmpp.Iq
        @param iq: received Iq
        """
        try:
            reply = iq.buildReply("result")
            entries = self.unpack_entries(iq)
            self.register_vms(entries)
        except Exception as ex:
            reply = build_error_iq(self, ex, iq,
                                   ARCHIPEL_ERROR_CODE_CENTRALAGENT)
        return reply

    def iq_update_vms(self, iq):
        """
        Called when the central agent receives a vms update event.
        @type iq: xmpp.Iq
        @param iq: received Iq
        """
        try:
            reply = iq.buildReply("result")
            entries = self.unpack_entries(iq)
            self.update_vms(entries)
        except Exception as ex:
            reply = build_error_iq(self, ex, iq,
                                   ARCHIPEL_ERROR_CODE_CENTRALAGENT)
        return reply

    def iq_update_vms_domain(self, iq):
        """
        Called when the central agent receives a vm domain update event.
        @type iq: xmpp.Iq
        @param iq: received Iq
        """
        try:
            reply = iq.buildReply("result")
            entries = self.unpack_entries(iq)
            entries = self.update_vms_domain(entries)
            for entry in self.pack_entries(entries):
                reply.addChild(node=entry)
        except Exception as ex:
            reply = build_error_iq(self, ex, iq,
                                   ARCHIPEL_ERROR_CODE_CENTRALAGENT)
        return reply

    def iq_update_hypervisors(self, iq):
        """
        Called when the central agent receives a vms update event.
        @type iq: xmpp.Iq
        @param iq: received Iq
        """
        try:
            reply = iq.buildReply("result")
            entries = self.unpack_entries(iq)
            self.update_hypervisors(entries)
        except Exception as ex:
            reply = build_error_iq(self, ex, iq,
                                   ARCHIPEL_ERROR_CODE_CENTRALAGENT)
        return reply

    def iq_unregister_hypervisors(self, iq):
        """
        Called when the central agent receives a hypervisor unregistration event.
        @type iq: xmpp.Iq
        @param iq: received Iq
        """
        try:
            reply = iq.buildReply("result")
            entries = self.unpack_entries(iq)
            self.unregister_hypervisors(entries)
        except Exception as ex:
            reply = build_error_iq(self, ex, iq,
                                   ARCHIPEL_ERROR_CODE_CENTRALAGENT)
        return reply

    def iq_unregister_vms(self, iq):
        """
        Called when the central agent receives a vms registration event.
        @type iq: xmpp.Iq
        @param iq: received Iq
        """
        try:
            reply = iq.buildReply("result")
            in_entries = self.unpack_entries(iq)
            out_entries = self.unregister_vms(in_entries)
            for entry in self.pack_entries(out_entries):
                reply.addChild(node=entry)
        except Exception as ex:
            reply = build_error_iq(self, ex, iq,
                                   ARCHIPEL_ERROR_CODE_CENTRALAGENT)
        return reply

    def read_hypervisors(self, columns, where_statement):
        """
        Reads list of hypervisors in central db.
        """
        read_statement = "select %s from hypervisors" % columns
        if where_statement:
            read_statement += " where %s" % where_statement
        rows = self.database.execute(read_statement)
        ret = []
        for row in rows:
            ret.append({"jid": row[0], "last_seen": row[1], "status": row[2]})
        return ret

    def read_vms(self, columns, where_statement):
        """
        Read list of vms in central db.
        """
        read_statement = "select %s from vms" % columns
        if where_statement:
            read_statement += " where %s" % where_statement
        rows = self.database.execute(read_statement)
        ret = []
        for row in rows:
            if columns == "*":
                ret.append({
                    "uuid": row[0],
                    "parker": row[1],
                    "creation_date": row[2],
                    "domain": row[3],
                    "hypervisor": row[4]
                })
            else:
                res = {}
                i = 0
                for col in columns.split(","):
                    res[col] = row[i]
                    i += 1
                ret.append(res)
        return ret

    def read_vms_started_elsewhere(self, entries, origin_hyp):
        """
        Based on a list of vms, and an hypervisor, return list of vms which 
        are defined in another, currently running, hypervisor.
        """
        uuids = []
        for entry in entries:
            uuids.append(entry["uuid"])
        self.log.debug("CENTRALAGENT: read_vms_started_elsewhere uuids :%s " %
                       uuids)
        read_statement = "select vms.uuid from vms join hypervisors on hypervisors.jid=vms.hypervisor where (vms.uuid='"
        read_statement += "' or vms.uuid='".join(uuids)
        read_statement += "') and hypervisors.jid != '%s'" % origin_hyp
        read_statement += " and hypervisors.status='Online'"

        self.log.debug("CENTRALAGENT: read statement %s" % read_statement)
        rows = self.database.execute(read_statement)
        ret = []
        for row in rows:
            ret.append({"uuid": row[0]})
        self.log.debug("CENTRALAGENT: return of read statement : %s" % ret)
        return ret

    def read_parked_vms(self, entries):
        """
        Based on a list of vms, and an hypervisor, return list of vms which 
        are parked (have no hypervisor, or have a hypervisor which is not online)
        """
        uuids = []
        for entry in entries:
            uuids.append(entry["uuid"])
        read_statement = "select uuid, domain from vms where uuid=('"
        read_statement += "' or vms.uuid='".join(uuids)
        read_statement += "') and hypervisor='None' or hypervisor not in (select jid from hypervisors where status='Online')"
        self.log.debug("CENTRALDB: read_parked_vms read statement :%s" %
                       read_statement)

        rows = self.database.execute(read_statement)
        ret = []
        for row in rows:
            ret.append({"uuid": row[0], "domain": row[1]})
        return ret

    def register_hypervisors(self, entries):
        """
        Register a list of hypervisors into central db.
        @type entries: List
        @param entries: list of hypervisors
        """
        self.db_commit(
            "insert into hypervisors values(:jid, :last_seen, :status, :stat1, :stat2, :stat3)",
            entries)

    def register_vms(self, entries):
        """
        Register a list of vms into central db.
        @type entries: List
        @param entries: list of vms
        """
        self.db_commit(
            "insert into vms values(:uuid, :parker, :creation_date, :domain, :hypervisor)",
            entries)

    def update_vms(self, entries):
        """
        Update a list of vms in central db.
        @type entries: List
        @param entries: list of vms
        """
        update_snipplets = []
        for key, val in entries[0].iteritems():
            if key != "uuid":
                update_snipplets.append("%s=:%s" % (key, key))
        command = "update vms set %s where uuid=:uuid" % (
            ", ".join(update_snipplets))
        self.db_commit(command, entries)

    def update_vms_domain(self, entries):
        """
        Update a list of vms domain in central db.
        Performs extra checks compared to a raw update.
        @type entries: List
        @param entries: list of vms
        """
        results = []

        # we check if vms are really parked
        parked_vms_ret = self.read_parked_vms(entries)
        parked_vms = {}
        for ret in parked_vms_ret:
            parked_vms[ret["uuid"]] = {
                "domain":
                xmpp.simplexml.NodeBuilder(data=ret["domain"]).getDom()
            }
        entries_to_commit = []

        for i in range(len(entries)):
            entry = entries[i]
            error = False
            for key, val in entry.iteritems():
                if key == "domain":
                    new_domain = xmpp.simplexml.NodeBuilder(data=val).getDom()
                if key == "uuid":
                    uuid = val
            if uuid not in parked_vms.keys():
                result = "ERROR: There is no virtual machine parked with uuid %s" % uuid
                error = True
            else:
                old_domain = parked_vms[uuid]["domain"]

                previous_uuid = old_domain.getTag("uuid").getData()
                previous_name = old_domain.getTag("name").getData()
                new_uuid = ""
                new_name = ""
                if new_domain.getTag("uuid"):
                    new_uuid = new_domain.getTag("uuid").getData()
                if new_domain.getTag("name"):
                    new_name = new_domain.getTag("name").getData()

                if not previous_uuid.lower() == new_uuid.lower():
                    result = "ERROR: UUID of new description must be the same (was %s, is %s)" % (
                        previous_uuid, new_uuid)
                    error = True
                if not previous_name.lower() == new_name.lower():
                    result = "ERROR: Name of new description must be the same (was %s, is %s)" % (
                        previous_name, new_name)
                    error = True
                if not new_name or new_name == "":
                    result = "ERROR: Missing name information"
                    error = True
                if not new_uuid or new_uuid == "":
                    result = "ERROR: Missing UUID information"
                    error = True
                if not error:
                    # all checks ok, performing update
                    if new_domain.getTag('description'):
                        new_domain.delChild("description")
                    previous_description = old_domain.getTag("description")
                    self.log.debug("CENTRALAGENT: previous description : %s" %
                                   str(previous_description))
                    new_domain.addChild(node=old_domain.getTag("description"))
                    result = "Central database updated with new information"
                    entries_to_commit.append({
                        "uuid": uuid,
                        "domain": str(new_domain)
                    })
                results.append({
                    "result": result,
                    "uuid": uuid,
                    "error": error
                })

        if len(entries_to_commit) > 0:
            command = "update vms set domain=:domain where uuid=:uuid"
            self.db_commit(command, entries_to_commit)
        return results

    def update_hypervisors(self, entries):
        """
        Update a list of hypervisors in central db.
        @type entries: List
        @param entries: list of vms
        """
        update_snipplets = []
        for key, val in entries[0].iteritems():
            if key != "jid":
                update_snipplets.append("%s=:%s" % (key, key))
        command = "update hypervisors set %s where jid=:jid" % (
            ", ".join(update_snipplets))
        self.db_commit(command, entries)

    def unregister_hypervisors(self, entries):
        """
        Unregister a list of hypervisors from central db.
        @type entries: List
        @param entries: list of hypervisors
        """
        self.db_commit(
            "delete from hypervisors where jid=:jid, last_seen=:last_seen, status=:status",
            entries)

    def unregister_vms(self, entries):
        """
        Unregister a list of vms from central db.
        @type entries: List
        @param entries: list of vms
        """
        # first, we extract jid so that the hypervisor can unregister them
        uuids = []
        for entry in entries:
            uuids.append(entry["uuid"])
        where_statement = "uuid = '"
        where_statement += "' or uuid='".join(uuids)
        where_statement += "'"

        # list of vms which have been found in central db, including uuid and jid
        cleaned_entries = self.read_vms("uuid,domain", where_statement)
        for i in range(len(cleaned_entries)):
            domain_xml = cleaned_entries[i]["domain"]
            if domain_xml != "None":
                domain = xmpp.simplexml.NodeBuilder(
                    data=cleaned_entries[i]["domain"]).getDom()
                cleaned_entries[i]["jid"] = xmpp.JID(
                    domain.getTag("description").getData().split("::::")[0])
                del (cleaned_entries[i]["domain"])

        self.db_commit("delete from vms where uuid=:uuid", cleaned_entries)
        return cleaned_entries

    def unpack_entries(self, iq):
        """
        Unpack the list of entries from iq for database processing.
        @type iq: xmpp.Iq
        @param event: received Iq
        """
        central_database_event = iq.getTag("query").getTag("archipel").getTag(
            "event")
        command = central_database_event.getAttr("command")
        entries = []
        for entry in central_database_event.getChildren():
            entry_dict = {}
            for entry_val in entry.getChildren():
                entry_dict[entry_val.getAttr("key")] = entry_val.getAttr(
                    "value")
            entries.append(entry_dict)
        return entries

    def pack_entries(self, entries):
        """
        Pack the list of entries to send to remote entity.
        @rtype: list
        @return: list of xmpp nodes, one per entry
        @type entries: list
        @param entries: list of dict entities
        """
        packed_entries = []
        for entry in entries:
            entryTag = xmpp.Node(tag="entry")
            for key, value in entry.iteritems():
                entryTag.addChild("item", attrs={"key": key, "value": value})
            packed_entries.append(entryTag)
        return packed_entries

    def db_commit(self, command, entries):
        if self.is_central_agent:
            self.log.debug("CENTRALAGENT: commit '%s' with entries %s" %
                           (command, entries))
            self.database.executemany(command, entries)
            self.database.commit()
        else:
            raise Exception("CENTRALAGENT: we are not central agent")

    def check_hyps(self):
        """
        Check that hypervisors are alive.
        """
        self.log.debug("CENTRALAGENT: now checking all hypervisors are alive")
        now = datetime.datetime.now()
        rows = self.database.execute("select jid,last_seen from hypervisors;")
        unreachable_hypervisors = []

        for row in rows:

            jid, last_seen = row

            last_seen_date = datetime.datetime.strptime(
                last_seen, "%Y-%m-%d %H:%M:%S.%f")

            if (now - last_seen_date
                ).seconds > ARCHIPEL_CENTRAL_HYP_CHECK_TIMEOUT:

                self.log.info("CENTRALAGENT: hyp %s timed out" % jid)
                unreachable_hypervisors.append({
                    "jid": jid,
                    "status": "Unreachable"
                })

        if len(unreachable_hypervisors) > 0:

            self.update_hypervisors(unreachable_hypervisors)

        self.last_hyp_check = datetime.datetime.now()

    ### Database Management

    def manage_database(self):
        """
        Create and / or recover the parking database
        """
        self.database.execute(
            "create table if not exists vms (uuid text unique on conflict replace, parker string, creation_date date, domain string, hypervisor string)"
        )
        self.database.execute(
            "create table if not exists hypervisors (jid text unique on conflict replace, last_seen date, status string, stat1 int, stat2 int, stat3 int)"
        )
        #By default on startup, put everything in the parking. Hypervisors will announce their vms.
        self.database.execute("update vms set hypervisor='None';")
        self.database.commit()

    ### Event loop

    def on_xmpp_loop_tick(self):

        if self.xmpp_authenticated:

            if not self.is_central_agent and self.central_agent_mode == "auto":

                # before becoming a central agent, we wait for timeout period plus a random amount of time
                # to avoid race conditions
                central_agent_timeout = ARCHIPEL_CENTRAL_AGENT_TIMEOUT * (
                    1 + self.random_wait)

                if (datetime.datetime.now() - self.last_keepalive_heard
                    ).seconds > central_agent_timeout:

                    self.log.info(
                        "CENTRALAGENT: has not detected any central agent for the last %s seconds, becoming central agent."
                        % central_agent_timeout)
                    self.become_central_agent()

            elif self.is_central_agent:  # we are central agent

                if (datetime.datetime.now() - self.last_keepalive_sent
                    ).seconds >= ARCHIPEL_CENTRAL_AGENT_KEEPALIVE:

                    self.central_keepalive_pubsub.add_item(
                        self.keepalive_event_with_date())
                    self.last_keepalive_sent = datetime.datetime.now()

                if self.ping_hypervisors:

                    if (datetime.datetime.now() - self.last_hyp_check
                        ).seconds >= ARCHIPEL_CENTRAL_HYP_CHECK_FREQUENCY:

                        self.check_hyps()
Example #18
0
class TNCentralDb (TNArchipelPlugin):
    """
    This contains the necessary interfaces to interact with central agent and central db
    """

    def __init__(self, configuration, entity, entry_point_group):
        """
        Initialize the plugin.
        @type configuration: Configuration object
        @param configuration: the configuration
        @type entity: L{TNArchipelEntity}
        @param entity: the entity that owns the plugin
        @type entry_point_group: string
        @param entry_point_group: the group name of plugin entry_point
        """
        TNArchipelPlugin.__init__(self, configuration=configuration, entity=entity, entry_point_group=entry_point_group)

        if self.entity.__class__.__name__ == "TNArchipelHypervisor":

            self.entity.register_hook("HOOK_ARCHIPELENTITY_XMPP_AUTHENTICATED", method=self.hypervisor_hook_xmpp_authenticated)

        if self.entity.__class__.__name__ == "TNArchipelVirtualMachine":

            self.entity.register_hook("HOOK_VM_DEFINE", method=self.hook_vm_event)
            self.entity.register_hook("HOOK_VM_INITIALIZE", method=self.hook_vm_event)
            self.entity.register_hook("HOOK_VM_TERMINATE", method=self.hook_vm_terminate)

        self.central_agent_jid_val = None

        self.xmpp_authenticated    = False
        self.required_statistics        = []

    ### Hooks
    def hook_vm_event(self, origin=None, user_info=None, arguments=None):
        """
        Called when a VM definition or change of definition occurs.
        This will advertise definition to the central agent
        """
        xmldesc = None

        if self.entity.definition:

            xmldesc = self.entity.xmldesc(mask_description=False)

        vm_info=[{"uuid":self.entity.uuid,"parker":None,"creation_date":None,"domain":xmldesc,"hypervisor":self.entity.hypervisor.jid}]
        self.register_vms(vm_info) 

    def hook_vm_terminate(self, origin=None, user_info=None, arguments=None):
        """
        Called when a VM termination occurs.
        This will advertise undefinition to the central agent.
        """
        self.unregister_vms([{"uuid":self.entity.uuid}], None)

    ### Pubsub management

    def hypervisor_hook_xmpp_authenticated(self, origin=None, user_info=None, arguments=None):
        """
        Triggered when we are authenticated. Initializes everything.
        @type origin: L{TNArchipelEnity}
        @param origin: the origin of the hook
        @type user_info: object
        @param user_info: random user information
        @type arguments: object
        @param arguments: runtime argument
        """

        self.xmpp_authenticated  = True
        self.central_keepalive_pubsub = TNPubSubNode(self.entity.xmppclient, self.entity.pubsubserver, ARCHIPEL_KEEPALIVE_PUBSUB)
        self.central_keepalive_pubsub.recover()
        self.central_keepalive_pubsub.subscribe(self.entity.jid, self.handle_central_keepalive_event)
        self.entity.log.info("CENTRALDB: entity %s is now subscribed to events from node %s" % (self.entity.jid, ARCHIPEL_KEEPALIVE_PUBSUB))
        self.last_keepalive_heard = datetime.datetime.now()

    def central_agent_jid(self):
        """
        Returns the jid of the central agent. In case we are a VM, query hypervisor.
        """
        if self.entity.__class__.__name__ == "TNArchipelHypervisor":

            return self.central_agent_jid_val

        else:

            return self.entity.hypervisor.get_plugin("centraldb").central_agent_jid_val
        

    def handle_central_keepalive_event(self,event):
        """
        Called when the central agent sends a keepalive.
        @type event: xmpp.Node
        @param event: the pubsub event node
        """
        items = event.getTag("event").getTag("items").getTags("item")

        for item in items:

            central_announcement_event = item.getTag("event")
            event_type                 = central_announcement_event.getAttr("type")

            if event_type == "keepalive":

                old_central_agent_jid = self.central_agent_jid()
                self.entity.log.debug("CENTRALDB: Keepalive heard : %s " % str(item))
                keepalive_jid              = xmpp.JID(central_announcement_event.getAttr("jid"))

                # we use central agent time in case of drift between hypervisors
                central_agent_time         = central_announcement_event.getAttr("central_agent_time")

                self.central_agent_jid_val = keepalive_jid
                self.last_keepalive_heard  = datetime.datetime.now()

                self.push_statistics_to_centraldb(central_agent_time)

                if old_central_agent_jid == None:

                    self.handle_first_keepalive(keepalive_jid)

                if central_announcement_event.getAttr("force_update") == "true" or keepalive_jid != old_central_agent_jid:

                    self.push_vms_in_central_db(central_announcement_event)

    def push_statistics_to_centraldb(self, central_agent_time):
        """
        each time we hear a keepalive, we push relevant statistics to central db
        @type central_agent_time: string
        @param central_agent_time: the time acccording to central agent as local time may drift - in database format
        """
        stats_results = {"jid":str(self.entity.jid), "last_seen": central_agent_time}

        if len(self.required_statistics) > 0 :

            stat_num = 0

            for stat in self.required_statistics:

                stat_num += 1
                value = eval("self.entity.get_plugin('hypervisor_health').collector.stats_%s" % stat["major"])[-1][stat["minor"]]
                stats_results["stat%s" % stat_num] = value

            self.entity.log.debug("CENTRALDB: updating central db with %s" % stats_results)

        self.update_hypervisors([stats_results])


    def handle_first_keepalive(self, keepalive_jid):
        """
        this is the first keepalive. We query hypervisors that have started somewhere else
        then we trigger method manage_persistence to start the vms.
        """
        vms_from_local_db = self.entity.get_vms_from_local_db()

        if len(vms_from_local_db) > 0:

            dbCommand = xmpp.Node(tag="event", attrs={"jid":self.entity.jid})

            for vm in vms_from_local_db:

                entryTag = xmpp.Node(tag="entry")
                uuid     = xmpp.JID(vm["string_jid"]).getNode()
                entryTag.addChild("item",attrs={"key":"uuid","value": uuid})
                dbCommand.addChild(node=entryTag)

            iq = xmpp.Iq(typ="set", queryNS=ARCHIPEL_NS_CENTRALAGENT, to=keepalive_jid)
            iq.getTag("query").addChild(name="archipel", attrs={"action":"read_vms_started_elsewhere"})
            iq.getTag("query").getTag("archipel").addChild(node=dbCommand)
            xmpp.dispatcher.ID += 1
            iq.setID("%s-%d" % (self.entity.jid.getNode(), xmpp.dispatcher.ID))

            def _read_vms_started_elsewhere_callback(conn, packed_vms):

                vms_started_elsewhere = self.unpack_entries(packed_vms)
                self.entity.manage_persistence(vms_from_local_db, vms_started_elsewhere)

            self.entity.xmppclient.SendAndCallForResponse(iq, _read_vms_started_elsewhere_callback)

        else:

            # update status to Online(0)
            self.entity.manage_persistence([], [])


    def push_vms_in_central_db(self, central_announcement_event):
        """
        there is a new central agent, or we just started.
        Consequently, we re-populate central database 
        since we are using "on conflict replace" mode of sqlite, inserting an existing uuid will overwrite it.
        """
        vm_table = []

        for vm,vmprops in self.entity.virtualmachines.iteritems():

            vm_table.append({"uuid":vmprops.uuid,"parker":None,"creation_date":None,"domain":vmprops.definition,"hypervisor":self.entity.jid})

        if len(vm_table) >= 1:

            self.register_vms(vm_table)

        self.register_hypervisors([{"jid":self.entity.jid, "status":"Online", "last_seen": datetime.datetime.now(), "stat1":0, "stat2":0, "stat3":0}])
        # parsing required statistics to be pushed to central agent
        self.required_statistics = []
        
        if central_announcement_event.getTag("required_stats"):
            for required_stat in central_announcement_event.getTag("required_stats").getChildren():
                self.required_statistics.append({"major":required_stat.getAttr("major"),"minor":required_stat.getAttr("minor")})

    ### Database Management

    #### read commands

    def read_hypervisors(self, columns, where_statement, callback):
        """
        List vm in database.
        @type table: list
        @param table: the list of hypervisors to insert
        """
        self.read_from_db("read_hypervisors", columns, where_statement, callback)

    def read_vms(self, columns, where_statement, callback):
        """
        Registers a list of vms into central database.
        @type table: list
        @param table: the list of vms to insert
        """
        self.read_from_db("read_vms", columns, where_statement, callback)
    
    #### write commands

    def register_hypervisors(self,table):
        """
        Registers a list of hypervisors into central database.
        @type table: list
        @param table: the list of hypervisors to insert
        """
        self.commit_to_db("register_hypervisors",table, None)

    def register_vms(self,table):
        """
        Registers a list of vms into central database.
        @type table: list
        @param table: the list of vms to insert
        """
        self.commit_to_db("register_vms",table, None)

    def unregister_hypervisors(self,table):
        """
        Unregisters a list of hypervisors from central database.
        @type table: list
        @param table: the list of hypervisors to remove
        """
        self.commit_to_db("unregister_hypervisors",table, None)

    def unregister_vms(self,table,callback):
        """
        Unregisters a list of vms from central database.
        @type table: list
        @param table: the list of vms to remove
        @type callback: func
        @para callback: will return  list of vms actually unregistered
        """
        self.commit_to_db("unregister_vms",table, callback)

    def update_vms(self,table):
        """
        Update a set of vms in central database.
        @type table: list
        @param table: the list of vms to update. Must contain the "uuid" attribute as 
                      this is the one used for key in the update statement.
        """
        self.commit_to_db("update_vms",table, None)

    def update_vms_domain(self,table,callback):
        """
        Update a set of vms in central database. 
        Performs additional checks for domain update when vm is offline.
        @type table: list
        @param table: the list of vms to update. Must contain the "uuid" attribute as 
                      this is the one used for key in the update statement.
        """
        self.commit_to_db("update_vms_domain",table, callback)

    def update_hypervisors(self,table):
        """
        Update a set of hypervisors in central database.
        @type table: list
        @param table: the list of hypervisors to update. Must contain the "jid" attribute as 
                      this is the one used for key in the update statement.
        """
        self.commit_to_db("update_hypervisors",table, None)

    def commit_to_db(self,action,table,callback):
        """
        Sends a command to active central agent for execution
        @type command: string
        @param command: the sql command to execute
        @type table: table
        @param command: the table of dicts of values associated with the command.
        """
        central_agent_jid = self.central_agent_jid()

        if central_agent_jid:

            # send an iq to central agent

            dbCommand = xmpp.Node(tag="event", attrs={"jid":self.entity.jid})

            for entry in table:

                entryTag = xmpp.Node(tag="entry")

                for key,value in entry.iteritems():

                    entryTag.addChild("item",attrs={"key":key,"value":value})

                dbCommand.addChild(node=entryTag)
        
            def commit_to_db_callback(conn,resp):

                if callback:

                    unpacked_entries = self.unpack_entries(resp)
                    callback(unpacked_entries)
    
            iq = xmpp.Iq(typ="set", queryNS=ARCHIPEL_NS_CENTRALAGENT, to=central_agent_jid)
            iq.getTag("query").addChild(name="archipel", attrs={"action":action})
            iq.getTag("query").getTag("archipel").addChild(node=dbCommand)
            self.entity.log.debug("CENTRALDB: commit to db request %s" % iq)
            xmpp.dispatcher.ID += 1
            iq.setID("%s-%d" % (self.entity.jid.getNode(), xmpp.dispatcher.ID))
            self.entity.xmppclient.SendAndCallForResponse(iq, commit_to_db_callback)

        else:

            self.entity.log.warning("CENTRALDB: cannot commit to db because we have not detected any central agent") 

    def read_from_db(self,action,columns, where_statement, callback):
        """
        Send a select statement to central db.
        @type command: string
        @param command: the sql command to execute
        @type columns: string
        @param columns: the list of database columns to return
        @type where_statement: string
        @param where_statement: for database reads, provides "where" constraint
        """
        central_agent_jid = self.central_agent_jid()

        if central_agent_jid:

            # send an iq to central agent
            dbCommand = xmpp.Node(tag="event", attrs={"jid":self.entity.jid})

            if where_statement:
                dbCommand.setAttr("where_statement", where_statement)

            if columns:
                dbCommand.setAttr("columns", columns)
        
            self.entity.log.debug("CENTRALDB: central agent jid %s" % central_agent_jid)
            iq = xmpp.Iq(typ="set", queryNS=ARCHIPEL_NS_CENTRALAGENT, to=central_agent_jid)
            iq.getTag("query").addChild(name="archipel", attrs={"action":action})
            iq.getTag("query").getTag("archipel").addChild(node=dbCommand)
            xmpp.dispatcher.ID += 1
            iq.setID("%s-%d" % (self.entity.jid.getNode(), xmpp.dispatcher.ID))

            def _read_from_db_callback(conn, resp):

                self.entity.log.debug("CENTRALDB: reply to read statement %s" % resp)
                unpacked_entries = self.unpack_entries(resp)
                self.entity.log.debug("CENTRALDB: unpacked reply %s" % unpacked_entries)
                callback(unpacked_entries)

            self.entity.xmppclient.SendAndCallForResponse(iq, _read_from_db_callback)

        else:

            self.entity.log.warning("CENTRALDB: cannot read from db because we have not detected any central agent") 

    def unpack_entries(self, iq):
        """
        Unpack the list of entries from iq for database processing.
        @type iq: xmpp.Iq
        @param event: received Iq
        """
        entries=[]

        for entry in iq.getChildren():

            entry_dict = {}

            for entry_val in entry.getChildren():

                if entry_val.getAttr("key"):

                     entry_dict[entry_val.getAttr("key")]=entry_val.getAttr("value")

            if entry_dict != {} :

                entries.append(entry_dict)

        return entries


    ### Plugin information

    @staticmethod
    def plugin_info():
        """
        Return informations about the plugin.
        @rtype: dict
        @return: dictionary contaning plugin informations
        """
        plugin_friendly_name = "Central db"
        plugin_identifier = "centraldb"
        plugin_configuration_section = "CENTRALDB"
        plugin_configuration_tokens = []
        return {"common-name": plugin_friendly_name,
                "identifier": plugin_identifier,
                "configuration-section": plugin_configuration_section,
                "configuration-tokens": plugin_configuration_tokens}
Example #19
0
class TNCentralDb(TNArchipelPlugin):
    """
    This contains the necessary interfaces to interact with central agent and central db
    """
    def __init__(self, configuration, entity, entry_point_group):
        """
        Initialize the plugin.
        @type configuration: Configuration object
        @param configuration: the configuration
        @type entity: L{TNArchipelEntity}
        @param entity: the entity that owns the plugin
        @type entry_point_group: string
        @param entry_point_group: the group name of plugin entry_point
        """
        TNArchipelPlugin.__init__(self,
                                  configuration=configuration,
                                  entity=entity,
                                  entry_point_group=entry_point_group)

        if self.entity.__class__.__name__ == "TNArchipelHypervisor":

            self.entity.register_hook(
                "HOOK_ARCHIPELENTITY_XMPP_AUTHENTICATED",
                method=self.hypervisor_hook_xmpp_authenticated)

        if self.entity.__class__.__name__ == "TNArchipelVirtualMachine":

            self.entity.register_hook("HOOK_VM_DEFINE",
                                      method=self.hook_vm_event)
            self.entity.register_hook("HOOK_VM_INITIALIZE",
                                      method=self.hook_vm_event)
            self.entity.register_hook("HOOK_VM_TERMINATE",
                                      method=self.hook_vm_terminate)

        self.central_agent_jid_val = None

        self.xmpp_authenticated = False
        self.required_statistics = []

    ### Hooks
    def hook_vm_event(self, origin=None, user_info=None, arguments=None):
        """
        Called when a VM definition or change of definition occurs.
        This will advertise definition to the central agent
        """
        xmldesc = None

        if self.entity.definition:

            xmldesc = self.entity.xmldesc(mask_description=False)

        vm_info = [{
            "uuid": self.entity.uuid,
            "parker": None,
            "creation_date": None,
            "domain": xmldesc,
            "hypervisor": self.entity.hypervisor.jid
        }]
        self.register_vms(vm_info)

    def hook_vm_terminate(self, origin=None, user_info=None, arguments=None):
        """
        Called when a VM termination occurs.
        This will advertise undefinition to the central agent.
        """
        self.unregister_vms([{"uuid": self.entity.uuid}], None)

    ### Pubsub management

    def hypervisor_hook_xmpp_authenticated(self,
                                           origin=None,
                                           user_info=None,
                                           arguments=None):
        """
        Triggered when we are authenticated. Initializes everything.
        @type origin: L{TNArchipelEnity}
        @param origin: the origin of the hook
        @type user_info: object
        @param user_info: random user information
        @type arguments: object
        @param arguments: runtime argument
        """

        self.xmpp_authenticated = True
        self.central_keepalive_pubsub = TNPubSubNode(
            self.entity.xmppclient, self.entity.pubsubserver,
            ARCHIPEL_KEEPALIVE_PUBSUB)
        self.central_keepalive_pubsub.recover()
        self.central_keepalive_pubsub.subscribe(
            self.entity.jid, self.handle_central_keepalive_event)
        self.entity.log.info(
            "CENTRALDB: entity %s is now subscribed to events from node %s" %
            (self.entity.jid, ARCHIPEL_KEEPALIVE_PUBSUB))
        self.last_keepalive_heard = datetime.datetime.now()

    def central_agent_jid(self):
        """
        Returns the jid of the central agent. In case we are a VM, query hypervisor.
        """
        if self.entity.__class__.__name__ == "TNArchipelHypervisor":

            return self.central_agent_jid_val

        else:

            return self.entity.hypervisor.get_plugin(
                "centraldb").central_agent_jid_val

    def handle_central_keepalive_event(self, event):
        """
        Called when the central agent sends a keepalive.
        @type event: xmpp.Node
        @param event: the pubsub event node
        """
        items = event.getTag("event").getTag("items").getTags("item")

        for item in items:

            central_announcement_event = item.getTag("event")
            event_type = central_announcement_event.getAttr("type")

            if event_type == "keepalive":

                old_central_agent_jid = self.central_agent_jid()
                self.entity.log.debug("CENTRALDB: Keepalive heard : %s " %
                                      str(item))
                keepalive_jid = xmpp.JID(
                    central_announcement_event.getAttr("jid"))

                # we use central agent time in case of drift between hypervisors
                central_agent_time = central_announcement_event.getAttr(
                    "central_agent_time")

                self.central_agent_jid_val = keepalive_jid
                self.last_keepalive_heard = datetime.datetime.now()

                self.push_statistics_to_centraldb(central_agent_time)

                if old_central_agent_jid == None:

                    self.handle_first_keepalive(keepalive_jid)

                if central_announcement_event.getAttr(
                        "force_update"
                ) == "true" or keepalive_jid != old_central_agent_jid:

                    self.push_vms_in_central_db(central_announcement_event)

    def push_statistics_to_centraldb(self, central_agent_time):
        """
        each time we hear a keepalive, we push relevant statistics to central db
        @type central_agent_time: string
        @param central_agent_time: the time acccording to central agent as local time may drift - in database format
        """
        stats_results = {
            "jid": str(self.entity.jid),
            "last_seen": central_agent_time
        }

        if len(self.required_statistics) > 0:

            stat_num = 0

            for stat in self.required_statistics:

                stat_num += 1
                value = eval(
                    "self.entity.get_plugin('hypervisor_health').collector.stats_%s"
                    % stat["major"])[-1][stat["minor"]]
                stats_results["stat%s" % stat_num] = value

            self.entity.log.debug("CENTRALDB: updating central db with %s" %
                                  stats_results)

        self.update_hypervisors([stats_results])

    def handle_first_keepalive(self, keepalive_jid):
        """
        this is the first keepalive. We query hypervisors that have started somewhere else
        then we trigger method manage_persistence to start the vms.
        """
        vms_from_local_db = self.entity.get_vms_from_local_db()

        if len(vms_from_local_db) > 0:

            dbCommand = xmpp.Node(tag="event", attrs={"jid": self.entity.jid})

            for vm in vms_from_local_db:

                entryTag = xmpp.Node(tag="entry")
                uuid = xmpp.JID(vm["string_jid"]).getNode()
                entryTag.addChild("item", attrs={"key": "uuid", "value": uuid})
                dbCommand.addChild(node=entryTag)

            iq = xmpp.Iq(typ="set",
                         queryNS=ARCHIPEL_NS_CENTRALAGENT,
                         to=keepalive_jid)
            iq.getTag("query").addChild(
                name="archipel",
                attrs={"action": "read_vms_started_elsewhere"})
            iq.getTag("query").getTag("archipel").addChild(node=dbCommand)
            xmpp.dispatcher.ID += 1
            iq.setID("%s-%d" % (self.entity.jid.getNode(), xmpp.dispatcher.ID))

            def _read_vms_started_elsewhere_callback(conn, packed_vms):

                vms_started_elsewhere = self.unpack_entries(packed_vms)
                self.entity.manage_persistence(vms_from_local_db,
                                               vms_started_elsewhere)

            self.entity.xmppclient.SendAndCallForResponse(
                iq, _read_vms_started_elsewhere_callback)

        else:

            # update status to Online(0)
            self.entity.manage_persistence([], [])

    def push_vms_in_central_db(self, central_announcement_event):
        """
        there is a new central agent, or we just started.
        Consequently, we re-populate central database 
        since we are using "on conflict replace" mode of sqlite, inserting an existing uuid will overwrite it.
        """
        vm_table = []

        for vm, vmprops in self.entity.virtualmachines.iteritems():

            vm_table.append({
                "uuid": vmprops.uuid,
                "parker": None,
                "creation_date": None,
                "domain": vmprops.definition,
                "hypervisor": self.entity.jid
            })

        if len(vm_table) >= 1:

            self.register_vms(vm_table)

        self.register_hypervisors([{
            "jid": self.entity.jid,
            "status": "Online",
            "last_seen": datetime.datetime.now(),
            "stat1": 0,
            "stat2": 0,
            "stat3": 0
        }])
        # parsing required statistics to be pushed to central agent
        self.required_statistics = []

        for required_stat in central_announcement_event.getTag(
                "required_stats").getChildren():

            self.required_statistics.append({
                "major":
                required_stat.getAttr("major"),
                "minor":
                required_stat.getAttr("minor")
            })

    ### Database Management

    #### read commands

    def read_hypervisors(self, columns, where_statement, callback):
        """
        List vm in database.
        @type table: list
        @param table: the list of hypervisors to insert
        """
        self.read_from_db("read_hypervisors", columns, where_statement,
                          callback)

    def read_vms(self, columns, where_statement, callback):
        """
        Registers a list of vms into central database.
        @type table: list
        @param table: the list of vms to insert
        """
        self.read_from_db("read_vms", columns, where_statement, callback)

    #### write commands

    def register_hypervisors(self, table):
        """
        Registers a list of hypervisors into central database.
        @type table: list
        @param table: the list of hypervisors to insert
        """
        self.commit_to_db("register_hypervisors", table, None)

    def register_vms(self, table):
        """
        Registers a list of vms into central database.
        @type table: list
        @param table: the list of vms to insert
        """
        self.commit_to_db("register_vms", table, None)

    def unregister_hypervisors(self, table):
        """
        Unregisters a list of hypervisors from central database.
        @type table: list
        @param table: the list of hypervisors to remove
        """
        self.commit_to_db("unregister_hypervisors", table, None)

    def unregister_vms(self, table, callback):
        """
        Unregisters a list of vms from central database.
        @type table: list
        @param table: the list of vms to remove
        @type callback: func
        @para callback: will return  list of vms actually unregistered
        """
        self.commit_to_db("unregister_vms", table, callback)

    def update_vms(self, table):
        """
        Update a set of vms in central database.
        @type table: list
        @param table: the list of vms to update. Must contain the "uuid" attribute as 
                      this is the one used for key in the update statement.
        """
        self.commit_to_db("update_vms", table, None)

    def update_vms_domain(self, table, callback):
        """
        Update a set of vms in central database. 
        Performs additional checks for domain update when vm is offline.
        @type table: list
        @param table: the list of vms to update. Must contain the "uuid" attribute as 
                      this is the one used for key in the update statement.
        """
        self.commit_to_db("update_vms_domain", table, callback)

    def update_hypervisors(self, table):
        """
        Update a set of hypervisors in central database.
        @type table: list
        @param table: the list of hypervisors to update. Must contain the "jid" attribute as 
                      this is the one used for key in the update statement.
        """
        self.commit_to_db("update_hypervisors", table, None)

    def commit_to_db(self, action, table, callback):
        """
        Sends a command to active central agent for execution
        @type command: string
        @param command: the sql command to execute
        @type table: table
        @param command: the table of dicts of values associated with the command.
        """
        central_agent_jid = self.central_agent_jid()

        if central_agent_jid:

            # send an iq to central agent

            dbCommand = xmpp.Node(tag="event", attrs={"jid": self.entity.jid})

            for entry in table:

                entryTag = xmpp.Node(tag="entry")

                for key, value in entry.iteritems():

                    entryTag.addChild("item",
                                      attrs={
                                          "key": key,
                                          "value": value
                                      })

                dbCommand.addChild(node=entryTag)

            def commit_to_db_callback(conn, resp):

                if callback:

                    unpacked_entries = self.unpack_entries(resp)
                    callback(unpacked_entries)

            iq = xmpp.Iq(typ="set",
                         queryNS=ARCHIPEL_NS_CENTRALAGENT,
                         to=central_agent_jid)
            iq.getTag("query").addChild(name="archipel",
                                        attrs={"action": action})
            iq.getTag("query").getTag("archipel").addChild(node=dbCommand)
            self.entity.log.debug("CENTRALDB: commit to db request %s" % iq)
            xmpp.dispatcher.ID += 1
            iq.setID("%s-%d" % (self.entity.jid.getNode(), xmpp.dispatcher.ID))
            self.entity.xmppclient.SendAndCallForResponse(
                iq, commit_to_db_callback)

        else:

            self.entity.log.warning(
                "CENTRALDB: cannot commit to db because we have not detected any central agent"
            )

    def read_from_db(self, action, columns, where_statement, callback):
        """
        Send a select statement to central db.
        @type command: string
        @param command: the sql command to execute
        @type columns: string
        @param columns: the list of database columns to return
        @type where_statement: string
        @param where_statement: for database reads, provides "where" constraint
        """
        central_agent_jid = self.central_agent_jid()

        if central_agent_jid:

            # send an iq to central agent
            dbCommand = xmpp.Node(tag="event", attrs={"jid": self.entity.jid})

            if where_statement:
                dbCommand.setAttr("where_statement", where_statement)

            if columns:
                dbCommand.setAttr("columns", columns)

            self.entity.log.debug("CENTRALDB: central agent jid %s" %
                                  central_agent_jid)
            iq = xmpp.Iq(typ="set",
                         queryNS=ARCHIPEL_NS_CENTRALAGENT,
                         to=central_agent_jid)
            iq.getTag("query").addChild(name="archipel",
                                        attrs={"action": action})
            iq.getTag("query").getTag("archipel").addChild(node=dbCommand)
            xmpp.dispatcher.ID += 1
            iq.setID("%s-%d" % (self.entity.jid.getNode(), xmpp.dispatcher.ID))

            def _read_from_db_callback(conn, resp):

                self.entity.log.debug("CENTRALDB: reply to read statement %s" %
                                      resp)
                unpacked_entries = self.unpack_entries(resp)
                self.entity.log.debug("CENTRALDB: unpacked reply %s" %
                                      unpacked_entries)
                callback(unpacked_entries)

            self.entity.xmppclient.SendAndCallForResponse(
                iq, _read_from_db_callback)

        else:

            self.entity.log.warning(
                "CENTRALDB: cannot read from db because we have not detected any central agent"
            )

    def unpack_entries(self, iq):
        """
        Unpack the list of entries from iq for database processing.
        @type iq: xmpp.Iq
        @param event: received Iq
        """
        entries = []

        for entry in iq.getChildren():

            entry_dict = {}

            for entry_val in entry.getChildren():

                if entry_val.getAttr("key"):

                    entry_dict[entry_val.getAttr("key")] = entry_val.getAttr(
                        "value")

            if entry_dict != {}:

                entries.append(entry_dict)

        return entries

    ### Plugin information

    @staticmethod
    def plugin_info():
        """
        Return informations about the plugin.
        @rtype: dict
        @return: dictionary contaning plugin informations
        """
        plugin_friendly_name = "Central db"
        plugin_identifier = "centraldb"
        plugin_configuration_section = "CENTRALDB"
        plugin_configuration_tokens = []
        return {
            "common-name": plugin_friendly_name,
            "identifier": plugin_identifier,
            "configuration-section": plugin_configuration_section,
            "configuration-tokens": plugin_configuration_tokens
        }
Example #20
0
class TNCentralDb (TNArchipelPlugin):
    """
    This contains the necessary interfaces to interact with central agent and central db
    """

    def __init__(self, configuration, entity, entry_point_group):
        """
        Initialize the plugin.
        @type configuration: Configuration object
        @param configuration: the configuration
        @type entity: L{TNArchipelEntity}
        @param entity: the entity that owns the plugin
        @type entry_point_group: string
        @param entry_point_group: the group name of plugin entry_point
        """
        TNArchipelPlugin.__init__(self, configuration=configuration, entity=entity, entry_point_group=entry_point_group)

        if self.entity.__class__.__name__ == "TNArchipelHypervisor":
            self.entity.register_hook("HOOK_ARCHIPELENTITY_XMPP_AUTHENTICATED", method=self.hypervisor_hook_xmpp_authenticated)
            self.entity.register_hook("HOOK_HYPERVISOR_WOKE_UP", method=self.push_vms_in_central_db)
            self.entity.register_hook("HOOK_HYPERVISOR_FREE",  method=self.hook_vm_unregistered)


        if self.entity.__class__.__name__ == "TNArchipelVirtualMachine":
            self.entity.register_hook("HOOK_VM_INITIALIZE", method=self.hook_missed_vms, oneshot=True)
            self.entity.register_hook("HOOK_VM_DEFINE",     method=self.hook_vm_event)

        self.central_agent_jid_val = None
        self.last_keepalive_heard = None
        self.keepalive_interval = int(ARCHIPEL_CENTRAL_AGENT_TIMEOUT * 2)
        self.hypervisor_timeout_threshold = int(ARCHIPEL_CENTRAL_AGENT_TIMEOUT)
        self.delayed_tasks = TNTasks(self.entity.log)
        self.vms_to_hook = set()

        self.xmpp_authenticated    = False

    # Hooks

    def hook_missed_vms(self, origin=None, user_info=None, arguments=None):
        """
        Called when a vm was not ready on first push_vms_in_central_db.
        """
        if self.entity.hypervisor.get_plugin("centraldb") and self.entity.uuid in self.entity.hypervisor.get_plugin("centraldb").vms_to_hook:
            self.entity.hypervisor.get_plugin("centraldb").vms_to_hook.remove(self.entity.uuid)
            self.entity.log.debug("CENTRALDB: Registering ourself in centraldb as we were not ready the first time.")
            self.hook_vm_event()

    def hook_vm_event(self, origin=None, user_info=None, arguments=None):
        """
        Called when a VM definition or change of definition occurs.
        This will advertise definition to the central agent
        """
        if not self.entity.definition:
            return

        xmldesc = self.entity.xmldesc(mask_description=False)
        vm_info = [{"uuid":self.entity.uuid,"parker":None,"creation_date":None,"domain":xmldesc,"hypervisor":self.entity.hypervisor.jid, 'name':xmldesc.getTag("name").getData()}]
        self.register_vms(vm_info)

    def hook_vm_unregistered(self, origin=None, user_info=None, arguments=None):
        """
        Called when a VM termination occurs.
        This will advertise undefinition to the central agent.
        """
        self.unregister_vms([{"uuid":arguments.uuid}], None)

    # Pubsub management

    def hypervisor_hook_xmpp_authenticated(self, origin=None, user_info=None, arguments=None):
        """
        Triggered when we are authenticated. Initializes everything.
        @type origin: L{TNArchipelEnity}
        @param origin: the origin of the hook
        @type user_info: object
        @param user_info: random user information
        @type arguments: object
        @param arguments: runtime argument
        """

        self.xmpp_authenticated  = True
        self.central_keepalive_pubsub = TNPubSubNode(self.entity.xmppclient, self.entity.pubsubserver, ARCHIPEL_KEEPALIVE_PUBSUB)
        self.central_keepalive_pubsub.recover()
        self.central_keepalive_pubsub.subscribe(self.entity.jid, self.handle_central_keepalive_event)
        self.entity.log.info("CENTRALDB: entity %s is now subscribed to events from node %s" % (self.entity.jid, ARCHIPEL_KEEPALIVE_PUBSUB))
        self.last_keepalive_heard = datetime.datetime.now()

    def central_agent_jid(self):
        """
        Returns the jid of the central agent. In case we are a VM, query hypervisor.
        """
        if self.entity.__class__.__name__ == "TNArchipelVirtualMachine":
            return self.entity.hypervisor.get_plugin("centraldb").central_agent_jid()

        if not self.central_agent_jid_val:
            return None

        if not self.entity.roster.getItem(self.central_agent_jid_val.getStripped()):
            self.entity.log.warning("CENTRALDB: CentralAgent not in my roster, adding it.")
            self.entity.add_jid(self.central_agent_jid_val)

        # if central agent has a status, it's available
        try:
            if self.entity.roster.getStatus(self.central_agent_jid_val.getStripped()):
                return self.central_agent_jid_val
        except:
            pass

        # If presence is not known check if we hit the keepalive threshold timeout
        # This could append when you restart it for example
        # Time interval is not an exact science so let's double it for timetout
        if self.last_keepalive_heard and (datetime.datetime.now() - self.last_keepalive_heard).total_seconds() > self.keepalive_interval * 2:
            self.entity.log.error("CENTRALDB: CentralAgent is down.")
            self.central_agent_jid_val = None
            return None
        else:
            self.entity.log.warning("CENTRALDB: Can't get central-agent presence, using keepalive to check it's availability.")
            return self.central_agent_jid_val

    def handle_central_keepalive_event(self, event):
        """
        Called when the central agent sends a keepalive.
        @type event: xmpp.Node
        @param event: the pubsub event node
        """
        items = event.getTag("event").getTag("items").getTags("item")

        for item in items:
            central_announcement_event = item.getTag("event")
            event_type                 = central_announcement_event.getAttr("type")

            if event_type == "keepalive":
                old_central_agent_jid = self.central_agent_jid()
                self.entity.log.debug("CENTRALDB: Keepalive heard : %s " % str(item))
                keepalive_jid              = xmpp.JID(central_announcement_event.getAttr("jid"))

                if central_announcement_event.getAttr("keepalive_interval"):
                    self.keepalive_interval           = int(central_announcement_event.getAttr("keepalive_interval"))

                if central_announcement_event.getAttr("hypervisor_timeout_threshold"):
                    self.hypervisor_timeout_threshold = int(central_announcement_event.getAttr("hypervisor_timeout_threshold"))

                self.central_agent_jid_val = keepalive_jid
                self.last_keepalive_heard  = datetime.datetime.now()

                self.delayed_tasks.add((self.hypervisor_timeout_threshold - self.keepalive_interval) * 2 / 3, self.push_statistics_to_centraldb, {'central_announcement_event':central_announcement_event})

                if not old_central_agent_jid:
                    self.delayed_tasks.add(self.keepalive_interval, self.handle_first_keepalive, {'keepalive_jid':keepalive_jid})
                elif central_announcement_event.getAttr("force_update") == "true" or keepalive_jid != old_central_agent_jid:
                    self.delayed_tasks.add(self.keepalive_interval, self.push_vms_in_central_db)

    def push_statistics_to_centraldb(self, central_announcement_event):
        """
        each time we hear a keepalive, we push relevant statistics to central db
        """
        # parsing required statistics to be pushed to central agent
        required_statistics = []
        if central_announcement_event.getTag("required_stats"):
            for required_stat in central_announcement_event.getTag("required_stats").getChildren():
                required_statistics.append({"major":required_stat.getAttr("major"),"minor":required_stat.getAttr("minor")})

        stats_results = {"jid":str(self.entity.jid)}
        if len(required_statistics) > 0:
            stat_num = 0
            for stat in required_statistics:
                stat_num += 1
                value = eval("self.entity.get_plugin('hypervisor_health').collector.stats_%s" % stat["major"])[-1][stat["minor"]]
                stats_results["stat%s" % stat_num] = value

            self.entity.log.debug("CENTRALDB: updating central db with %s" % stats_results)

        self.update_hypervisors([stats_results])

    def handle_first_keepalive(self, keepalive_jid, callback=None, kwargs={}):
        """
        this is the first keepalive. We query hypervisors that have vm entities somewhere else
        then we trigger method manage_persistence to instantiate vm entities.
        """
        vms_from_local_db = self.entity.get_vms_from_local_db()

        if len(vms_from_local_db) > 0:
            dbCommand = xmpp.Node(tag="event", attrs={"jid":self.entity.jid})

            def _get_existing_vms_instances_callback(conn, packed_vms):
                existing_vms_entities = self.unpack_entries(packed_vms)
                self.entity.manage_persistence(vms_from_local_db, existing_vms_entities)
                if callback:
                    callback(**kwargs)

            for vm in vms_from_local_db:
                entryTag = xmpp.Node(tag="entry")
                uuid     = xmpp.JID(vm["string_jid"]).getNode()
                entryTag.addChild("item",attrs={"key":"uuid","value": uuid})
                dbCommand.addChild(node=entryTag)

            iq = xmpp.Iq(typ="set", queryNS=ARCHIPEL_NS_CENTRALAGENT, to=keepalive_jid)
            iq.getTag("query").addChild(name="archipel", attrs={"action":"get_existing_vms_instances"})
            iq.getTag("query").getTag("archipel").addChild(node=dbCommand)
            self.entity.xmppclient.SendAndCallForResponse(iq, _get_existing_vms_instances_callback)

        else:
            # update status to Online(0)
            self.entity.manage_persistence()
            if callback:
                callback(**kwargs)

    def push_vms_in_central_db(self, origin=None, user_info=None, arguments=None):
        """
        there is a new central agent, or we just started.
        Consequently, we re-populate central database
        since we are using "on conflict replace" mode of sqlite, inserting an existing uuid will overwrite it.
        """
        vms = []
        for vm,vmprops in self.entity.virtualmachines.iteritems():
            if vmprops.definition:
                vms.append({"uuid":vmprops.uuid,"parker":None,"creation_date":None,"domain":vmprops.definition,"hypervisor":self.entity.jid, "name":vmprops.definition.getTag("name").getData()})
            else:
                self.entity.log.debug("[CENTRALDB] The entity %s looks not ready, postponing it's registration." % vmprops.uuid)
                self.vms_to_hook.add(vmprops.uuid)

        if len(vms) >= 1:
            self.register_vms(vms)

        self.register_hypervisors([{"jid":self.entity.jid, "status":"Online", "last_seen": datetime.datetime.now(), "stat1":0, "stat2":0, "stat3":0}])

    # Database Management
    # read commands

    def read_hypervisors(self, columns, where_statement, callback):
        """
        List vm in database.
        @type table: list
        @param table: the list of hypervisors to insert
        """
        self.read_from_db("read_hypervisors", columns, where_statement, callback)

    def read_vms(self, columns, where_statement, callback):
        """
        Registers a list of vms into central database.
        @type table: list
        @param table: the list of vms to insert
        """
        self.read_from_db("read_vms", columns, where_statement, callback)

    # write commands

    def register_hypervisors(self,table):
        """
        Registers a list of hypervisors into central database.
        @type table: list
        @param table: the list of hypervisors to insert
        """
        self.commit_to_db("register_hypervisors",table, None)

    def register_vms(self,table):
        """
        Registers a list of vms into central database.
        @type table: list
        @param table: the list of vms to insert
        """
        self.commit_to_db("register_vms",table, None)

    def unregister_hypervisors(self,table):
        """
        Unregisters a list of hypervisors from central database.
        @type table: list
        @param table: the list of hypervisors to remove
        """
        self.commit_to_db("unregister_hypervisors",table, None)

    def unregister_vms(self,table,callback):
        """
        Unregisters a list of vms from central database.
        @type table: list
        @param table: the list of vms to remove
        @type callback: func
        @para callback: will return  list of vms actually unregistered
        """
        self.commit_to_db("unregister_vms",table, callback)

    def update_vms(self,table):
        """
        Update a set of vms in central database.
        @type table: list
        @param table: the list of vms to update. Must contain the "uuid" attribute as
                      this is the one used for key in the update statement.
        """
        self.commit_to_db("update_vms",table, None)

    def update_vms_domain(self,table,callback):
        """
        Update a set of vms in central database.
        Performs additional checks for domain update when vm is offline.
        @type table: list
        @param table: the list of vms to update. Must contain the "uuid" attribute as
                      this is the one used for key in the update statement.
        """
        self.commit_to_db("update_vms_domain",table, callback)

    def update_hypervisors(self,table):
        """
        Update a set of hypervisors in central database.
        @type table: list
        @param table: the list of hypervisors to update. Must contain the "jid" attribute as
                      this is the one used for key in the update statement.
        """
        self.commit_to_db("update_hypervisors",table, None)

    def commit_to_db(self,action,table,callback):
        """
        Sends a command to active central agent for execution
        @type command: string
        @param command: the sql command to execute
        @type table: table
        @param command: the table of dicts of values associated with the command.
        """

        def commit_to_db_callback(conn, resp):
            if callback:
                unpacked_entries = self.unpack_entries(resp)
                callback(unpacked_entries)

        central_agent_jid = self.central_agent_jid()

        if central_agent_jid:
            dbCommand = xmpp.Node(tag="event", attrs={"jid":self.entity.jid})
            for entry in table:
                entryTag = xmpp.Node(tag="entry")
                for key,value in entry.iteritems():
                    entryTag.addChild("item",attrs={"key":key,"value":value})

                dbCommand.addChild(node=entryTag)

            iq = xmpp.Iq(typ="set", queryNS=ARCHIPEL_NS_CENTRALAGENT, to=central_agent_jid)
            iq.getTag("query").addChild(name="archipel", attrs={"action":action})
            iq.getTag("query").getTag("archipel").addChild(node=dbCommand)
            self.entity.log.debug("CENTRALDB [%s]: \n%s" % (action.upper(), iq))
            self.entity.xmppclient.SendAndCallForResponse(iq, commit_to_db_callback)
        else:
            self.entity.log.warning("CENTRALDB: cannot commit to db because we have not detected any central agent")

    def read_from_db(self,action,columns, where_statement, callback):
        """
        Send a select statement to central db.
        @type command: string
        @param command: the sql command to execute
        @type columns: string
        @param columns: the list of database columns to return
        @type where_statement: string
        @param where_statement: for database reads, provides "where" constraint
        """

        def _read_from_db_callback(conn, resp):
            unpacked_entries = self.unpack_entries(resp)
            self.entity.log.debug("CENTRALDB: read %s entries from db response" % len(unpacked_entries))
            callback(unpacked_entries)

        central_agent_jid = self.central_agent_jid()

        if central_agent_jid:
            dbCommand = xmpp.Node(tag="event", attrs={"jid":self.entity.jid})
            if where_statement:
                dbCommand.setAttr("where_statement", where_statement)

            if columns:
                dbCommand.setAttr("columns", columns)

            self.entity.log.debug("CENTRALDB: Asking central db for [%s] %s %s" % (action.upper(), columns, where_statement))
            iq = xmpp.Iq(typ="set", queryNS=ARCHIPEL_NS_CENTRALAGENT, to=central_agent_jid)
            iq.getTag("query").addChild(name="archipel", attrs={"action":action})
            iq.getTag("query").getTag("archipel").addChild(node=dbCommand)
            self.entity.xmppclient.SendAndCallForResponse(iq, _read_from_db_callback)
        else:
            self.entity.log.warning("CENTRALDB: cannot read from db because we have not detected any central agent")

    def unpack_entries(self, iq):
        """
        Unpack the list of entries from iq for database processing.
        @type iq: xmpp.Iq
        @param event: received Iq
        """
        entries = []

        for entry in iq.getChildren():
            entry_dict = {}
            for entry_val in entry.getChildren():
                if entry_val.getAttr("key"):
                    entry_dict[entry_val.getAttr("key")] = entry_val.getAttr("value")
            if entry_dict != {}:
                entries.append(entry_dict)
        return entries

    # Plugin information

    @staticmethod
    def plugin_info():
        """
        Return informations about the plugin.
        @rtype: dict
        @return: dictionary contaning plugin informations
        """
        plugin_friendly_name         = "Central db"
        plugin_identifier            = "centraldb"
        plugin_configuration_section = None
        plugin_configuration_tokens  = []
        return {"common-name": plugin_friendly_name,
                "identifier": plugin_identifier,
                "configuration-section": plugin_configuration_section,
                "configuration-tokens": plugin_configuration_tokens}