Merge "Add new Kubernetes resource kind: "Network""
authorRodolfo Alonso Hernandez <rodolfo.alonso.hernandez@intel.com>
Fri, 6 Jul 2018 09:00:20 +0000 (09:00 +0000)
committerGerrit Code Review <gerrit@opnfv.org>
Fri, 6 Jul 2018 09:00:20 +0000 (09:00 +0000)
yardstick/benchmark/contexts/kubernetes.py
yardstick/common/exceptions.py
yardstick/common/kubernetes_utils.py
yardstick/orchestrator/kubernetes.py
yardstick/tests/unit/common/test_kubernetes_utils.py
yardstick/tests/unit/orchestrator/test_kubernetes.py

index 82d7915..4ba9eee 100644 (file)
@@ -48,6 +48,7 @@ class KubernetesContext(Context):
         self._set_ssh_key()
 
         self._create_crd()
+        self._create_networks()
         LOG.info('Launch containers')
         self._create_rcs()
         self._create_services()
@@ -61,6 +62,7 @@ class KubernetesContext(Context):
         self._delete_rcs()
         self._delete_pods()
         self._delete_services()
+        self._delete_networks()
         self._delete_crd()
 
         super(KubernetesContext, self).undeploy()
@@ -118,6 +120,16 @@ class KubernetesContext(Context):
         for crd in self.template.crd:
             crd.delete()
 
+    def _create_networks(self):  # pragma: no cover
+        LOG.info('Create Network elements')
+        for net in self.template.network_objs:
+            net.create()
+
+    def _delete_networks(self):  # pragma: no cover
+        LOG.info('Create Network elements')
+        for net in self.template.network_objs:
+            net.delete()
+
     def _get_key_path(self):
         task_id = self.name.split('-')[-1]
         k = 'files/yardstick_key-{}'.format(task_id)
index 981e6f9..2d160be 100644 (file)
@@ -219,6 +219,15 @@ class KubernetesCRDObjectDefinitionError(YardstickException):
                'parameters: %(missing_parameters)s')
 
 
+class KubernetesNetworkObjectDefinitionError(YardstickException):
+    message = ('Kubernetes Network object definition error, missing '
+               'parameters: %(missing_parameters)s')
+
+
+class KubernetesNetworkObjectKindMissing(YardstickException):
+    message = 'Kubernetes kind "Network" is not defined'
+
+
 class ScenarioCreateNetworkError(YardstickException):
     message = 'Create Neutron Network Scenario failed'
 
index a472b6d..42267fc 100644 (file)
@@ -36,6 +36,14 @@ def get_extensions_v1beta_api():
     return client.ApiextensionsV1beta1Api()
 
 
+def get_custom_objects_api():
+    try:
+        config.load_kube_config(config_file=consts.K8S_CONF_FILE)
+    except IOError:
+        raise exceptions.KubernetesConfigFileNotFound()
+    return client.CustomObjectsApi()
+
+
 def get_node_list(**kwargs):        # pragma: no cover
     core_v1_api = get_core_api()
     try:
@@ -220,6 +228,45 @@ def delete_custom_resource_definition(name):
             action='delete', resource='CustomResourceDefinition')
 
 
+def get_custom_resource_definition(kind):
+    api = get_extensions_v1beta_api()
+    try:
+        crd_list = api.list_custom_resource_definition()
+        for crd_obj in (crd_obj for crd_obj in crd_list.items
+                        if crd_obj.spec.names.kind == kind):
+            return crd_obj
+        return None
+    except ApiException:
+        raise exceptions.KubernetesApiException(
+            action='delete', resource='CustomResourceDefinition')
+
+
+def create_network(scope, group, version, plural, body, namespace='default'):
+    api = get_custom_objects_api()
+    try:
+        if scope == consts.SCOPE_CLUSTER:
+            api.create_cluster_custom_object(group, version, plural, body)
+        else:
+            api.create_namespaced_custom_object(
+                group, version, namespace, plural, body)
+    except ApiException:
+        raise exceptions.KubernetesApiException(
+            action='create', resource='Custom Object: Network')
+
+
+def delete_network(scope, group, version, plural, name, namespace='default'):
+    api = get_custom_objects_api()
+    try:
+        if scope == consts.SCOPE_CLUSTER:
+            api.delete_cluster_custom_object(group, version, plural, name, {})
+        else:
+            api.delete_namespaced_custom_object(
+                group, version, namespace, plural, name, {})
+    except ApiException:
+        raise exceptions.KubernetesApiException(
+            action='delete', resource='Custom Object: Network')
+
+
 def get_pod_list(namespace='default'):      # pragma: no cover
     core_v1_api = get_core_api()
     try:
index 637abd8..44a333e 100644 (file)
@@ -91,9 +91,7 @@ class KubernetesObject(object):
                 "replicas": 1,
                 "template": {
                     "metadata": {
-                        "labels": {
-                            "app": name
-                        }
+                        "labels": {"app": name}
                     },
                     "spec": {
                         "containers": [],
@@ -237,6 +235,98 @@ class CustomResourceDefinitionObject(object):
         k8s_utils.delete_custom_resource_definition(self._name)
 
 
+class NetworkObject(object):
+
+    MANDATORY_PARAMETERS = {'name', 'plugin', 'args'}
+    KIND = 'Network'
+
+    def __init__(self, **kwargs):
+        if not self.MANDATORY_PARAMETERS.issubset(kwargs):
+            missing_parameters = ', '.join(
+                str(param) for param in
+                (self.MANDATORY_PARAMETERS - set(kwargs)))
+            raise exceptions.KubernetesNetworkObjectDefinitionError(
+                missing_parameters=missing_parameters)
+
+        self._name = kwargs['name']
+        self._plugin = kwargs['plugin']
+        self._args = kwargs['args']
+        self._crd = None
+        self._template = None
+        self._group = None
+        self._version = None
+        self._plural = None
+        self._scope = None
+
+    @property
+    def crd(self):
+        if self._crd:
+            return self._crd
+        crd = k8s_utils.get_custom_resource_definition(self.KIND)
+        if not crd:
+            raise exceptions.KubernetesNetworkObjectKindMissing()
+        self._crd = crd
+        return self._crd
+
+    @property
+    def group(self):
+        if self._group:
+            return self._group
+        self._group = self.crd.spec.group
+        return self._group
+
+    @property
+    def version(self):
+        if self._version:
+            return self._version
+        self._version = self.crd.spec.version
+        return self._version
+
+    @property
+    def plural(self):
+        if self._plural:
+            return self._plural
+        self._plural = self.crd.spec.names.plural
+        return self._plural
+
+    @property
+    def scope(self):
+        if self._scope:
+            return self._scope
+        self._scope = self.crd.spec.scope
+        return self._scope
+
+    @property
+    def template(self):
+        """"Network" object template
+
+        This template can be rendered only once the CRD "Network" is created in
+        Kubernetes. This function call must be delayed until the creation of
+        the CRD "Network".
+        """
+        if self._template:
+            return self._template
+
+        self._template = {
+            'apiVersion': '{}/{}'.format(self.group, self.version),
+            'kind': self.KIND,
+            'metadata': {
+                'name': self._name
+            },
+            'plugin': self._plugin,
+            'args': self._args
+        }
+        return self._template
+
+    def create(self):
+        k8s_utils.create_network(self.scope, self.group, self.version,
+                                 self.plural, self.template)
+
+    def delete(self):
+        k8s_utils.delete_network(self.scope, self.group, self.version,
+                                 self.plural, self._name)
+
+
 class KubernetesTemplate(object):
 
     def __init__(self, name, context_cfg):
@@ -248,6 +338,7 @@ class KubernetesTemplate(object):
         context_cfg = copy.deepcopy(context_cfg)
         servers_cfg = context_cfg.pop('servers', {})
         crd_cfg = context_cfg.pop('custom_resources', [])
+        networks_cfg = context_cfg.pop('networks', [])
         self.name = name
         self.ssh_key = '{}-key'.format(name)
 
@@ -259,7 +350,7 @@ class KubernetesTemplate(object):
         self.service_objs = [ServiceObject(s) for s in self.rcs]
         self.crd = [CustomResourceDefinitionObject(self.name, **crd)
                     for crd in crd_cfg]
-
+        self.network_objs = [NetworkObject(**nobj) for nobj in networks_cfg]
         self.pods = []
 
     def _get_rc_name(self, rc_name):
index e264fd9..bf9992b 100644 (file)
@@ -40,6 +40,23 @@ class GetExtensionsV1betaApiTestCase(base.BaseUnitTestCase):
             kubernetes_utils.get_extensions_v1beta_api()
 
 
+class GetCustomObjectsApiTestCase(base.BaseUnitTestCase):
+
+    @mock.patch.object(client, 'CustomObjectsApi', return_value='api')
+    @mock.patch.object(config, 'load_kube_config')
+    def test_execute_correct(self, mock_load_kube_config, mock_api):
+        self.assertEqual('api', kubernetes_utils.get_custom_objects_api())
+        mock_load_kube_config.assert_called_once_with(
+            config_file=constants.K8S_CONF_FILE)
+        mock_api.assert_called_once()
+
+    @mock.patch.object(config, 'load_kube_config')
+    def test_execute_exception(self, mock_load_kube_config):
+        mock_load_kube_config.side_effect = IOError
+        with self.assertRaises(exceptions.KubernetesConfigFileNotFound):
+            kubernetes_utils.get_custom_objects_api()
+
+
 class CreateCustomResourceDefinitionTestCase(base.BaseUnitTestCase):
 
     @mock.patch.object(client, 'V1beta1CustomResourceDefinition',
@@ -103,3 +120,105 @@ class DeleteCustomResourceDefinitionTestCase(base.BaseUnitTestCase):
         mock_delobj.assert_called_once()
         mock_delete_crd.delete_custom_resource_definition.\
             assert_called_once_with('name', 'del_obj')
+
+
+class GetCustomResourceDefinitionTestCase(base.BaseUnitTestCase):
+
+    @mock.patch.object(kubernetes_utils, 'get_extensions_v1beta_api')
+    def test_execute_value(self, mock_get_api):
+        crd_obj = mock.Mock()
+        crd_obj.spec.names.kind = 'some_kind'
+        crd_list = mock.Mock()
+        crd_list.items = [crd_obj]
+        mock_api = mock.Mock()
+        mock_api.list_custom_resource_definition.return_value = crd_list
+        mock_get_api.return_value = mock_api
+        self.assertEqual(
+            crd_obj,
+            kubernetes_utils.get_custom_resource_definition('some_kind'))
+
+    @mock.patch.object(kubernetes_utils, 'get_extensions_v1beta_api')
+    def test_execute_none(self, mock_get_api):
+        crd_obj = mock.Mock()
+        crd_obj.spec.names.kind = 'some_kind'
+        crd_list = mock.Mock()
+        crd_list.items = [crd_obj]
+        mock_api = mock.Mock()
+        mock_api.list_custom_resource_definition.return_value = crd_list
+        mock_get_api.return_value = mock_api
+        self.assertIsNone(
+            kubernetes_utils.get_custom_resource_definition('other_kind'))
+
+    @mock.patch.object(kubernetes_utils, 'get_extensions_v1beta_api')
+    def test_execute_exception(self, mock_get_api):
+        mock_api = mock.Mock()
+        mock_api.list_custom_resource_definition.\
+            side_effect = rest.ApiException
+        mock_get_api.return_value = mock_api
+        with self.assertRaises(exceptions.KubernetesApiException):
+            kubernetes_utils.get_custom_resource_definition('kind')
+
+
+class CreateNetworkTestCase(base.BaseUnitTestCase):
+    @mock.patch.object(kubernetes_utils, 'get_custom_objects_api')
+    def test_execute_correct(self, mock_get_api):
+        mock_api = mock.Mock()
+        mock_get_api.return_value = mock_api
+        group = 'group.com'
+        version = mock.Mock()
+        plural = 'networks'
+        body = mock.Mock()
+
+        kubernetes_utils.create_network(
+            constants.SCOPE_CLUSTER, group, version, plural, body)
+        mock_api.create_cluster_custom_object.assert_called_once_with(
+            group, version, plural, body)
+
+        mock_api.reset_mock()
+        kubernetes_utils.create_network(
+            constants.SCOPE_NAMESPACED, group, version, plural, body)
+        mock_api.create_namespaced_custom_object.assert_called_once_with(
+            group, version, 'default', plural, body)
+
+
+    @mock.patch.object(kubernetes_utils, 'get_custom_objects_api')
+    def test_execute_exception(self, mock_get_api):
+        mock_api = mock.Mock()
+        mock_api.create_cluster_custom_object.side_effect = rest.ApiException
+        mock_get_api.return_value = mock_api
+        with self.assertRaises(exceptions.KubernetesApiException):
+            kubernetes_utils.create_network(
+                constants.SCOPE_CLUSTER, mock.ANY, mock.ANY, mock.ANY,
+                mock.ANY)
+
+
+class DeleteNetworkTestCase(base.BaseUnitTestCase):
+    @mock.patch.object(kubernetes_utils, 'get_custom_objects_api')
+    def test_execute_correct(self, mock_get_api):
+        mock_api = mock.Mock()
+        mock_get_api.return_value = mock_api
+        group = 'group.com'
+        version = mock.Mock()
+        plural = 'networks'
+        name = 'network'
+
+        kubernetes_utils.delete_network(
+            constants.SCOPE_CLUSTER, group, version, plural, name)
+        mock_api.delete_cluster_custom_object.assert_called_once_with(
+            group, version, plural, name, {})
+
+        mock_api.reset_mock()
+        kubernetes_utils.delete_network(
+            constants.SCOPE_NAMESPACED, group, version, plural, name)
+        mock_api.delete_namespaced_custom_object.assert_called_once_with(
+            group, version, 'default', plural, name, {})
+
+    @mock.patch.object(kubernetes_utils, 'get_custom_objects_api')
+    def test_execute_exception(self, mock_get_api):
+        mock_api = mock.Mock()
+        mock_api.delete_cluster_custom_object.side_effect = rest.ApiException
+        mock_get_api.return_value = mock_api
+        with self.assertRaises(exceptions.KubernetesApiException):
+            kubernetes_utils.delete_network(
+                constants.SCOPE_CLUSTER, mock.ANY, mock.ANY, mock.ANY,
+                mock.ANY)
index 50c6b27..e45545d 100644 (file)
@@ -297,3 +297,91 @@ class CustomResourceDefinitionObjectTestCase(base.BaseUnitTestCase):
         with self.assertRaises(exceptions.KubernetesCRDObjectDefinitionError):
             kubernetes.CustomResourceDefinitionObject('ctx_name',
                                                       noname='name')
+
+
+class NetworkObjectTestCase(base.BaseUnitTestCase):
+
+    def setUp(self):
+        self.net_obj = kubernetes.NetworkObject(name='fake_name',
+                                                plugin='fake_plugin',
+                                                args='fake_args')
+
+    def test__init_missing_parameter(self):
+        with self.assertRaises(
+                exceptions.KubernetesNetworkObjectDefinitionError):
+            kubernetes.NetworkObject(name='name', plugin='plugin')
+        with self.assertRaises(
+                exceptions.KubernetesNetworkObjectDefinitionError):
+            kubernetes.NetworkObject(name='name', args='args')
+        with self.assertRaises(
+                exceptions.KubernetesNetworkObjectDefinitionError):
+            kubernetes.NetworkObject(args='args', plugin='plugin')
+
+    @mock.patch.object(kubernetes_utils, 'get_custom_resource_definition')
+    def test_crd(self, mock_get_crd):
+        mock_crd = mock.Mock()
+        mock_get_crd.return_value = mock_crd
+        net_obj = copy.deepcopy(self.net_obj)
+        self.assertEqual(mock_crd, net_obj.crd)
+
+    def test_template(self):
+        net_obj = copy.deepcopy(self.net_obj)
+        expected = {'apiVersion': 'group.com/v2',
+                    'kind': kubernetes.NetworkObject.KIND,
+                    'metadata': {
+                        'name': 'fake_name'},
+                    'plugin': 'fake_plugin',
+                    'args': 'fake_args'}
+        crd = mock.Mock()
+        crd.spec.group = 'group.com'
+        crd.spec.version = 'v2'
+        net_obj._crd = crd
+        self.assertEqual(expected, net_obj.template)
+
+    def test_group(self):
+        net_obj = copy.deepcopy(self.net_obj)
+        net_obj._crd = mock.Mock()
+        net_obj._crd.spec.group = 'fake_group'
+        self.assertEqual('fake_group', net_obj.group)
+
+    def test_version(self):
+        net_obj = copy.deepcopy(self.net_obj)
+        net_obj._crd = mock.Mock()
+        net_obj._crd.spec.version = 'version_4'
+        self.assertEqual('version_4', net_obj.version)
+
+    def test_plural(self):
+        net_obj = copy.deepcopy(self.net_obj)
+        net_obj._crd = mock.Mock()
+        net_obj._crd.spec.names.plural = 'name_ending_in_s'
+        self.assertEqual('name_ending_in_s', net_obj.plural)
+
+    def test_scope(self):
+        net_obj = copy.deepcopy(self.net_obj)
+        net_obj._crd = mock.Mock()
+        net_obj._crd.spec.scope = 'Cluster'
+        self.assertEqual('Cluster', net_obj.scope)
+
+    @mock.patch.object(kubernetes_utils, 'create_network')
+    def test_create(self, mock_create_network):
+        net_obj = copy.deepcopy(self.net_obj)
+        net_obj._scope = 'scope'
+        net_obj._group = 'group'
+        net_obj._version = 'version'
+        net_obj._plural = 'plural'
+        net_obj._template = 'template'
+        net_obj.create()
+        mock_create_network.assert_called_once_with(
+            'scope', 'group', 'version', 'plural', 'template')
+
+    @mock.patch.object(kubernetes_utils, 'delete_network')
+    def test_delete(self, mock_delete_network):
+        net_obj = copy.deepcopy(self.net_obj)
+        net_obj._scope = 'scope'
+        net_obj._group = 'group'
+        net_obj._version = 'version'
+        net_obj._plural = 'plural'
+        net_obj._name = 'name'
+        net_obj.delete()
+        mock_delete_network.assert_called_once_with(
+            'scope', 'group', 'version', 'plural', 'name')