Example #1
0
    def __init__(self, config, simulate, hostnames = None, offline = False, 
                 deploy = True, remote = None):
        self._config = config
        self.offline = offline
        self.deploy = deploy
        self._offline_files = None
        self.remote = remote
        
        self._sched = Scheduler()
        
        if "agent" in config and "logfile" in config["agent"]:
            logging.basicConfig(filename=config["agent"]["logfile"],
                            filemode='w', level=logging.DEBUG)

        if hostnames is None or len(hostnames) == 0:
            self._hostnames = [self._get_hostname()]
        else:
            self._hostnames = hostnames
            
        self._config = config
        self.stop = False
        self._dm = DependencyManager()
        self._queue = QueueManager()
        
        self._last_update = 0
        
        
        if not self.offline:
            self._loader = CodeLoader(self._config["agent"]["code_dir"])
Example #2
0
class Agent(object):
    """
        An agent to enact changes upon resources. This agent listens to the 
        message bus for changes.
    """
    def __init__(self, config, simulate, hostnames = None, offline = False, 
                 deploy = True, remote = None):
        self._config = config
        self.offline = offline
        self.deploy = deploy
        self._offline_files = None
        self.remote = remote
        
        self._sched = Scheduler()
        
        if "agent" in config and "logfile" in config["agent"]:
            logging.basicConfig(filename=config["agent"]["logfile"],
                            filemode='w', level=logging.DEBUG)

        if hostnames is None or len(hostnames) == 0:
            self._hostnames = [self._get_hostname()]
        else:
            self._hostnames = hostnames
            
        self._config = config
        self.stop = False
        self._dm = DependencyManager()
        self._queue = QueueManager()
        
        self._last_update = 0
        
        
        if not self.offline:
            self._loader = CodeLoader(self._config["agent"]["code_dir"])
        
    def _connect(self):
        """
            Connect to the message bus
        """
        # connect
        self._conn = amqp.Connection(host = self._config["communication"]["host"], 
                            userid = self._config["communication"]["user"],
                            password = self._config["communication"]["password"],
                            virtual_host = "/")
        
        self._exchange_name =  self._config["communication"]["exchange"]
        
        self._channel = self._conn.channel()
        self._channel.exchange_declare(exchange = self._exchange_name, type = "topic")
        
        result = self._channel.queue_declare(exclusive = True)
        queue_name = result[0]
        
        self._subscribe_resource(queue_name)

        self._channel.basic_consume(queue = queue_name, callback=self.on_message, no_ack=True)
        
        while self._channel.callbacks:
            self._channel.wait()

    def _subscribe_resource(self, queue_name):
        """
            Subscribe to the resource updates on the message bus
        """
        LOGGER.info("Subscribing %s to resource updates" % self._hostnames)
        
        for name in self._hostnames:
            short_hostname = name.split(".")[0]
                
            self._channel.queue_bind(exchange = self._exchange_name, queue = queue_name,
                       routing_key = "resources.%s.*" % (short_hostname))
            
            if name != short_hostname:
                self._channel.queue_bind(exchange = self._exchange_name, queue = queue_name,
                       routing_key = "resources.%s.*" % (name))
        
        self._channel.queue_bind(exchange = self._exchange_name, queue = queue_name,
                       routing_key="control")
        self._channel.queue_bind(exchange = self._exchange_name, queue = queue_name,
                       routing_key="updated")
        
    def on_error(self, headers, message):
        """
            Called when an error is received
        """
        LOGGER.error("Received an error %s" % message)
        
    def update(self, res_obj):
        """
            Process an update
        """
        for req in res_obj.requires:
            self._dm.add_dependency(res_obj, req.version, req.resource_str())
            
        self._queue.add_resource(res_obj)
        self._last_update = time.time()
        
    def get_facts(self, res):
        """
            Get status 
        """
        try:
            provider = Commander.get_provider(self, res.id)
        except Exception:
            LOGGER.error("Unable to find a handler for %s" % res.id)
            
        return provider.facts(res)
    
    def _mq_send(self, routing_key, operation, body):
        body["operation"] = operation
        body["source"] = self._hostnames
        
        msg = amqp.Message(json.dumps(body))
        msg.content_type = "application/json"
        
        self._channel.basic_publish(msg, exchange = self._exchange_name,
                    routing_key = routing_key)
        
    def _handle_op(self, operation, message):
        """
            Handle an operation
        """
        if operation == "PING":
            LOGGER.info("Got ping request, sending pong back")
            response = {"hostname" : self._hostnames }
            
            self._mq_send("control", "PONG", response)
            
        elif operation == "UPDATE":
            LOGGER.debug("Received update for %s", message["resource"]["id"])
            resource = Resource.deserialize(message["resource"])
            self.update(resource)
            
        elif operation == "UPDATED":
            rid = Id.parse_id(message["id"])
            version = message["version"]
            reload = message["reload"]
            self._dm.resource_update(rid.resource_str(), version, reload)
            
        elif operation == "STATUS":
            resource = Id.parse_id(message["id"]).get_instance()
            
            if resource is None:
                self._mq_send("control", "STATUS_REPLY", {"code" : 404})
                return
            
            try:
                provider = Commander.get_provider(self, resource.id)
            except Exception:
                LOGGER.exception("Unable to find a handler for %s" % resource)
            
            try:
                result = provider.check_resource(resource)
                self._mq_send("control", "STATUS_REPLY", result)
                
            except Exception:
                LOGGER.exception("Unable to check status of %s" % resource)
                self._mq_send("control", "STATUS_REPLY", {"code" : 404})
                
        elif operation == "FACTS":
            resource_id = Id.parse_id(message["id"])
            
            try:
                resource = Resource.deserialize(message["resource"])
                provider = Commander.get_provider(self, resource_id)
                
                try:
                    result = provider.facts(resource)
                    response = {"operation" : "FACTS_REPLY", "subject" : str(resource_id), "facts" : result}
                    self._mq_send("control", "FACTS_REPLY", response)
                    
                except Exception:
                    LOGGER.exception("Unable to retrieve fact")
                    self._mq_send("control", "FACTS_REPLY", {"subject" : str(resource_id), "code": 404})
            except Exception:
                LOGGER.exception("Unable to find a handler for %s" % resource_id)
            
        elif operation == "QUEUE":
            response = {"queue" : ["%s,v=%d" % (x.id, x.version) for x in self._queue.all()]}
                
            self._mq_send("control", "QUEUE_REPLY", response)
                
        elif operation == "DEPLOY":
            self.deploy_config()
                
        elif operation == "INFO":
            
            response = {"threads" : [x.name for x in enumerate()],
                    "queue length" : self._queue.size(),
                    "queue ready length" : self._queue.ready_size(),
                }
                
            self._mq_send("control", "INFO_REPLY", response)
            
        elif operation == "DUMP":
            LOGGER.info("Dumping!")
            self._queue.dump()
            
        elif operation == "MODULE_UPDATE":
            version = message["version"]
            modules = message["modules"]
            self._loader.deploy_version(version, modules)
        
    def on_message(self, msg):
        """
            Method called when a message is received
        """
        body = json.loads(msg.body)
        
        # first check if it is for us
        match = False
        if "agent" not in body:
            match = True
        else:
            for h in self._hostnames:
                if fnmatch.fnmatch(h, body["agent"]):
                    match = True
        
        if "operation" in body and match:
            return self._handle_op(body["operation"], body)
        
    def _get_hostname(self):
        """
            Determine the hostname of this machine
        """
        return socket.gethostname()

    def run(self):
        """
            The main loop of the agent.
        """
        self._sched.add_action(self.deploy_config, 10)
        LOGGER.debug("Scheduled deploy config every 10 seconds")
        self._sched.add_action(self.ping, 600)
        LOGGER.debug("Scheduled ping broadcast every 600 seconds")
        
        #self._sched.add_action(self.get_desired_state, FORCE_UPDATE_INTERVAL)
        #LOGGER.debug("Scheduled fetching the desired state from the server synchronously every %d" % FORCE_UPDATE_INTERVAL)
        
        self._sched.start()
        
        if not self.offline:
            self._connect()
            
        while True:
            time.sleep(1)
        
    def get_desired_state(self):
        """
            Retrieve the desired state from the IMP server if we have not 
            received any updates in the last X seconds
        """
        if (self._last_update + FORCE_UPDATE_INTERVAL) < time.time():
            # for an update by fetching an update from the IMP server
            
            for name in self._hostnames:
                conn = client.HTTPConnection("127.0.0.1", 8888)
                conn.request("GET", "/agentstate/" + name)
                res = conn.getresponse()
                
                data = res.read().decode("utf-8")
                
                for res in json.loads(data):
                    self.update(res)
    
                
    def ping(self):
        """
            Broadcast a ping to let everyone know we are here
        """
        LOGGER.info("Sending out a ping")
        response = {"hostname" : self._hostnames }
        self._mq_send("control", "PONG", response)
            
    def deploy_config(self):
        """
            Deploy a configuration is there are items in the queue
        """
        LOGGER.debug("Execute deploy config")
        
        LOGGER.info("Need to update %d resources" % self._queue.size())        
        while self._queue.size() > 0:
            resource = self._queue.pop()
            if resource is None:
                LOGGER.info("No resources ready for deploy.")
                break
            
            try:
                provider = Commander.get_provider(self, resource.id)
            except Exception as e:
                LOGGER.exception("Unable to find a handler for %s" % resource.id, e)
                
                # TODO: submit failure
                self._queue.remove(resource)
                continue
                    
            try:
                provider.execute(resource, self.deploy)
                
                if resource.do_reload and provider.can_reload():
                    LOGGER.warning("Reloading %s because of updated dependencies" % resource.id)
                    provider.do_reload(resource)
                        
                LOGGER.debug("Finished %s" % resource)
                self._queue.remove(resource)
            except Exception as e:
                LOGGER.exception(e)
                # TODO: report back
                self._queue.remove(resource)

        return
    
    def _server_connection(self):
        """
            A connection to a server
        """
        parts = urllib.parse.urlparse(self._config["config"]["server"])
        host, port = parts.netloc.split(":")
        
        return client.HTTPConnection(host, port)
        
    def get_file(self, hash_id):
        """
            Retrieve a file from the fileserver identified with the given hash
        """
        if self.offline:
            return self._offline_files[hash_id]
        else:
            conn = self._server_connection()
            conn.request("GET", "/file/" + hash_id)
            res = conn.getresponse()
    
            # upload the file
            if res.status == 404:
                return None
            else:
                data = res.read()
                return data
               
    def resource_updated(self, resource, reload_requires = False):
        """
            A resource with id $rid calls this method to indicate that it is 
            now at version $version.
        """
        reload = False
        if hasattr(resource, "reload") and resource.reload and reload_requires:
            reload = True
        
        self._dm.resource_update(resource.id.resource_str(), resource.id.version, reload)
        
        if not self.offline:
            # send out the resource update
            self._mq_send("control", "UPDATED", {"id" : str(resource.id), "version" : resource.id.version, "reload" : reload})
            
    def close(self):
        """
            Cleanup
        """
        Commander.close()