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 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 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)
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 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 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 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 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 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()
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
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()
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)
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
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
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 }
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)
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()
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}
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 }
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}