Exemple #1
0
    def on_get(self, req, resp):
        """
        Handles GET requests for Hosts.

        :param req: Request instance that will be passed through.
        :type req: falcon.Request
        :param resp: Response instance that will be passed through.
        :type resp: falcon.Response
        """
        try:
            hosts_dir = self.store.get('/commissaire/hosts/')
            self.logger.debug('Etcd Response: {0}'.format(hosts_dir))
        except etcd.EtcdKeyNotFound:
            self.logger.warn(
                'Etcd does not have any hosts. Returning [] and 404.')
            resp.status = falcon.HTTP_404
            req.context['model'] = None
            return
        results = []
        # Don't let an empty host directory through
        if len(hosts_dir._children):
            for host in hosts_dir.leaves:
                results.append(Host(**json.loads(host.value)))
            resp.status = falcon.HTTP_200
            req.context['model'] = Hosts(hosts=results)
        else:
            self.logger.debug(
                'Etcd has a hosts directory but no content.')
            resp.status = falcon.HTTP_200
            req.context['model'] = None
Exemple #2
0
    def _calculate_hosts(self, cluster):
        """
        Calculates the hosts metadata for the cluster.

        :param cluster: The name of the cluster.
        :type cluster: str
        """
        # XXX: Not sure which wil be more efficient: fetch all
        #      the host data in one etcd call and sort through
        #      them, or fetch the ones we need individually.
        #      For the MVP phase, fetch all is better.
        etcd_resp, error = cherrypy.engine.publish('store-get',
                                                   '/commissaire/hosts')[0]

        if error:
            self.logger.warn('Etcd does not have any hosts. '
                             'Cannot determine cluster stats.')
            return

        available = unavailable = total = 0
        for child in etcd_resp._children:
            host = Host(**json.loads(child['value']))
            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
Exemple #3
0
    def test_investigator(self):
        """
        Verify the investigator.
        """
        with mock.patch('commissaire.transport.ansibleapi.Transport') as _tp:

            _tp().get_info.return_value = (
                0,
                {
                    'os': 'fedora',
                    'cpus': 2,
                    'memory': 11989228,
                    'space': 487652,
                }
            )

            _tp().bootstrap.return_value = (0, {})

            request_queue = Queue()
            response_queue = MagicMock(Queue)

            to_investigate = {
                'address': '10.0.0.2',
                'ssh_priv_key': 'dGVzdAo=',
                'remote_user': '******'
            }

            manager = MagicMock(StoreHandlerManager)
            manager.get.return_value = Host(**json.loads(self.etcd_host))

            request_queue.put_nowait((
                manager, to_investigate, Cluster.new().__dict__))
            investigator(request_queue, response_queue, run_once=True)

            # Investigator saves *after* bootstrapping.
            self.assertEquals(0, manager.save.call_count)

            self.assertEquals(1, response_queue.put.call_count)
            host, error = response_queue.put.call_args[0][0]
            self.assertEquals(host.status, 'inactive')
            self.assertIsNone(error)
Exemple #4
0
    def on_get(self, req, resp, address):
        """
        Handles retrieval of an existing Host.

        :param req: Request instance that will be passed through.
        :type req: falcon.Request
        :param resp: Response instance that will be passed through.
        :type resp: falcon.Response
        :param address: The address of the Host being requested.
        :type address: str
        """
        # TODO: Verify input
        try:
            etcd_resp = self.store.get(util.etcd_host_key(address))
            self.logger.debug('Etcd Response: {0}'.format(etcd_resp))
        except etcd.EtcdKeyNotFound:
            resp.status = falcon.HTTP_404
            return

        resp.status = falcon.HTTP_200
        req.context['model'] = Host(**json.loads(etcd_resp.value))
Exemple #5
0
    def on_get(self, req, resp, address):
        """
        Handles retrieval of an existing Host.

        :param req: Request instance that will be passed through.
        :type req: falcon.Request
        :param resp: Response instance that will be passed through.
        :type resp: falcon.Response
        :param address: The address of the Host being requested.
        :type address: str
        """
        # TODO: Verify input
        etcd_resp, error = cherrypy.engine.publish(
            'store-get', util.etcd_host_key(address))[0]
        self.logger.debug('Etcd Response: {0}'.format(etcd_resp))

        if error:
            resp.status = falcon.HTTP_404
            return

        resp.status = falcon.HTTP_200
        req.context['model'] = Host(**json.loads(etcd_resp.value))
Exemple #6
0
    def on_put(self, req, resp, address):
        """
        Handles the creation of a new Host.

        :param req: Request instance that will be passed through.
        :type req: falcon.Request
        :param resp: Response instance that will be passed through.
        :type resp: falcon.Response
        :param address: The address of the Host being requested.
        :type address: str
        """
        try:
            # Extract what we need from the input data.
            # Don't treat it as a skeletal host record.
            req_data = req.stream.read()
            req_body = json.loads(req_data.decode())
            ssh_priv_key = req_body['ssh_priv_key']
            # Cluster member is optional.
            cluster_name = req_body.get('cluster', None)
        except (KeyError, ValueError):
            self.logger.info(
                'Bad client PUT request for host {0}: {1}'.
                format(address, req_data))
            resp.status = falcon.HTTP_400
            return

        key = util.etcd_host_key(address)
        try:
            etcd_resp = self.store.get(key)
            self.logger.debug('Etcd Response: {0}'.format(etcd_resp))

            # Check if the request conflicts with the existing host.
            existing_host = Host(**json.loads(etcd_resp.value))
            if existing_host.ssh_priv_key != ssh_priv_key:
                resp.status = falcon.HTTP_409
                return
            if cluster_name:
                try:
                    assert util.etcd_cluster_has_host(
                        self.store, cluster_name, address)
                except (AssertionError, KeyError):
                    resp.status = falcon.HTTP_409
                    return

            # Request is compatible with the existing host, so
            # we're done.  (Not using HTTP_201 since we didn't
            # actually create anything.)
            resp.status = falcon.HTTP_200
            req.context['model'] = existing_host
            return
        except etcd.EtcdKeyNotFound:
            pass

        host_creation = {
            'address': address,
            'ssh_priv_key': ssh_priv_key,
            'os': '',
            'status': 'investigating',
            'cpus': -1,
            'memory': -1,
            'space': -1,
            'last_check': None
        }

        # Verify the cluster exists, if given.  Do it now
        # so we can fail before writing anything to etcd.
        if cluster_name:
            if not util.etcd_cluster_exists(self.store, cluster_name):
                resp.status = falcon.HTTP_409
                return

        host = Host(**host_creation)
        new_host = self.store.set(key, host.to_json(secure=True))
        INVESTIGATE_QUEUE.put((host_creation, ssh_priv_key))

        # Add host to the requested cluster.
        if cluster_name:
            util.etcd_cluster_add_host(self.store, cluster_name, address)

        resp.status = falcon.HTTP_201
        req.context['model'] = Host(**json.loads(new_host.value))
Exemple #7
0
# Copyright (C) 2016  Red Hat, Inc
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU Affero General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
# GNU Affero General Public License for more details.
#
# You should have received a copy of the GNU Affero General Public License
# along with this program.  If not, see <http://www.gnu.org/licenses/>.
"""
Host(s) handlers.

"""
import falcon
import etcd
import json

from commissaire.queues import INVESTIGATE_QUEUE
from commissaire.resource import Resource
from commissaire.handlers.models import Cluster, Host, Hosts
import commissaire.handlers.util as util


class HostsResource(Resource):
    """
    Resource for working with Hosts.
Exemple #8
0
def etcd_host_create(address, ssh_priv_key, cluster_name=None):
    """
    Creates a new host record in etcd and optionally adds the host to
    the specified cluster.  Returns a (status, host) tuple where status
    is the Falcon HTTP status and host is a Host model instance, which
    may be None if an error occurred.

    This function is idempotent so long as the host parameters agree
    with an existing host record and cluster membership.

    :param address: Host address.
    :type address: str
    :param ssh_priv_key: Host's SSH key, base64-encoded.
    :type ssh_priv_key: str
    :param cluster_name: Name of the cluster to join, or None
    :type cluster_name: str or None
    :return: (status, host)
    :rtype: tuple
    """
    key = etcd_host_key(address)
    etcd_resp, error = cherrypy.engine.publish('store-get', key)[0]

    if not error:
        # Check if the request conflicts with the existing host.
        existing_host = Host(**json.loads(etcd_resp.value))
        if existing_host.ssh_priv_key != ssh_priv_key:
            return (falcon.HTTP_409, None)
        if cluster_name:
            try:
                assert etcd_cluster_has_host(cluster_name, address)
            except (AssertionError, KeyError):
                return (falcon.HTTP_409, None)

        # Request is compatible with the existing host, so
        # we're done.  (Not using HTTP_201 since we didn't
        # actually create anything.)
        return (falcon.HTTP_200, existing_host)

    host_creation = {
        'address': address,
        'ssh_priv_key': ssh_priv_key,
        'os': '',
        'status': 'investigating',
        'cpus': -1,
        'memory': -1,
        'space': -1,
        'last_check': None
    }

    # Verify the cluster exists, if given.  Do it now
    # so we can fail before writing anything to etcd.
    if cluster_name:
        if not etcd_cluster_exists(cluster_name):
            return (falcon.HTTP_409, None)

    host = Host(**host_creation)

    new_host, _ = cherrypy.engine.publish(
        'store-save', key, host.to_json(secure=True))[0]

    # Add host to the requested cluster.
    if cluster_name:
        etcd_cluster_add_host(cluster_name, address)

    INVESTIGATE_QUEUE.put((host_creation, ssh_priv_key))

    return (falcon.HTTP_201, Host(**json.loads(new_host.value)))