aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorRodolfo Alonso Hernandez <rodolfo.alonso.hernandez@intel.com>2018-05-08 17:42:08 +0100
committerRodolfo Alonso Hernandez <rodolfo.alonso.hernandez@intel.com>2018-06-14 07:15:35 +0000
commitfa269e3789d19f1335ae9207817203c6ad58cf42 (patch)
treed08c2f7fdd09d2e1a7a8913732ed8c1f435937ea
parent3b43905b749b65bd229db8f3f1ce10c29f9b9e37 (diff)
Add new Kubernetes resource kind: "CustomResourceDefinition"
Custom resource definition example in Kubernetes: apiVersion: "apiextensions.k8s.io/v1beta" kind: CustomResourceDefinition metadata: name: networks.kubernetes.com spec: group: kubernetes.com version: v1 scope: Namespaced names: plural: networks singular: network kind: Network Proposed Kubernetes context network definition: context: custom_resources:     - name: network        # name of the resource (singular)       version: v1          # optional, "v1" by default       scope: Namespaced    # optional, "Namespaced" by default From this definition, we will extract the Kubernetes parameters: - metadata.name: custom_resources.name + "s" + context_name + ".com" - spec.group: context_name + ".com" - spec.scope: custom_resources.scope - spec.version: custom_resources.version - spec.names.plural: custom_resources.name + "s" - spec.names.singular: custom_resources.name - spec.names.kind: custom_resources.name with first capital letter [1] https://kubernetes.io/docs/concepts/api-extension/custom-resources/ JIRA: YARDSTICK-1163 Change-Id: If8980dc3f6ddf9c6949bf15be8011aa98482ddc9 Signed-off-by: Rodolfo Alonso Hernandez <rodolfo.alonso.hernandez@intel.com>
-rw-r--r--yardstick/benchmark/contexts/kubernetes.py12
-rw-r--r--yardstick/common/constants.py4
-rw-r--r--yardstick/common/exceptions.py16
-rw-r--r--yardstick/common/kubernetes_utils.py39
-rw-r--r--yardstick/orchestrator/kubernetes.py45
-rw-r--r--yardstick/tests/unit/common/test_kubernetes_utils.py105
-rw-r--r--yardstick/tests/unit/orchestrator/test_kubernetes.py27
7 files changed, 245 insertions, 3 deletions
diff --git a/yardstick/benchmark/contexts/kubernetes.py b/yardstick/benchmark/contexts/kubernetes.py
index 6b00c0374..8cc5dc994 100644
--- a/yardstick/benchmark/contexts/kubernetes.py
+++ b/yardstick/benchmark/contexts/kubernetes.py
@@ -48,6 +48,7 @@ class KubernetesContext(Context):
LOG.info('Creating ssh key')
self._set_ssh_key()
+ self._create_crd()
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_crd()
super(KubernetesContext, self).undeploy()
@@ -107,6 +109,16 @@ class KubernetesContext(Context):
def _delete_pod(self, pod):
k8s_utils.delete_pod(pod)
+ def _create_crd(self):
+ LOG.info('Create Custom Resource Definition elements')
+ for crd in self.template.crd:
+ crd.create()
+
+ def _delete_crd(self):
+ LOG.info('Delete Custom Resource Definition elements')
+ for crd in self.template.crd:
+ crd.delete()
+
def _get_key_path(self):
task_id = self.name.split('-')[-1]
k = 'files/yardstick_key-{}'.format(task_id)
diff --git a/yardstick/common/constants.py b/yardstick/common/constants.py
index f6e4ab7e9..954e59235 100644
--- a/yardstick/common/constants.py
+++ b/yardstick/common/constants.py
@@ -170,3 +170,7 @@ TESTSUITE_PRE = 'opnfv_'
# OpenStack cloud default config parameters
OS_CLOUD_DEFAULT_CONFIG = {'verify': False}
+
+# Kubernetes
+SCOPE_NAMESPACED = 'Namespaced'
+SCOPE_CLUSTER = 'Cluster'
diff --git a/yardstick/common/exceptions.py b/yardstick/common/exceptions.py
index 5f362b356..e370b92bf 100644
--- a/yardstick/common/exceptions.py
+++ b/yardstick/common/exceptions.py
@@ -14,6 +14,8 @@
from oslo_utils import excutils
+from yardstick.common import constants
+
class ProcessExecutionError(RuntimeError):
def __init__(self, message, returncode):
@@ -195,10 +197,24 @@ class WaitTimeout(YardstickException):
message = 'Wait timeout while waiting for condition'
+class KubernetesApiException(YardstickException):
+ message = ('Kubernetes API errors. Action: %(action)s, '
+ 'resource: %(resource)s')
+
+
+class KubernetesConfigFileNotFound(YardstickException):
+ message = 'Config file (%s) not found' % constants.K8S_CONF_FILE
+
+
class KubernetesTemplateInvalidVolumeType(YardstickException):
message = 'No valid "volume" types present in %(volume)s'
+class KubernetesCRDObjectDefinitionError(YardstickException):
+ message = ('Kubernetes Custom Resource Definition Object error, missing '
+ 'parameters: %(missing_parameters)s')
+
+
class ScenarioCreateNetworkError(YardstickException):
message = 'Create Neutron Network Scenario failed'
diff --git a/yardstick/common/kubernetes_utils.py b/yardstick/common/kubernetes_utils.py
index ee8e8edcd..a472b6de5 100644
--- a/yardstick/common/kubernetes_utils.py
+++ b/yardstick/common/kubernetes_utils.py
@@ -13,6 +13,8 @@ from kubernetes import config
from kubernetes.client.rest import ApiException
from yardstick.common import constants as consts
+from yardstick.common import exceptions
+
LOG = logging.getLogger(__name__)
LOG.setLevel(logging.DEBUG)
@@ -22,12 +24,18 @@ def get_core_api(): # pragma: no cover
try:
config.load_kube_config(config_file=consts.K8S_CONF_FILE)
except IOError:
- LOG.exception('config file not found')
- raise
-
+ raise exceptions.KubernetesConfigFileNotFound()
return client.CoreV1Api()
+def get_extensions_v1beta_api():
+ try:
+ config.load_kube_config(config_file=consts.K8S_CONF_FILE)
+ except IOError:
+ raise exceptions.KubernetesConfigFileNotFound()
+ return client.ApiextensionsV1beta1Api()
+
+
def get_node_list(**kwargs): # pragma: no cover
core_v1_api = get_core_api()
try:
@@ -187,6 +195,31 @@ def delete_config_map(name,
raise
+def create_custom_resource_definition(body):
+ api = get_extensions_v1beta_api()
+ body_obj = client.V1beta1CustomResourceDefinition(
+ spec=body['spec'], metadata=body['metadata'])
+ try:
+ api.create_custom_resource_definition(body_obj)
+ except ValueError:
+ # NOTE(ralonsoh): bug in kubernetes-client/python 6.0.0
+ # https://github.com/kubernetes-client/python/issues/491
+ pass
+ except ApiException:
+ raise exceptions.KubernetesApiException(
+ action='create', resource='CustomResourceDefinition')
+
+
+def delete_custom_resource_definition(name):
+ api = get_extensions_v1beta_api()
+ body_obj = client.V1DeleteOptions()
+ try:
+ api.delete_custom_resource_definition(name, body_obj)
+ except ApiException:
+ raise exceptions.KubernetesApiException(
+ action='delete', resource='CustomResourceDefinition')
+
+
def get_pod_list(namespace='default'): # pragma: no cover
core_v1_api = get_core_api()
try:
diff --git a/yardstick/orchestrator/kubernetes.py b/yardstick/orchestrator/kubernetes.py
index 25adff7d4..637abd813 100644
--- a/yardstick/orchestrator/kubernetes.py
+++ b/yardstick/orchestrator/kubernetes.py
@@ -9,6 +9,7 @@
import copy
+from yardstick.common import constants
from yardstick.common import exceptions
from yardstick.common import utils
from yardstick.common import kubernetes_utils as k8s_utils
@@ -195,6 +196,47 @@ class ServiceObject(object):
k8s_utils.delete_service(self.name)
+class CustomResourceDefinitionObject(object):
+
+ MANDATORY_PARAMETERS = {'name'}
+
+ def __init__(self, ctx_name, **kwargs):
+ if not self.MANDATORY_PARAMETERS.issubset(kwargs):
+ missing_parameters = ', '.join(
+ str(param) for param in
+ (self.MANDATORY_PARAMETERS - set(kwargs)))
+ raise exceptions.KubernetesCRDObjectDefinitionError(
+ missing_parameters=missing_parameters)
+
+ singular = kwargs['name']
+ plural = singular + 's'
+ kind = singular.title()
+ version = kwargs.get('version', 'v1')
+ scope = kwargs.get('scope', constants.SCOPE_NAMESPACED)
+ group = ctx_name + '.com'
+ self._name = metadata_name = plural + '.' + group
+
+ self._template = {
+ 'metadata': {
+ 'name': metadata_name
+ },
+ 'spec': {
+ 'group': group,
+ 'version': version,
+ 'scope': scope,
+ 'names': {'plural': plural,
+ 'singular': singular,
+ 'kind': kind}
+ }
+ }
+
+ def create(self):
+ k8s_utils.create_custom_resource_definition(self._template)
+
+ def delete(self):
+ k8s_utils.delete_custom_resource_definition(self._name)
+
+
class KubernetesTemplate(object):
def __init__(self, name, context_cfg):
@@ -205,6 +247,7 @@ class KubernetesTemplate(object):
"""
context_cfg = copy.deepcopy(context_cfg)
servers_cfg = context_cfg.pop('servers', {})
+ crd_cfg = context_cfg.pop('custom_resources', [])
self.name = name
self.ssh_key = '{}-key'.format(name)
@@ -214,6 +257,8 @@ class KubernetesTemplate(object):
**cfg)
for rc, cfg in servers_cfg.items()]
self.service_objs = [ServiceObject(s) for s in self.rcs]
+ self.crd = [CustomResourceDefinitionObject(self.name, **crd)
+ for crd in crd_cfg]
self.pods = []
diff --git a/yardstick/tests/unit/common/test_kubernetes_utils.py b/yardstick/tests/unit/common/test_kubernetes_utils.py
new file mode 100644
index 000000000..e264fd94b
--- /dev/null
+++ b/yardstick/tests/unit/common/test_kubernetes_utils.py
@@ -0,0 +1,105 @@
+# Copyright (c) 2018 Intel Corporation
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+import mock
+from kubernetes import client
+from kubernetes.client import rest
+from kubernetes import config
+
+from yardstick.common import constants
+from yardstick.common import exceptions
+from yardstick.common import kubernetes_utils
+from yardstick.tests.unit import base
+
+
+class GetExtensionsV1betaApiTestCase(base.BaseUnitTestCase):
+
+ @mock.patch.object(client, 'ApiextensionsV1beta1Api', 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_extensions_v1beta_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_extensions_v1beta_api()
+
+
+class CreateCustomResourceDefinitionTestCase(base.BaseUnitTestCase):
+
+ @mock.patch.object(client, 'V1beta1CustomResourceDefinition',
+ return_value='crd_obj')
+ @mock.patch.object(kubernetes_utils, 'get_extensions_v1beta_api')
+ def test_execute_correct(self, mock_get_api, mock_crd):
+ mock_create_crd = mock.Mock()
+ mock_get_api.return_value = mock_create_crd
+ body = {'spec': 'fake_spec', 'metadata': 'fake_metadata'}
+
+ kubernetes_utils.create_custom_resource_definition(body)
+ mock_get_api.assert_called_once()
+ mock_crd.assert_called_once_with(spec='fake_spec',
+ metadata='fake_metadata')
+ mock_create_crd.create_custom_resource_definition.\
+ assert_called_once_with('crd_obj')
+
+ @mock.patch.object(client, 'V1beta1CustomResourceDefinition',
+ return_value='crd_obj')
+ @mock.patch.object(kubernetes_utils, 'get_extensions_v1beta_api')
+ def test_execute_exception(self, mock_get_api, mock_crd):
+ mock_create_crd = mock.Mock()
+ mock_create_crd.create_custom_resource_definition.\
+ side_effect = rest.ApiException
+ mock_get_api.return_value = mock_create_crd
+ body = {'spec': 'fake_spec', 'metadata': 'fake_metadata'}
+
+ with self.assertRaises(exceptions.KubernetesApiException):
+ kubernetes_utils.create_custom_resource_definition(body)
+ mock_get_api.assert_called_once()
+ mock_crd.assert_called_once_with(spec='fake_spec',
+ metadata='fake_metadata')
+ mock_create_crd.create_custom_resource_definition.\
+ assert_called_once_with('crd_obj')
+
+
+class DeleteCustomResourceDefinitionTestCase(base.BaseUnitTestCase):
+
+ @mock.patch.object(client, 'V1DeleteOptions', return_value='del_obj')
+ @mock.patch.object(kubernetes_utils, 'get_extensions_v1beta_api')
+ def test_execute_correct(self, mock_get_api, mock_delobj):
+ mock_delete_crd = mock.Mock()
+ mock_get_api.return_value = mock_delete_crd
+
+ kubernetes_utils.delete_custom_resource_definition('name')
+ mock_get_api.assert_called_once()
+ mock_delobj.assert_called_once()
+ mock_delete_crd.delete_custom_resource_definition.\
+ assert_called_once_with('name', 'del_obj')
+
+ @mock.patch.object(client, 'V1DeleteOptions', return_value='del_obj')
+ @mock.patch.object(kubernetes_utils, 'get_extensions_v1beta_api')
+ def test_execute_exception(self, mock_get_api, mock_delobj):
+ mock_delete_crd = mock.Mock()
+ mock_delete_crd.delete_custom_resource_definition.\
+ side_effect = rest.ApiException
+ mock_get_api.return_value = mock_delete_crd
+
+ with self.assertRaises(exceptions.KubernetesApiException):
+ kubernetes_utils.delete_custom_resource_definition('name')
+ mock_delobj.assert_called_once()
+ mock_delete_crd.delete_custom_resource_definition.\
+ assert_called_once_with('name', 'del_obj')
diff --git a/yardstick/tests/unit/orchestrator/test_kubernetes.py b/yardstick/tests/unit/orchestrator/test_kubernetes.py
index 99aca0426..50c6b2773 100644
--- a/yardstick/tests/unit/orchestrator/test_kubernetes.py
+++ b/yardstick/tests/unit/orchestrator/test_kubernetes.py
@@ -270,3 +270,30 @@ class ContainerObjectTestCase(base.BaseUnitTestCase):
'volumeMounts': container_obj._create_volume_mounts(),
'securityContext': {'key': 'value'}}
self.assertEqual(expected, container_obj.get_container_item())
+
+
+class CustomResourceDefinitionObjectTestCase(base.BaseUnitTestCase):
+
+ def test__init(self):
+ template = {
+ 'metadata': {
+ 'name': 'newcrds.ctx_name.com'
+ },
+ 'spec': {
+ 'group': 'ctx_name.com',
+ 'version': 'v2',
+ 'scope': 'scope',
+ 'names': {'plural': 'newcrds',
+ 'singular': 'newcrd',
+ 'kind': 'Newcrd'}
+ }
+ }
+ crd_obj = kubernetes.CustomResourceDefinitionObject(
+ 'ctx_name', name='newcrd', version='v2', scope='scope')
+ self.assertEqual('newcrds.ctx_name.com', crd_obj._name)
+ self.assertEqual(template, crd_obj._template)
+
+ def test__init_missing_parameter(self):
+ with self.assertRaises(exceptions.KubernetesCRDObjectDefinitionError):
+ kubernetes.CustomResourceDefinitionObject('ctx_name',
+ noname='name')