def update_system(self, system_id, new_commands=None, **kwargs): """Update a System Args: system_id (str): The System ID new_commands (Optional[List[Command]]): New System commands Keyword Args: add_instance (Instance): An Instance to append metadata (dict): New System metadata description (str): New System description display_name (str): New System display name icon_name (str): New System icon name template (str): New System template Returns: System: The updated system """ operations = [] if new_commands is not None: commands = SchemaParser.serialize_command(new_commands, to_string=False, many=True) operations.append(PatchOperation("replace", "/commands", commands)) add_instance = kwargs.pop("add_instance", None) if add_instance: instance = SchemaParser.serialize_instance(add_instance, to_string=False) operations.append(PatchOperation("add", "/instance", instance)) metadata = kwargs.pop("metadata", {}) if metadata: operations.append(PatchOperation("update", "/metadata", metadata)) # The remaining kwargs are all strings # Sending an empty string (instead of None) ensures they're actually cleared for key, value in kwargs.items(): operations.append( PatchOperation("replace", "/%s" % key, value or "")) return self.client.patch_system( system_id, SchemaParser.serialize_patch(operations, many=True))
class BartenderHandler(object): """Implements the BREWMASTER Thrift interface.""" def __init__(self, registry, clients, plugin_manager, request_validator): self.logger = logging.getLogger(__name__) self.registry = registry self.clients = clients self.plugin_manager = plugin_manager self.request_validator = request_validator self.parser = SchemaParser() def processRequest(self, request_id): """Validates and publishes a Request. :param str request_id: The ID of the Request to process :raises InvalidRequest: If the Request is invalid in some way :return: None """ request_id = str(request_id) self.logger.info("Processing Request: %s", request_id) try: request = Request.find_or_none(request_id) if request is None: raise ModelValidationError( "Could not find request with ID '%s'" % request_id) # Validates the request based on what is in the database. # This includes the validation of the request parameters, # systems are there, commands are there etc. request = self.request_validator.validate_request(request) request.save() try: self.clients["pika"].publish_request( request, confirm=True, mandatory=True, delivery_mode=pika.spec.PERSISTENT_DELIVERY_MODE, ) except Exception: msg = "Error while publishing request to queue (%s[%s]-%s %s)" % ( request.system, request.system_version, request.instance_name, request.command, ) raise bg_utils.bg_thrift.PublishException(msg) except (mongoengine.ValidationError, ModelValidationError, RestError) as ex: self.logger.exception(ex) raise bg_utils.bg_thrift.InvalidRequest(request_id, str(ex)) def initializeInstance(self, instance_id): """Initializes an instance. :param instance_id: The ID of the instance :return: QueueInformation object describing message queue for this system """ instance = self._get_instance(instance_id) system = self._get_system(instance) self.logger.info( "Initializing instance %s[%s]-%s", system.name, instance.name, system.version, ) routing_words = [system.name, system.version, instance.name] req_name = get_routing_key(*routing_words) req_args = {"durable": True, "arguments": {"x-max-priority": 1}} req_queue = self.clients["pika"].setup_queue(req_name, req_args, [req_name]) routing_words.append("".join( random.choice(string.ascii_lowercase + string.digits) for _ in range(10))) admin_keys = get_routing_keys(*routing_words, is_admin=True) admin_args = {"durable": True} admin_queue = self.clients["pika"].setup_queue(admin_keys[-1], admin_args, admin_keys) connection = { "host": bartender.config.publish_hostname, "port": bartender.config.amq.connections.message.port, "user": bartender.config.amq.connections.message.user, "password": bartender.config.amq.connections.message.password, "virtual_host": bartender.config.amq.virtual_host, "ssl": { "enabled": bartender.config.amq.connections.message.ssl.enabled }, } instance.status = "INITIALIZING" instance.status_info = StatusInfo(heartbeat=datetime.utcnow()) instance.queue_type = "rabbitmq" instance.queue_info = { "admin": admin_queue, "request": req_queue, "connection": connection, "url": self.clients["public"].connection_url, } instance.save() # Send a request to start to the plugin on the plugin's admin queue self.clients["pika"].start(system=system.name, version=system.version, instance=instance.name) return self.parser.serialize_instance(instance, to_string=True) def startInstance(self, instance_id): """Starts an instance. :param instance_id: The ID of the instance :return: None """ instance = self._get_instance(instance_id) self.plugin_manager.start_plugin( self._get_plugin_from_instance_id(instance_id)) return self.parser.serialize_instance(instance, to_string=True) def stopInstance(self, instance_id): """Stops an instance. :param instance_id: The ID of the instance :return: None """ instance = self._get_instance(instance_id) local_plugin = self._get_plugin_from_instance_id(instance_id) if local_plugin: self.plugin_manager.stop_plugin(local_plugin) else: system = self._get_system(instance) # This causes the request consumer to terminate itself, which ends the plugin self.clients["pika"].stop(system=system.name, version=system.version, instance=instance.name) return self.parser.serialize_instance(instance, to_string=True) def restartInstance(self, instance_id): """Restarts a System. Currently unused.""" self.stopInstance(instance_id) self.startInstance(instance_id) def reloadSystem(self, system_id): """Reload a system configuration :param system_id: The system id :return None """ try: system = System.objects.get(id=system_id) self.logger.info("Reloading system: %s-%s", system.name, system.version) self.plugin_manager.reload_system(system.name, system.version) except mongoengine.DoesNotExist: raise bg_utils.bg_thrift.InvalidSystem( "", "Couldn't find system %s" % system_id) def removeSystem(self, system_id): """Removes a system from the registry if necessary. :param system_id: The system id :return: """ try: system = System.objects.get(id=system_id) except mongoengine.DoesNotExist: raise bg_utils.bg_thrift.InvalidSystem( "", "Couldn't find system %s" % system_id) # Attempt to stop the plugins registered = self.registry.get_plugins_by_system( system.name, system.version) # Local plugins get stopped by us if registered: for plugin in registered: self.plugin_manager.stop_plugin(plugin) self.registry.remove(plugin.unique_name) # Remote plugins get a stop request else: self.clients["pika"].stop(system=system.name, version=system.version) count = 0 while (any(instance.status != "STOPPED" for instance in system.instances) and count < bartender.config.plugin.local.timeout.shutdown): sleep(1) count += 1 system.reload() system.reload() # Now clean up the message queues for instance in system.instances: # It is possible for the request or admin queue to be none if we are # stopping an instance that was not properly started. request_queue = instance.queue_info.get("request", {}).get("name") admin_queue = instance.queue_info.get("admin", {}).get("name") self.clients["pyrabbit"].destroy_queue( request_queue, force_disconnect=(instance.status != "STOPPED")) self.clients["pyrabbit"].destroy_queue( admin_queue, force_disconnect=(instance.status != "STOPPED")) # Finally, actually delete the system system.deep_delete() def rescanSystemDirectory(self): """Scans plugin directory and starts any new Systems""" self.logger.info("Rescanning system directory.") self.plugin_manager.scan_plugin_path() def getQueueInfo(self, system_name, system_version, instance_name): """Gets the size of a queue :param system_name: The system name :param system_version: The system version :param instance_name: The instance name :return size of the queue :raises Exception: If queue does not exist """ routing_key = get_routing_key(system_name, system_version, instance_name) self.logger.debug("Get the queue state for %s", routing_key) return bg_utils.bg_thrift.QueueInfo( routing_key, self.clients["pyrabbit"].get_queue_size(routing_key)) def clearQueue(self, queue_name): """Clear all Requests in the given queue Will iterate through all requests on a queue and mark them as "CANCELED". :param queue_name: The queue to clean :raises InvalidSystem: If the system_name/instance_name does not match a queue """ try: self.logger.debug("Clearing queue %s", queue_name) self.clients["pyrabbit"].clear_queue(queue_name) except HTTPError as ex: if ex.status == 404: raise bg_utils.bg_thrift.InvalidSystem( queue_name, "No queue named %s" % queue_name) else: raise def clearAllQueues(self): """Clears all queues that Bartender knows about. :return: None """ self.logger.debug("Clearing all queues") systems = System.objects.all() for system in systems: for instance in system.instances: routing_key = get_routing_key(system.name, system.version, instance.name) self.clearQueue(routing_key) def getVersion(self): """Gets the current version of the backend""" self.logger.debug("Getting Version") return bartender._version.__version__ def ping(self): """A simple Method to test connectivity.""" self.logger.info("Ping.") def _get_plugin_from_instance_id(self, instance_id): instance = self._get_instance(instance_id) system = self._get_system(instance) unique_name = self.registry.get_unique_name(system.name, system.version, instance.name) return self.registry.get_plugin(unique_name) @staticmethod def _get_instance(instance_id): try: return Instance.objects.get(id=instance_id) except mongoengine.DoesNotExist: raise bg_utils.bg_thrift.InvalidSystem( "", "Couldn't find instance %s" % instance_id) @staticmethod def _get_system(instance): try: return System.objects.get(instances__contains=instance) except mongoengine.DoesNotExist: raise bg_utils.bg_thrift.InvalidSystem( "", "Couldn't find system " "with instance %s" % instance.id)