diff options
Diffstat (limited to 'yardstick/common')
-rw-r--r-- | yardstick/common/constants.py | 4 | ||||
-rw-r--r-- | yardstick/common/exceptions.py | 42 | ||||
-rw-r--r-- | yardstick/common/kubernetes_utils.py | 86 | ||||
-rw-r--r-- | yardstick/common/messaging/__init__.py | 27 | ||||
-rw-r--r-- | yardstick/common/messaging/consumer.py | 10 | ||||
-rw-r--r-- | yardstick/common/messaging/payloads.py | 20 | ||||
-rw-r--r-- | yardstick/common/messaging/producer.py | 13 | ||||
-rw-r--r-- | yardstick/common/utils.py | 22 |
8 files changed, 197 insertions, 27 deletions
diff --git a/yardstick/common/constants.py b/yardstick/common/constants.py index 1ebd32509..2f14d4bc4 100644 --- a/yardstick/common/constants.py +++ b/yardstick/common/constants.py @@ -171,3 +171,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 935c77866..641c4e1c4 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): @@ -191,6 +193,15 @@ class TaskRenderError(YardstickException): message = 'Failed to render template:\n%(input_task)s' +class RunnerIterationIPCSetupActionNeeded(YardstickException): + message = ('IterationIPC needs the "setup" action to retrieve the VNF ' + 'handling processes PIDs to receive the messages sent') + + +class RunnerIterationIPCNoCtxs(YardstickException): + message = 'Benchmark "setup" action did not return any VNF process PID' + + class TimerTimeout(YardstickException): message = 'Timer timeout expired, %(timeout)s seconds' @@ -199,10 +210,41 @@ 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 KubernetesNetworkObjectDefinitionError(YardstickException): + message = ('Kubernetes Network object definition error, missing ' + 'parameters: %(missing_parameters)s') + + +class KubernetesNetworkObjectKindMissing(YardstickException): + message = 'Kubernetes kind "Network" is not defined' + + +class KubernetesWrongRestartPolicy(YardstickException): + message = 'Restart policy "%(rpolicy)s" is not valid' + + +class KubernetesContainerPortNotDefined(YardstickException): + message = 'Container port not defined in "%(port)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..42267fc41 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,26 @@ 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_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: @@ -187,6 +203,70 @@ 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_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: diff --git a/yardstick/common/messaging/__init__.py b/yardstick/common/messaging/__init__.py index f0f012ec3..bd700d9b1 100644 --- a/yardstick/common/messaging/__init__.py +++ b/yardstick/common/messaging/__init__.py @@ -1,14 +1,3 @@ -# 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. @@ -28,9 +17,17 @@ TRANSPORT_URL = (MQ_SERVICE + '://' + MQ_USER + ':' + MQ_PASS + '@' + SERVER + RPC_SERVER_EXECUTOR = 'threading' # Topics. -RUNNER = 'runner' +TOPIC_TG = 'topic_traffic_generator' +TOPIC_RUNNER = 'topic_runner' # Methods. -# RUNNER methods: -RUNNER_INFO = 'runner_info' -RUNNER_LOOP = 'runner_loop' +# Traffic generator consumers methods. Names must match the methods implemented +# in the consumer endpoint class. +TG_METHOD_STARTED = 'tg_method_started' +TG_METHOD_FINISHED = 'tg_method_finished' +TG_METHOD_ITERATION = 'tg_method_iteration' + +# Runner consumers methods. Names must match the methods implemented in the +# consumer endpoint class. +RUNNER_METHOD_START_ITERATION = "runner_method_start_iteration" +RUNNER_METHOD_STOP_ITERATION = "runner_method_stop_iteration" diff --git a/yardstick/common/messaging/consumer.py b/yardstick/common/messaging/consumer.py index 24ec6f184..c99d7ed27 100644 --- a/yardstick/common/messaging/consumer.py +++ b/yardstick/common/messaging/consumer.py @@ -29,9 +29,9 @@ LOG = logging.getLogger(__name__) class NotificationHandler(object): """Abstract class to define a endpoint object for a MessagingConsumer""" - def __init__(self, _id, ctx_pids, queue): + def __init__(self, _id, ctx_ids, queue): self._id = _id - self._ctx_pids = ctx_pids + self._ctx_ids = ctx_ids self._queue = queue @@ -43,11 +43,11 @@ class MessagingConsumer(object): the messages published by a `MessagingNotifier`. """ - def __init__(self, topic, pids, endpoints, fanout=True): + def __init__(self, topic, ids, endpoints, fanout=True): """Init function. :param topic: (string) MQ exchange topic - :param pids: (list of int) list of PIDs of the processes implementing + :param ids: (list of int) list of IDs of the processes implementing the MQ Notifier which will be in the message context :param endpoints: (list of class) list of classes implementing the methods (see `MessagingNotifier.send_message) used by @@ -58,7 +58,7 @@ class MessagingConsumer(object): :returns: `MessagingConsumer` class object """ - self._pids = pids + self._ids = ids self._endpoints = endpoints self._transport = oslo_messaging.get_rpc_transport( cfg.CONF, url=messaging.TRANSPORT_URL) diff --git a/yardstick/common/messaging/payloads.py b/yardstick/common/messaging/payloads.py index d29d79808..8ede1e58e 100644 --- a/yardstick/common/messaging/payloads.py +++ b/yardstick/common/messaging/payloads.py @@ -51,3 +51,23 @@ class Payload(object): def dict_to_obj(cls, _dict): """Returns a Payload object built from the dictionary elements""" return cls(**_dict) + + +class TrafficGeneratorPayload(Payload): + """Base traffic generator payload class""" + REQUIRED_FIELDS = { + 'version', # (str) version of the payload transmitted. + 'iteration', # (int) iteration index during the traffic injection, + # starting from 1. + 'kpi' # (dict) collection of KPIs collected from the traffic + # injection. The content will depend on the generator and the + # traffic type. + } + + +class RunnerPayload(Payload): + """Base runner payload class""" + REQUIRED_FIELDS = { + 'version', # (str) version of the payload transmitted. + 'data' # (dict) generic container of data to be used if needed. + } diff --git a/yardstick/common/messaging/producer.py b/yardstick/common/messaging/producer.py index b6adc0c17..aadab649d 100644 --- a/yardstick/common/messaging/producer.py +++ b/yardstick/common/messaging/producer.py @@ -34,18 +34,18 @@ class MessagingProducer(object): messages in a message queue. """ - def __init__(self, topic, pid=os.getpid(), fanout=True): + def __init__(self, topic, _id=os.getpid(), fanout=True): """Init function. :param topic: (string) MQ exchange topic - :param pid: (int) PID of the process implementing this MQ Notifier + :param id: (int) ID of the process implementing this MQ Notifier :param fanout: (bool) MQ clients may request that a copy of the message be delivered to all servers listening on a topic by setting fanout to ``True``, rather than just one of them :returns: `MessagingNotifier` class object """ self._topic = topic - self._pid = pid + self._id = _id self._fanout = fanout self._transport = oslo_messaging.get_rpc_transport( cfg.CONF, url=messaging.TRANSPORT_URL) @@ -65,6 +65,11 @@ class MessagingProducer(object): consumer endpoints :param payload: (subclass `Payload`) payload content """ - self._notifier.cast({'pid': self._pid}, + self._notifier.cast({'id': self._id}, method, **payload.obj_to_dict()) + + @property + def id(self): + """Return MQ producer ID""" + return self._id diff --git a/yardstick/common/utils.py b/yardstick/common/utils.py index f9fe0e336..85cecc714 100644 --- a/yardstick/common/utils.py +++ b/yardstick/common/utils.py @@ -527,3 +527,25 @@ def wait_until_true(predicate, timeout=60, sleep=1, exception=None): if exception and issubclass(exception, Exception): raise exception # pylint: disable=raising-bad-type raise exceptions.WaitTimeout + + +def send_socket_command(host, port, command): + """Send a string command to a specific port in a host + + :param host: (str) ip or hostname of the host + :param port: (int) port number + :param command: (str) command to send + :return: 0 if success, error number if error + """ + sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM) + ret = 0 + try: + err_number = sock.connect_ex((host, int(port))) + if err_number != 0: + return err_number + sock.sendall(six.b(command)) + except Exception: # pylint: disable=broad-except + ret = 1 + finally: + sock.close() + return ret |