def on_put(self, req, resp, name): """ Handles the creation of a new Cluster. :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 name: The name of the Cluster being created. :type name: str """ # PUT is idempotent, and since there's no body to this request, # there's nothing to conflict with. The request should always # succeed, even if we didn't actually do anything. try: store_manager = cherrypy.engine.publish('get-store-manager')[0] cluster = store_manager.get(Cluster.new(name=name)) self.logger.info( 'Creation of already exisiting cluster {0} requested.'.format( name)) except: pass args = {} data = req.stream.read() if data: try: args = json.loads(data.decode()) self.logger.debug('Cluster args received: "{0}"'.format(args)) except ValueError as error: self.logger.error( 'Unable to parse cluster arguments: {0}'.format(error)) try: self.logger.debug('Looking for network {0}'.format( args['network'])) network = store_manager.get( Network.new(name=args['network'])) except KeyError: network = Network.new(**C.DEFAULT_CLUSTER_NETWORK_JSON) cluster = Cluster.new( name=name, type=args.get('type', C.CLUSTER_TYPE_DEFAULT), network=network.name, status='ok', hostset=[]) self.logger.debug('Cluster to create: {0}'.format( cluster.to_json_with_hosts())) store_manager.save(cluster) self.logger.info( 'Created cluster {0} per request.'.format(name)) self.logger.debug('New Cluster: {0}'.format( cluster.to_json_with_hosts())) resp.status = falcon.HTTP_201
def on_put(self, req, resp, name): """ Handles the creation of a new Network. :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 name: The friendly name of the network. :type address: str """ try: req_data = req.stream.read() req_body = json.loads(req_data.decode()) network_type = req_body['type'] options = req_body.get('options', {}) except (KeyError, ValueError): self.logger.info( 'Bad client PUT request for network {0}: {1}'.format( name, req_data)) resp.status = falcon.HTTP_400 return store_manager = cherrypy.engine.publish('get-store-manager')[0] # If the type is flannel_etcd yet we have not etcd backend configured # don't create and notify the caller if network_type == C.NETWORK_TYPE_FLANNEL_ETCD: backend_found = False for handler_type, _, _ in store_manager.list_store_handlers(): if handler_type is EtcdStoreHandler: backend_found = True break if not backend_found: self.logger.info( 'Network {0} can not be created as type flannel_etcd ' 'as no etcd backend is configured.'.format(name)) resp.status = falcon.HTTP_CONFLICT return network = Network.new(name=name, type=network_type, options=options) self.logger.debug('Saving network: {0}'.format(network.to_json())) store_manager.save(network) resp.status = falcon.HTTP_CREATED req.context['model'] = network
def on_get(self, req, resp, name): """ Handles retrieval of an existing Network. :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 name: The friendly name of the network. :type address: str """ try: store_manager = cherrypy.engine.publish('get-store-manager')[0] network = store_manager.get(Network.new(name=name)) resp.status = falcon.HTTP_200 req.context['model'] = network except: resp.status = falcon.HTTP_404 return
def on_delete(self, req, resp, name): """ Handles the Deletion of a Network. :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 name: The friendly name of the network. :type address: str """ resp.body = '{}' store_manager = cherrypy.engine.publish('get-store-manager')[0] try: store_manager.delete(Network.new(name=name)) resp.status = falcon.HTTP_200 except Exception as error: self.logger.warn('{}: {}'.format(type(error), error)) resp.status = falcon.HTTP_404
def test_bootstrap(self): """ Verify Transport().bootstrap works as expected. """ with patch( 'commissaire.transport.ansibleapi.TaskQueueManager') as _tqm: _tqm().run.return_value = 0 transport = ansibleapi.Transport() transport.variable_manager._fact_cache = {} oscmd = MagicMock(OSCmdBase) result, facts = transport.bootstrap('10.2.0.2', Cluster.new().__dict__, 'test/fake_key', MagicMock(), oscmd) # We should have a successful response self.assertEquals(0, result) # We should see expected calls self.assertEquals(1, oscmd.install_docker.call_count) self.assertEquals(1, oscmd.install_kube.call_count) # Check user-config to playbook-variable translation. etcd_config = { 'server_url': 'https://192.168.1.1:1234', 'certificate_ca_path': '/path/to/etcd/ca/cert', 'certificate_path': '/path/to/etcd/client/cert', 'certificate_key_path': '/path/to/etcd/client/key' } kube_config = { 'server_url': 'https://192.168.2.2:4567', 'certificate_path': '/path/to/kube/client/cert', 'certificate_key_path': '/path/to/kube/client/key' } store_manager = MagicMock(StoreHandlerManager) store_manager.list_store_handlers.return_value = [ (EtcdStoreHandler, etcd_config, ()), (KubernetesStoreHandler, kube_config, ()) ] store_manager.get.return_value = Network.new(name='default', type='flannel_etcd') cluster_data = Cluster.new(name='default', network='default').__dict__ transport = ansibleapi.Transport() transport._run = MagicMock() transport._run.return_value = (0, {}) result, facts = transport.bootstrap('10.2.0.2', cluster_data, 'test/fake_key', store_manager, oscmd) play_vars = transport._run.call_args[0][4] self.assertEqual(play_vars['commissaire_etcd_server_url'], 'https://192.168.1.1:1234') self.assertEqual(play_vars['commissaire_etcd_ca_path_local'], '/path/to/etcd/ca/cert') self.assertEqual( play_vars['commissaire_etcd_client_cert_path_local'], '/path/to/etcd/client/cert') self.assertEqual( play_vars['commissaire_etcd_client_key_path_local'], '/path/to/etcd/client/key') # Check 'commissaire_enable_pkg_repos' playbook variable # for various operating systems. transport = ansibleapi.Transport() transport._run = MagicMock() transport._run.return_value = (0, {}) needs_enable_repos = ('redhat', 'rhel') for os_type in available_os_types: oscmd = get_oscmd(os_type) result, facts = transport.bootstrap('10.2.0.2.', cluster_data, 'test/fake_key', MagicMock(), oscmd) play_vars = transport._run.call_args[0][4] command = play_vars['commissaire_enable_pkg_repos'] if os_type in needs_enable_repos: self.assertIn('subscription-manager repos', command) else: self.assertEqual('true', command) # no-op command
def bootstrap(self, ip, cluster_data, key_file, store_manager, oscmd): """ Bootstraps a host via ansible. :param ip: IP address to bootstrap. :type ip: str :param cluster_data: The data required to create a Cluster instance. :type cluster_data: dict or None :param key_file: Full path to the file holding the private SSH key. :type key_file: str :param store_manager: Remote object for remote stores :type store_manager: commissaire.store.storehandlermanager. StoreHandlerManager :param oscmd: OSCmd class to use :type oscmd: commissaire.oscmd.OSCmdBase :returns: tuple -- (exitcode(int), facts(dict)). """ self.logger.debug('Using {0} as the oscmd class for {1}'.format( oscmd.os_type, ip)) # cluster_data can be None. If it is change it to an empty dict if cluster_data is None: cluster_data = {} cluster_type = C.CLUSTER_TYPE_HOST network = Network.new(**C.DEFAULT_CLUSTER_NETWORK_JSON) try: self.logger.debug( 'Grabbing cluster type from {0}'.format(cluster_data)) cluster = Cluster.new(**cluster_data) cluster_type = cluster.type self.logger.debug('Found network {0}'.format(cluster.network)) network = store_manager.get(Network.new(name=cluster.network)) except KeyError: # Not part of a cluster pass etcd_config = self._get_etcd_config(store_manager) kube_config = self._get_kube_config(store_manager) play_vars = { 'commissaire_cluster_type': cluster_type, 'commissaire_bootstrap_ip': ip, 'commissaire_kubernetes_api_server_url': kube_config['server_url'], 'commissaire_kubernetes_bearer_token': kube_config['token'], # TODO: Where do we get this? 'commissaire_docker_registry_host': '127.0.0.1', # TODO: Where do we get this? 'commissaire_docker_registry_port': 8080, # TODO: Where do we get this? 'commissaire_flannel_key': '/atomic01/network', 'commissaire_docker_config_local': resource_filename('commissaire', 'data/templates/docker'), 'commissaire_flanneld_config_local': resource_filename('commissaire', 'data/templates/flanneld'), 'commissaire_kubelet_config_local': resource_filename('commissaire', 'data/templates/kubelet'), 'commissaire_kubernetes_config_local': resource_filename('commissaire', 'data/templates/kube_config'), 'commissaire_kubeconfig_config_local': resource_filename('commissaire', 'data/templates/kubeconfig'), 'commissaire_install_libselinux_python': " ".join(oscmd.install_libselinux_python()), 'commissaire_docker_config': oscmd.docker_config, 'commissaire_flanneld_config': oscmd.flanneld_config, 'commissaire_kubelet_config': oscmd.kubelet_config, 'commissaire_kubernetes_config': oscmd.kubernetes_config, 'commissaire_kubeconfig_config': oscmd.kubernetes_kubeconfig, 'commissaire_install_flannel': " ".join(oscmd.install_flannel()), 'commissaire_install_docker': " ".join(oscmd.install_docker()), 'commissaire_install_kube': " ".join(oscmd.install_kube()), 'commissaire_flannel_service': oscmd.flannel_service, 'commissaire_docker_service': oscmd.flannel_service, 'commissaire_kubelet_service': oscmd.kubelet_service, 'commissaire_kubeproxy_service': oscmd.kubelet_proxy_service, } # If we are a flannel_server network then set the var if network.type == 'flannel_server': play_vars['commissaire_flanneld_server'] = network.options.get( 'address') elif network.type == 'flannel_etcd': play_vars['commissaire_etcd_server_url'] = etcd_config[ 'server_url'] # Provide the CA if etcd is being used over https if (etcd_config['server_url'].startswith('https:') and 'certificate_ca_path' in etcd_config): play_vars['commissaire_etcd_ca_path'] = oscmd.etcd_ca play_vars['commissaire_etcd_ca_path_local'] = ( etcd_config['certificate_ca_path']) # Client Certificate additions if 'certificate_path' in etcd_config: self.logger.info('Using etcd client certs') play_vars['commissaire_etcd_client_cert_path'] = ( oscmd.etcd_client_cert) play_vars['commissaire_etcd_client_cert_path_local'] = ( etcd_config['certificate_path']) play_vars['commissaire_etcd_client_key_path'] = ( oscmd.etcd_client_key) play_vars['commissaire_etcd_client_key_path_local'] = ( etcd_config['certificate_key_path']) if 'certificate_path' in kube_config: self.logger.info('Using kubernetes client certs') play_vars['commissaire_kubernetes_client_cert_path'] = ( oscmd.kube_client_cert) play_vars['commissaire_kubernetes_client_cert_path_local'] = ( kube_config['certificate_path']) play_vars['commissaire_kubernetes_client_key_path'] = ( oscmd.kube_client_key) play_vars['commissaire_kubernetes_client_key_path_local'] = ( kube_config['certificate_key_path']) # XXX: Need to enable some package repositories for OS 'rhel' # (or 'redhat'). This is a hack for a single corner case. # We discussed how to generalize future cases where we need # extra commands for a specific OS but decided to defer until # more crop up. # # See https://github.com/projectatomic/commissaire/pull/56 # if oscmd.os_type in ('rhel', 'redhat'): play_vars['commissaire_enable_pkg_repos'] = ( 'subscription-manager repos ' '--enable=rhel-7-server-extras-rpms ' '--enable=rhel-7-server-optional-rpms') else: play_vars['commissaire_enable_pkg_repos'] = 'true' self.logger.debug('Variables for bootstrap: {0}'.format(play_vars)) play_file = resource_filename('commissaire', 'data/ansible/playbooks/bootstrap.yaml') results = self._run(ip, key_file, play_file, [0], play_vars) return results
# 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 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 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/>. """ Network(s) handlers. """ import json import cherrypy import falcon from commissaire import constants as C from commissaire.resource import Resource from commissaire.handlers.models import Network, Networks from commissaire.store.etcdstorehandler import EtcdStoreHandler class NetworksResource(Resource): """