def delete_cluster_member(message, bus): """ Deletes a member from 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: cluster = bus.request( 'storage.get', params=['Cluster', { 'name': message['params']['name'] }, True]) if message['params']['host'] in cluster['result']['hostset']: # 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. idx = cluster['result']['hostset'].index(message['params']['host']) cluster['result']['hostset'].pop(idx) bus.request('storage.save', params=['Cluster', cluster['result'], True]) return create_response(message['id'], []) except Exception as error: return create_response(message['id'], error=error, error_code=JSONRPC_ERRORS['NOT_FOUND'])
def check_cluster_member(message, bus): """ Checks is a member is part of 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: cluster = bus.request( 'storage.get', params=['Cluster', { 'name': message['params']['name'] }, True]) if message['params']['host'] in cluster['result']['hostset']: # Return back the host in a list return create_response(message['id'], [message['params']['host']]) else: return create_response( message['id'], error='The requested host is not part of the cluster.', error_code=JSONRPC_ERRORS['NOT_FOUND']) except Exception as error: return create_response(message['id'], error=error, error_code=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: cluster = bus.request('storage.get', params=[ 'Cluster', {'name': message['params']['name']}, True]) if message['params']['host'] not in cluster['result']['hostset']: # FIXME: Need input validation. # - 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'].append(message['params']['host']) bus.request('storage.save', params=[ 'Cluster', cluster['result'], True]) # Return back the host in a list return create_response(message['id'], [message['params']['host']]) except Exception as error: return create_response( message['id'], error=error, error_code=JSONRPC_ERRORS['INTERNAL_ERROR'])
def test_get_network(self): """ Verify get_network responds with the right information. """ bus = mock.MagicMock() bus.request.return_value = create_response(ID, NETWORK.to_dict()) self.assertEquals(create_response(ID, NETWORK.to_dict()), networks.get_network(SIMPLE_NETWORK_REQUEST, bus))
def test_get_host(self): """ Verify get_host responds with the right information. """ bus = mock.MagicMock() bus.request.return_value = create_response(ID, HOST.to_dict()) self.assertEquals( create_response(ID, HOST.to_dict()), hosts.get_host(SIMPLE_HOST_REQUEST, bus))
def test_list_clusters(self): """ Verify list_clusters responds with the right information. """ bus = mock.MagicMock() bus.request.return_value = create_response(ID, [{'name': 'test'}]) self.assertEquals( create_response(ID, [CLUSTER.name]), clusters.list_clusters(bus.request.return_value, bus))
def test_get_host_creds(self): """ Verify get_hostcreds responds with the right information. """ bus = mock.MagicMock() bus.request.return_value = create_response(ID, HOST.to_dict(True)) self.assertEquals( create_response(ID, {'ssh_priv_key': '', 'remote_user': '******'}), hosts.get_hostcreds(SIMPLE_HOST_REQUEST, bus))
def test_create_cluster(self): """ Verify create_cluster saves new clusters. """ bus = mock.MagicMock() cluster_json = Cluster.new(name='test').to_json() bus.request.return_value = create_response(ID, cluster_json) self.assertEquals(create_response(ID, cluster_json), clusters.create_cluster(SIMPLE_CLUSTER_REQUEST, bus))
def test_get_host_status(self): """ Verify get_host_status responds with status information. """ bus = mock.MagicMock() bus.request.return_value = create_response(ID, HOST.to_dict()) host_status = HostStatus.new( host={'last_check': '', 'status': ''}, type=C.CLUSTER_TYPE_HOST) self.assertEquals( create_response(ID, host_status.to_dict()), hosts.get_host_status(SIMPLE_HOST_REQUEST, bus))
def test_delete_cluster_member_with_valid_member(self): """ Verify that delete_cluster_member actually removes a member. """ bus = mock.MagicMock() cluster = Cluster.new(name='test', hostset=['127.0.0.1']) bus.request.return_value = create_response(ID, cluster.to_dict(True)) self.assertEquals( create_response(ID, []), clusters.delete_cluster_member(CHECK_CLUSTER_REQUEST, bus))
def test_create_network_idempotent(self): """ Verify create_network acts idempotent. """ bus = mock.MagicMock() bus.request.side_effect = ( # Network exists create_response(ID, NETWORK.to_dict()), # Creation response create_response(ID, NETWORK.to_dict())) self.assertEquals(create_response(ID, NETWORK.to_dict()), networks.create_network(SIMPLE_NETWORK_REQUEST, bus))
def test_check_cluster_member_with_valid_member(self): """ Verify that check_cluster_member returns proper data when a valid member is requested. """ bus = mock.MagicMock() cluster = Cluster.new(name='test', hostset=['127.0.0.1']) bus.request.return_value = create_response(ID, cluster.to_dict(True)) self.assertEquals( create_response(ID, ['127.0.0.1']), clusters.check_cluster_member(CHECK_CLUSTER_REQUEST, bus))
def test_add_cluster_member_with_valid_member(self): """ Verify that add_cluster_member actually adds a new member.. """ bus = mock.MagicMock() cluster = Cluster.new(name='test', hostset=[]) bus.request.return_value = create_response(ID, cluster.to_dict(True)) expected_response = create_response(ID, ['127.0.0.1']) self.assertEquals( expected_response, clusters.add_cluster_member(CHECK_CLUSTER_REQUEST, bus))
def test_create_network(self): """ Verify create_network can create a new network. """ bus = mock.MagicMock() bus.request.side_effect = ( # Network doesn't yet exist _bus.RemoteProcedureCallError('test'), # Creation response create_response(ID, NETWORK.to_dict())) self.assertEquals(create_response(ID, NETWORK.to_dict()), networks.create_network(SIMPLE_NETWORK_REQUEST, bus))
def test_create_host(self): """ Verify create_host saves new hosts. """ bus = mock.MagicMock() bus.request.side_effect = ( # Host doesn't exist yet _bus.RemoteProcedureCallError('test'), # Result from save create_response(ID, HOST.to_dict()) ) self.assertEquals( create_response(ID, HOST.to_dict()), hosts.create_host(SIMPLE_HOST_REQUEST, bus))
def test_create_host_with_the_same_existing_host(self): """ Verify create_host succeeds when a new host matches an existing one. """ bus = mock.MagicMock() bus.request.side_effect = ( # Existing host create_response(ID, HOST.to_dict(True)), # Result from save create_response(ID, HOST.to_dict()) ) self.assertEquals( create_response(ID, HOST.to_dict()), hosts.create_host(SIMPLE_HOST_REQUEST, bus))
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: 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 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 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 get_cluster(message, bus): """ Gets a specific 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 """ cluster_response = bus.request( 'storage.get', params=['Cluster', { 'name': message['params']['name'] }, True]) cluster = models.Cluster.new(**cluster_response['result']) hosts_response = bus.request('storage.list', params=['Hosts']) available = unavailable = total = 0 for host in hosts_response['result']: if host['address'] in cluster.hostset: total += 1 if host['status'] == 'active': available += 1 else: unavailable += 1 cluster.hosts['total'] = total cluster.hosts['available'] = available cluster.hosts['unavailable'] = unavailable return create_response(message['id'], cluster.to_dict_with_hosts())
def test_create_response_with_result(self): """ Verify create_response creates the proper result jsonrpc structure. """ response = handlers.create_response(UID, result={'test': 'data'}) self.assertEquals('2.0', response['jsonrpc']) self.assertEquals(UID, response['id']) self.assertEquals({'test': 'data'}, response['result'])
def test_create_host_with_existing_host_different_cluster_memebership(self): """ Verify create_host returns conflict existing cluster memebership does not match. """ bus = mock.MagicMock() different = HOST.to_dict() different['ssh_priv_key'] = '' bus.request.side_effect = ( # Existing host create_response(ID, different), # Cluster create_response(ID, {'hostset': []}), create_response(ID, {'hostset': []}) ) self.assertEquals( expected_error(ID, JSONRPC_ERRORS['CONFLICT']), hosts.create_host(CLUSTER_HOST_REQUEST, bus))
def test_delete_cluster(self): """ Verify delete_cluster deletes existing clusters. """ bus = mock.MagicMock() bus.request.side_effect = ( # The delete shouldn't return anything None, ) self.assertEquals(create_response(ID, []), clusters.delete_cluster(SIMPLE_CLUSTER_REQUEST, bus))
def test_create_host_with_cluster(self): """ Verify create_host saves new hosts with it's cluster. """ bus = mock.MagicMock() bus.request.side_effect = ( # Host doesn't exist yet _bus.RemoteProcedureCallError('test'), # Request the cluster create_response(ID, {'hostset': []}), # Save of the cluster create_response(ID, {'hostset': []}), # Result from save (ignored) create_response(ID, HOST.to_dict()) ) self.assertEquals( create_response(ID, HOST.to_dict()), hosts.create_host(CLUSTER_HOST_REQUEST, bus))
def test_list_hosts(self): """ Verify list_hosts responds with the right information. """ bus = mock.MagicMock() bus.request.return_value = { 'jsonrpc': '2.0', 'result': [HOST.to_dict()], 'id': '123'} self.assertEquals( create_response(ID, [HOST.to_dict()]), hosts.list_hosts(SIMPLE_HOST_REQUEST, bus))
def test_create_host_with_existing_host_different_ssh_key(self): """ Verify create_host returns conflict existing hosts ssh keys do not match. """ bus = mock.MagicMock() different = HOST.to_dict() different['ssh_priv_key'] = 'aaa' bus.request.return_value = create_response(ID, different) self.assertEquals( expected_error(ID, JSONRPC_ERRORS['CONFLICT']), hosts.create_host(SIMPLE_HOST_REQUEST, bus))
def test_create_response_with_error(self): """ Verify create_response creates the proper error jsonrpc structure. """ for (error, expected_response) in [ (Exception('test'), 'test'), ('test', 'test') ]: response = handlers.create_response(UID, error=error) self.assertEquals('2.0', response['jsonrpc']) self.assertEquals(UID, response['id']) print(response) self.assertEquals(expected_response, response['error']['message'])
def test_list_networks(self): """ Verify list_networks responds with the right information. """ bus = mock.MagicMock() bus.request.return_value = { 'jsonrpc': '2.0', 'result': [NETWORK.to_dict()], 'id': '123' } self.assertEquals( create_response(ID, ['test']), clusters.list_clusters(bus.request.return_value, bus))
def list_hosts(message, bus): """ Lists all hosts. :param message: jsonrpc message structure. :type message: dict :param bus: Bus instance. :type bus: commissaire_http.bus.Bus :returns: A jsonrpc structure. :rtype: dict """ hosts_msg = bus.request('storage.list', params=['Hosts']) return create_response(message['id'], hosts_msg['result'])