Skip to content
This repository has been archived by the owner on Feb 2, 2018. It is now read-only.

Commit

Permalink
ContainerManagerService updates. (#66)
Browse files Browse the repository at this point in the history
* storage: StorageHandlers no longer handle container work.

* containermgr: Various clean up items.

* containermgr: Added check_config to KubeContainerManager.

KubeContainerManager now has a check_config class method which is used
via initialization. This change also introduces tests for
KubeContainerManager.

* containermgr: ContainerHandlers now use friendly class names.
  • Loading branch information
ashcrow authored and mbarnes committed Dec 6, 2016
1 parent bf72644 commit 9b3cf0f
Show file tree
Hide file tree
Showing 5 changed files with 150 additions and 25 deletions.
4 changes: 0 additions & 4 deletions src/commissaire/containermgr/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -18,16 +18,12 @@

import logging

from commissaire import constants as C


class ContainerManagerBase(object): # pragma: no cover
"""
Base class for all container managers.
"""

cluster_type = C.CLUSTER_TYPE_HOST

def __init__(self, config):
"""
Creates a new instance of the ContainerManagerBase.
Expand Down
66 changes: 48 additions & 18 deletions src/commissaire/containermgr/kubernetes/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -18,19 +18,17 @@

import requests

from urllib.parse import urljoin
from urllib.parse import urljoin, urlparse

from commissaire import constants as C
from commissaire.containermgr import ContainerManagerBase
from commissaire.util.config import ConfigurationError


class ContainerManager(ContainerManagerBase):
class KubeContainerManager(ContainerManagerBase):
"""
Kubernetes container manager implementation.
"""

cluster_type = C.CLUSTER_TYPE_KUBERNETES

def __init__(self, config):
"""
Creates an instance of the Kubernetes Container Manager.
Expand All @@ -39,29 +37,61 @@ def __init__(self, config):
:type config: dict
"""
ContainerManagerBase.__init__(self, config)
self.__class__.check_config(config)
self.con = requests.Session()
token = config.get('token', None)
if token:
self.con.headers["Authorization"] = "Bearer {0}".format(token)
self.con.headers['Authorization'] = 'Bearer {}'.format(token)
self.logger.info('Using bearer token')
self.logger.debug('Bearer token: {0}'.format(token))
self.logger.debug('Bearer token: {}'.format(token))

certificate_path = config.get('certificate_path')
certificate_key_path = config.get('certificate_key_path')
if certificate_path and certificate_key_path:
self.con.cert = (certificate_path, certificate_key_path)
self.logger.info(
'Using client side certificate. Certificate path: {0} '
'Certificate Key Path: {1}'.format(
'Using client side certificate. Certificate path: {} '
'Certificate Key Path: {}'.format(
certificate_path, certificate_key_path))

# TODO: Verify TLS!!!
self.con.verify = False
self.base_uri = urljoin(config['server_url'], '/api/v1')
self.logger.info('Kubernetes Container Manager created: {0}'.format(
self.logger.info('Kubernetes Container Manager created: {}'.format(
self.base_uri))
self.logger.debug(
'Kubernetes Container Manager: {0}'.format(self.__dict__))
'Kubernetes Container Manager: {}'.format(self.__dict__))

@classmethod
def check_config(cls, config):
"""
Examines the configuration parameters for an ContainerManager
and throws a ConfigurationError if any parameters are invalid.
:param cls: ContainerManager class.
:type cls: class
:param config: Configuration dictionary to check.
:type config: dict
:returns: True if configuration is valid
:rtype: bool
:raises: commissaire.util.config.ConfigurationError
"""
try:
url = urlparse(config['server_url'])
except KeyError:
raise ConfigurationError(
'server_url is a required configuration item')

if (bool(config.get('certificate_path')) ^
bool(config.get('certificate_key_path'))):
raise ConfigurationError(
'Both "certificate_path" and "certificate_key_path" '
'must be provided to use a client side certificate')
if config.get('certificate_path'):
if url.scheme != 'https':
raise ConfigurationError(
'Server URL scheme must be "https" when using client '
'side certificates (got "{}")'.format(url.scheme))

def _get(self, part, *args, **kwargs):
"""
Expand All @@ -79,12 +109,12 @@ def _get(self, part, *args, **kwargs):
if not part.startswith('/'):
self.logger.debug(
'Part given without starting slash. Adding...')
part = '/{0}'.format(part)
part = '/{}'.format(part)

self.logger.debug('Executing GET for {0}'.format(part))
self.logger.debug('Executing GET for {}'.format(part))
resp = self.con.get(
'{0}{1}'.format(self.base_uri, part), *args, **kwargs)
self.logger.debug('Response for {0}. Status: {1}'.format(
'{}{}'.format(self.base_uri, part), *args, **kwargs)
self.logger.debug('Response for {}. Status: {}'.format(
part, resp.status_code))
return resp

Expand Down Expand Up @@ -115,13 +145,13 @@ def get_host_status(self, address, raw=False):
:returns: The response back from kubernetes.
:rtype: requests.Response
"""
part = '/nodes/{0}'.format(address)
part = '/nodes/{}'.format(address)
resp = self._get(part)
data = resp.json()
if raw:
data = data['status']
return (resp.status_code, data)


#: Friendly name for the class
KubeContainerManager = ContainerManager
#: Common name for the class
ContainerHandler = KubeContainerManager
3 changes: 0 additions & 3 deletions src/commissaire/storage/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -31,9 +31,6 @@ class StoreHandlerBase(object):
Base class for all StoreHandler classes.
"""

#: Subclasses override this, if applicable.
container_manager_class = None

@classmethod
def check_config(cls, config):
"""
Expand Down
2 changes: 2 additions & 0 deletions test/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,8 @@
import os
import unittest

from unittest import mock

from commissaire.models import Model

# Keep this list synchronized with oscmd modules.
Expand Down
100 changes: 100 additions & 0 deletions test/test_containermgr_kubernetes.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,100 @@
# 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/>.
"""
Tests for the commissaire.containermgr.kubernetes module.
"""

import json

from unittest import mock

from . import TestCase, mock

from commissaire.containermgr import kubernetes
from commissaire.util.config import ConfigurationError


CONTAINER_MGR_CONFIG = {
'server_url': 'http://127.0.0.1:8080/'
}


class TestKubeContainerManager(TestCase):
"""
Tests for the commissaire.containermgr.kubernetes module.
"""

def setUp(self):
"""
Set up an instance for every test.
"""
self.instance = kubernetes.KubeContainerManager(CONTAINER_MGR_CONFIG)

def test_initialization(self):
"""
Verify using new on a model creates a default instance.
"""

self.assertEquals('http://127.0.0.1:8080/api/v1', self.instance.base_uri)
self.assertTrue(self.instance.con)

def test_initialization_with_bad_config(self):
"""
Verify using new on a model creates a default instance.
"""
for config in (
{},
{'server_url': 'http://127.0.0.1:8080/', 'certificate_path': '/tmp/'},
{'server_url': 'http://127.0.0.1:8080/', 'certificate_path': '/tmp/',
'certificate_key_path': '/tmp/'}):
self.assertRaises(
ConfigurationError,
kubernetes.KubeContainerManager,
config)

def test__get(self):
"""
Verify _get makes proper HTTP requests.
"""
self.instance.con = mock.MagicMock()
self.instance._get('test')
self.instance.con.get.assert_called_once_with(
CONTAINER_MGR_CONFIG['server_url'] + 'api/v1/test')

def test_node_registered(self):
"""
Verify node_registered makes the proper remote call and returns the proper result.
"""
for code, result in ((200, True), (404, False)):
self.instance.con = mock.MagicMock()
self.instance.con.get.return_value = mock.MagicMock(status_code=code)
self.assertEquals(result, self.instance.node_registered('test'))
self.instance.con.get.assert_called_once_with(
CONTAINER_MGR_CONFIG['server_url'] + 'api/v1/nodes/test')

def test_get_host_status(self):
"""
Verify get_host_status makes the proper remote call.
"""
data = {'data': 'data', 'status': 'status'}
for raw, result in ((True, 'status'), (False, data)):
self.instance.con = mock.MagicMock()
resp = mock.MagicMock(json=mock.MagicMock(return_value=data))
self.instance.con.get.return_value = resp
self.instance.con.get().status_code = 200
self.assertEquals((200, result), self.instance.get_host_status('test', raw))
self.instance.con.get.assert_called_with(
CONTAINER_MGR_CONFIG['server_url'] + 'api/v1/nodes/test')

0 comments on commit 9b3cf0f

Please sign in to comment.