def delete_container_manager(message, bus): """ Deletes an exisiting ContainerManagerConfig. :param message: jsonrpc message structure. :type message: dict :param bus: Bus instance. :type bus: commissaire_http.bus.Bus :returns: A jsonrpc structure. :rtype: dict """ try: name = message['params']['name'] LOGGER.debug( 'Attempting to delete ContainerManagerConfig "{}"'.format(name)) bus.storage.delete(models.ContainerManagerConfig.new(name=name)) return create_jsonrpc_response(message['id'], []) except _bus.StorageLookupError as error: return create_jsonrpc_error(message, error, JSONRPC_ERRORS['NOT_FOUND']) except Exception as error: LOGGER.debug('Error deleting ContainerManagerConfig: {}: {}'.format( type(error), error)) return create_jsonrpc_error(message, error, JSONRPC_ERRORS['INTERNAL_ERROR'])
def delete_cluster(message, bus): """ Deletes an existing cluster. :param message: jsonrpc message structure. :type message: dict :param bus: Bus instance. :type bus: commissaire_http.bus.Bus :returns: A jsonrpc structure. :rtype: dict """ try: name = message['params']['name'] LOGGER.debug('Attempting to delete cluster "%s"', name) cluster = bus.storage.get_cluster(name) if cluster.container_manager: params = [cluster.container_manager] bus.request('container.remove_all_nodes', params=params) bus.storage.delete(cluster) return create_jsonrpc_response(message['id'], []) except _bus.StorageLookupError as error: return create_jsonrpc_error(message, error, JSONRPC_ERRORS['NOT_FOUND']) except Exception as error: LOGGER.debug('Error deleting cluster: %s: %s'.format( type(error), error)) return create_jsonrpc_error(message, error, JSONRPC_ERRORS['INTERNAL_ERROR'])
def update_new_cluster_member_status(bus, cluster, *hosts): """ Call this helper function when adding new hosts to a cluster. If applicable, it will register each host with the cluster's container manager. Then it will update the host status accordingly and save the model(s) to permanent storage. :param bus: Bus instance :type bus: commissaire_http.bus.Bus :param cluster: A Cluster model instance :type cluster: commissaire.models.Cluster :param hosts: Tuple of Host model instances :type hosts: (commissaire.models.Host, ...) """ for host in hosts: try: host.status = C.HOST_STATUS_DISASSOCIATED if cluster.container_manager: bus.request('container.register_node', params=[cluster.container_manager, host.address]) host.status = C.HOST_STATUS_ACTIVE except Exception as error: LOGGER.warn('Unable to register %s to container manager "%s": %s', host.address, cluster.container_manager, error.args[0]) raise error # Save the updated host models. bus.storage.save_many(hosts)
def get_cluster_deploy(message, bus): """ Gets a specific deployment. :param message: jsonrpc message structure. :type message: dict :param bus: Bus instance. :type bus: commissaire_http.bus.Bus :returns: A jsonrpc structure. :rtype: dict """ try: name = message['params']['name'] cluster_deploy = bus.storage.get(models.ClusterDeploy.new(name=name)) cluster_deploy._validate() return create_jsonrpc_response(message['id'], cluster_deploy.to_dict_safe()) except models.ValidationError as error: LOGGER.info('Invalid data retrieved. "%s"', error) LOGGER.debug('Data="%s"', message['params']) return create_jsonrpc_error(message, error, JSONRPC_ERRORS['INVALID_REQUEST']) except _bus.StorageLookupError as error: return create_jsonrpc_error(message, error, JSONRPC_ERRORS['NOT_FOUND']) except Exception as error: return create_jsonrpc_error(message, error, JSONRPC_ERRORS['INTERNAL_ERROR'])
def get_hostcreds(message, bus): """ Gets credentials for a host. :param message: jsonrpc message structure. :type message: dict :param bus: Bus instance. :type bus: commissaire_http.bus.Bus :returns: A jsonrpc structure. :rtype: dict """ try: host_response = bus.request( 'storage.get', params=['Host', { 'address': message['params']['address'] }, True]) creds = { 'remote_user': host_response['result']['remote_user'], 'ssh_priv_key': host_response['result']['ssh_priv_key'] } return create_response(message['id'], creds) except _bus.RemoteProcedureCallError as error: LOGGER.debug('Client requested a non-existant host: "{}"'.format( message['params']['address'])) return return_error(message, error, JSONRPC_ERRORS['NOT_FOUND'])
def create_cluster(message, bus): """ Creates a new cluster. :param message: jsonrpc message structure. :type message: dict :param bus: Bus instance. :type bus: commissaire_http.bus.Bus :returns: A jsonrpc structure. :rtype: dict """ try: name = message['params']['name'] bus.storage.get_cluster(name) LOGGER.debug('Creation of already exisiting cluster %s requested.', name) except Exception as error: LOGGER.debug('Brand new cluster being created.') network_name = message['params'].get('network') if network_name: # Verify the networks existence try: bus.storage.get_network(network_name) except Exception as error: # Default if the network doesn't exist message['params']['network'] = C.DEFAULT_CLUSTER_NETWORK_JSON[ 'name'] # noqa try: cluster = bus.storage.save(models.Cluster.new(**message['params'])) return create_jsonrpc_response(message['id'], cluster.to_dict_safe()) except models.ValidationError as error: return create_jsonrpc_error(message, error, JSONRPC_ERRORS['INVALID_REQUEST'])
def get_host_status(message, bus): """ Gets the status of an exisiting host. :param message: jsonrpc message structure. :type message: dict :param bus: Bus instance. :type bus: commissaire_http.bus.Bus :returns: A jsonrpc structure. :rtype: dict """ try: address = message['params']['address'] host = bus.storage.get_host(address) container_manager = {} # Find the host's container manager status (if applicable) container = bus.storage.list(models.Clusters) for cluster in container.clusters: if address in cluster.hostset: if cluster.container_manager: try: params = [cluster.container_manager, address] container_manager = bus.request( 'container.get_node_status', params=params) except _bus.ContainerManagerError as error: # If we fail to get the container manager's # status for the host, leave that part of the # status structure empty. pass # A host can only be part of one cluster so break the loop. break status = models.HostStatus.new( host={ 'last_check': host.last_check, 'status': host.status, }, container_manager=container_manager, # TODO: Update when we add other types. type='host_only') LOGGER.debug('Status for host "{0}": "{1}"'.format( host.address, status.to_json_safe())) return create_jsonrpc_response(message['id'], status.to_dict_safe()) except _bus.RemoteProcedureCallError as error: return create_jsonrpc_error(message, error, JSONRPC_ERRORS['NOT_FOUND']) except Exception as error: LOGGER.debug('Host Status exception caught for {0}: {1}:{2}'.format( address, type(error), error)) return create_jsonrpc_error(message, error, JSONRPC_ERRORS['INTERNAL_ERROR'])
def add_cluster_member(message, bus): """ Adds a member to the cluster. :param message: jsonrpc message structure. :type message: dict :param bus: Bus instance. :type bus: commissaire_http.bus.Bus :returns: A jsonrpc structure. :rtype: dict """ try: address = message['params']['host'] name = message['params']['name'] except KeyError as error: return create_jsonrpc_error( message, error, JSONRPC_ERRORS['BAD_REQUEST']) try: host = bus.storage.get_host(address) cluster = bus.storage.get_cluster(name) except _bus.StorageLookupError as error: return create_jsonrpc_error( message, error, JSONRPC_ERRORS['NOT_FOUND']) if host.address not in cluster.hostset: # FIXME: Need more input validation. # - Does the host already belong to another cluster? # FIXME: Should guard against races here, since we're fetching # the cluster record and writing it back with some parts # unmodified. Use either locking or a conditional write # with the etcd 'modifiedIndex'. Deferring for now. if host_suitable_for_cluster(host): cluster.hostset.append(host.address) bus.storage.save(cluster) # Register new host with the cluster's container manager # (if applicable), and update its status. update_new_cluster_member_status(bus, cluster, host) else: msg = ( 'Host {} (status: {}) not ready to join cluster ' '"{}"'.format(host.address, host.status, cluster.name)) LOGGER.error(msg) return create_jsonrpc_error( message, msg, JSONRPC_ERRORS['METHOD_NOT_ALLOWED']) # Return back the host in a list return create_jsonrpc_response(message['id'], [host.address])
def delete_host(message, bus): """ Deletes an existing host. :param message: jsonrpc message structure. :type message: dict :param bus: Bus instance. :type bus: commissaire_http.bus.Bus :returns: A jsonrpc structure. :rtype: dict """ try: address = message['params']['address'] LOGGER.debug('Attempting to delete host "{}"'.format(address)) bus.request('storage.delete', params=['Host', {'address': address}]) # TODO: kick off service job to remove the host? # Remove from a cluster for cluster in bus.request('storage.list', params=['Clusters', True])['result']: if address in cluster['hostset']: LOGGER.info('Removing host "{}" from cluster "{}"'.format( address, cluster['name'])) cluster['hostset'].pop(cluster['hostset'].index(address)) bus.request('storage.save', params=['Cluster', cluster]) # A host can only be part of one cluster so break the loop break return create_response(message['id'], []) except _bus.RemoteProcedureCallError as error: LOGGER.debug('Error deleting host: {}: {}'.format(type(error), error)) return return_error(message, error, JSONRPC_ERRORS['NOT_FOUND']) except Exception as error: LOGGER.debug('Error deleting host: {}: {}'.format(type(error), error)) return return_error(message, error, JSONRPC_ERRORS['INTERNAL_ERROR'])
def update_cluster_members(message, bus): """ Updates the list of members in a cluster. :param message: jsonrpc message structure. :type message: dict :param bus: Bus instance. :type bus: commissaire_http.bus.Bus :returns: A jsonrpc structure. :rtype: dict """ try: old_hosts = set(message['params']['old']) # Ensures no duplicates new_hosts = set(message['params']['new']) # Ensures no duplicates LOGGER.debug('old_hosts="{}", new_hosts="{}"'.format( old_hosts, new_hosts)) except Exception as error: return return_error(message, error, JSONRPC_ERRORS['BAD_REQUEST']) try: cluster = bus.request( 'storage.get', params=['Cluster', { 'name': message['params']['name'] }, True]) except Exception as error: return return_error(message, error, JSONRPC_ERRORS['NOT_FOUND']) if old_hosts != set(cluster['result']['hostset']): msg = 'Conflict setting hosts for cluster {0}'.format( cluster['result']['name']) LOGGER.error(msg) return return_error(message, msg, JSONRPC_ERRORS['CONFLICT']) # FIXME: Need input validation. For each new host, # - Does the host exist at /commissaire/hosts/{IP}? # - Does the host already belong to another cluster? # FIXME: Should guard against races here, since we're fetching # the cluster record and writing it back with some parts # unmodified. Use either locking or a conditional write # with the etcd 'modifiedIndex'. Deferring for now. cluster['result']['hostset'] = list(new_hosts) cluster = models.Cluster.new(**cluster['result']) cluster._validate() response = bus.request('storage.save', params=['Cluster', cluster.to_dict()]) return create_response(message['id'], response['result'])
def create_cluster_operation(model_cls, message, bus, routing_key): """ Creates a new operation based on the model_cls. :param model_cls: The model class to use. :type model_cls: class :param message: jsonrpc message structure. :type message: dict :param bus: Bus instance. :type bus: commissaire_http.bus.Bus :returns: A jsonrpc structure. :param routing_key: Routing key for the cluster operation request. :type routing_key: str :rtype: dict """ # Verify cluster exists first cluster_name = message['params']['name'] try: bus.storage.get_cluster(cluster_name) LOGGER.debug('Found cluster "%s"', cluster_name) except: error_msg = 'Cluster "{}" does not exist.'.format(cluster_name) LOGGER.debug(error_msg) return create_jsonrpc_error(message, error_msg, JSONRPC_ERRORS['NOT_FOUND']) try: model = model_cls.new( name=cluster_name, started_at=datetime.datetime.utcnow().isoformat()) model._validate() # XXX Assumes the only method argument is cluster_name. result = bus.request(routing_key, params=[cluster_name]) return create_jsonrpc_response(message['id'], result['result']) except models.ValidationError as error: LOGGER.info('Invalid data provided. "%s"', error) LOGGER.debug('Data="%s"', message['params']) return create_jsonrpc_error(message, error, JSONRPC_ERRORS['INVALID_REQUEST']) except Exception as error: LOGGER.debug('Error creating %s: %s: %s', model_cls.__name__, type(error), error) return create_jsonrpc_error(message, error, JSONRPC_ERRORS['INTERNAL_ERROR'])
def create_cluster(message, bus): """ Creates a new cluster. :param message: jsonrpc message structure. :type message: dict :param bus: Bus instance. :type bus: commissaire_http.bus.Bus :returns: A jsonrpc structure. :rtype: dict """ try: bus.request('storage.get', params=['Cluster', { 'name': message['params']['name'] }]) LOGGER.debug( 'Creation of already exisiting cluster {0} requested.'.format( message['params']['name'])) except Exception as error: LOGGER.debug('Brand new cluster being created.') if message['params'].get('network'): # Verify the networks existence try: bus.request( 'storage.get', params=['Network', { 'name': message['params']['network'] }]) except Exception as error: # Default if the network doesn't exist message['params']['network'] = C.DEFAULT_CLUSTER_NETWORK_JSON[ 'name'] # noqa try: cluster = models.Cluster.new(**message['params']) cluster._validate() response = bus.request('storage.save', params=['Cluster', cluster.to_dict()]) return create_response(message['id'], response['result']) except models.ValidationError as error: return return_error(message, error, JSONRPC_ERRORS['INVALID_REQUEST'])
def get_host(message, bus): """ Gets a specific host. :param message: jsonrpc message structure. :type message: dict :param bus: Bus instance. :type bus: commissaire_http.bus.Bus :returns: A jsonrpc structure. :rtype: dict """ try: address = message['params']['address'] host = bus.storage.get_host(address) return create_jsonrpc_response(message['id'], host.to_dict_safe()) except _bus.RemoteProcedureCallError as error: LOGGER.debug('Client requested a non-existant host: "{}"'.format( message['params']['address'])) return create_jsonrpc_error(message, error, JSONRPC_ERRORS['NOT_FOUND'])
def list_cluster_members(message, bus): """ Lists hosts in a cluster. :param message: jsonrpc message structure. :type message: dict :param bus: Bus instance. :type bus: commissaire_http.bus.Bus :returns: A jsonrpc structure. :rtype: dict """ response = None try: cluster = bus.request('storage.get', params=[ 'Cluster', {'name': message['params']['name']}, True]) LOGGER.debug('Cluster found: {}'.format(cluster['result']['name'])) LOGGER.debug('Returning: {}'.format(response)) return create_response( message['id'], result=cluster['result']['hostset']) except Exception as error: return return_error(message, error, JSONRPC_ERRORS['NOT_FOUND'])
def create_cluster_deploy(message, bus): """ Creates a new cluster deployment. :param message: jsonrpc message structure. :type message: dict :param bus: Bus instance. :type bus: commissaire_http.bus.Bus :returns: A jsonrpc structure. :rtype: dict """ try: cluster_deploy = models.ClusterDeploy.new( name=message['params'].get('name'), version=message['params'].get('version')) cluster_deploy._validate() result = bus.request( 'jobs.clusterexec.deploy', params=[cluster_deploy.name, cluster_deploy.version]) return create_jsonrpc_response(message['id'], result['result']) except models.ValidationError as error: LOGGER.info('Invalid data provided. "%s"', error) LOGGER.debug('Data="%s"', message['params']) return create_jsonrpc_error(message, error, JSONRPC_ERRORS['INVALID_REQUEST']) except Exception as error: LOGGER.debug('Error creating ClusterDeploy: %s: %s', type(error), error) return create_jsonrpc_error(message, error, JSONRPC_ERRORS['INTERNAL_ERROR'])
def list_cluster_members(message, bus): """ Lists hosts in a cluster. :param message: jsonrpc message structure. :type message: dict :param bus: Bus instance. :type bus: commissaire_http.bus.Bus :returns: A jsonrpc structure. :rtype: dict """ try: name = message['params']['name'] cluster = bus.storage.get_cluster(name) LOGGER.debug('Cluster found: {}'.format(cluster.name)) LOGGER.debug('Returning: {}'.format(cluster.hostset)) return create_jsonrpc_response( message['id'], result=cluster.hostset) except _bus.StorageLookupError as error: return create_jsonrpc_error( message, error, JSONRPC_ERRORS['NOT_FOUND']) except Exception as error: LOGGER.debug('Error listing cluster: {}: {}'.format( type(error), error)) return create_jsonrpc_error( message, error, JSONRPC_ERRORS['INTERNAL_ERROR'])
def get_cluster_operation(model_cls, message, bus): """ Gets a specific operation based on the model_cls. :param model_cls: The model class to use. :type model_cls: class :param message: jsonrpc message structure. :type message: dict :param bus: Bus instance. :type bus: commissaire_http.bus.Bus :returns: A jsonrpc structure. :rtype: dict """ try: name = message['params']['name'] model = bus.storage.get(model_cls.new(name=name)) model._validate() return create_jsonrpc_response(message['id'], model.to_dict_safe()) except models.ValidationError as error: LOGGER.info('Invalid data retrieved. "%s"', error) LOGGER.debug('Data="%s"', message['params']) return create_jsonrpc_error(message, error, JSONRPC_ERRORS['INVALID_REQUEST']) except _bus.StorageLookupError as error: return create_jsonrpc_error(message, error, JSONRPC_ERRORS['NOT_FOUND']) except Exception as error: LOGGER.debug('Error getting %s: %s: %s', model_cls.__name__, type(error), error) return create_jsonrpc_error(message, error, JSONRPC_ERRORS['INTERNAL_ERROR'])
def delete_network(message, bus): """ Deletes an exisiting network. :param message: jsonrpc message structure. :type message: dict :param bus: Bus instance. :type bus: commissaire_http.bus.Bus :returns: A jsonrpc structure. :rtype: dict """ try: LOGGER.debug('Attempting to delete network "{}"'.format( message['params']['name'])) bus.request('storage.delete', params=['Network', { 'name': message['params']['name'] }]) return create_response(message['id'], []) except _bus.RemoteProcedureCallError as error: LOGGER.debug('Error deleting network: {}: {}'.format( type(error), error)) return return_error(message, error, JSONRPC_ERRORS['NOT_FOUND']) except Exception as error: LOGGER.debug('Error deleting network: {}: {}'.format( type(error), error)) return return_error(message, error, JSONRPC_ERRORS['INTERNAL_ERROR'])
def create_network(message, bus): """ Creates a new network. :param message: jsonrpc message structure. :type message: dict :param bus: Bus instance. :type bus: commissaire_http.bus.Bus :returns: A jsonrpc structure. :rtype: dict """ try: LOGGER.debug('create_network params: {}'.format(message['params'])) # Check to see if we already have a network with that name network = bus.request( 'storage.get', params=['Network', { 'name': message['params']['name'] }]) LOGGER.debug( 'Creation of already exisiting network "{0}" requested.'.format( message['params']['name'])) # If they are the same thing then go ahead and return success if models.Network.new( **network['result']).to_dict() == models.Network.new( **message['params']).to_dict(): return create_response(message['id'], network['result']) # Otherwise error with a CONFLICT return return_error(message, 'A network with that name already exists.', JSONRPC_ERRORS['CONFLICT']) except _bus.RemoteProcedureCallError as error: LOGGER.debug('Error getting network: {}: {}'.format( type(error), error)) LOGGER.info('Attempting to create new network: "{}"'.format( message['params'])) # Create the new network try: network = models.Network.new(**message['params']) network._validate() response = bus.request('storage.save', params=['Network', network.to_dict()]) return create_response(message['id'], response['result']) except models.ValidationError as error: return return_error(message, error, JSONRPC_ERRORS['INVALID_REQUEST'])
def delete_network(message, bus): """ Deletes an exisiting network. :param message: jsonrpc message structure. :type message: dict :param bus: Bus instance. :type bus: commissaire_http.bus.Bus :returns: A jsonrpc structure. :rtype: dict """ try: name = message['params']['name'] LOGGER.debug('Attempting to delete network "%s"', name) bus.storage.delete(models.Network.new(name=name)) return create_jsonrpc_response(message['id'], []) except _bus.StorageLookupError as error: return create_jsonrpc_error(message, error, JSONRPC_ERRORS['NOT_FOUND']) except Exception as error: LOGGER.debug('Error deleting network: %s: %s', type(error), error) return create_jsonrpc_error(message, error, JSONRPC_ERRORS['INTERNAL_ERROR'])
def delete_host(message, bus): """ Deletes an existing host. :param message: jsonrpc message structure. :type message: dict :param bus: Bus instance. :type bus: commissaire_http.bus.Bus :returns: A jsonrpc structure. :rtype: dict """ try: address = message['params']['address'] LOGGER.debug('Attempting to delete host "{}"'.format(address)) bus.storage.delete(models.Host.new(address=address)) # TODO: kick off service job to remove the host? try: # Remove from a cluster container = bus.storage.list(models.Clusters) for cluster in container.clusters: if address in cluster.hostset: LOGGER.info('Removing host "{}" from cluster "{}"'.format( address, cluster.name)) cluster.hostset.pop(cluster.hostset.index(address)) bus.storage.save(cluster) # Remove from container manager (if applicable) if cluster.container_manager: params = [cluster.container_manager, address] bus.request('container.remove_node', params=params) # A host can only be part of one cluster so break the loop break except _bus.RemoteProcedureCallError as error: LOGGER.info('{} not part of a cluster.'.format(address)) return create_jsonrpc_response(message['id'], []) except _bus.RemoteProcedureCallError as error: return create_jsonrpc_error(message, error, JSONRPC_ERRORS['NOT_FOUND']) except Exception as error: LOGGER.debug('Error deleting host: {}: {}'.format(type(error), error)) return create_jsonrpc_error(message, error, JSONRPC_ERRORS['INTERNAL_ERROR'])
def _does_cluster_exist(bus, cluster_name): """ Shorthand to check and see if a cluster exists. If it does, return the message response data. :param bus: Bus instance. :type bus: commissaire_http.bus.Bus :param cluster_name: The name of the Cluster to look up. :type cluster_name: str :returns: The found Cluster instance or None :rtype: mixed """ LOGGER.debug('Checking on cluster "{}"'.format(cluster_name)) try: cluster = bus.request('storage.get', params=[ 'Cluster', {'name': cluster_name}, True]) LOGGER.debug('Found cluster: "{}"'.format(cluster)) return models.Cluster.new(**cluster['result']) except _bus.RemoteProcedureCallError as error: LOGGER.warn( 'create_host could not find cluster "{}"'.format(cluster_name)) LOGGER.debug('Error: {}: "{}"'.format(type(error), error))
def create_network(message, bus): """ Creates a new network. :param message: jsonrpc message structure. :type message: dict :param bus: Bus instance. :type bus: commissaire_http.bus.Bus :returns: A jsonrpc structure. :rtype: dict """ try: name = message['params']['name'] LOGGER.debug('create_network params: {}'.format(message['params'])) # Check to see if we already have a network with that name input_network = models.Network.new(**message['params']) saved_network = bus.storage.get(input_network) LOGGER.debug( 'Creation of already exisiting network "{0}" requested.'.format( name)) # If they are the same thing then go ahead and return success if saved_network.to_dict() == input_network.to_dict(): return create_jsonrpc_response( message['id'], saved_network.to_dict_safe()) # Otherwise error with a CONFLICT return create_jsonrpc_error( message, 'A network with that name already exists.', JSONRPC_ERRORS['CONFLICT']) except _bus.StorageLookupError as error: LOGGER.info('Attempting to create new network: "{}"'.format( message['params'])) # Create the new network try: input_network = models.Network.new(**message['params']) saved_network = bus.storage.save(input_network) return create_jsonrpc_response( message['id'], saved_network.to_dict_safe()) except models.ValidationError as error: return create_jsonrpc_error( message, error, JSONRPC_ERRORS['INVALID_REQUEST'])
def _does_cluster_exist(bus, cluster_name): """ Shorthand to check and see if a cluster exists. If it does, return the message response data. :param bus: Bus instance. :type bus: commissaire_http.bus.Bus :param cluster_name: The name of the Cluster to look up. :type cluster_name: str :returns: The found Cluster instance or None :rtype: mixed """ LOGGER.debug('Checking on cluster "{}"'.format(cluster_name)) try: cluster = bus.storage.get_cluster(cluster_name) LOGGER.debug('Found cluster: "{}"'.format(cluster)) return cluster except _bus.StorageLookupError as error: LOGGER.warn( 'create_host could not find cluster "{}"'.format(cluster_name)) return None
def get_host_status(message, bus): """ Gets the status of an exisiting host. :param message: jsonrpc message structure. :type message: dict :param bus: Bus instance. :type bus: commissaire_http.bus.Bus :returns: A jsonrpc structure. :rtype: dict """ try: host_response = bus.request( 'storage.get', params=['Host', { 'address': message['params']['address'] }]) host = models.Host.new(**host_response['result']) status = models.HostStatus.new( host={ 'last_check': host.last_check, 'status': host.status, }, # TODO: Update when we add other types. type=C.CLUSTER_TYPE_HOST) LOGGER.debug('Status for host "{0}": "{1}"'.format( host.address, status.to_json())) return create_response(message['id'], status.to_dict()) except _bus.RemoteProcedureCallError as error: LOGGER.warn('Could not retrieve host "{}". {}: {}'.format( message['params']['address'], type(error), error)) return return_error(message, error, JSONRPC_ERRORS['NOT_FOUND']) except Exception as error: LOGGER.debug('Host Status exception caught for {0}: {1}:{2}'.format( host.address, type(error), error)) return return_error(message, error, JSONRPC_ERRORS['INTERNAL_ERROR'])
def create_host(message, bus): """ Creates a new host. :param message: jsonrpc message structure. :type message: dict :param bus: Bus instance. :type bus: commissaire_http.bus.Bus :returns: A jsonrpc structure. :rtype: dict """ LOGGER.debug('create_host params: "{}"'.format(message['params'])) try: address = message['params']['address'] except KeyError: return create_jsonrpc_error( message, '"address" must be given in the url or in the PUT body', JSONRPC_ERRORS['BAD_REQUEST']) # If a cluster if provided, grab it from storage cluster_data = {} cluster_name = message['params'].get('cluster') if cluster_name: cluster = _does_cluster_exist(bus, cluster_name) if not cluster: return create_jsonrpc_error(message, 'Cluster does not exist', JSONRPC_ERRORS['CONFLICT']) else: cluster_data = cluster.to_dict() LOGGER.debug('Found cluster. Data: "{}"'.format(cluster)) try: host = bus.storage.get_host(address) LOGGER.debug('Host "{}" already exisits.'.format(address)) # Verify the keys match if host.ssh_priv_key != message['params'].get('ssh_priv_key', ''): return create_jsonrpc_error(message, 'Host already exists', JSONRPC_ERRORS['CONFLICT']) # Verify the host is in the cluster if it is expected if cluster_name and address not in cluster.hostset: LOGGER.debug('Host "{}" is not in cluster "{}"'.format( address, cluster_name)) return create_jsonrpc_error(message, 'Host not in cluster', JSONRPC_ERRORS['CONFLICT']) # Return out now. No more processing needed. return create_jsonrpc_response(message['id'], host.to_dict_safe()) except _bus.RemoteProcedureCallError as error: LOGGER.debug('Brand new host "{}" being created.'.format( message['params']['address'])) # Save the host to the cluster if it isn't already there if cluster_name: if address not in cluster.hostset: cluster.hostset.append(address) bus.storage.save(cluster) LOGGER.debug('Saved host "{}" to cluster "{}"'.format( address, cluster_name)) try: host = bus.storage.save(models.Host.new(**message['params'])) # pass this off to the investigator bus.notify('jobs.investigate', params={ 'address': address, 'cluster_data': cluster_data }) # Push the host to the Watcher queue watcher_record = models.WatcherRecord( address=address, last_check=_dt.utcnow().isoformat()) bus.producer.publish(watcher_record.to_json(), 'jobs.watcher') return create_jsonrpc_response(message['id'], host.to_dict_safe()) except models.ValidationError as error: return create_jsonrpc_error(message, error, JSONRPC_ERRORS['INVALID_REQUEST'])
def update_cluster_members(message, bus): """ Updates the list of members in a cluster. :param message: jsonrpc message structure. :type message: dict :param bus: Bus instance. :type bus: commissaire_http.bus.Bus :returns: A jsonrpc structure. :rtype: dict """ try: old_hosts = set(message['params']['old']) # Ensures no duplicates new_hosts = set(message['params']['new']) # Ensures no duplicates LOGGER.debug('old_hosts="%s", new_hosts="%s"', old_hosts, new_hosts) except Exception as error: return create_jsonrpc_error(message, error, JSONRPC_ERRORS['BAD_REQUEST']) try: name = message['params']['name'] cluster = bus.storage.get_cluster(name) except _bus.StorageLookupError as error: return create_jsonrpc_error(message, error, JSONRPC_ERRORS['NOT_FOUND']) if old_hosts != set(cluster.hostset): msg = 'Conflict setting hosts for cluster {}'.format(name) LOGGER.error(msg) return create_jsonrpc_error(message, msg, JSONRPC_ERRORS['CONFLICT']) # FIXME: Need more input validation. For each new host, # - Does the host already belong to another cluster? # Only verify *new* hosts are suitable to add to the cluster. # Rejecting existing cluster members would be surprising to users. actual_new_hosts = new_hosts.difference(old_hosts) LOGGER.debug('Checking status of new hosts (ignoring existing): %s', ', '.join(actual_new_hosts)) list_of_hosts = bus.storage.get_many( [models.Host.new(address=x) for x in actual_new_hosts]) hosts_not_ready = [ host.address for host in list_of_hosts if not host_suitable_for_cluster(host) ] if hosts_not_ready: msg = 'Hosts not ready to join cluster "{}": {}'.format( name, ','.join(hosts_not_ready)) LOGGER.error(msg) return create_jsonrpc_error(message, msg, JSONRPC_ERRORS['METHOD_NOT_ALLOWED']) # FIXME: Should guard against races here, since we're fetching # the cluster record and writing it back with some parts # unmodified. Use either locking or a conditional write # with the etcd 'modifiedIndex'. Deferring for now. cluster.hostset = list(new_hosts) saved_cluster = bus.storage.save(cluster) # Register newly added hosts with the cluster's container manager # (if applicable), and update their status. update_new_cluster_member_status(bus, cluster, *list_of_hosts) # XXX Using to_dict() instead of to_dict_safe() to include hostset. return create_jsonrpc_response(message['id'], saved_cluster.to_dict())
def create_host(message, bus): """ Creates a new host. :param message: jsonrpc message structure. :type message: dict :param bus: Bus instance. :type bus: commissaire_http.bus.Bus :returns: A jsonrpc structure. :rtype: dict """ LOGGER.debug('create_host params: "{}"'.format(message['params'])) try: address = message['params']['address'] except KeyError: return return_error( message, '"address" must be given in the url or in the PUT body', JSONRPC_ERRORS['INVALID_PARAMETERS']) try: host = bus.request('storage.get', params=[ 'Host', {'address': address}, True]) LOGGER.debug('Host "{}" already exisits.'.format(address)) # Verify the keys match if (host['result']['ssh_priv_key'] != message['params'].get('ssh_priv_key', '')): return return_error( message, 'Host already exists', JSONRPC_ERRORS['CONFLICT']) # Verify the cluster exists and it's in the cluster if message['params'].get('cluster'): cluster = _does_cluster_exist(bus, message['params']['cluster']) if not cluster: return return_error( message, 'Cluster does not exist', JSONRPC_ERRORS['INVALID_PARAMETERS']) # Verify the host is in the cluster if address not in cluster.hostset: LOGGER.debug('Host "{}" is not in cluster "{}"'.format( address, message['params']['cluster'])) return return_error( message, 'Host not in cluster', JSONRPC_ERRORS['CONFLICT']) # Return out now. No more processing needed. return create_response( message['id'], models.Host.new(**host['result']).to_dict()) except _bus.RemoteProcedureCallError as error: LOGGER.debug('Brand new host "{}" being created.'.format( message['params']['address'])) if message['params'].get('cluster'): # Verify the cluster existence and add the host to it cluster_name = message['params']['cluster'] cluster = _does_cluster_exist(bus, message['params']['cluster']) if not cluster: LOGGER.warn( 'create_host could not find cluster "{}" for the creation ' 'of new host "{}"'.format(cluster_name, address)) return return_error( message, 'Cluster does not exist', JSONRPC_ERRORS['INVALID_PARAMETERS']) LOGGER.debug('Found cluster. Data: "{}"'.format(cluster)) if address not in cluster.hostset: cluster.hostset.append(address) bus.request('storage.save', params=[ 'Cluster', cluster.to_dict(True)]) try: # TODO: pass this off to the bootstrap process host = models.Host.new(**message['params']) host._validate() bus.request( 'storage.save', params=[ 'Host', host.to_dict(True)]) return create_response(message['id'], host.to_dict()) except models.ValidationError as error: return return_error(message, error, JSONRPC_ERRORS['INVALID_REQUEST'])