def test_update_new_cluster_member_status(self): """ Verify update_new_cluster_member_status works as expected """ bus = mock.MagicMock() hosts = ( Host.new(address='192.168.1.1'), Host.new(address='192.168.1.2') ) cluster = Cluster.new(name='test') cluster.container_manager = None clusters.update_new_cluster_member_status(bus, cluster, *hosts) bus.request.assert_not_called() for n in range(len(hosts)): self.assertEquals(hosts[n].status, C.HOST_STATUS_DISASSOCIATED) bus.storage.save_many.assert_called_once_with(hosts) bus.reset_mock() cluster.container_manager = C.CONTAINER_MANAGER_OPENSHIFT clusters.update_new_cluster_member_status(bus, cluster, *hosts) for n in range(len(hosts)): bus.request.assert_any_call( 'container.register_node', params=[C.CONTAINER_MANAGER_OPENSHIFT, hosts[n].address]) self.assertEquals(hosts[n].status, C.HOST_STATUS_ACTIVE) bus.storage.save_many.assert_called_once_with(hosts)
def test_update_cluster_members_with_failed_new_host(self): """ Verify that update_cluster_members rejects a failed host """ bus = mock.MagicMock() old_hostset = ['192.168.1.1'] new_hostset = old_hostset + ['192.168.1.2'] cluster = Cluster.new(name='test', hostset=old_hostset) hosts = [Host.new(address=new_hostset[0], status=C.HOST_STATUS_ACTIVE), Host.new(address=new_hostset[1], status=C.HOST_STATUS_FAILED)] bus.storage.get_cluster.return_value = cluster bus.storage.get_many.return_value = hosts message = { 'jsonrpc': '2.0', 'id': ID, 'params': { 'name': 'test', 'old': old_hostset, 'new': new_hostset } } with mock.patch('commissaire_http.handlers.clusters.' 'update_new_cluster_member_status') as uncms: result = clusters.update_cluster_members.handler(message, bus) self.assertEquals( result, expected_error(ID, JSONRPC_ERRORS['METHOD_NOT_ALLOWED']))
def test_get_uniform_model_type_with_valid_types(self): """ Verify get_uniform_model_type works when types are the same. """ self.assertEquals( Host, storage.get_uniform_model_type([ Host.new(address='127.0.0.1'), Host.new(address='127.0.0.2')]))
def _check(self, address): """ Initiates an check on the requested host. :param address: Host address to investigate :type address: str :param cluster_data: Optional data for the associated cluster :type cluster_data: dict """ # Statuses follow: # http://commissaire.readthedocs.org/en/latest/enums.html#host-statuses self.logger.info('Checking host "{}".'.format(address)) try: response = self.request('storage.get', params={ 'model_type_name': 'Host', 'model_json_data': Host.new(address=address).to_json(), 'secure': True, }) host = Host.new(**response['result']) except Exception as error: self.logger.warn('Unable to continue for host "{}" due to ' '{}: {}. Returning...'.format( address, type(error), error)) raise error transport = ansibleapi.Transport(host.remote_user) with TemporarySSHKey(host, self.logger) as key: try: self.logger.debug( 'Starting watcher run for host "{}"'.format(address)) result = transport.check_host_availability(host, key.path) host.last_check = datetime.utcnow().isoformat() self.logger.debug('Watcher result for host {}: {}'.format( address, result)) except Exception as error: self.logger.warn( 'Failed to connect to host node "{}"'.format(address)) self.logger.debug( 'Watcher failed for host node "{}" with {}: {}'.format( address, str(error), error)) host.status = 'failed' raise error finally: # Save the model self.request('storage.save', params={ 'model_type_name': host.__class__.__name__, 'model_json_data': host.to_json(), }) self.logger.info( 'Finished watcher run for host "{}"'.format(address))
def test_update_cluster_members_with_failed_old_host(self): """ Verify that update_cluster_members keeps members regardless of status """ bus = mock.MagicMock() old_hostset = ['192.168.1.1'] new_hostset = old_hostset + ['192.168.1.2'] cluster = Cluster.new(name='test', hostset=old_hostset) hosts = [Host.new(address=new_hostset[1], status=C.HOST_STATUS_ACTIVE)] bus.storage.get_cluster.return_value = cluster bus.storage.get_many.return_value = hosts message = { 'jsonrpc': '2.0', 'id': ID, 'params': { 'name': 'test', 'old': old_hostset, 'new': new_hostset } } with mock.patch('commissaire_http.handlers.clusters.' 'update_new_cluster_member_status') as uncms: result = clusters.update_cluster_members.handler(message, bus) # Check the 1st positional argument. args, kwargs = bus.storage.get_many.call_args list_of_host_models = args[0] self.assertEquals( [x.address for x in list_of_host_models], ['192.168.1.2'])
def test_on_get_with_list(self): """ Verify StorageService.on_get handles list input """ address1 = '192.168.1.1' address2 = '192.168.1.2' type_name = 'Host' json_data = [{'address': address1}, {'address': address2}] message = mock.MagicMock() result = self.service_instance.on_get(message, type_name, json_data) self.assertIsInstance(result, list) self.assertEquals(len(result), 2) self.assertEquals(self.service_instance._manager.get.call_count, 2) self.assertEquals(result[0], Host.new(address=address1).to_dict()) self.assertEquals(result[1], Host.new(address=address2).to_dict())
def test_get_uniform_model_type_with_multiple_types(self): """ Verify get_uniform_model_type raises when types are not the same. """ self.assertRaises( TypeError, storage.get_uniform_model_type, [Host.new(address='127.0.0.1'), Cluster.new(name='test')])
def test_save_many_invalid(self): """ Verify StorageClient.save_many rejects an invalid model """ storage = StorageClient(mock.MagicMock()) storage.bus_mixin.logger = mock.MagicMock() storage.bus_mixin.request.side_effect = ValidationError('test') bad_host = Host.new(**FULL_HOST_DICT) bad_host.address = None self.assertRaises(ValidationError, storage.save_many, [bad_host]) storage.bus_mixin.request.assert_not_called()
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.new(**HOST.to_dict()) different.ssh_priv_key = 'aaa' bus.storage.save.return_value = different self.assertEquals(expected_error(ID, JSONRPC_ERRORS['CONFLICT']), hosts.create_host.handler(SIMPLE_HOST_REQUEST, bus))
def test_on_get_with_dict(self): """ Verify StorageService.on_get handles dictionary input """ address = '127.0.0.1' type_name = 'Host' json_data = {'address': address} message = mock.MagicMock() result = self.service_instance.on_get(message, type_name, json_data) self.assertEquals(self.service_instance._manager.get.call_count, 1) self.assertEquals(result, Host.new(address=address).to_dict())
def test_on_get_with_str(self): """ Verify StorageService.on_get handles string input """ address = '127.0.0.1' type_name = 'Host' json_data = '{{"address": "{}"}}'.format(address) message = mock.MagicMock() result = self.service_instance.on_get(message, type_name, json_data) self.assertEquals(self.service_instance._manager.get.call_count, 1) self.assertEquals(result, Host.new(address=address).to_dict())
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'] = '' # Existing host bus.storage.get_host.return_value = Host.new(**different) # Cluster bus.storage.get_cluster.return_value = Cluster.new(name='test') self.assertEquals(expected_error(ID, JSONRPC_ERRORS['CONFLICT']), hosts.create_host.handler(CLUSTER_HOST_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.storage.get_host.return_value = Host.new( address='127.0.0.1', status=C.HOST_STATUS_DISASSOCIATED) bus.storage.get_cluster.return_value = cluster bus.storage.save.return_value = None expected_response = create_jsonrpc_response(ID, ['127.0.0.1']) with mock.patch('commissaire_http.handlers.clusters.' 'update_new_cluster_member_status') as uncms: actual_response = clusters.add_cluster_member.handler( CHECK_CLUSTER_REQUEST, bus) self.assertEquals(actual_response, expected_response)
def test_add_cluster_member_with_various_status(self): """ Verify that add_cluster_member rejects hosts with bad status """ bus = mock.MagicMock() host = Host.new(address='127.0.0.1') cluster = Cluster.new(name='test') bus.storage.get_host.return_value = host bus.storage.get_cluster.return_value = cluster bus.storage.save.return_value = None host_statuses = [ (C.HOST_STATUS_INVESTIGATING, False), (C.HOST_STATUS_BOOTSTRAPPING, False), (C.HOST_STATUS_ACTIVE, True), (C.HOST_STATUS_DISASSOCIATED, True), (C.HOST_STATUS_FAILED, False) ] for status, expect_to_add in host_statuses: host.status = status cluster.hostset = [] with mock.patch('commissaire_http.handlers.clusters.' 'update_new_cluster_member_status') as uncms: actual_result = clusters.add_cluster_member.handler( CHECK_CLUSTER_REQUEST, bus) if expect_to_add: expected_result = create_jsonrpc_response(ID, ['127.0.0.1']) else: expected_result = expected_error( ID, JSONRPC_ERRORS['METHOD_NOT_ALLOWED']) self.assertEquals(actual_result, expected_result)
def on_investigate(self, message, address, cluster_data={}): """ Initiates an investigation of the requested host. :param message: A message instance :type message: kombu.message.Message :param address: Host address to investigate :type address: str :param cluster_data: Optional data for the associated cluster :type cluster_data: dict """ # Statuses follow: # http://commissaire.readthedocs.org/en/latest/enums.html#host-statuses self.logger.info('{} is now in investigating.'.format(address)) self.logger.debug('Investigating: {}'.format(address)) if cluster_data: self.logger.debug('Related cluster: {}'.format(cluster_data)) host = self.storage.get_host(address) host_creds = self.storage.get(HostCreds.new(address=host.address)) transport = ansibleapi.Transport(host.remote_user) key = TemporarySSHKey(host_creds, self.logger) try: key.create() except Exception as error: self.logger.warn('Unable to continue for {} due to ' '{}: {}. Returning...'.format( address, type(error), error)) raise error try: facts = transport.get_info(address, key.path) # recreate the host instance with new data data = json.loads(host.to_json()) data.update(facts) host = Host.new(**data) host.last_check = formatted_dt() host.status = C.HOST_STATUS_BOOTSTRAPPING self.logger.info('Facts for {} retrieved'.format(address)) self.logger.debug('Data: {}'.format(host.to_json())) except Exception as error: self.logger.warn('Getting info failed for {}: {}'.format( address, str(error))) host.status = C.HOST_STATUS_FAILED key.remove() raise error finally: # Save the updated host model. self.storage.save(host) self.logger.info( 'Finished and stored investigation data for {}'.format(address)) self.logger.debug('Finished investigation update for {}: {}'.format( address, host.to_json())) self.logger.info('{} is now in bootstrapping'.format(address)) oscmd = get_oscmd(host.os) try: etcd_config = self._get_etcd_config() cluster, network = self._get_cluster_and_network_models( cluster_data) container_manager = None if cluster: if cluster.container_manager: container_manager = cluster.container_manager self.logger.info( 'Using cluster "{}" managed by "{}"'.format( cluster.name, container_manager)) else: self.logger.info('Using unmanaged cluster "{}"'.format( cluster.name)) self.logger.info('Using network "{}" of type "{}"'.format( network.name, network.type)) transport.bootstrap(address, key.path, oscmd, etcd_config, network) host.status = C.HOST_STATUS_DISASSOCIATED except Exception as error: self.logger.warn('Unable to start bootstraping for {}: {}'.format( address, str(error))) host.status = C.HOST_STATUS_FAILED key.remove() raise error finally: # Save the updated host model. self.storage.save(host) # Register with container manager (if applicable). try: if container_manager: self.request('container.register_node', container_manager, address) host.status = C.HOST_STATUS_ACTIVE except Exception as error: self.logger.warn( 'Unable to register {} to container manager "{}": {}'.format( address, container_manager, error.args[0])) key.remove() raise error finally: # Save the updated host model. self.storage.save(host) self.logger.info('Finished bootstrapping for {}'.format(address)) self.logger.debug('Finished bootstrapping for {}: {}'.format( address, host.to_json())) # XXX TEMPORARILY DISABLED # WATCHER_QUEUE.put_nowait((host, datetime.datetime.utcnow())) key.remove() return host.to_json()
# You should have received a copy of the GNU General Public License # along with this program. If not, see <http://www.gnu.org/licenses/>. """ Constants for test cases. """ import copy import json from commissaire.models import Host, HostCreds def make_new(instance): """ Returns a new deep copy of an instance. """ return copy.deepcopy(instance) #: Response JSON for a single host HOST_JSON = ('{"address": "10.2.0.2",' ' "status": "available", "os": "fedora",' ' "cpus": 2, "memory": 11989228, "space": 487652,' ' "last_check": "2015-12-17T15:48:18.710454"}') #: Host model for most tests HOST = Host.new(**json.loads(HOST_JSON)) #: HostCreds model for most tests HOST_CREDS = HostCreds.new(address=HOST.address, ssh_priv_key='dGVzdAo=', remote_user='******')
def on_investigate(self, message, address, cluster_data={}): """ Initiates an investigation of the requested host. :param message: A message instance :type message: kombu.message.Message :param address: Host address to investigate :type address: str :param cluster_data: Optional data for the associated cluster :type cluster_data: dict """ # Statuses follow: # http://commissaire.readthedocs.org/en/latest/enums.html#host-statuses self.logger.info('{0} is now in investigating.'.format(address)) self.logger.debug('Investigating: {0}'.format(address)) if cluster_data: self.logger.debug('Related cluster: {0}'.format(cluster_data)) try: params = { 'model_type_name': 'Host', 'model_json_data': Host.new(address=address).to_json(), 'secure': True } response = self.request('storage.get', params=params) host = Host.new(**response['result']) except Exception as error: self.logger.warn( 'Unable to continue for {0} due to ' '{1}: {2}. Returning...'.format(address, type(error), error)) raise error transport = ansibleapi.Transport(host.remote_user) key = TemporarySSHKey(host, self.logger) try: key.create() except Exception as error: self.logger.warn( 'Unable to continue for {0} due to ' '{1}: {2}. Returning...'.format(address, type(error), error)) raise error try: facts = transport.get_info(address, key.path) # recreate the host instance with new data data = json.loads(host.to_json(secure=True)) data.update(facts) host = Host.new(**data) host.last_check = datetime.datetime.utcnow().isoformat() host.status = 'bootstrapping' self.logger.info('Facts for {0} retrieved'.format(address)) self.logger.debug('Data: {0}'.format(host.to_json())) except Exception as error: self.logger.warn('Getting info failed for {0}: {1}'.format( address, str(error))) host.status = 'failed' key.remove() raise error finally: # Save the updated host model. params = { 'model_type_name': host.__class__.__name__, 'model_json_data': host.to_json() } self.request('storage.save', params=params) self.logger.info( 'Finished and stored investigation data for {0}'.format(address)) self.logger.debug( 'Finished investigation update for {0}: {1}'.format( address, host.to_json())) self.logger.info('{0} is now in bootstrapping'.format(address)) oscmd = get_oscmd(host.os) try: etcd_config = self._get_etcd_config() cluster, network = self._get_cluster_and_network_models( cluster_data) self.logger.info( 'Using cluster "{0}" of type "{1}"'.format( cluster.name, cluster.type)) self.logger.info( 'Using network "{0}" of type "{1}"'.format( network.name, network.type)) transport.bootstrap( address, key.path, oscmd, etcd_config, cluster, network) host.status = 'inactive' except Exception as error: self.logger.warn( 'Unable to start bootstraping for {0}: {1}'.format( address, str(error))) host.status = 'disassociated' key.remove() raise error finally: # Save the updated host model. params = { 'model_type_name': host.__class__.__name__, 'model_json_data': host.to_json() } self.request('storage.save', params=params) # Verify association with relevant container managers params = { 'cluster_type': cluster.type, 'address': address } response = self.request('storage.node_registered', params=params) if response['result']: host.status = 'active' self.logger.info( 'Finished bootstrapping for {0}'.format(address)) self.logger.debug('Finished bootstrapping for {0}: {1}'.format( address, host.to_json())) # XXX TEMPORARILY DISABLED # WATCHER_QUEUE.put_nowait((host, datetime.datetime.utcnow())) key.remove() return host.to_json()
# GNU General Public License for more details. # # You should have received a copy of the GNU General Public License # along with this program. If not, see <http://www.gnu.org/licenses/>. """ Constants for test cases. """ import copy import json from commissaire.models import Host def make_new(instance): """ Returns a new deep copy of an instance. """ return copy.deepcopy(instance) # Response JSON for a single host HOST_JSON = ('{"address": "10.2.0.2",' ' "status": "available", "os": "fedora",' ' "cpus": 2, "memory": 11989228, "space": 487652,' ' "last_check": "2015-12-17T15:48:18.710454"}') # Host model for most tests HOST = Host.new(ssh_priv_key='dGVzdAo=', remote_user='******', **json.loads(HOST_JSON))
from unittest import mock from . import TestCase, expected_error from commissaire import bus as _bus from commissaire import constants as C from commissaire.constants import JSONRPC_ERRORS from commissaire_http.handlers import hosts, create_jsonrpc_response, clusters from commissaire.models import (Host, Hosts, HostStatus, Cluster, Clusters, ValidationError) # Globals reused in host tests #: Message ID ID = '123' #: Generic host instance HOST = Host.new(address='127.0.0.1') #: Generic jsonrpc host request by address SIMPLE_HOST_REQUEST = { 'jsonrpc': '2.0', 'id': ID, 'params': HOST.to_dict(), } CLUSTER_HOST_REQUEST = { 'jsonrpc': '2.0', 'id': ID, 'params': { 'address': HOST.address, 'cluster': 'mycluster', }, } #: Generic jsonrpc request with no parameters
import json from unittest import mock from . import TestCase from commissaire.bus import BusMixin, RemoteProcedureCallError from commissaire.models import Host, Hosts, Cluster, ValidationError from commissaire.storage.client import StorageClient #: Message ID ID = '123' #: Minimal host dictionary MINI_HOST_DICT = {'address': '127.0.0.1'} #: Minimal host instance MINI_HOST = Host.new(**MINI_HOST_DICT) #: Full host dictionary FULL_HOST_DICT = { 'address': '127.0.0.1', 'status': 'groovy', 'os': 'amiga', 'cpus': 1, 'memory': 32768, 'space': 0, 'last_check': '', 'source': '' } #: Full host instance FULL_HOST = Host.new(**FULL_HOST_DICT) #: Minimal cluster dictionary