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']))
Example #3
0
 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')]))
Example #4
0
    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'])
Example #6
0
    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())
Example #7
0
 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')])
Example #8
0
 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()
Example #9
0
    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))
Example #10
0
    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())
Example #11
0
    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())
Example #12
0
    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)
Example #15
0
    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()
Example #16
0
# 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='******')
Example #17
0
    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()
Example #18
0
# 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))
Example #19
0
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
Example #20
0
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