diff options
239 files changed, 18980 insertions, 1710 deletions
diff --git a/.gitignore b/.gitignore index f2c8fd9c0..d98b4a070 100644 --- a/.gitignore +++ b/.gitignore @@ -1,3 +1,5 @@ +*.DS_Store +*.log *.pyc .vimrc .ropeproject @@ -15,14 +17,13 @@ build htmlcov .agignore .coverage +*.retry Session*.vim .tags* .coverage.* *~ setuptools*zip dist/ -pep8.log -test.log .testrepository/ cover/ .*.sw? diff --git a/ansible/inventory.ini b/ansible/inventory.ini index 440e625bd..79a6ee0aa 100644 --- a/ansible/inventory.ini +++ b/ansible/inventory.ini @@ -1,8 +1,11 @@ [controller] host1 ansible_host=10.1.0.50 ansible_user=root ansible_ssh_pass=root host2 ansible_host=10.1.0.51 ansible_user=root ansible_ssh_pass=root -host3 ansible_host=10.1.0.52 ansible_user=root ansible_ssh_pass=root [compute] host4 ansible_host=10.1.0.53 ansible_user=root ansible_ssh_pass=root host5 ansible_host=10.1.0.54 ansible_user=root ansible_ssh_pass=root + +[nodes:children] +controller +compute diff --git a/ansible/migrate_pinning_setup.yaml b/ansible/migrate_pinning_setup.yaml new file mode 100644 index 000000000..ee5eef3ff --- /dev/null +++ b/ansible/migrate_pinning_setup.yaml @@ -0,0 +1,49 @@ +--- +############################################################################## +# Copyright (c) 2017 Huawei Technologies Co.,Ltd and others. +# +# All rights reserved. This program and the accompanying materials +# are made available under the terms of the Apache License, Version 2.0 +# which accompanies this distribution, and is available at +# http://www.apache.org/licenses/LICENSE-2.0 +############################################################################## + +- hosts: localhost + roles: + - create_flavor + - role: set_flavor_property + key: "hw:cpu_policy" + value: "dedicated" + - role: set_flavor_property + key: "hw:numa_nodes" + value: "1" + +- hosts: nodes + roles: + - backup_nova_conf + - role: set_nova_conf + section: "DEFAULT" + key: "live_migration_flag" + value: "VIR_MIGRATE_UNDEFINE_SOURCE,VIR_MIGRATE_PEER2PEER,VIR_MIGRATE_LIVE,VIR_MIGRATE_TUNNELLED" + - role: set_nova_conf + section: "DEFAULT" + key: "vncserver_listen" + value: "0.0.0.0" + +- hosts: controller + roles: + - role: set_nova_conf + section: "DEFAULT" + key: "scheduler_default_filters" + value: "NUMATopologyFilter" + - role: restart_nova_service + service: "nova-scheduler" + +- hosts: compute + roles: + - role: set_nova_conf + section: "DEFAULT" + key: "vcpu_pin_set" + value: "{{ cpu_set }}" + - role: restart_nova_service + service: "nova-compute" diff --git a/ansible/migrate_pinning_teardown.yaml b/ansible/migrate_pinning_teardown.yaml new file mode 100644 index 000000000..13dd6113c --- /dev/null +++ b/ansible/migrate_pinning_teardown.yaml @@ -0,0 +1,31 @@ +--- +############################################################################## +# Copyright (c) 2017 Huawei Technologies Co.,Ltd and others. +# +# All rights reserved. This program and the accompanying materials +# are made available under the terms of the Apache License, Version 2.0 +# which accompanies this distribution, and is available at +# http://www.apache.org/licenses/LICENSE-2.0 +############################################################################## + +- hosts: localhost + roles: + - delete_flavor + +- hosts: nodes + roles: + - recover_nova_conf + +- hosts: controller + roles: + - role: restart_nova_service + service: "nova-scheduler" + - role: restart_nova_service + service: "nova-api" + - role: restart_nova_service + service: "nova-conductor" + +- hosts: compute + roles: + - role: restart_nova_service + service: "nova-compute" diff --git a/ansible/roles/backup_nova_conf/tasks/main.yaml b/ansible/roles/backup_nova_conf/tasks/main.yaml new file mode 100644 index 000000000..ca95bac59 --- /dev/null +++ b/ansible/roles/backup_nova_conf/tasks/main.yaml @@ -0,0 +1,18 @@ +--- +############################################################################## +# Copyright (c) 2017 Huawei Technologies Co.,Ltd and others. +# +# All rights reserved. This program and the accompanying materials +# are made available under the terms of the Apache License, Version 2.0 +# which accompanies this distribution, and is available at +# http://www.apache.org/licenses/LICENSE-2.0 +############################################################################## + +- name: backup nova.conf file + copy: + src: /etc/nova/nova.conf + dest: /tmp/nova.conf + owner: nova + group: nova + remote_src: True + become: true diff --git a/ansible/roles/create_flavor/tasks/main.yaml b/ansible/roles/create_flavor/tasks/main.yaml new file mode 100644 index 000000000..9b776c694 --- /dev/null +++ b/ansible/roles/create_flavor/tasks/main.yaml @@ -0,0 +1,16 @@ +############################################################################## +# Copyright (c) 2017 Huawei Technologies Co.,Ltd and others. +# +# All rights reserved. This program and the accompanying materials +# are made available under the terms of the Apache License, Version 2.0 +# which accompanies this distribution, and is available at +# http://www.apache.org/licenses/LICENSE-2.0 +############################################################################## +- name: create flavor {{ flavor }} + os_nova_flavor: + cloud: opnfv + state: present + name: "{{ flavor }}" + ram: "{{ ram }}" + vcpus: "{{ vcpus }}" + disk: "{{ disk }}" diff --git a/ansible/roles/delete_flavor/tasks/main.yaml b/ansible/roles/delete_flavor/tasks/main.yaml new file mode 100644 index 000000000..dc9fc88ce --- /dev/null +++ b/ansible/roles/delete_flavor/tasks/main.yaml @@ -0,0 +1,13 @@ +############################################################################## +# Copyright (c) 2017 Huawei Technologies Co.,Ltd and others. +# +# All rights reserved. This program and the accompanying materials +# are made available under the terms of the Apache License, Version 2.0 +# which accompanies this distribution, and is available at +# http://www.apache.org/licenses/LICENSE-2.0 +############################################################################## +- name: delete flavor {{ flavor }} + os_nova_flavor: + cloud: opnfv + state: absent + name: "{{ flavor }}" diff --git a/ansible/roles/recover_nova_conf/tasks/main.yaml b/ansible/roles/recover_nova_conf/tasks/main.yaml new file mode 100644 index 000000000..44919d2ae --- /dev/null +++ b/ansible/roles/recover_nova_conf/tasks/main.yaml @@ -0,0 +1,18 @@ +--- +############################################################################## +# Copyright (c) 2017 Huawei Technologies Co.,Ltd and others. +# +# All rights reserved. This program and the accompanying materials +# are made available under the terms of the Apache License, Version 2.0 +# which accompanies this distribution, and is available at +# http://www.apache.org/licenses/LICENSE-2.0 +############################################################################## + +- name: recover nova.conf file + copy: + src: /tmp/nova.conf + dest: /etc/nova/nova.conf + owner: nova + group: nova + remote_src: True + become: true diff --git a/ansible/roles/restart_nova_service/tasks/main.yaml b/ansible/roles/restart_nova_service/tasks/main.yaml new file mode 100644 index 000000000..2bdce652d --- /dev/null +++ b/ansible/roles/restart_nova_service/tasks/main.yaml @@ -0,0 +1,23 @@ +--- +############################################################################## +# Copyright (c) 2017 Huawei Technologies Co.,Ltd and others. +# +# All rights reserved. This program and the accompanying materials +# are made available under the terms of the Apache License, Version 2.0 +# which accompanies this distribution, and is available at +# http://www.apache.org/licenses/LICENSE-2.0 +############################################################################## + +- name: restart "{{ service }}" service + service: + name: "{{ service }}" + state: restarted + become: true + when: ansible_os_family == "Debian" + +- name: restart "openstack-{{ service }}" service + service: + name: "openstack-{{ service }}" + state: restarted + become: true + when: ansible_os_family == "RedHat" diff --git a/ansible/roles/set_flavor_property/tasks/main.yaml b/ansible/roles/set_flavor_property/tasks/main.yaml new file mode 100644 index 000000000..f98988783 --- /dev/null +++ b/ansible/roles/set_flavor_property/tasks/main.yaml @@ -0,0 +1,14 @@ +############################################################################## +# Copyright (c) 2017 Huawei Technologies Co.,Ltd and others. +# +# All rights reserved. This program and the accompanying materials +# are made available under the terms of the Apache License, Version 2.0 +# which accompanies this distribution, and is available at +# http://www.apache.org/licenses/LICENSE-2.0 +############################################################################## +- name: set flavor "{{ flavor }}" property {{ key }} = {{ value }} + shell: + source /etc/yardstick/openstack.creds; + openstack flavor set --property {{ key }}={{ value }} {{ flavor }}; + args: + executable: /bin/bash diff --git a/ansible/roles/set_nova_conf/tasks/main.yaml b/ansible/roles/set_nova_conf/tasks/main.yaml new file mode 100644 index 000000000..ae665c5d0 --- /dev/null +++ b/ansible/roles/set_nova_conf/tasks/main.yaml @@ -0,0 +1,17 @@ +--- +############################################################################## +# Copyright (c) 2017 Huawei Technologies Co.,Ltd and others. +# +# All rights reserved. This program and the accompanying materials +# are made available under the terms of the Apache License, Version 2.0 +# which accompanies this distribution, and is available at +# http://www.apache.org/licenses/LICENSE-2.0 +############################################################################## + +- name: set "{{ key }}" value + ini_file: + dest: /etc/nova/nova.conf + section: "{{ section }}" + option: "{{ key }}" + value: "{{ value }}" + become: true diff --git a/api/__init__.py b/api/__init__.py index c6cbbf104..c5aefffe8 100644 --- a/api/__init__.py +++ b/api/__init__.py @@ -1,4 +1,67 @@ -from yardstick import _init_logging +############################################################################## +# Copyright (c) 2016 Huawei Technologies Co.,Ltd and others. +# +# All rights reserved. This program and the accompanying materials +# are made available under the terms of the Apache License, Version 2.0 +# which accompanies this distribution, and is available at +# http://www.apache.org/licenses/LICENSE-2.0 +############################################################################## +from __future__ import absolute_import +import logging + +from flask import request +from flask_restful import Resource +from yardstick import _init_logging +from yardstick.common import constants as consts +from yardstick.common import utils as common_utils _init_logging() +LOG = logging.getLogger(__name__) +LOG.setLevel(logging.DEBUG) + + +class ApiResource(Resource): + + def _post_args(self): + data = request.json if request.json else {} + params = common_utils.translate_to_str(data) + action = params.get('action', request.form.get('action', '')) + args = params.get('args', {}) + + try: + args['file'] = request.files['file'] + except KeyError: + pass + + args.update({k: v for k, v in request.form.items()}) + LOG.debug('Input args is: action: %s, args: %s', action, args) + + return action, args + + def _get_args(self): + args = common_utils.translate_to_str(request.args) + LOG.debug('Input args is: args: %s', args) + + return args + + def _dispatch_post(self, **kwargs): + action, args = self._post_args() + args.update(kwargs) + return self._dispatch(args, action) + + def _dispatch(self, args, action): + try: + return getattr(self, action)(args) + except AttributeError: + common_utils.result_handler(consts.API_ERROR, 'No such action') + + +class Url(object): + + def __init__(self, url, target): + super(Url, self).__init__() + self.url = url + self.target = target + +common_utils.import_modules_from_package("api.resources") diff --git a/api/base.py b/api/base.py deleted file mode 100644 index 0f1e76a57..000000000 --- a/api/base.py +++ /dev/null @@ -1,65 +0,0 @@ -############################################################################## -# Copyright (c) 2016 Huawei Technologies Co.,Ltd and others. -# -# All rights reserved. This program and the accompanying materials -# are made available under the terms of the Apache License, Version 2.0 -# which accompanies this distribution, and is available at -# http://www.apache.org/licenses/LICENSE-2.0 -############################################################################## -from __future__ import absolute_import -import re -import importlib -import logging - -from flask import request -from flask_restful import Resource - -from api.utils import common as common_utils -from yardstick.common import constants as consts - -logger = logging.getLogger(__name__) -logger.setLevel(logging.DEBUG) - - -class ApiResource(Resource): - - def _post_args(self): - data = request.json if request.json else {} - params = common_utils.translate_to_str(data) - action = params.get('action', request.form.get('action', '')) - args = params.get('args', {}) - - try: - args['file'] = request.files['file'] - except KeyError: - pass - - logger.debug('Input args is: action: %s, args: %s', action, args) - - return action, args - - def _get_args(self): - args = common_utils.translate_to_str(request.args) - logger.debug('Input args is: args: %s', args) - - return args - - def _dispatch_post(self): - action, args = self._post_args() - return self._dispatch(args, action) - - def _dispatch_get(self, **kwargs): - args = self._get_args() - args.update(kwargs) - return self._dispatch(args) - - def _dispatch(self, args, action='default'): - module_name = re.sub(r'([A-Z][a-z]*)', r'_\1', - self.__class__.__name__)[1:].lower() - - module_name = 'api.resources.%s' % module_name - resources = importlib.import_module(module_name) - try: - return getattr(resources, action)(args) - except AttributeError: - common_utils.result_handler(consts.API_ERROR, 'No such action') diff --git a/api/database/v2/__init__.py b/api/database/v2/__init__.py new file mode 100644 index 000000000..e69de29bb --- /dev/null +++ b/api/database/v2/__init__.py diff --git a/api/database/v2/handlers.py b/api/database/v2/handlers.py new file mode 100644 index 000000000..1bc32bf0e --- /dev/null +++ b/api/database/v2/handlers.py @@ -0,0 +1,200 @@ +############################################################################## +# Copyright (c) 2017 Huawei Technologies Co.,Ltd. +# +# All rights reserved. This program and the accompanying materials +# are made available under the terms of the Apache License, Version 2.0 +# which accompanies this distribution, and is available at +# http://www.apache.org/licenses/LICENSE-2.0 +############################################################################## +from api.database import db_session +from api.database.v2.models import V2Environment +from api.database.v2.models import V2Openrc +from api.database.v2.models import V2Image +from api.database.v2.models import V2Pod +from api.database.v2.models import V2Container +from api.database.v2.models import V2Project +from api.database.v2.models import V2Task + + +class V2EnvironmentHandler(object): + + def insert(self, kwargs): + environment = V2Environment(**kwargs) + db_session.add(environment) + db_session.commit() + return environment + + def list_all(self): + return V2Environment.query.all() + + def get_by_uuid(self, uuid): + environment = V2Environment.query.filter_by(uuid=uuid).first() + if not environment: + raise ValueError + return environment + + def update_attr(self, uuid, attr): + environment = self.get_by_uuid(uuid) + for k, v in attr.items(): + setattr(environment, k, v) + db_session.commit() + + def append_attr(self, uuid, attr): + environment = self.get_by_uuid(uuid) + for k, v in attr.items(): + value = getattr(environment, k) + new = '{},{}'.format(value, v) if value else v + setattr(environment, k, new) + db_session.commit() + + def delete_by_uuid(self, uuid): + environment = self.get_by_uuid(uuid) + db_session.delete(environment) + db_session.commit() + + +class V2OpenrcHandler(object): + + def insert(self, kwargs): + openrc = V2Openrc(**kwargs) + db_session.add(openrc) + db_session.commit() + return openrc + + def get_by_uuid(self, uuid): + openrc = V2Openrc.query.filter_by(uuid=uuid).first() + if not openrc: + raise ValueError + return openrc + + def delete_by_uuid(self, uuid): + openrc = self.get_by_uuid(uuid) + db_session.delete(openrc) + db_session.commit() + + +class V2ImageHandler(object): + + def insert(self, kwargs): + image = V2Image(**kwargs) + db_session.add(image) + db_session.commit() + return image + + def get_by_uuid(self, uuid): + image = V2Image.query.filter_by(uuid=uuid).first() + if not image: + raise ValueError + return image + + +class V2PodHandler(object): + + def insert(self, kwargs): + pod = V2Pod(**kwargs) + db_session.add(pod) + db_session.commit() + return pod + + def get_by_uuid(self, uuid): + pod = V2Pod.query.filter_by(uuid=uuid).first() + if not pod: + raise ValueError + return pod + + def delete_by_uuid(self, uuid): + pod = self.get_by_uuid(uuid) + db_session.delete(pod) + db_session.commit() + + +class V2ContainerHandler(object): + + def insert(self, kwargs): + container = V2Container(**kwargs) + db_session.add(container) + db_session.commit() + return container + + def get_by_uuid(self, uuid): + container = V2Container.query.filter_by(uuid=uuid).first() + if not container: + raise ValueError + return container + + def update_attr(self, uuid, attr): + container = self.get_by_uuid(uuid) + for k, v in attr.items(): + setattr(container, k, v) + db_session.commit() + + def delete_by_uuid(self, uuid): + container = self.get_by_uuid(uuid) + db_session.delete(container) + db_session.commit() + + +class V2ProjectHandler(object): + + def list_all(self): + return V2Project.query.all() + + def insert(self, kwargs): + project = V2Project(**kwargs) + db_session.add(project) + db_session.commit() + return project + + def get_by_uuid(self, uuid): + project = V2Project.query.filter_by(uuid=uuid).first() + if not project: + raise ValueError + return project + + def update_attr(self, uuid, attr): + project = self.get_by_uuid(uuid) + for k, v in attr.items(): + setattr(project, k, v) + db_session.commit() + + def append_attr(self, uuid, attr): + project = self.get_by_uuid(uuid) + for k, v in attr.items(): + value = getattr(project, k) + new = '{},{}'.format(value, v) if value else v + setattr(project, k, new) + db_session.commit() + + def delete_by_uuid(self, uuid): + project = self.get_by_uuid(uuid) + db_session.delete(project) + db_session.commit() + + +class V2TaskHandler(object): + + def list_all(self): + return V2Task.query.all() + + def insert(self, kwargs): + task = V2Task(**kwargs) + db_session.add(task) + db_session.commit() + return task + + def get_by_uuid(self, uuid): + task = V2Task.query.filter_by(uuid=uuid).first() + if not task: + raise ValueError + return task + + def update_attr(self, uuid, attr): + task = self.get_by_uuid(uuid) + for k, v in attr.items(): + setattr(task, k, v) + db_session.commit() + + def delete_by_uuid(self, uuid): + task = self.get_by_uuid(uuid) + db_session.delete(task) + db_session.commit() diff --git a/api/database/v2/models.py b/api/database/v2/models.py new file mode 100644 index 000000000..1e85559cb --- /dev/null +++ b/api/database/v2/models.py @@ -0,0 +1,100 @@ +############################################################################## +# Copyright (c) 2017 Huawei Technologies Co.,Ltd. +# +# All rights reserved. This program and the accompanying materials +# are made available under the terms of the Apache License, Version 2.0 +# which accompanies this distribution, and is available at +# http://www.apache.org/licenses/LICENSE-2.0 +############################################################################## +from __future__ import absolute_import +from sqlalchemy import Column +from sqlalchemy import Integer +from sqlalchemy import String +from sqlalchemy import Text +from sqlalchemy import DateTime +from sqlalchemy import Boolean + +from api.database import Base + + +class V2Environment(Base): + __tablename__ = 'v2_environment' + id = Column(Integer, primary_key=True) + uuid = Column(String(30)) + name = Column(String(30)) + description = Column(Text) + openrc_id = Column(String(10)) + image_id = Column(String(30)) + container_id = Column(Text) + pod_id = Column(String(10)) + time = Column(DateTime) + + +class V2Openrc(Base): + __tablename__ = 'v2_openrc' + id = Column(Integer, primary_key=True) + uuid = Column(String(30)) + name = Column(String(30)) + description = Column(Text) + environment_id = Column(String(30)) + content = Column(Text) + time = Column(DateTime) + + +class V2Image(Base): + __tablename__ = 'v2_image' + id = Column(Integer, primary_key=True) + uuid = Column(String(30)) + name = Column(String(30)) + description = Column(Text) + environment_id = Column(String(30)) + size = Column(String(30)) + status = Column(String(30)) + time = Column(DateTime) + + +class V2Container(Base): + __tablename__ = 'v2_container' + id = Column(Integer, primary_key=True) + uuid = Column(String(30)) + name = Column(String(30)) + environment_id = Column(String(30)) + status = Column(Integer) + port = Column(Integer) + time = Column(String(30)) + + +class V2Pod(Base): + __tablename__ = 'v2_pod' + id = Column(Integer, primary_key=True) + uuid = Column(String(30)) + environment_id = Column(String(30)) + content = Column(Text) + time = Column(String(30)) + + +class V2Project(Base): + __tablename__ = 'v2_project' + id = Column(Integer, primary_key=True) + uuid = Column(String(30)) + name = Column(String(30)) + description = Column(Text) + time = Column(DateTime) + tasks = Column(Text) + + +class V2Task(Base): + __tablename__ = 'v2_task' + id = Column(Integer, primary_key=True) + uuid = Column(String(30)) + name = Column(String(30)) + description = Column(Text) + project_id = Column(String(30)) + environment_id = Column(String(30)) + time = Column(DateTime) + case_name = Column(String(30)) + suite = Column(Boolean) + content = Column(Text) + result = Column(Text) + error = Column(Text) + status = Column(Integer) diff --git a/api/resources/asynctask.py b/api/resources/asynctask.py deleted file mode 100644 index 39b47c0ee..000000000 --- a/api/resources/asynctask.py +++ /dev/null @@ -1,64 +0,0 @@ -# ############################################################################ -# Copyright (c) 2017 Huawei Technologies Co.,Ltd and others. -# -# All rights reserved. This program and the accompanying materials -# are made available under the terms of the Apache License, Version 2.0 -# which accompanies this distribution, and is available at -# http://www.apache.org/licenses/LICENSE-2.0 -# ############################################################################ -import uuid -import logging - -from api.utils.common import result_handler -from api.database.v1.handlers import AsyncTaskHandler -from yardstick.common import constants as consts - -LOG = logging.getLogger(__name__) -LOG.setLevel(logging.DEBUG) - - -def default(args): - return _get_status(args) - - -def _get_status(args): - try: - task_id = args['task_id'] - except KeyError: - return result_handler(consts.API_ERROR, 'task_id must be provided') - - try: - uuid.UUID(task_id) - except ValueError: - return result_handler(consts.API_ERROR, 'invalid task_id') - - asynctask_handler = AsyncTaskHandler() - try: - asynctask = asynctask_handler.get_task_by_taskid(task_id) - except ValueError: - return result_handler(consts.API_ERROR, 'invalid task_id') - - def _unfinished(): - return result_handler(consts.TASK_NOT_DONE, {}) - - def _finished(): - return result_handler(consts.TASK_DONE, {}) - - def _error(): - return result_handler(consts.TASK_FAILED, asynctask.error) - - status = asynctask.status - LOG.debug('Task status is: %s', status) - - if status not in [consts.TASK_NOT_DONE, - consts.TASK_DONE, - consts.TASK_FAILED]: - return result_handler(consts.API_ERROR, 'internal server error') - - switcher = { - consts.TASK_NOT_DONE: _unfinished, - consts.TASK_DONE: _finished, - consts.TASK_FAILED: _error - } - - return switcher.get(status)() diff --git a/api/resources/case_docs.py b/api/resources/case_docs.py deleted file mode 100644 index 289410d2d..000000000 --- a/api/resources/case_docs.py +++ /dev/null @@ -1,30 +0,0 @@ -import os -import logging - -from api.utils.common import result_handler -from yardstick.common import constants as consts - -LOG = logging.getLogger(__name__) -LOG.setLevel(logging.DEBUG) - - -def default(args): - return get_case_docs(args) - - -def get_case_docs(args): - try: - case_name = args['case_name'] - except KeyError: - return result_handler(consts.API_ERROR, 'case_name must be provided') - - docs_path = os.path.join(consts.DOCS_DIR, '{}.rst'.format(case_name)) - - if not os.path.exists(docs_path): - return result_handler(consts.API_ERROR, 'case not exists') - - LOG.info('Reading %s', case_name) - with open(docs_path) as f: - content = f.read() - - return result_handler(consts.API_SUCCESS, {'docs': content}) diff --git a/api/resources/env_action.py b/api/resources/env_action.py deleted file mode 100644 index fed987063..000000000 --- a/api/resources/env_action.py +++ /dev/null @@ -1,427 +0,0 @@ -############################################################################## -# Copyright (c) 2016 Huawei Technologies Co.,Ltd and others. -# -# All rights reserved. This program and the accompanying materials -# are made available under the terms of the Apache License, Version 2.0 -# which accompanies this distribution, and is available at -# http://www.apache.org/licenses/LICENSE-2.0 -############################################################################## -from __future__ import absolute_import - -import errno -import logging -import os -import subprocess -import threading -import time -import uuid -import glob -import yaml -import collections -from subprocess import PIPE - -from six.moves import configparser -from oslo_serialization import jsonutils -from docker import Client - -from api.database.v1.handlers import AsyncTaskHandler -from api.utils import influx -from api.utils.common import result_handler -from yardstick.common import constants as consts -from yardstick.common import utils as common_utils -from yardstick.common import openstack_utils -from yardstick.common.httpClient import HttpClient - - -LOG = logging.getLogger(__name__) -LOG.setLevel(logging.DEBUG) - -async_handler = AsyncTaskHandler() - - -def create_grafana(args): - task_id = str(uuid.uuid4()) - - thread = threading.Thread(target=_create_grafana, args=(task_id,)) - thread.start() - - return result_handler(consts.API_SUCCESS, {'task_id': task_id}) - - -def _create_grafana(task_id): - _create_task(task_id) - - client = Client(base_url=consts.DOCKER_URL) - - try: - LOG.info('Checking if grafana image exist') - image = '{}:{}'.format(consts.GRAFANA_IMAGE, consts.GRAFANA_TAG) - if not _check_image_exist(client, image): - LOG.info('Grafana image not exist, start pulling') - client.pull(consts.GRAFANA_IMAGE, consts.GRAFANA_TAG) - - LOG.info('Createing grafana container') - _create_grafana_container(client) - LOG.info('Grafana container is created') - - time.sleep(5) - - LOG.info('Creating data source for grafana') - _create_data_source() - - LOG.info('Creating dashboard for grafana') - _create_dashboard() - - _update_task_status(task_id) - LOG.info('Finished') - except Exception as e: - _update_task_error(task_id, str(e)) - LOG.exception('Create grafana failed') - - -def _create_dashboard(): - url = 'http://admin:admin@%s:3000/api/dashboards/db' % consts.GRAFANA_IP - path = os.path.join(consts.REPOS_DIR, 'dashboard', '*dashboard.json') - - for i in sorted(glob.iglob(path)): - with open(i) as f: - data = jsonutils.load(f) - try: - HttpClient().post(url, data) - except Exception: - LOG.exception('Create dashboard %s failed', i) - raise - - -def _create_data_source(): - url = 'http://admin:admin@%s:3000/api/datasources' % consts.GRAFANA_IP - data = { - "name": "yardstick", - "type": "influxdb", - "access": "proxy", - "url": "http://%s:8086" % consts.INFLUXDB_IP, - "password": "root", - "user": "root", - "database": "yardstick", - "basicAuth": True, - "basicAuthUser": "admin", - "basicAuthPassword": "admin", - "isDefault": False, - } - try: - HttpClient().post(url, data) - except Exception: - LOG.exception('Create datasources failed') - raise - - -def _create_grafana_container(client): - ports = [3000] - port_bindings = {k: k for k in ports} - restart_policy = {"MaximumRetryCount": 0, "Name": "always"} - host_config = client.create_host_config(port_bindings=port_bindings, - restart_policy=restart_policy) - - LOG.info('Creating container') - container = client.create_container(image='%s:%s' % (consts.GRAFANA_IMAGE, - consts.GRAFANA_TAG), - ports=ports, - detach=True, - tty=True, - host_config=host_config) - LOG.info('Starting container') - client.start(container) - - -def _check_image_exist(client, t): - return any(t in a['RepoTags'][0] for a in client.images() if a['RepoTags']) - - -def create_influxdb(args): - task_id = str(uuid.uuid4()) - - thread = threading.Thread(target=_create_influxdb, args=(task_id,)) - thread.start() - - return result_handler(consts.API_SUCCESS, {'task_id': task_id}) - - -def _create_influxdb(task_id): - _create_task(task_id) - - client = Client(base_url=consts.DOCKER_URL) - - try: - LOG.info('Changing output to influxdb') - _change_output_to_influxdb() - - LOG.info('Checking if influxdb image exist') - if not _check_image_exist(client, '%s:%s' % (consts.INFLUXDB_IMAGE, - consts.INFLUXDB_TAG)): - LOG.info('Influxdb image not exist, start pulling') - client.pull(consts.INFLUXDB_IMAGE, tag=consts.INFLUXDB_TAG) - - LOG.info('Createing influxdb container') - _create_influxdb_container(client) - LOG.info('Influxdb container is created') - - time.sleep(5) - - LOG.info('Config influxdb') - _config_influxdb() - - _update_task_status(task_id) - - LOG.info('Finished') - except Exception as e: - _update_task_error(task_id, str(e)) - LOG.exception('Creating influxdb failed') - - -def _create_influxdb_container(client): - - ports = [8083, 8086] - port_bindings = {k: k for k in ports} - restart_policy = {"MaximumRetryCount": 0, "Name": "always"} - host_config = client.create_host_config(port_bindings=port_bindings, - restart_policy=restart_policy) - - LOG.info('Creating container') - container = client.create_container(image='%s:%s' % (consts.INFLUXDB_IMAGE, - consts.INFLUXDB_TAG), - ports=ports, - detach=True, - tty=True, - host_config=host_config) - LOG.info('Starting container') - client.start(container) - - -def _config_influxdb(): - try: - client = influx.get_data_db_client() - client.create_user(consts.INFLUXDB_USER, - consts.INFLUXDB_PASS, - consts.INFLUXDB_DB_NAME) - client.create_database(consts.INFLUXDB_DB_NAME) - LOG.info('Success to config influxDB') - except Exception: - LOG.exception('Config influxdb failed') - - -def _change_output_to_influxdb(): - common_utils.makedirs(consts.CONF_DIR) - - parser = configparser.ConfigParser() - LOG.info('Reading output sample configuration') - parser.read(consts.CONF_SAMPLE_FILE) - - LOG.info('Set dispatcher to influxdb') - parser.set('DEFAULT', 'dispatcher', 'influxdb') - parser.set('dispatcher_influxdb', 'target', - 'http://%s:8086' % consts.INFLUXDB_IP) - - LOG.info('Writing to %s', consts.CONF_FILE) - with open(consts.CONF_FILE, 'w') as f: - parser.write(f) - - -def prepare_env(args): - task_id = str(uuid.uuid4()) - - thread = threading.Thread(target=_prepare_env_daemon, args=(task_id,)) - thread.start() - - return result_handler(consts.API_SUCCESS, {'task_id': task_id}) - - -def _already_source_openrc(): - """Check if openrc is sourced already""" - return all(os.environ.get(k) for k in ['OS_AUTH_URL', 'OS_USERNAME', - 'OS_PASSWORD', 'EXTERNAL_NETWORK']) - - -def _prepare_env_daemon(task_id): - _create_task(task_id) - - try: - _create_directories() - - rc_file = consts.OPENRC - - LOG.info('Checkout Openrc Environment variable') - if not _already_source_openrc(): - LOG.info('Openrc variable not found in Environment') - if not os.path.exists(rc_file): - LOG.info('Openrc file not found') - installer_ip = os.environ.get('INSTALLER_IP', '192.168.200.2') - installer_type = os.environ.get('INSTALLER_TYPE', 'compass') - LOG.info('Getting openrc file from %s', installer_type) - _get_remote_rc_file(rc_file, installer_ip, installer_type) - LOG.info('Source openrc file') - _source_file(rc_file) - LOG.info('Appending external network') - _append_external_network(rc_file) - LOG.info('Openrc file exist, source openrc file') - _source_file(rc_file) - - LOG.info('Cleaning images') - _clean_images() - - LOG.info('Loading images') - _load_images() - - _update_task_status(task_id) - LOG.info('Finished') - except Exception as e: - _update_task_error(task_id, str(e)) - LOG.exception('Prepare env failed') - - -def _create_directories(): - common_utils.makedirs(consts.CONF_DIR) - - -def _source_file(rc_file): - common_utils.source_env(rc_file) - - -def _get_remote_rc_file(rc_file, installer_ip, installer_type): - - os_fetch_script = os.path.join(consts.RELENG_DIR, consts.FETCH_SCRIPT) - - try: - cmd = [os_fetch_script, '-d', rc_file, '-i', installer_type, - '-a', installer_ip] - p = subprocess.Popen(cmd, stdout=subprocess.PIPE) - p.communicate() - - if p.returncode != 0: - LOG.error('Failed to fetch credentials from installer') - except OSError as e: - if e.errno != errno.EEXIST: - raise - - -def _append_external_network(rc_file): - neutron_client = openstack_utils.get_neutron_client() - networks = neutron_client.list_networks()['networks'] - try: - ext_network = next(n['name'] for n in networks if n['router:external']) - except StopIteration: - LOG.warning("Can't find external network") - else: - cmd = 'export EXTERNAL_NETWORK=%s' % ext_network - try: - with open(rc_file, 'a') as f: - f.write(cmd + '\n') - except OSError as e: - if e.errno != errno.EEXIST: - raise - - -def _clean_images(): - cmd = [consts.CLEAN_IMAGES_SCRIPT] - p = subprocess.Popen(cmd, stdout=subprocess.PIPE, cwd=consts.REPOS_DIR) - output = p.communicate()[0] - LOG.debug(output) - - -def _load_images(): - cmd = [consts.LOAD_IMAGES_SCRIPT] - p = subprocess.Popen(cmd, stdout=subprocess.PIPE, cwd=consts.REPOS_DIR) - output = p.communicate()[0] - LOG.debug(output) - - -def _create_task(task_id): - async_handler.insert({'status': 0, 'task_id': task_id}) - - -def _update_task_status(task_id): - async_handler.update_attr(task_id, {'status': 1}) - - -def _update_task_error(task_id, error): - async_handler.update_attr(task_id, {'status': 2, 'error': error}) - - -def update_openrc(args): - try: - openrc_vars = args['openrc'] - except KeyError: - return result_handler(consts.API_ERROR, 'openrc must be provided') - else: - if not isinstance(openrc_vars, collections.Mapping): - return result_handler(consts.API_ERROR, 'args should be a dict') - - lines = ['export {}={}\n'.format(k, v) for k, v in openrc_vars.items()] - LOG.debug('Writing: %s', ''.join(lines)) - - LOG.info('Writing openrc: Writing') - common_utils.makedirs(consts.CONF_DIR) - - with open(consts.OPENRC, 'w') as f: - f.writelines(lines) - LOG.info('Writing openrc: Done') - - LOG.info('Source openrc: Sourcing') - try: - _source_file(consts.OPENRC) - except Exception as e: - LOG.exception('Failed to source openrc') - return result_handler(consts.API_ERROR, str(e)) - LOG.info('Source openrc: Done') - - return result_handler(consts.API_SUCCESS, {'openrc': openrc_vars}) - - -def upload_pod_file(args): - try: - pod_file = args['file'] - except KeyError: - return result_handler(consts.API_ERROR, 'file must be provided') - - LOG.info('Checking file') - data = yaml.load(pod_file.read()) - if not isinstance(data, collections.Mapping): - return result_handler(consts.API_ERROR, 'invalid yaml file') - - LOG.info('Writing file') - with open(consts.POD_FILE, 'w') as f: - yaml.dump(data, f, default_flow_style=False) - LOG.info('Writing finished') - - return result_handler(consts.API_SUCCESS, {'pod_info': data}) - - -def update_pod_file(args): - try: - pod_dic = args['pod'] - except KeyError: - return result_handler(consts.API_ERROR, 'pod must be provided') - else: - if not isinstance(pod_dic, collections.Mapping): - return result_handler(consts.API_ERROR, 'pod should be a dict') - - LOG.info('Writing file') - with open(consts.POD_FILE, 'w') as f: - yaml.dump(pod_dic, f, default_flow_style=False) - LOG.info('Writing finished') - - return result_handler(consts.API_SUCCESS, {'pod_info': pod_dic}) - - -def update_hosts(hosts_ip): - if not isinstance(hosts_ip, dict): - return result_handler(consts.API_ERROR, 'Error, args should be a dict') - LOG.info('Writing hosts: Writing') - LOG.debug('Writing: %s', hosts_ip) - cmd = ["sudo", "python", "write_hosts.py"] - p = subprocess.Popen(cmd, stdin=PIPE, stdout=PIPE, stderr=PIPE, - cwd = os.path.join(consts.REPOS_DIR, "api/resources")) - _, err = p.communicate(jsonutils.dumps(hosts_ip)) - if p.returncode != 0 : - return result_handler(consts.API_ERROR, err) - LOG.info('Writing hosts: Done') - return result_handler(consts.API_SUCCESS, 'success') diff --git a/api/resources/release_action.py b/api/resources/release_action.py deleted file mode 100644 index 9871c1fc3..000000000 --- a/api/resources/release_action.py +++ /dev/null @@ -1,44 +0,0 @@ -############################################################################## -# Copyright (c) 2016 Huawei Technologies Co.,Ltd and others. -# -# All rights reserved. This program and the accompanying materials -# are made available under the terms of the Apache License, Version 2.0 -# which accompanies this distribution, and is available at -# http://www.apache.org/licenses/LICENSE-2.0 -############################################################################## -from __future__ import absolute_import -import uuid -import os -import logging - -from api.utils.common import result_handler -from api.utils.thread import TaskThread -from yardstick.common import constants as consts -from yardstick.benchmark.core import Param -from yardstick.benchmark.core.task import Task - -logger = logging.getLogger(__name__) -logger.setLevel(logging.DEBUG) - - -def run_test_case(args): - try: - case_name = args['testcase'] - except KeyError: - return result_handler(consts.API_ERROR, 'testcase must be provided') - - testcase = os.path.join(consts.TESTCASE_DIR, '{}.yaml'.format(case_name)) - - task_id = str(uuid.uuid4()) - - task_args = { - 'inputfile': [testcase], - 'task_id': task_id - } - task_args.update(args.get('opts', {})) - - param = Param(task_args) - task_thread = TaskThread(Task().start, param) - task_thread.start() - - return result_handler(consts.API_SUCCESS, {'task_id': task_id}) diff --git a/api/resources/results.py b/api/resources/results.py deleted file mode 100644 index 692e00cc6..000000000 --- a/api/resources/results.py +++ /dev/null @@ -1,69 +0,0 @@ -############################################################################## -# Copyright (c) 2016 Huawei Technologies Co.,Ltd and others. -# -# All rights reserved. This program and the accompanying materials -# are made available under the terms of the Apache License, Version 2.0 -# which accompanies this distribution, and is available at -# http://www.apache.org/licenses/LICENSE-2.0 -############################################################################## -from __future__ import absolute_import -import logging -import uuid -import json - -from api.utils.common import result_handler -from api.database.v1.handlers import TasksHandler -from yardstick.common import constants as consts - -logger = logging.getLogger(__name__) -logger.setLevel(logging.DEBUG) - - -def default(args): - return getResult(args) - - -def getResult(args): - try: - task_id = args['task_id'] - except KeyError: - return result_handler(consts.API_ERROR, 'task_id must be provided') - - try: - uuid.UUID(task_id) - except ValueError: - return result_handler(consts.API_ERROR, 'invalid task_id') - - task_handler = TasksHandler() - try: - task = task_handler.get_task_by_taskid(task_id) - except ValueError: - return result_handler(consts.API_ERROR, 'invalid task_id') - - def _unfinished(): - return result_handler(consts.TASK_NOT_DONE, {}) - - def _finished(): - if task.result: - return result_handler(consts.TASK_DONE, json.loads(task.result)) - else: - return result_handler(consts.TASK_DONE, {}) - - def _error(): - return result_handler(consts.TASK_FAILED, task.error) - - status = task.status - logger.debug('Task status is: %s', status) - - if status not in [consts.TASK_NOT_DONE, - consts.TASK_DONE, - consts.TASK_FAILED]: - return result_handler(consts.API_ERROR, 'internal server error') - - switcher = { - consts.TASK_NOT_DONE: _unfinished, - consts.TASK_DONE: _finished, - consts.TASK_FAILED: _error - } - - return switcher.get(status)() diff --git a/api/resources/samples_action.py b/api/resources/samples_action.py deleted file mode 100644 index 10b9980af..000000000 --- a/api/resources/samples_action.py +++ /dev/null @@ -1,45 +0,0 @@ -############################################################################## -# Copyright (c) 2016 Huawei Technologies Co.,Ltd and others. -# -# All rights reserved. This program and the accompanying materials -# are made available under the terms of the Apache License, Version 2.0 -# which accompanies this distribution, and is available at -# http://www.apache.org/licenses/LICENSE-2.0 -############################################################################## -from __future__ import absolute_import -import uuid -import os -import logging - -from api.utils.common import result_handler -from api.utils.thread import TaskThread -from yardstick.common import constants as consts -from yardstick.benchmark.core import Param -from yardstick.benchmark.core.task import Task - -logger = logging.getLogger(__name__) -logger.setLevel(logging.DEBUG) - - -def run_test_case(args): - try: - case_name = args['testcase'] - except KeyError: - return result_handler(consts.API_ERROR, 'testcase must be provided') - - testcase = os.path.join(consts.SAMPLE_CASE_DIR, - '{}.yaml'.format(case_name)) - - task_id = str(uuid.uuid4()) - - task_args = { - 'inputfile': [testcase], - 'task_id': task_id - } - task_args.update(args.get('opts', {})) - - param = Param(task_args) - task_thread = TaskThread(Task().start, param) - task_thread.start() - - return result_handler(consts.API_SUCCESS, {'task_id': task_id}) diff --git a/api/resources/testcases.py b/api/resources/testcases.py deleted file mode 100644 index 6ee15efb3..000000000 --- a/api/resources/testcases.py +++ /dev/null @@ -1,21 +0,0 @@ -# ############################################################################ -# Copyright (c) 2017 Huawei Technologies Co.,Ltd and others. -# -# All rights reserved. This program and the accompanying materials -# are made available under the terms of the Apache License, Version 2.0 -# which accompanies this distribution, and is available at -# http://www.apache.org/licenses/LICENSE-2.0 -# ############################################################################ -from yardstick.benchmark.core.testcase import Testcase -from yardstick.benchmark.core import Param -from api.utils import common as common_utils - - -def default(args): - return listAllTestcases(args) - - -def listAllTestcases(args): - param = Param(args) - testcase_list = Testcase().list_all(param) - return common_utils.result_handler(1, testcase_list) diff --git a/api/resources/testsuites_action.py b/api/resources/testsuites_action.py deleted file mode 100644 index e37eacc3e..000000000 --- a/api/resources/testsuites_action.py +++ /dev/null @@ -1,46 +0,0 @@ -############################################################################## -# Copyright (c) 2016 Huawei Technologies Co.,Ltd and others. -# -# All rights reserved. This program and the accompanying materials -# are made available under the terms of the Apache License, Version 2.0 -# which accompanies this distribution, and is available at -# http://www.apache.org/licenses/LICENSE-2.0 -############################################################################## -from __future__ import absolute_import -import uuid -import os -import logging - -from api.utils.common import result_handler -from api.utils.thread import TaskThread -from yardstick.common import constants as consts -from yardstick.benchmark.core import Param -from yardstick.benchmark.core.task import Task - -logger = logging.getLogger(__name__) -logger.setLevel(logging.DEBUG) - - -def run_test_suite(args): - try: - suite_name = args['testsuite'] - except KeyError: - return result_handler(consts.API_ERROR, 'testsuite must be provided') - - testsuite = os.path.join(consts.TESTSUITE_DIR, - '{}.yaml'.format(suite_name)) - - task_id = str(uuid.uuid4()) - - task_args = { - 'inputfile': [testsuite], - 'task_id': task_id, - 'suite': True - } - task_args.update(args.get('opts', {})) - - param = Param(task_args) - task_thread = TaskThread(Task().start, param) - task_thread.start() - - return result_handler(consts.API_SUCCESS, {'task_id': task_id}) diff --git a/api/resources/v1/__init__.py b/api/resources/v1/__init__.py new file mode 100644 index 000000000..e69de29bb --- /dev/null +++ b/api/resources/v1/__init__.py diff --git a/api/resources/v1/asynctasks.py b/api/resources/v1/asynctasks.py new file mode 100644 index 000000000..759df214c --- /dev/null +++ b/api/resources/v1/asynctasks.py @@ -0,0 +1,65 @@ +# ############################################################################ +# Copyright (c) 2017 Huawei Technologies Co.,Ltd and others. +# +# All rights reserved. This program and the accompanying materials +# are made available under the terms of the Apache License, Version 2.0 +# which accompanies this distribution, and is available at +# http://www.apache.org/licenses/LICENSE-2.0 +# ############################################################################ +import uuid +import logging + +from api import ApiResource +from api.database.v1.handlers import AsyncTaskHandler +from yardstick.common import constants as consts +from yardstick.common.utils import result_handler + +LOG = logging.getLogger(__name__) +LOG.setLevel(logging.DEBUG) + + +class V1AsyncTask(ApiResource): + + def get(self): + args = self._get_args() + + try: + task_id = args['task_id'] + except KeyError: + return result_handler(consts.API_ERROR, 'task_id must be provided') + + try: + uuid.UUID(task_id) + except ValueError: + return result_handler(consts.API_ERROR, 'invalid task_id') + + asynctask_handler = AsyncTaskHandler() + try: + asynctask = asynctask_handler.get_task_by_taskid(task_id) + except ValueError: + return result_handler(consts.API_ERROR, 'invalid task_id') + + def _unfinished(): + return result_handler(consts.TASK_NOT_DONE, {}) + + def _finished(): + return result_handler(consts.TASK_DONE, {}) + + def _error(): + return result_handler(consts.TASK_FAILED, asynctask.error) + + status = asynctask.status + LOG.debug('Task status is: %s', status) + + if status not in [consts.TASK_NOT_DONE, + consts.TASK_DONE, + consts.TASK_FAILED]: + return result_handler(consts.API_ERROR, 'internal server error') + + switcher = { + consts.TASK_NOT_DONE: _unfinished, + consts.TASK_DONE: _finished, + consts.TASK_FAILED: _error + } + + return switcher.get(status)() diff --git a/api/resources/v1/env.py b/api/resources/v1/env.py new file mode 100644 index 000000000..8943db3d1 --- /dev/null +++ b/api/resources/v1/env.py @@ -0,0 +1,439 @@ +############################################################################## +# Copyright (c) 2016 Huawei Technologies Co.,Ltd and others. +# +# All rights reserved. This program and the accompanying materials +# are made available under the terms of the Apache License, Version 2.0 +# which accompanies this distribution, and is available at +# http://www.apache.org/licenses/LICENSE-2.0 +############################################################################## +from __future__ import absolute_import + +import errno +import logging +import os +import subprocess +import threading +import time +import uuid +import glob +import yaml +import collections + +from six.moves import configparser +from oslo_serialization import jsonutils +from docker import Client + +from api.database.v1.handlers import AsyncTaskHandler +from api.utils import influx +from api import ApiResource +from yardstick.common import constants as consts +from yardstick.common import utils +from yardstick.common.utils import result_handler +from yardstick.common import openstack_utils +from yardstick.common.httpClient import HttpClient + + +LOG = logging.getLogger(__name__) +LOG.setLevel(logging.DEBUG) + +async_handler = AsyncTaskHandler() + + +class V1Env(ApiResource): + + def post(self): + return self._dispatch_post() + + def create_grafana(self, args): + task_id = str(uuid.uuid4()) + + thread = threading.Thread(target=self._create_grafana, args=(task_id,)) + thread.start() + + return result_handler(consts.API_SUCCESS, {'task_id': task_id}) + + def _create_grafana(self, task_id): + self._create_task(task_id) + + client = Client(base_url=consts.DOCKER_URL) + + try: + LOG.info('Checking if grafana image exist') + image = '{}:{}'.format(consts.GRAFANA_IMAGE, consts.GRAFANA_TAG) + if not self._check_image_exist(client, image): + LOG.info('Grafana image not exist, start pulling') + client.pull(consts.GRAFANA_IMAGE, consts.GRAFANA_TAG) + + LOG.info('Createing grafana container') + container = self._create_grafana_container(client) + LOG.info('Grafana container is created') + + time.sleep(5) + + container = client.inspect_container(container['Id']) + ip = container['NetworkSettings']['Networks']['bridge']['IPAddress'] + LOG.debug('container ip is: %s', ip) + + LOG.info('Creating data source for grafana') + self._create_data_source(ip) + + LOG.info('Creating dashboard for grafana') + self._create_dashboard(ip) + + self._update_task_status(task_id) + LOG.info('Finished') + except Exception as e: + self._update_task_error(task_id, str(e)) + LOG.exception('Create grafana failed') + + def _create_dashboard(self, ip): + url = 'http://admin:admin@{}:{}/api/dashboards/db'.format(ip, consts.GRAFANA_PORT) + path = os.path.join(consts.REPOS_DIR, 'dashboard', '*dashboard.json') + + for i in sorted(glob.iglob(path)): + with open(i) as f: + data = jsonutils.load(f) + try: + HttpClient().post(url, data) + except Exception: + LOG.exception('Create dashboard %s failed', i) + raise + + def _create_data_source(self, ip): + url = 'http://admin:admin@{}:{}/api/datasources'.format(ip, consts.GRAFANA_PORT) + influx_conf = utils.parse_ini_file(consts.CONF_FILE) + + try: + influx_url = influx_conf['dispatcher_influxdb']['target'] + except KeyError: + LOG.exception('influxdb url not set in yardstick.conf') + raise + + data = { + "name": "yardstick", + "type": "influxdb", + "access": "proxy", + "url": influx_url, + "password": "root", + "user": "root", + "database": "yardstick", + "basicAuth": True, + "basicAuthUser": "admin", + "basicAuthPassword": "admin", + "isDefault": False, + } + try: + HttpClient().post(url, data) + except Exception: + LOG.exception('Create datasources failed') + raise + + def _create_grafana_container(self, client): + ports = [consts.GRAFANA_PORT] + port_bindings = {consts.GRAFANA_PORT: consts.GRAFANA_MAPPING_PORT} + restart_policy = {"MaximumRetryCount": 0, "Name": "always"} + host_config = client.create_host_config(port_bindings=port_bindings, + restart_policy=restart_policy) + + LOG.info('Creating container') + container = client.create_container(image='%s:%s' % + (consts.GRAFANA_IMAGE, + consts.GRAFANA_TAG), + ports=ports, + detach=True, + tty=True, + host_config=host_config) + LOG.info('Starting container') + client.start(container) + return container + + def _check_image_exist(self, client, t): + return any(t in a['RepoTags'][0] + for a in client.images() if a['RepoTags']) + + def create_influxdb(self, args): + task_id = str(uuid.uuid4()) + + thread = threading.Thread(target=self._create_influxdb, args=(task_id,)) + thread.start() + + return result_handler(consts.API_SUCCESS, {'task_id': task_id}) + + def _create_influxdb(self, task_id): + self._create_task(task_id) + + client = Client(base_url=consts.DOCKER_URL) + + try: + LOG.info('Checking if influxdb image exist') + if not self._check_image_exist(client, '%s:%s' % + (consts.INFLUXDB_IMAGE, + consts.INFLUXDB_TAG)): + LOG.info('Influxdb image not exist, start pulling') + client.pull(consts.INFLUXDB_IMAGE, tag=consts.INFLUXDB_TAG) + + LOG.info('Createing influxdb container') + container = self._create_influxdb_container(client) + LOG.info('Influxdb container is created') + + time.sleep(5) + + container = client.inspect_container(container['Id']) + ip = container['NetworkSettings']['Networks']['bridge']['IPAddress'] + LOG.debug('container ip is: %s', ip) + + LOG.info('Changing output to influxdb') + self._change_output_to_influxdb(ip) + + LOG.info('Config influxdb') + self._config_influxdb() + + self._update_task_status(task_id) + + LOG.info('Finished') + except Exception as e: + self._update_task_error(task_id, str(e)) + LOG.exception('Creating influxdb failed') + + def _create_influxdb_container(self, client): + + ports = [consts.INFLUXDB_DASHBOARD_PORT, consts.INFLUXDB_PORT] + port_bindings = {k: k for k in ports} + restart_policy = {"MaximumRetryCount": 0, "Name": "always"} + host_config = client.create_host_config(port_bindings=port_bindings, + restart_policy=restart_policy) + + LOG.info('Creating container') + container = client.create_container(image='%s:%s' % + (consts.INFLUXDB_IMAGE, + consts.INFLUXDB_TAG), + ports=ports, + detach=True, + tty=True, + host_config=host_config) + LOG.info('Starting container') + client.start(container) + return container + + def _config_influxdb(self): + try: + client = influx.get_data_db_client() + client.create_user(consts.INFLUXDB_USER, + consts.INFLUXDB_PASS, + consts.INFLUXDB_DB_NAME) + client.create_database(consts.INFLUXDB_DB_NAME) + LOG.info('Success to config influxDB') + except Exception: + LOG.exception('Config influxdb failed') + + def _change_output_to_influxdb(self, ip): + utils.makedirs(consts.CONF_DIR) + + parser = configparser.ConfigParser() + LOG.info('Reading output sample configuration') + parser.read(consts.CONF_SAMPLE_FILE) + + LOG.info('Set dispatcher to influxdb') + parser.set('DEFAULT', 'dispatcher', 'influxdb') + parser.set('dispatcher_influxdb', 'target', + 'http://{}:{}'.format(ip, consts.INFLUXDB_PORT)) + + LOG.info('Writing to %s', consts.CONF_FILE) + with open(consts.CONF_FILE, 'w') as f: + parser.write(f) + + def prepare_env(self, args): + task_id = str(uuid.uuid4()) + + thread = threading.Thread(target=self._prepare_env_daemon, + args=(task_id,)) + thread.start() + + return result_handler(consts.API_SUCCESS, {'task_id': task_id}) + + def _already_source_openrc(self): + """Check if openrc is sourced already""" + return all(os.environ.get(k) for k in ['OS_AUTH_URL', + 'OS_USERNAME', + 'OS_PASSWORD', + 'EXTERNAL_NETWORK']) + + def _prepare_env_daemon(self, task_id): + self._create_task(task_id) + + try: + self._create_directories() + + rc_file = consts.OPENRC + + LOG.info('Checkout Openrc Environment variable') + if not self._already_source_openrc(): + LOG.info('Openrc variable not found in Environment') + if not os.path.exists(rc_file): + LOG.info('Openrc file not found') + installer_ip = os.environ.get('INSTALLER_IP', + '192.168.200.2') + installer_type = os.environ.get('INSTALLER_TYPE', 'compass') + LOG.info('Getting openrc file from %s', installer_type) + self._get_remote_rc_file(rc_file, + installer_ip, + installer_type) + LOG.info('Source openrc file') + self._source_file(rc_file) + LOG.info('Appending external network') + self._append_external_network(rc_file) + LOG.info('Openrc file exist, source openrc file') + self._source_file(rc_file) + + LOG.info('Cleaning images') + self._clean_images() + + LOG.info('Loading images') + self._load_images() + + self._update_task_status(task_id) + LOG.info('Finished') + except Exception as e: + self._update_task_error(task_id, str(e)) + LOG.exception('Prepare env failed') + + def _create_directories(self): + utils.makedirs(consts.CONF_DIR) + + def _source_file(self, rc_file): + utils.source_env(rc_file) + + def _get_remote_rc_file(self, rc_file, installer_ip, installer_type): + + os_fetch_script = os.path.join(consts.RELENG_DIR, consts.FETCH_SCRIPT) + + try: + cmd = [os_fetch_script, '-d', rc_file, '-i', installer_type, + '-a', installer_ip] + p = subprocess.Popen(cmd, stdout=subprocess.PIPE) + p.communicate() + + if p.returncode != 0: + LOG.error('Failed to fetch credentials from installer') + except OSError as e: + if e.errno != errno.EEXIST: + raise + + def _append_external_network(self, rc_file): + neutron_client = openstack_utils.get_neutron_client() + networks = neutron_client.list_networks()['networks'] + try: + ext_network = next(n['name'] + for n in networks if n['router:external']) + except StopIteration: + LOG.warning("Can't find external network") + else: + cmd = 'export EXTERNAL_NETWORK=%s' % ext_network + try: + with open(rc_file, 'a') as f: + f.write(cmd + '\n') + except OSError as e: + if e.errno != errno.EEXIST: + raise + + def _clean_images(self): + cmd = [consts.CLEAN_IMAGES_SCRIPT] + p = subprocess.Popen(cmd, stdout=subprocess.PIPE, cwd=consts.REPOS_DIR) + output = p.communicate()[0] + LOG.debug(output) + + def _load_images(self): + cmd = [consts.LOAD_IMAGES_SCRIPT] + p = subprocess.Popen(cmd, stdout=subprocess.PIPE, cwd=consts.REPOS_DIR) + output = p.communicate()[0] + LOG.debug(output) + + def _create_task(self, task_id): + async_handler.insert({'status': 0, 'task_id': task_id}) + + def _update_task_status(self, task_id): + async_handler.update_attr(task_id, {'status': 1}) + + def _update_task_error(self, task_id, error): + async_handler.update_attr(task_id, {'status': 2, 'error': error}) + + def update_openrc(self, args): + try: + openrc_vars = args['openrc'] + except KeyError: + return result_handler(consts.API_ERROR, 'openrc must be provided') + else: + if not isinstance(openrc_vars, collections.Mapping): + return result_handler(consts.API_ERROR, 'args should be a dict') + + lines = ['export {}={}\n'.format(k, v) for k, v in openrc_vars.items()] + LOG.debug('Writing: %s', ''.join(lines)) + + LOG.info('Writing openrc: Writing') + utils.makedirs(consts.CONF_DIR) + + with open(consts.OPENRC, 'w') as f: + f.writelines(lines) + LOG.info('Writing openrc: Done') + + LOG.info('Source openrc: Sourcing') + try: + self._source_file(consts.OPENRC) + except Exception as e: + LOG.exception('Failed to source openrc') + return result_handler(consts.API_ERROR, str(e)) + LOG.info('Source openrc: Done') + + return result_handler(consts.API_SUCCESS, {'openrc': openrc_vars}) + + def upload_pod_file(self, args): + try: + pod_file = args['file'] + except KeyError: + return result_handler(consts.API_ERROR, 'file must be provided') + + LOG.info('Checking file') + data = yaml.load(pod_file.read()) + if not isinstance(data, collections.Mapping): + return result_handler(consts.API_ERROR, 'invalid yaml file') + + LOG.info('Writing file') + with open(consts.POD_FILE, 'w') as f: + yaml.dump(data, f, default_flow_style=False) + LOG.info('Writing finished') + + return result_handler(consts.API_SUCCESS, {'pod_info': data}) + + def update_pod_file(self, args): + try: + pod_dic = args['pod'] + except KeyError: + return result_handler(consts.API_ERROR, 'pod must be provided') + else: + if not isinstance(pod_dic, collections.Mapping): + return result_handler(consts.API_ERROR, 'pod should be a dict') + + LOG.info('Writing file') + with open(consts.POD_FILE, 'w') as f: + yaml.dump(pod_dic, f, default_flow_style=False) + LOG.info('Writing finished') + + return result_handler(consts.API_SUCCESS, {'pod_info': pod_dic}) + + def update_hosts(self, hosts_ip): + if not isinstance(hosts_ip, collections.Mapping): + return result_handler(consts.API_ERROR, 'args should be a dict') + LOG.info('Writing hosts: Writing') + LOG.debug('Writing: %s', hosts_ip) + cmd = ["sudo", "python", "write_hosts.py"] + p = subprocess.Popen(cmd, + stdin=subprocess.PIPE, + stdout=subprocess.PIPE, + stderr=subprocess.PIPE, + cwd=os.path.join(consts.REPOS_DIR, + "api/resources")) + _, err = p.communicate(jsonutils.dumps(hosts_ip)) + if p.returncode != 0: + return result_handler(consts.API_ERROR, err) + LOG.info('Writing hosts: Done') + return result_handler(consts.API_SUCCESS, 'success') diff --git a/api/resources/v1/results.py b/api/resources/v1/results.py new file mode 100644 index 000000000..0493b43b6 --- /dev/null +++ b/api/resources/v1/results.py @@ -0,0 +1,78 @@ +############################################################################## +# Copyright (c) 2016 Huawei Technologies Co.,Ltd and others. +# +# All rights reserved. This program and the accompanying materials +# are made available under the terms of the Apache License, Version 2.0 +# which accompanies this distribution, and is available at +# http://www.apache.org/licenses/LICENSE-2.0 +############################################################################## +from __future__ import absolute_import +import logging +import uuid +import json +import os + +from flasgger.utils import swag_from + +from api import ApiResource +from api.database.v1.handlers import TasksHandler +from yardstick.common import constants as consts +from yardstick.common.utils import result_handler +from api.swagger import models + +LOG = logging.getLogger(__name__) +LOG.setLevel(logging.DEBUG) + + +ResultModel = models.ResultModel + + +class V1Result(ApiResource): + + @swag_from(os.path.join(consts.REPOS_DIR, 'api/swagger/docs/results.yaml')) + def get(self): + args = self._get_args() + + try: + task_id = args['task_id'] + except KeyError: + return result_handler(consts.API_ERROR, 'task_id must be provided') + + try: + uuid.UUID(task_id) + except ValueError: + return result_handler(consts.API_ERROR, 'invalid task_id') + + task_handler = TasksHandler() + try: + task = task_handler.get_task_by_taskid(task_id) + except ValueError: + return result_handler(consts.API_ERROR, 'invalid task_id') + + def _unfinished(): + return result_handler(consts.TASK_NOT_DONE, {}) + + def _finished(): + if task.result: + return result_handler(consts.TASK_DONE, json.loads(task.result)) + else: + return result_handler(consts.TASK_DONE, {}) + + def _error(): + return result_handler(consts.TASK_FAILED, task.error) + + status = task.status + LOG.debug('Task status is: %s', status) + + if status not in [consts.TASK_NOT_DONE, + consts.TASK_DONE, + consts.TASK_FAILED]: + return result_handler(consts.API_ERROR, 'internal server error') + + switcher = { + consts.TASK_NOT_DONE: _unfinished, + consts.TASK_DONE: _finished, + consts.TASK_FAILED: _error + } + + return switcher.get(status)() diff --git a/api/resources/v1/testcases.py b/api/resources/v1/testcases.py new file mode 100644 index 000000000..f15947245 --- /dev/null +++ b/api/resources/v1/testcases.py @@ -0,0 +1,115 @@ +# ############################################################################ +# Copyright (c) 2017 Huawei Technologies Co.,Ltd and others. +# +# All rights reserved. This program and the accompanying materials +# are made available under the terms of the Apache License, Version 2.0 +# which accompanies this distribution, and is available at +# http://www.apache.org/licenses/LICENSE-2.0 +# ############################################################################ + +from __future__ import absolute_import +import uuid +import os +import logging + +from flasgger.utils import swag_from + +from yardstick.benchmark.core.testcase import Testcase +from yardstick.benchmark.core.task import Task +from yardstick.benchmark.core import Param +from yardstick.common import constants as consts +from yardstick.common.utils import result_handler +from api.utils.thread import TaskThread +from api import ApiResource +from api.swagger import models +from api.database.v1.handlers import TasksHandler + +LOG = logging.getLogger(__name__) +LOG.setLevel(logging.DEBUG) + + +class V1Testcase(ApiResource): + + def get(self): + param = Param({}) + testcase_list = Testcase().list_all(param) + return result_handler(consts.API_SUCCESS, testcase_list) + + +class V1CaseDocs(ApiResource): + + def get(self, case_name): + docs_path = os.path.join(consts.DOCS_DIR, '{}.rst'.format(case_name)) + + if not os.path.exists(docs_path): + return result_handler(consts.API_ERROR, 'case not exists') + + LOG.info('Reading %s', case_name) + with open(docs_path) as f: + content = f.read() + + return result_handler(consts.API_SUCCESS, {'docs': content}) + + +TestCaseActionModel = models.TestCaseActionModel +TestCaseActionArgsModel = models.TestCaseActionArgsModel +TestCaseActionArgsOptsModel = models.TestCaseActionArgsOptsModel +TestCaseActionArgsOptsTaskArgModel = models.TestCaseActionArgsOptsTaskArgModel + + +class V1ReleaseCase(ApiResource): + + @swag_from(os.path.join(consts.REPOS_DIR, + 'api/swagger/docs/release_action.yaml')) + def post(self): + return self._dispatch_post() + + def run_test_case(self, args): + try: + name = args['testcase'] + except KeyError: + return result_handler(consts.API_ERROR, 'testcase must be provided') + + testcase = os.path.join(consts.TESTCASE_DIR, '{}.yaml'.format(name)) + + task_id = str(uuid.uuid4()) + + task_args = { + 'inputfile': [testcase], + 'task_id': task_id + } + task_args.update(args.get('opts', {})) + + param = Param(task_args) + task_thread = TaskThread(Task().start, param, TasksHandler()) + task_thread.start() + + return result_handler(consts.API_SUCCESS, {'task_id': task_id}) + + +class V1SampleCase(ApiResource): + + def post(self): + return self._dispatch_post() + + def run_test_case(self, args): + try: + name = args['testcase'] + except KeyError: + return result_handler(consts.API_ERROR, 'testcase must be provided') + + testcase = os.path.join(consts.SAMPLE_CASE_DIR, '{}.yaml'.format(name)) + + task_id = str(uuid.uuid4()) + + task_args = { + 'inputfile': [testcase], + 'task_id': task_id + } + task_args.update(args.get('opts', {})) + + param = Param(task_args) + task_thread = TaskThread(Task().start, param, TasksHandler()) + task_thread.start() + + return result_handler(consts.API_SUCCESS, {'task_id': task_id}) diff --git a/api/resources/v1/testsuites.py b/api/resources/v1/testsuites.py new file mode 100644 index 000000000..5f72c2ea6 --- /dev/null +++ b/api/resources/v1/testsuites.py @@ -0,0 +1,64 @@ +############################################################################## +# Copyright (c) 2016 Huawei Technologies Co.,Ltd and others. +# +# All rights reserved. This program and the accompanying materials +# are made available under the terms of the Apache License, Version 2.0 +# which accompanies this distribution, and is available at +# http://www.apache.org/licenses/LICENSE-2.0 +############################################################################## +from __future__ import absolute_import +import uuid +import os +import logging + +from flasgger.utils import swag_from + +from api import ApiResource +from api.utils.thread import TaskThread +from yardstick.common import constants as consts +from yardstick.common.utils import result_handler +from yardstick.benchmark.core import Param +from yardstick.benchmark.core.task import Task +from api.swagger import models + +LOG = logging.getLogger(__name__) +LOG.setLevel(logging.DEBUG) + + +TestSuiteActionModel = models.TestSuiteActionModel +TestSuiteActionArgsModel = models.TestSuiteActionArgsModel +TestSuiteActionArgsOptsModel = models.TestSuiteActionArgsOptsModel +TestSuiteActionArgsOptsTaskArgModel = \ + models.TestSuiteActionArgsOptsTaskArgModel + + +class V1Testsuite(ApiResource): + + @swag_from(os.path.join(consts.REPOS_DIR, + 'api/swagger/docs/testsuites_action.yaml')) + def post(self): + return self._dispatch_post() + + def run_test_suite(self, args): + try: + name = args['testsuite'] + except KeyError: + return result_handler(consts.API_ERROR, + 'testsuite must be provided') + + testsuite = os.path.join(consts.TESTSUITE_DIR, '{}.yaml'.format(name)) + + task_id = str(uuid.uuid4()) + + task_args = { + 'inputfile': [testsuite], + 'task_id': task_id, + 'suite': True + } + task_args.update(args.get('opts', {})) + + param = Param(task_args) + task_thread = TaskThread(Task().start, param) + task_thread.start() + + return result_handler(consts.API_SUCCESS, {'task_id': task_id}) diff --git a/api/resources/v2/__init__.py b/api/resources/v2/__init__.py new file mode 100644 index 000000000..e69de29bb --- /dev/null +++ b/api/resources/v2/__init__.py diff --git a/api/resources/v2/containers.py b/api/resources/v2/containers.py new file mode 100644 index 000000000..66dc94120 --- /dev/null +++ b/api/resources/v2/containers.py @@ -0,0 +1,383 @@ +############################################################################## +# Copyright (c) 2017 Huawei Technologies Co.,Ltd. +# +# All rights reserved. This program and the accompanying materials +# are made available under the terms of the Apache License, Version 2.0 +# which accompanies this distribution, and is available at +# http://www.apache.org/licenses/LICENSE-2.0 +############################################################################## +from __future__ import absolute_import + +import logging +import threading +import time +import uuid +import os +import glob + +from six.moves import configparser +from oslo_serialization import jsonutils +from docker import Client + +from api import ApiResource +from api.utils import influx +from api.database.v2.handlers import V2ContainerHandler +from api.database.v2.handlers import V2EnvironmentHandler +from yardstick.common import constants as consts +from yardstick.common import utils +from yardstick.common.utils import result_handler +from yardstick.common.utils import get_free_port +from yardstick.common.httpClient import HttpClient + + +LOG = logging.getLogger(__name__) +LOG.setLevel(logging.DEBUG) + +environment_handler = V2EnvironmentHandler() +container_handler = V2ContainerHandler() + + +class V2Containers(ApiResource): + + def post(self): + return self._dispatch_post() + + def create_influxdb(self, args): + try: + environment_id = args['environment_id'] + except KeyError: + return result_handler(consts.API_ERROR, 'environment_id must be provided') + + try: + uuid.UUID(environment_id) + except ValueError: + return result_handler(consts.API_ERROR, 'invalid environment id') + + try: + environment = environment_handler.get_by_uuid(environment_id) + except ValueError: + return result_handler(consts.API_ERROR, 'no such environment id') + + container_info = environment.container_id + container_info = jsonutils.loads(container_info) if container_info else {} + + if container_info.get('influxdb'): + return result_handler(consts.API_ERROR, 'influxdb container already exist') + + name = 'influxdb-{}'.format(environment_id[:8]) + port = get_free_port(consts.SERVER_IP) + container_id = str(uuid.uuid4()) + LOG.info('%s will launch on : %s', name, port) + + LOG.info('launch influxdb background') + args = (name, port, container_id) + thread = threading.Thread(target=self._create_influxdb, args=args) + thread.start() + + LOG.info('record container in database') + container_init_data = { + 'uuid': container_id, + 'environment_id': environment_id, + 'name': name, + 'port': port, + 'status': 0 + } + container_handler.insert(container_init_data) + + LOG.info('update container in environment') + container_info['influxdb'] = container_id + environment_info = {'container_id': jsonutils.dumps(container_info)} + environment_handler.update_attr(environment_id, environment_info) + + return result_handler(consts.API_SUCCESS, {'uuid': container_id}) + + def _check_image_exist(self, client, t): + return any(t in a['RepoTags'][0] + for a in client.images() if a['RepoTags']) + + def _create_influxdb(self, name, port, container_id): + client = Client(base_url=consts.DOCKER_URL) + + try: + LOG.info('Checking if influxdb image exist') + if not self._check_image_exist(client, '%s:%s' % + (consts.INFLUXDB_IMAGE, + consts.INFLUXDB_TAG)): + LOG.info('Influxdb image not exist, start pulling') + client.pull(consts.INFLUXDB_IMAGE, tag=consts.INFLUXDB_TAG) + + LOG.info('Createing influxdb container') + container = self._create_influxdb_container(client, name, port) + LOG.info('Influxdb container is created') + + time.sleep(5) + + container = client.inspect_container(container['Id']) + ip = container['NetworkSettings']['Networks']['bridge']['IPAddress'] + LOG.debug('container ip is: %s', ip) + + LOG.info('Changing output to influxdb') + self._change_output_to_influxdb(ip) + + LOG.info('Config influxdb') + self._config_influxdb() + + container_handler.update_attr(container_id, {'status': 1}) + + LOG.info('Finished') + except Exception: + container_handler.update_attr(container_id, {'status': 2}) + LOG.exception('Creating influxdb failed') + + def _create_influxdb_container(self, client, name, port): + + ports = [port] + port_bindings = {8086: port} + restart_policy = {"MaximumRetryCount": 0, "Name": "always"} + host_config = client.create_host_config(port_bindings=port_bindings, + restart_policy=restart_policy) + + LOG.info('Creating container') + container = client.create_container(image='%s:%s' % + (consts.INFLUXDB_IMAGE, + consts.INFLUXDB_TAG), + ports=ports, + name=name, + detach=True, + tty=True, + host_config=host_config) + LOG.info('Starting container') + client.start(container) + return container + + def _config_influxdb(self): + try: + client = influx.get_data_db_client() + client.create_user(consts.INFLUXDB_USER, + consts.INFLUXDB_PASS, + consts.INFLUXDB_DB_NAME) + client.create_database(consts.INFLUXDB_DB_NAME) + LOG.info('Success to config influxDB') + except Exception: + LOG.exception('Config influxdb failed') + + def _change_output_to_influxdb(self, ip): + utils.makedirs(consts.CONF_DIR) + + parser = configparser.ConfigParser() + LOG.info('Reading output sample configuration') + parser.read(consts.CONF_SAMPLE_FILE) + + LOG.info('Set dispatcher to influxdb') + parser.set('DEFAULT', 'dispatcher', 'influxdb') + parser.set('dispatcher_influxdb', 'target', + 'http://{}:{}'.format(ip, 8086)) + + LOG.info('Writing to %s', consts.CONF_FILE) + with open(consts.CONF_FILE, 'w') as f: + parser.write(f) + + def create_grafana(self, args): + try: + environment_id = args['environment_id'] + except KeyError: + return result_handler(consts.API_ERROR, 'environment_id must be provided') + + try: + uuid.UUID(environment_id) + except ValueError: + return result_handler(consts.API_ERROR, 'invalid environment id') + + try: + environment = environment_handler.get_by_uuid(environment_id) + except ValueError: + return result_handler(consts.API_ERROR, 'no such environment id') + + container_info = environment.container_id + container_info = jsonutils.loads(container_info) if container_info else {} + + if not container_info.get('influxdb'): + return result_handler(consts.API_ERROR, 'influxdb not set') + + if container_info.get('grafana'): + return result_handler(consts.API_ERROR, 'grafana container already exists') + + name = 'grafana-{}'.format(environment_id[:8]) + port = get_free_port(consts.SERVER_IP) + container_id = str(uuid.uuid4()) + + args = (name, port, container_id) + thread = threading.Thread(target=self._create_grafana, args=args) + thread.start() + + container_init_data = { + 'uuid': container_id, + 'environment_id': environment_id, + 'name': name, + 'port': port, + 'status': 0 + } + container_handler.insert(container_init_data) + + container_info['grafana'] = container_id + environment_info = {'container_id': jsonutils.dumps(container_info)} + environment_handler.update_attr(environment_id, environment_info) + + return result_handler(consts.API_SUCCESS, {'uuid': container_id}) + + def _create_grafana(self, name, port, container_id): + client = Client(base_url=consts.DOCKER_URL) + + try: + LOG.info('Checking if grafana image exist') + image = '{}:{}'.format(consts.GRAFANA_IMAGE, consts.GRAFANA_TAG) + if not self._check_image_exist(client, image): + LOG.info('Grafana image not exist, start pulling') + client.pull(consts.GRAFANA_IMAGE, consts.GRAFANA_TAG) + + LOG.info('Createing grafana container') + container = self._create_grafana_container(client, name, port) + LOG.info('Grafana container is created') + + time.sleep(5) + + container = client.inspect_container(container['Id']) + ip = container['NetworkSettings']['Networks']['bridge']['IPAddress'] + LOG.debug('container ip is: %s', ip) + + LOG.info('Creating data source for grafana') + self._create_data_source(ip) + + LOG.info('Creating dashboard for grafana') + self._create_dashboard(ip) + + container_handler.update_attr(container_id, {'status': 1}) + LOG.info('Finished') + except Exception: + container_handler.update_attr(container_id, {'status': 2}) + LOG.exception('Create grafana failed') + + def _create_dashboard(self, ip): + url = 'http://admin:admin@{}:{}/api/dashboards/db'.format(ip, 3000) + path = os.path.join(consts.REPOS_DIR, 'dashboard', '*dashboard.json') + + for i in sorted(glob.iglob(path)): + with open(i) as f: + data = jsonutils.load(f) + try: + HttpClient().post(url, data) + except Exception: + LOG.exception('Create dashboard %s failed', i) + raise + + def _create_data_source(self, ip): + url = 'http://admin:admin@{}:{}/api/datasources'.format(ip, 3000) + + influx_conf = utils.parse_ini_file(consts.CONF_FILE) + try: + influx_url = influx_conf['dispatcher_influxdb']['target'] + except KeyError: + LOG.exception('influxdb url not set in yardstick.conf') + raise + + data = { + "name": "yardstick", + "type": "influxdb", + "access": "proxy", + "url": influx_url, + "password": "root", + "user": "root", + "database": "yardstick", + "basicAuth": True, + "basicAuthUser": "admin", + "basicAuthPassword": "admin", + "isDefault": False, + } + try: + HttpClient().post(url, data) + except Exception: + LOG.exception('Create datasources failed') + raise + + def _create_grafana_container(self, client, name, port): + ports = [3000] + port_bindings = {3000: port} + restart_policy = {"MaximumRetryCount": 0, "Name": "always"} + host_config = client.create_host_config(port_bindings=port_bindings, + restart_policy=restart_policy) + + LOG.info('Creating container') + container = client.create_container(image='%s:%s' % + (consts.GRAFANA_IMAGE, + consts.GRAFANA_TAG), + name=name, + ports=ports, + detach=True, + tty=True, + host_config=host_config) + LOG.info('Starting container') + client.start(container) + return container + + +class V2Container(ApiResource): + + def get(self, container_id): + try: + uuid.UUID(container_id) + except ValueError: + return result_handler(consts.API_ERROR, 'invalid container id') + + try: + container = container_handler.get_by_uuid(container_id) + except ValueError: + return result_handler(consts.API_ERROR, 'no such container id') + + name = container.name + client = Client(base_url=consts.DOCKER_URL) + info = client.inspect_container(name) + + data = { + 'name': name, + 'status': info.get('State', {}).get('Status', 'error'), + 'time': info.get('Created'), + 'port': container.port + } + + return result_handler(consts.API_SUCCESS, {'container': data}) + + def delete(self, container_id): + try: + uuid.UUID(container_id) + except ValueError: + return result_handler(consts.API_ERROR, 'invalid container id') + + try: + container = container_handler.get_by_uuid(container_id) + except ValueError: + return result_handler(consts.API_ERROR, 'no such container id') + + environment_id = container.environment_id + + client = Client(base_url=consts.DOCKER_URL) + LOG.info('delete container: %s', container.name) + try: + client.remove_container(container.name, force=True) + except Exception: + LOG.exception('delete container failed') + return result_handler(consts.API_ERROR, 'delete container failed') + + LOG.info('delete container in database') + container_handler.delete_by_uuid(container_id) + + LOG.info('update container in environment') + environment = environment_handler.get_by_uuid(environment_id) + container_info = jsonutils.loads(environment.container_id) + key = next((k for k, v in container_info.items() if v == container_id)) + container_info.pop(key) + environment_delete_data = { + 'container_id': jsonutils.dumps(container_info) + } + environment_handler.update_attr(environment_id, environment_delete_data) + + return result_handler(consts.API_SUCCESS, {'container': container_id}) diff --git a/api/resources/v2/environments.py b/api/resources/v2/environments.py new file mode 100644 index 000000000..f021a3c5a --- /dev/null +++ b/api/resources/v2/environments.py @@ -0,0 +1,125 @@ +############################################################################## +# Copyright (c) 2017 Huawei Technologies Co.,Ltd. +# +# All rights reserved. This program and the accompanying materials +# are made available under the terms of the Apache License, Version 2.0 +# which accompanies this distribution, and is available at +# http://www.apache.org/licenses/LICENSE-2.0 +############################################################################## +import uuid +import logging + +from oslo_serialization import jsonutils +from docker import Client + +from api import ApiResource +from api.database.v2.handlers import V2EnvironmentHandler +from api.database.v2.handlers import V2OpenrcHandler +from api.database.v2.handlers import V2PodHandler +from api.database.v2.handlers import V2ContainerHandler +from yardstick.common.utils import result_handler +from yardstick.common.utils import change_obj_to_dict +from yardstick.common import constants as consts + +LOG = logging.getLogger(__name__) +LOG.setLevel(logging.DEBUG) + + +class V2Environments(ApiResource): + + def get(self): + environment_handler = V2EnvironmentHandler() + environments = [change_obj_to_dict(e) for e in environment_handler.list_all()] + + for e in environments: + container_info = e['container_id'] + e['container_id'] = jsonutils.loads(container_info) if container_info else {} + + data = { + 'environments': environments + } + + return result_handler(consts.API_SUCCESS, data) + + def post(self): + return self._dispatch_post() + + def create_environment(self, args): + try: + name = args['name'] + except KeyError: + return result_handler(consts.API_ERROR, 'name must be provided') + + env_id = str(uuid.uuid4()) + + environment_handler = V2EnvironmentHandler() + + env_init_data = { + 'name': name, + 'uuid': env_id + } + environment_handler.insert(env_init_data) + + return result_handler(consts.API_SUCCESS, {'uuid': env_id}) + + +class V2Environment(ApiResource): + + def get(self, environment_id): + try: + uuid.UUID(environment_id) + except ValueError: + return result_handler(consts.API_ERROR, 'invalid environment id') + + environment_handler = V2EnvironmentHandler() + try: + environment = environment_handler.get_by_uuid(environment_id) + except ValueError: + return result_handler(consts.API_ERROR, 'no such environment id') + + environment = change_obj_to_dict(environment) + container_id = environment['container_id'] + environment['container_id'] = jsonutils.loads(container_id) if container_id else {} + return result_handler(consts.API_SUCCESS, {'environment': environment}) + + def delete(self, environment_id): + try: + uuid.UUID(environment_id) + except ValueError: + return result_handler(consts.API_ERROR, 'invalid environment id') + + environment_handler = V2EnvironmentHandler() + try: + environment = environment_handler.get_by_uuid(environment_id) + except ValueError: + return result_handler(consts.API_ERROR, 'no such environment id') + + if environment.openrc_id: + LOG.info('delete openrc: %s', environment.openrc_id) + openrc_handler = V2OpenrcHandler() + openrc_handler.delete_by_uuid(environment.openrc_id) + + if environment.pod_id: + LOG.info('delete pod: %s', environment.pod_id) + pod_handler = V2PodHandler() + pod_handler.delete_by_uuid(environment.pod_id) + + if environment.container_id: + LOG.info('delete containers') + container_info = jsonutils.loads(environment.container_id) + + container_handler = V2ContainerHandler() + client = Client(base_url=consts.DOCKER_URL) + for k, v in container_info.items(): + LOG.info('start delete: %s', k) + container = container_handler.get_by_uuid(v) + LOG.debug('container name: %s', container.name) + try: + client.remove_container(container.name, force=True) + except Exception: + LOG.exception('remove container failed') + container_handler.delete_by_uuid(v) + + environment_handler.delete_by_uuid(environment_id) + + return result_handler(consts.API_SUCCESS, {'environment': environment_id}) diff --git a/api/resources/v2/images.py b/api/resources/v2/images.py new file mode 100644 index 000000000..8359e105b --- /dev/null +++ b/api/resources/v2/images.py @@ -0,0 +1,82 @@ +############################################################################## +# Copyright (c) 2017 Huawei Technologies Co.,Ltd. +# +# All rights reserved. This program and the accompanying materials +# are made available under the terms of the Apache License, Version 2.0 +# which accompanies this distribution, and is available at +# http://www.apache.org/licenses/LICENSE-2.0 +############################################################################## +import logging +import subprocess +import threading + +from api import ApiResource +from yardstick.common.utils import result_handler +from yardstick.common.utils import source_env +from yardstick.common.utils import change_obj_to_dict +from yardstick.common.openstack_utils import get_nova_client +from yardstick.common import constants as consts + +LOG = logging.getLogger(__name__) +LOG.setLevel(logging.DEBUG) + + +class V2Images(ApiResource): + + def get(self): + try: + source_env(consts.OPENRC) + except: + return result_handler(consts.API_ERROR, 'source openrc error') + + nova_client = get_nova_client() + try: + images_list = nova_client.images.list() + except: + return result_handler(consts.API_ERROR, 'get images error') + else: + images = [self.get_info(change_obj_to_dict(i)) for i in images_list] + status = 1 if all(i['status'] == 'ACTIVE' for i in images) else 0 + if not images: + status = 0 + + return result_handler(consts.API_SUCCESS, {'status': status, 'images': images}) + + def post(self): + return self._dispatch_post() + + def get_info(self, data): + result = { + 'name': data.get('name', ''), + 'size': data.get('OS-EXT-IMG-SIZE:size', ''), + 'status': data.get('status', ''), + 'time': data.get('updated', '') + } + return result + + def load_image(self, args): + thread = threading.Thread(target=self._load_images) + thread.start() + return result_handler(consts.API_SUCCESS, {}) + + def _load_images(self): + LOG.info('source openrc') + source_env(consts.OPENRC) + + LOG.info('clean images') + cmd = [consts.CLEAN_IMAGES_SCRIPT] + p = subprocess.Popen(cmd, stdout=subprocess.PIPE, + cwd=consts.REPOS_DIR) + _, err = p.communicate() + if p.returncode != 0: + LOG.error('clean image failed: %s', err) + + LOG.info('load images') + cmd = [consts.LOAD_IMAGES_SCRIPT] + p = subprocess.Popen(cmd, stdout=subprocess.PIPE, + cwd=consts.REPOS_DIR) + _, err = p.communicate() + if p.returncode != 0: + LOG.error('load image failed: %s', err) + + LOG.info('Done') diff --git a/api/resources/v2/openrcs.py b/api/resources/v2/openrcs.py new file mode 100644 index 000000000..cb506d0e8 --- /dev/null +++ b/api/resources/v2/openrcs.py @@ -0,0 +1,219 @@ +############################################################################## +# Copyright (c) 2017 Huawei Technologies Co.,Ltd. +# +# All rights reserved. This program and the accompanying materials +# are made available under the terms of the Apache License, Version 2.0 +# which accompanies this distribution, and is available at +# http://www.apache.org/licenses/LICENSE-2.0 +############################################################################## +import uuid +import logging +import re +import os + +import yaml +from oslo_serialization import jsonutils + +from api import ApiResource +from api.database.v2.handlers import V2OpenrcHandler +from api.database.v2.handlers import V2EnvironmentHandler +from yardstick.common import constants as consts +from yardstick.common.utils import result_handler +from yardstick.common.utils import makedirs +from yardstick.common.utils import source_env + +LOG = logging.getLogger(__name__) +LOG.setLevel(logging.DEBUG) + + +class V2Openrcs(ApiResource): + + def post(self): + return self._dispatch_post() + + def upload_openrc(self, args): + try: + upload_file = args['file'] + except KeyError: + return result_handler(consts.API_ERROR, 'file must be provided') + + try: + environment_id = args['environment_id'] + except KeyError: + return result_handler(consts.API_ERROR, 'environment_id must be provided') + + try: + uuid.UUID(environment_id) + except ValueError: + return result_handler(consts.API_ERROR, 'invalid environment id') + + LOG.info('writing openrc: %s', consts.OPENRC) + makedirs(consts.CONF_DIR) + upload_file.save(consts.OPENRC) + source_env(consts.OPENRC) + + LOG.info('parsing openrc') + try: + openrc_data = self._get_openrc_dict() + except Exception: + LOG.exception('parse openrc failed') + return result_handler(consts.API_ERROR, 'parse openrc failed') + + openrc_id = str(uuid.uuid4()) + self._write_into_database(environment_id, openrc_id, openrc_data) + + LOG.info('writing ansible cloud conf') + try: + self._generate_ansible_conf_file(openrc_data) + except Exception: + LOG.exception('write cloud conf failed') + return result_handler(consts.API_ERROR, 'genarate ansible conf failed') + LOG.info('finish writing ansible cloud conf') + + return result_handler(consts.API_SUCCESS, {'openrc': openrc_data, 'uuid': openrc_id}) + + def update_openrc(self, args): + try: + openrc_vars = args['openrc'] + except KeyError: + return result_handler(consts.API_ERROR, 'openrc must be provided') + + try: + environment_id = args['environment_id'] + except KeyError: + return result_handler(consts.API_ERROR, 'environment_id must be provided') + + try: + uuid.UUID(environment_id) + except ValueError: + return result_handler(consts.API_ERROR, 'invalid environment id') + + LOG.info('writing openrc: %s', consts.OPENRC) + makedirs(consts.CONF_DIR) + + lines = ['export {}={}\n'.format(k, v) for k, v in openrc_vars.items()] + LOG.debug('writing: %s', ''.join(lines)) + with open(consts.OPENRC, 'w') as f: + f.writelines(lines) + LOG.info('writing openrc: Done') + + LOG.info('source openrc: %s', consts.OPENRC) + try: + source_env(consts.OPENRC) + except Exception: + LOG.exception('source openrc failed') + return result_handler(consts.API_ERROR, 'source openrc failed') + LOG.info('source openrc: Done') + + openrc_id = str(uuid.uuid4()) + self._write_into_database(environment_id, openrc_id, openrc_vars) + + LOG.info('writing ansible cloud conf') + try: + self._generate_ansible_conf_file(openrc_vars) + except Exception: + LOG.exception('write cloud conf failed') + return result_handler(consts.API_ERROR, 'genarate ansible conf failed') + LOG.info('finish writing ansible cloud conf') + + return result_handler(consts.API_SUCCESS, {'openrc': openrc_vars, 'uuid': openrc_id}) + + def _write_into_database(self, environment_id, openrc_id, openrc_data): + LOG.info('writing openrc to database') + openrc_handler = V2OpenrcHandler() + openrc_init_data = { + 'uuid': openrc_id, + 'environment_id': environment_id, + 'content': jsonutils.dumps(openrc_data) + } + openrc_handler.insert(openrc_init_data) + + LOG.info('binding openrc to environment: %s', environment_id) + environment_handler = V2EnvironmentHandler() + environment_handler.update_attr(environment_id, {'openrc_id': openrc_id}) + + def _get_openrc_dict(self): + with open(consts.OPENRC) as f: + content = f.readlines() + + result = {} + for line in content: + m = re.search(r'(\ .*)=(.*)', line) + if m: + try: + value = os.environ[m.group(1).strip()] + except KeyError: + pass + else: + result.update({m.group(1).strip(): value}) + + return result + + def _generate_ansible_conf_file(self, openrc_data): + ansible_conf = { + 'clouds': { + 'opnfv': { + 'auth': { + } + } + } + } + black_list = ['OS_IDENTITY_API_VERSION', 'OS_IMAGE_API_VERSION'] + + for k, v in openrc_data.items(): + if k.startswith('OS') and k not in black_list: + key = k[3:].lower() + ansible_conf['clouds']['opnfv']['auth'][key] = v + + try: + value = openrc_data['OS_IDENTITY_API_VERSION'] + except KeyError: + pass + else: + ansible_conf['clouds']['opnfv']['identity_api_version'] = value + + makedirs(consts.OPENSTACK_CONF_DIR) + with open(consts.CLOUDS_CONF, 'w') as f: + yaml.dump(ansible_conf, f, default_flow_style=False) + + +class V2Openrc(ApiResource): + + def get(self, openrc_id): + try: + uuid.UUID(openrc_id) + except ValueError: + return result_handler(consts.API_ERROR, 'invalid openrc id') + + LOG.info('Geting openrc: %s', openrc_id) + openrc_handler = V2OpenrcHandler() + try: + openrc = openrc_handler.get_by_uuid(openrc_id) + except ValueError: + return result_handler(consts.API_ERROR, 'no such openrc id') + + LOG.info('load openrc content') + content = jsonutils.loads(openrc.content) + + return result_handler(consts.API_ERROR, {'openrc': content}) + + def delete(self, openrc_id): + try: + uuid.UUID(openrc_id) + except ValueError: + return result_handler(consts.API_ERROR, 'invalid openrc id') + + LOG.info('Geting openrc: %s', openrc_id) + openrc_handler = V2OpenrcHandler() + try: + openrc = openrc_handler.get_by_uuid(openrc_id) + except ValueError: + return result_handler(consts.API_ERROR, 'no such openrc id') + + LOG.info('update openrc in environment') + environment_handler = V2EnvironmentHandler() + environment_handler.update_attr(openrc.environment_id, {'openrc_id': None}) + + openrc_handler.delete_by_uuid(openrc_id) + + return result_handler(consts.API_SUCCESS, {'openrc': openrc_id}) diff --git a/api/resources/v2/pods.py b/api/resources/v2/pods.py new file mode 100644 index 000000000..f2316d353 --- /dev/null +++ b/api/resources/v2/pods.py @@ -0,0 +1,108 @@ +############################################################################## +# Copyright (c) 2017 Huawei Technologies Co.,Ltd. +# +# All rights reserved. This program and the accompanying materials +# are made available under the terms of the Apache License, Version 2.0 +# which accompanies this distribution, and is available at +# http://www.apache.org/licenses/LICENSE-2.0 +############################################################################## +import uuid +import yaml +import logging + +from oslo_serialization import jsonutils + +from api import ApiResource +from api.database.v2.handlers import V2PodHandler +from api.database.v2.handlers import V2EnvironmentHandler +from yardstick.common import constants as consts +from yardstick.common.utils import result_handler +from yardstick.common.task_template import TaskTemplate + +LOG = logging.getLogger(__name__) +LOG.setLevel(logging.DEBUG) + + +class V2Pods(ApiResource): + + def post(self): + return self._dispatch_post() + + def upload_pod_file(self, args): + try: + upload_file = args['file'] + except KeyError: + return result_handler(consts.API_ERROR, 'file must be provided') + + try: + environment_id = args['environment_id'] + except KeyError: + return result_handler(consts.API_ERROR, 'environment_id must be provided') + + try: + uuid.UUID(environment_id) + except ValueError: + return result_handler(consts.API_ERROR, 'invalid environment id') + + LOG.info('writing pod file: %s', consts.POD_FILE) + upload_file.save(consts.POD_FILE) + + with open(consts.POD_FILE) as f: + data = yaml.safe_load(TaskTemplate.render(f.read())) + LOG.debug('pod content is: %s', data) + + LOG.info('create pod in database') + pod_id = str(uuid.uuid4()) + pod_handler = V2PodHandler() + pod_init_data = { + 'uuid': pod_id, + 'environment_id': environment_id, + 'content': jsonutils.dumps(data) + } + pod_handler.insert(pod_init_data) + + LOG.info('update pod in environment') + environment_handler = V2EnvironmentHandler() + environment_handler.update_attr(environment_id, {'pod_id': pod_id}) + + return result_handler(consts.API_SUCCESS, {'uuid': pod_id, 'pod': data}) + + +class V2Pod(ApiResource): + + def get(self, pod_id): + try: + uuid.UUID(pod_id) + except ValueError: + return result_handler(consts.API_ERROR, 'invalid pod id') + + pod_handler = V2PodHandler() + try: + pod = pod_handler.get_by_uuid(pod_id) + except ValueError: + return result_handler(consts.API_ERROR, 'no such pod') + + content = jsonutils.loads(pod.content) + + return result_handler(consts.API_SUCCESS, {'pod': content}) + + def delete(self, pod_id): + try: + uuid.UUID(pod_id) + except ValueError: + return result_handler(consts.API_ERROR, 'invalid pod id') + + pod_handler = V2PodHandler() + try: + pod = pod_handler.get_by_uuid(pod_id) + except ValueError: + return result_handler(consts.API_ERROR, 'no such pod') + + LOG.info('update pod in environment') + environment_handler = V2EnvironmentHandler() + environment_handler.update_attr(pod.environment_id, {'pod_id': None}) + + LOG.info('delete pod in database') + pod_handler.delete_by_uuid(pod_id) + + return result_handler(consts.API_SUCCESS, {'pod': pod_id}) diff --git a/api/resources/v2/projects.py b/api/resources/v2/projects.py new file mode 100644 index 000000000..2ff61d0fe --- /dev/null +++ b/api/resources/v2/projects.py @@ -0,0 +1,105 @@ +############################################################################## +# Copyright (c) 2017 Huawei Technologies Co.,Ltd. +# +# All rights reserved. This program and the accompanying materials +# are made available under the terms of the Apache License, Version 2.0 +# which accompanies this distribution, and is available at +# http://www.apache.org/licenses/LICENSE-2.0 +############################################################################## +import uuid +import logging + +from datetime import datetime + +from api import ApiResource +from api.database.v2.handlers import V2ProjectHandler +from api.database.v2.handlers import V2TaskHandler +from yardstick.common.utils import result_handler +from yardstick.common.utils import change_obj_to_dict +from yardstick.common import constants as consts + +LOG = logging.getLogger(__name__) +LOG.setLevel(logging.DEBUG) + + +class V2Projects(ApiResource): + + def get(self): + project_handler = V2ProjectHandler() + projects = [change_obj_to_dict(p) for p in project_handler.list_all()] + + for p in projects: + tasks = p['tasks'] + p['tasks'] = tasks.split(',') if tasks else [] + + return result_handler(consts.API_SUCCESS, {'projects': projects}) + + def post(self): + return self._dispatch_post() + + def create_project(self, args): + try: + name = args['name'] + except KeyError: + return result_handler(consts.API_ERROR, 'name must be provided') + + project_id = str(uuid.uuid4()) + create_time = datetime.now() + project_handler = V2ProjectHandler() + + project_init_data = { + 'uuid': project_id, + 'name': name, + 'time': create_time + } + project_handler.insert(project_init_data) + + return result_handler(consts.API_SUCCESS, {'uuid': project_id}) + + +class V2Project(ApiResource): + + def get(self, project_id): + try: + uuid.UUID(project_id) + except ValueError: + return result_handler(consts.API_ERROR, 'invalid project id') + + project_handler = V2ProjectHandler() + try: + project = project_handler.get_by_uuid(project_id) + except ValueError: + return result_handler(consts.API_ERROR, 'no such project id') + + project_info = change_obj_to_dict(project) + tasks = project_info['tasks'] + project_info['tasks'] = tasks.split(',') if tasks else [] + + return result_handler(consts.API_SUCCESS, {'project': project_info}) + + def delete(self, project_id): + try: + uuid.UUID(project_id) + except ValueError: + return result_handler(consts.API_ERROR, 'invalid project id') + + project_handler = V2ProjectHandler() + try: + project = project_handler.get_by_uuid(project_id) + except ValueError: + return result_handler(consts.API_ERROR, 'no such project id') + + if project.tasks: + LOG.info('delete related task') + task_handler = V2TaskHandler() + for task_id in project.tasks.split(','): + LOG.debug('delete task: %s', task_id) + try: + task_handler.delete_by_uuid(task_id) + except ValueError: + LOG.exception('no such task id: %s', task_id) + + LOG.info('delete project in database') + project_handler.delete_by_uuid(project_id) + + return result_handler(consts.API_SUCCESS, {'project': project_id}) diff --git a/api/resources/v2/tasks.py b/api/resources/v2/tasks.py new file mode 100644 index 000000000..885a190c6 --- /dev/null +++ b/api/resources/v2/tasks.py @@ -0,0 +1,254 @@ +############################################################################## +# Copyright (c) 2017 Huawei Technologies Co.,Ltd. +# +# All rights reserved. This program and the accompanying materials +# are made available under the terms of the Apache License, Version 2.0 +# which accompanies this distribution, and is available at +# http://www.apache.org/licenses/LICENSE-2.0 +############################################################################## +import uuid +import logging +from datetime import datetime + +from oslo_serialization import jsonutils + +from api import ApiResource +from api.database.v2.handlers import V2TaskHandler +from api.database.v2.handlers import V2ProjectHandler +from api.database.v2.handlers import V2EnvironmentHandler +from api.utils.thread import TaskThread +from yardstick.common.utils import result_handler +from yardstick.common.utils import change_obj_to_dict +from yardstick.common import constants as consts +from yardstick.benchmark.core.task import Task +from yardstick.benchmark.core import Param + +LOG = logging.getLogger(__name__) +LOG.setLevel(logging.DEBUG) + + +class V2Tasks(ApiResource): + + def get(self): + task_handler = V2TaskHandler() + tasks = [change_obj_to_dict(t) for t in task_handler.list_all()] + + for t in tasks: + result = t['result'] + t['result'] = jsonutils.loads(result) if result else None + + return result_handler(consts.API_SUCCESS, {'tasks': tasks}) + + def post(self): + return self._dispatch_post() + + def create_task(self, args): + try: + name = args['name'] + except KeyError: + return result_handler(consts.API_ERROR, 'name must be provided') + + try: + project_id = args['project_id'] + except KeyError: + return result_handler(consts.API_ERROR, 'project_id must be provided') + + task_id = str(uuid.uuid4()) + create_time = datetime.now() + task_handler = V2TaskHandler() + + LOG.info('create task in database') + task_init_data = { + 'uuid': task_id, + 'project_id': project_id, + 'name': name, + 'time': create_time, + 'status': -1 + } + task_handler.insert(task_init_data) + + LOG.info('create task in project') + project_handler = V2ProjectHandler() + project_handler.append_attr(project_id, {'tasks': task_id}) + + return result_handler(consts.API_SUCCESS, {'uuid': task_id}) + + +class V2Task(ApiResource): + + def get(self, task_id): + try: + uuid.UUID(task_id) + except ValueError: + return result_handler(consts.API_ERROR, 'invalid task id') + + task_handler = V2TaskHandler() + try: + task = task_handler.get_by_uuid(task_id) + except ValueError: + return result_handler(consts.API_ERROR, 'no such task id') + + task_info = change_obj_to_dict(task) + result = task_info['result'] + task_info['result'] = jsonutils.loads(result) if result else None + + return result_handler(consts.API_SUCCESS, {'task': task_info}) + + def delete(self, task_id): + try: + uuid.UUID(task_id) + except ValueError: + return result_handler(consts.API_ERROR, 'invalid task id') + + task_handler = V2TaskHandler() + try: + project_id = task_handler.get_by_uuid(task_id).project_id + except ValueError: + return result_handler(consts.API_ERROR, 'no such task id') + + LOG.info('delete task in database') + task_handler.delete_by_uuid(task_id) + + project_handler = V2ProjectHandler() + project = project_handler.get_by_uuid(project_id) + + if project.tasks: + LOG.info('update tasks in project') + new_task_list = project.tasks.split(',') + new_task_list.remove(task_id) + if new_task_list: + new_tasks = ','.join(new_task_list) + else: + new_tasks = None + project_handler.update_attr(project_id, {'tasks': new_tasks}) + + return result_handler(consts.API_SUCCESS, {'task': task_id}) + + def put(self, task_id): + + try: + uuid.UUID(task_id) + except ValueError: + return result_handler(consts.API_ERROR, 'invalid task id') + + task_handler = V2TaskHandler() + try: + task_handler.get_by_uuid(task_id) + except ValueError: + return result_handler(consts.API_ERROR, 'no such task id') + + return self._dispatch_post(task_id=task_id) + + def add_environment(self, args): + + task_id = args['task_id'] + try: + environment_id = args['environment_id'] + except KeyError: + return result_handler(consts.API_ERROR, 'environment_id must be provided') + + try: + uuid.UUID(environment_id) + except ValueError: + return result_handler(consts.API_ERROR, 'invalid environment id') + + environment_handler = V2EnvironmentHandler() + try: + environment_handler.get_by_uuid(environment_id) + except ValueError: + return result_handler(consts.API_ERROR, 'no such environment id') + + LOG.info('update environment_id in task') + task_handler = V2TaskHandler() + task_handler.update_attr(task_id, {'environment_id': environment_id}) + + return result_handler(consts.API_SUCCESS, {'uuid': task_id}) + + def add_case(self, args): + task_id = args['task_id'] + try: + name = args['case_name'] + except KeyError: + return result_handler(consts.API_ERROR, 'case_name must be provided') + + try: + content = args['case_content'] + except KeyError: + return result_handler(consts.API_ERROR, 'case_content must be provided') + + LOG.info('update case info in task') + task_handler = V2TaskHandler() + task_update_data = { + 'case_name': name, + 'content': content, + 'suite': False + } + task_handler.update_attr(task_id, task_update_data) + + return result_handler(consts.API_SUCCESS, {'uuid': task_id}) + + def add_suite(self, args): + task_id = args['task_id'] + try: + name = args['suite_name'] + except KeyError: + return result_handler(consts.API_ERROR, 'suite_name must be provided') + + try: + content = args['suite_content'] + except KeyError: + return result_handler(consts.API_ERROR, 'suite_content must be provided') + + LOG.info('update suite info in task') + task_handler = V2TaskHandler() + task_update_data = { + 'case_name': name, + 'content': content, + 'suite': True + } + task_handler.update_attr(task_id, task_update_data) + + return result_handler(consts.API_SUCCESS, {'uuid': task_id}) + + def run(self, args): + try: + task_id = args['task_id'] + except KeyError: + return result_handler(consts.API_ERROR, 'task_id must be provided') + + try: + uuid.UUID(task_id) + except ValueError: + return result_handler(consts.API_ERROR, 'invalid task id') + + task_handler = V2TaskHandler() + try: + task = task_handler.get_by_uuid(task_id) + except ValueError: + return result_handler(consts.API_ERROR, 'no such task id') + + if not task.environment_id: + return result_handler(consts.API_ERROR, 'environment not set') + + if not task.case_name or not task.content: + return result_handler(consts.API_ERROR, 'case not set') + + if task.status == 0: + return result_handler(consts.API_ERROR, 'task is already running') + + with open('/tmp/{}.yaml'.format(task.case_name), 'w') as f: + f.write(task.content) + + data = { + 'inputfile': ['/tmp/{}.yaml'.format(task.case_name)], + 'task_id': task_id + } + if task.suite: + data.update({'suite': True}) + + LOG.info('start task thread') + param = Param(data) + task_thread = TaskThread(Task().start, param, task_handler) + task_thread.start() + + return result_handler(consts.API_SUCCESS, {'uuid': task_id}) diff --git a/api/resources/v2/testcases.py b/api/resources/v2/testcases.py new file mode 100644 index 000000000..b47a8f6b7 --- /dev/null +++ b/api/resources/v2/testcases.py @@ -0,0 +1,70 @@ +############################################################################## +# Copyright (c) 2017 Huawei Technologies Co.,Ltd. +# +# All rights reserved. This program and the accompanying materials +# are made available under the terms of the Apache License, Version 2.0 +# which accompanies this distribution, and is available at +# http://www.apache.org/licenses/LICENSE-2.0 +############################################################################## +import logging +import errno +import os + +from api import ApiResource +from yardstick.common.utils import result_handler +from yardstick.common import constants as consts +from yardstick.benchmark.core import Param +from yardstick.benchmark.core.testcase import Testcase + +LOG = logging.getLogger(__name__) +LOG.setLevel(logging.DEBUG) + + +class V2Testcases(ApiResource): + + def get(self): + param = Param({}) + testcase_list = Testcase().list_all(param) + return result_handler(consts.API_SUCCESS, {'testcases': testcase_list}) + + def post(self): + return self._dispatch_post() + + def upload_case(self, args): + try: + upload_file = args['file'] + except KeyError: + return result_handler(consts.API_ERROR, 'file must be provided') + + case_name = os.path.join(consts.TESTCASE_DIR, upload_file.filename) + + LOG.info('save case file') + upload_file.save(case_name) + + return result_handler(consts.API_SUCCESS, {'testcase': upload_file.filename}) + + +class V2Testcase(ApiResource): + + def get(self, case_name): + case_path = os.path.join(consts.TESTCASE_DIR, '{}.yaml'.format(case_name)) + + try: + with open(case_path) as f: + data = f.read() + except IOError as e: + if e.errno == errno.ENOENT: + return result_handler(consts.API_ERROR, 'case does not exist') + + return result_handler(consts.API_SUCCESS, {'testcase': data}) + + def delete(self, case_name): + case_path = os.path.join(consts.TESTCASE_DIR, '{}.yaml'.format(case_name)) + + try: + os.remove(case_path) + except IOError as e: + if e.errno == errno.ENOENT: + return result_handler(consts.API_ERROR, 'case does not exist') + + return result_handler(consts.API_SUCCESS, {'testcase': case_name}) diff --git a/api/resources/v2/testsuites.py b/api/resources/v2/testsuites.py new file mode 100644 index 000000000..56ad47375 --- /dev/null +++ b/api/resources/v2/testsuites.py @@ -0,0 +1,89 @@ +############################################################################## +# Copyright (c) 2017 Huawei Technologies Co.,Ltd. +# +# All rights reserved. This program and the accompanying materials +# are made available under the terms of the Apache License, Version 2.0 +# which accompanies this distribution, and is available at +# http://www.apache.org/licenses/LICENSE-2.0 +############################################################################## +import os +import errno +import logging + +import yaml + +from api import ApiResource +from yardstick.common.utils import result_handler +from yardstick.common import constants as consts +from yardstick.benchmark.core.testsuite import Testsuite +from yardstick.benchmark.core import Param + +LOG = logging.getLogger(__name__) +LOG.setLevel(logging.DEBUG) + + +class V2Testsuites(ApiResource): + + def get(self): + param = Param({}) + testsuite_list = Testsuite().list_all(param) + + data = { + 'testsuites': testsuite_list + } + + return result_handler(consts.API_SUCCESS, data) + + def post(self): + return self._dispatch_post() + + def create_suite(self, args): + try: + suite_name = args['name'] + except KeyError: + return result_handler(consts.API_ERROR, 'name must be provided') + + try: + testcases = args['testcases'] + except KeyError: + return result_handler(consts.API_ERROR, 'testcases must be provided') + + testcases = [{'file_name': '{}.yaml'.format(t)} for t in testcases] + + suite = os.path.join(consts.TESTSUITE_DIR, '{}.yaml'.format(suite_name)) + suite_content = { + 'schema': 'yardstick:suite:0.1', + 'name': suite_name, + 'test_cases_dir': 'tests/opnfv/test_cases/', + 'test_cases': testcases + } + + LOG.info('write test suite') + with open(suite, 'w') as f: + yaml.dump(suite_content, f, default_flow_style=False) + + return result_handler(consts.API_SUCCESS, {'suite': suite_name}) + + +class V2Testsuite(ApiResource): + + def get(self, suite_name): + suite_path = os.path.join(consts.TESTSUITE_DIR, '{}.yaml'.format(suite_name)) + try: + with open(suite_path) as f: + data = f.read() + except IOError as e: + if e.errno == errno.ENOENT: + return result_handler(consts.API_ERROR, 'suite does not exist') + + return result_handler(consts.API_SUCCESS, {'testsuite': data}) + + def delete(self, suite_name): + suite_path = os.path.join(consts.TESTSUITE_DIR, '{}.yaml'.format(suite_name)) + try: + os.remove(suite_path) + except IOError as e: + if e.errno == errno.ENOENT: + return result_handler(consts.API_ERROR, 'suite does not exist') + + return result_handler(consts.API_SUCCESS, {'testsuite': suite_name}) diff --git a/api/server.py b/api/server.py index d39c44544..158b8a508 100644 --- a/api/server.py +++ b/api/server.py @@ -10,6 +10,7 @@ from __future__ import absolute_import import inspect import logging +import socket from six.moves import filter from flasgger import Swagger @@ -21,9 +22,17 @@ from api.database import db_session from api.database import engine from api.database.v1 import models from api.urls import urlpatterns +from api import ApiResource from yardstick import _init_logging +from yardstick.common import utils +from yardstick.common import constants as consts -logger = logging.getLogger(__name__) +try: + from urlparse import urljoin +except ImportError: + from urllib.parse import urljoin + +LOG = logging.getLogger(__name__) app = Flask(__name__) @@ -37,8 +46,10 @@ def shutdown_session(exception=None): db_session.remove() -for u in urlpatterns: - api.add_resource(u.resource, u.url, endpoint=u.endpoint) +def get_resource(resource_name): + name = ''.join(resource_name.split('_')) + return next((r for r in utils.itersubclasses(ApiResource) + if r.__name__.lower() == name)) def init_db(): @@ -51,7 +62,7 @@ def init_db(): return False subclses = filter(func, inspect.getmembers(models, inspect.isclass)) - logger.debug('Import models: %s', [a[1] for a in subclses]) + LOG.debug('Import models: %s', [a[1] for a in subclses]) Base.metadata.create_all(bind=engine) @@ -60,9 +71,21 @@ def app_wrapper(*args, **kwargs): return app(*args, **kwargs) +def get_endpoint(url): + ip = socket.gethostbyname(socket.gethostname()) + return urljoin('http://{}:{}'.format(ip, consts.API_PORT), url) + + +for u in urlpatterns: + try: + api.add_resource(get_resource(u.target), u.url, endpoint=get_endpoint(u.url)) + except StopIteration: + LOG.error('url resource not found: %s', u.url) + + if __name__ == '__main__': _init_logging() - logger.setLevel(logging.DEBUG) - logger.info('Starting server') + LOG.setLevel(logging.DEBUG) + LOG.info('Starting server') init_db() app.run(host='0.0.0.0') diff --git a/api/urls.py b/api/urls.py index 13c6c7675..3fef91af8 100644 --- a/api/urls.py +++ b/api/urls.py @@ -8,17 +8,52 @@ ############################################################################## from __future__ import absolute_import -from api import views -from api.utils.common import Url +from api import Url urlpatterns = [ - Url('/yardstick/asynctask', views.Asynctask, 'asynctask'), - Url('/yardstick/testcases', views.Testcases, 'testcases'), - Url('/yardstick/testcases/release/action', views.ReleaseAction, 'release'), - Url('/yardstick/testcases/samples/action', views.SamplesAction, 'samples'), - Url('/yardstick/testcases/<case_name>/docs', views.CaseDocs, 'casedocs'), - Url('/yardstick/testsuites/action', views.TestsuitesAction, 'testsuites'), - Url('/yardstick/results', views.Results, 'results'), - Url('/yardstick/env/action', views.EnvAction, 'env') + Url('/yardstick/asynctask', 'v1_async_task'), + Url('/yardstick/testcases', 'v1_test_case'), + Url('/yardstick/testcases/release/action', 'v1_release_case'), + Url('/yardstick/testcases/samples/action', 'v1_sample_case'), + Url('/yardstick/testcases/<case_name>/docs', 'v1_case_docs'), + Url('/yardstick/testsuites/action', 'v1_test_suite'), + Url('/yardstick/results', 'v1_result'), + Url('/yardstick/env/action', 'v1_env'), + + # api v2 + Url('/api/v2/yardstick/environments', 'v2_environments'), + Url('/api/v2/yardstick/environments/action', 'v2_environments'), + Url('/api/v2/yardstick/environments/<environment_id>', 'v2_environment'), + + Url('/api/v2/yardstick/openrcs', 'v2_openrcs'), + Url('/api/v2/yardstick/openrcs/action', 'v2_openrcs'), + Url('/api/v2/yardstick/openrcs/<openrc_id>', 'v2_openrc'), + + Url('/api/v2/yardstick/pods', 'v2_pods'), + Url('/api/v2/yardstick/pods/action', 'v2_pods'), + Url('/api/v2/yardstick/pods/<pod_id>', 'v2_pod'), + + Url('/api/v2/yardstick/images', 'v2_images'), + Url('/api/v2/yardstick/images/action', 'v2_images'), + + Url('/api/v2/yardstick/containers', 'v2_containers'), + Url('/api/v2/yardstick/containers/action', 'v2_containers'), + Url('/api/v2/yardstick/containers/<container_id>', 'v2_container'), + + Url('/api/v2/yardstick/projects', 'v2_projects'), + Url('/api/v2/yardstick/projects/action', 'v2_projects'), + Url('/api/v2/yardstick/projects/<project_id>', 'v2_project'), + + Url('/api/v2/yardstick/tasks', 'v2_tasks'), + Url('/api/v2/yardstick/tasks/action', 'v2_tasks'), + Url('/api/v2/yardstick/tasks/<task_id>', 'v2_task'), + + Url('/api/v2/yardstick/testcases', 'v2_testcases'), + Url('/api/v2/yardstick/testcases/action', 'v2_testcases'), + Url('/api/v2/yardstick/testcases/<case_name>', 'v2_testcase'), + + Url('/api/v2/yardstick/testsuites', 'v2_testsuites'), + Url('/api/v2/yardstick/testsuites/action', 'v2_testsuites'), + Url('/api/v2/yardstick/testsuites/<suite_name>', 'v2_testsuite') ] diff --git a/api/utils/common.py b/api/utils/common.py deleted file mode 100644 index eda9c17dd..000000000 --- a/api/utils/common.py +++ /dev/null @@ -1,44 +0,0 @@ -############################################################################## -# Copyright (c) 2016 Huawei Technologies Co.,Ltd and others. -# -# All rights reserved. This program and the accompanying materials -# are made available under the terms of the Apache License, Version 2.0 -# which accompanies this distribution, and is available at -# http://www.apache.org/licenses/LICENSE-2.0 -############################################################################## -from __future__ import absolute_import -import collections -import logging - -from flask import jsonify -import six - -LOG = logging.getLogger(__name__) -LOG.setLevel(logging.DEBUG) - - -def translate_to_str(obj): - if isinstance(obj, collections.Mapping): - return {str(k): translate_to_str(v) for k, v in obj.items()} - elif isinstance(obj, list): - return [translate_to_str(ele) for ele in obj] - elif isinstance(obj, six.text_type): - return str(obj) - return obj - - -def result_handler(status, data): - result = { - 'status': status, - 'result': data - } - return jsonify(result) - - -class Url(object): - - def __init__(self, url, resource, endpoint): - super(Url, self).__init__() - self.url = url - self.resource = resource - self.endpoint = endpoint diff --git a/api/utils/thread.py b/api/utils/thread.py index 2106548f5..20bd07a12 100644 --- a/api/utils/thread.py +++ b/api/utils/thread.py @@ -1,37 +1,53 @@ +############################################################################## +# Copyright (c) 2017 Huawei Technologies Co.,Ltd. +# +# All rights reserved. This program and the accompanying materials +# are made available under the terms of the Apache License, Version 2.0 +# which accompanies this distribution, and is available at +# http://www.apache.org/licenses/LICENSE-2.0 +############################################################################## import threading +import os import logging from oslo_serialization import jsonutils -from api.database.v1.handlers import TasksHandler from yardstick.common import constants as consts -logger = logging.getLogger(__name__) -logger.setLevel(logging.DEBUG) +LOG = logging.getLogger(__name__) +LOG.setLevel(logging.DEBUG) class TaskThread(threading.Thread): - def __init__(self, target, args): + def __init__(self, target, args, handler): super(TaskThread, self).__init__(target=target, args=args) self.target = target self.args = args + self.handler = handler def run(self): - task_handler = TasksHandler() - data = {'task_id': self.args.task_id, 'status': consts.TASK_NOT_DONE} - task_handler.insert(data) + if self.handler.__class__.__name__.lower().startswith('v2'): + self.handler.update_attr(self.args.task_id, {'status': consts.TASK_NOT_DONE}) + else: + update_data = {'task_id': self.args.task_id, 'status': consts.TASK_NOT_DONE} + self.handler.insert(update_data) - logger.info('Starting run task') + LOG.info('Starting run task') try: data = self.target(self.args) except Exception as e: - logger.exception('Task Failed') + LOG.exception('Task Failed') update_data = {'status': consts.TASK_FAILED, 'error': str(e)} - task_handler.update_attr(self.args.task_id, update_data) + self.handler.update_attr(self.args.task_id, update_data) else: - logger.info('Task Finished') - logger.debug('Result: %s', data) - - data['result'] = jsonutils.dumps(data.get('result', {})) - task_handler.update_attr(self.args.task_id, data) + LOG.info('Task Finished') + LOG.debug('Result: %s', data) + + if self.handler.__class__.__name__.lower().startswith('v2'): + new_data = {'status': consts.TASK_DONE, 'result': jsonutils.dumps(data['result'])} + self.handler.update_attr(self.args.task_id, new_data) + os.remove(self.args.inputfile[0]) + else: + data['result'] = jsonutils.dumps(data.get('result', {})) + self.handler.update_attr(self.args.task_id, data) diff --git a/api/views.py b/api/views.py deleted file mode 100644 index 9c9ca4ef9..000000000 --- a/api/views.py +++ /dev/null @@ -1,82 +0,0 @@ -############################################################################## -# Copyright (c) 2016 Huawei Technologies Co.,Ltd and others. -# -# All rights reserved. This program and the accompanying materials -# are made available under the terms of the Apache License, Version 2.0 -# which accompanies this distribution, and is available at -# http://www.apache.org/licenses/LICENSE-2.0 -############################################################################## -from __future__ import absolute_import -import logging -import os - -from flasgger.utils import swag_from - -from api.base import ApiResource -from api.swagger import models - -logger = logging.getLogger(__name__) -logger.setLevel(logging.DEBUG) - - -TestCaseActionModel = models.TestCaseActionModel -TestCaseActionArgsModel = models.TestCaseActionArgsModel -TestCaseActionArgsOptsModel = models.TestCaseActionArgsOptsModel -TestCaseActionArgsOptsTaskArgModel = models.TestCaseActionArgsOptsTaskArgModel - - -class Asynctask(ApiResource): - def get(self): - return self._dispatch_get() - - -class Testcases(ApiResource): - def get(self): - return self._dispatch_get() - - -class ReleaseAction(ApiResource): - @swag_from(os.getcwd() + '/swagger/docs/release_action.yaml') - def post(self): - return self._dispatch_post() - - -class SamplesAction(ApiResource): - - def post(self): - return self._dispatch_post() - - -TestSuiteActionModel = models.TestSuiteActionModel -TestSuiteActionArgsModel = models.TestSuiteActionArgsModel -TestSuiteActionArgsOptsModel = models.TestSuiteActionArgsOptsModel -TestSuiteActionArgsOptsTaskArgModel = \ - models.TestSuiteActionArgsOptsTaskArgModel - - -class TestsuitesAction(ApiResource): - @swag_from(os.getcwd() + '/swagger/docs/testsuites_action.yaml') - def post(self): - return self._dispatch_post() - - -ResultModel = models.ResultModel - - -class Results(ApiResource): - - @swag_from(os.getcwd() + '/swagger/docs/results.yaml') - def get(self): - return self._dispatch_get() - - -class EnvAction(ApiResource): - - def post(self): - return self._dispatch_post() - - -class CaseDocs(ApiResource): - - def get(self, case_name): - return self._dispatch_get(case_name=case_name) diff --git a/docker/Dockerfile b/docker/Dockerfile index 2c4270a09..b48a550bf 100644 --- a/docker/Dockerfile +++ b/docker/Dockerfile @@ -37,6 +37,7 @@ RUN git clone --depth 1 -b $BRANCH https://gerrit.opnfv.org/gerrit/storperf ${ST WORKDIR ${YARDSTICK_REPO_DIR} RUN ${YARDSTICK_REPO_DIR}/install.sh +RUN ${YARDSTICK_REPO_DIR}/docker/supervisor.sh RUN echo "daemon off;" >> /etc/nginx/nginx.conf diff --git a/docker/nginx.sh b/docker/nginx.sh new file mode 100755 index 000000000..26937d134 --- /dev/null +++ b/docker/nginx.sh @@ -0,0 +1,31 @@ +#!/bin/bash +############################################################################## +# Copyright (c) 2017 Huawei Technologies Co.,Ltd and others. +# +# All rights reserved. This program and the accompanying materials +# are made available under the terms of the Apache License, Version 2.0 +# which accompanies this distribution, and is available at +# http://www.apache.org/licenses/LICENSE-2.0 +############################################################################## + +# nginx config +nginx_config='/etc/nginx/conf.d/yardstick.conf' + +if [[ ! -e "${nginx_config}" ]];then + + cat << EOF > "${nginx_config}" +server { + listen 5000; + server_name localhost; + index index.htm index.html; + location / { + include uwsgi_params; + uwsgi_pass unix:///var/run/yardstick.sock; + } + + location /gui/ { + alias /etc/nginx/yardstick/gui/; + } +} +EOF +fi diff --git a/docker/supervisor.sh b/docker/supervisor.sh new file mode 100755 index 000000000..b67de2212 --- /dev/null +++ b/docker/supervisor.sh @@ -0,0 +1,26 @@ +#!/bin/bash +############################################################################## +# Copyright (c) 2017 Huawei Technologies Co.,Ltd and others. +# +# All rights reserved. This program and the accompanying materials +# are made available under the terms of the Apache License, Version 2.0 +# which accompanies this distribution, and is available at +# http://www.apache.org/licenses/LICENSE-2.0 +############################################################################## + +# nginx service start when boot +supervisor_config='/etc/supervisor/conf.d/yardstick.conf' + +if [[ ! -e "${supervisor_config}" ]];then + cat << EOF > "${supervisor_config}" +[supervisord] +nodaemon = true + +[program:nginx] +command = service nginx restart + +[program:yardstick_uwsgi] +directory = /etc/yardstick +command = uwsgi -i yardstick.ini +EOF +fi diff --git a/api/api-prepare.sh b/docker/uwsgi.sh index 7632d9da9..cf4612332 100755 --- a/api/api-prepare.sh +++ b/docker/uwsgi.sh @@ -1,6 +1,6 @@ #!/bin/bash ############################################################################## -# Copyright (c) 2016 Huawei Technologies Co.,Ltd and others. +# Copyright (c) 2017 Huawei Technologies Co.,Ltd and others. # # All rights reserved. This program and the accompanying materials # are made available under the terms of the Apache License, Version 2.0 @@ -12,6 +12,13 @@ # generate uwsgi config file mkdir -p /etc/yardstick + +# create api log directory +mkdir -p /var/log/yardstick + +# create yardstick.sock for communicating +touch /var/run/yardstick.sock + uwsgi_config='/etc/yardstick/yardstick.ini' if [[ ! -e "${uwsgi_config}" ]];then @@ -37,48 +44,3 @@ EOF echo "virtualenv = ${YARDSTICK_VENV}" >> "${uwsgi_config}" fi fi - -# nginx config -nginx_config='/etc/nginx/conf.d/yardstick.conf' - -if [[ ! -e "${nginx_config}" ]];then - - cat << EOF > "${nginx_config}" -server { - listen 5000; - server_name localhost; - index index.htm index.html; - location / { - include uwsgi_params; - uwsgi_pass unix:///var/run/yardstick.sock; - } -} -EOF -fi - -# nginx service start when boot -supervisor_config='/etc/supervisor/conf.d/yardstick.conf' - -if [[ ! -e "${supervisor_config}" ]];then - cat << EOF > "${supervisor_config}" -[supervisord] -nodaemon = true - -[program:yardstick_nginx] -user = root -command = service nginx restart -autorestart = true - -[program:yardstick_uwsgi] -user = root -directory = /etc/yardstick -command = uwsgi -i yardstick.ini -autorestart = true -EOF -fi - -# create api log directory -mkdir -p /var/log/yardstick - -# create yardstick.sock for communicating -touch /var/run/yardstick.sock diff --git a/docs/release/release-notes/release-notes.rst b/docs/release/release-notes/release-notes.rst index d89f9ed24..6d55ada86 100644 --- a/docs/release/release-notes/release-notes.rst +++ b/docs/release/release-notes/release-notes.rst @@ -38,7 +38,11 @@ Version History | *Date* | *Version* | *Comment* | | | | | +----------------+--------------------+---------------------------------+ -| | 3.0 | Yardstick for Danube release | +| | 3.1 | Yardstick for Danube release | +| | | | +| | | Note: The 3.1 tag is due to git | +| | | tag issue during Danube 3.0 | +| | | release | | | | | +----------------+--------------------+---------------------------------+ | May 4th, 2017 | 2.0 | Yardstick for Danube release | @@ -139,19 +143,19 @@ Release Data | **Project** | Yardstick | | | | +--------------------------------------+--------------------------------------+ -| **Repo/tag** | yardstick/Danube.2.0 | +| **Repo/tag** | yardstick/Danube.3.1 | | | | +--------------------------------------+--------------------------------------+ -| **Yardstick Docker image tag** | Danube.2.0 | +| **Yardstick Docker image tag** | Danube.3.1 | | | | +--------------------------------------+--------------------------------------+ | **Release designation** | Danube | | | | +--------------------------------------+--------------------------------------+ -| **Release date** | May 4th, 2017 | +| **Release date** | July 14th, 2017 | | | | +--------------------------------------+--------------------------------------+ -| **Purpose of the delivery** | OPNFV Danube release 2.0 | +| **Purpose of the delivery** | OPNFV Danube release 3.0 | | | | +--------------------------------------+--------------------------------------+ @@ -171,7 +175,7 @@ Software Deliverables --------------------- - - The Yardstick Docker image: https://hub.docker.com/r/opnfv/yardstick (tag: danube.2.0) + - The Yardstick Docker image: https://hub.docker.com/r/opnfv/yardstick (tag: danube.3.1) **Contexts** @@ -515,7 +519,7 @@ Feature additions Scenario Matrix =============== -For Danube 2.0, Yardstick was tested on the following scenarios: +For Danube 3.0, Yardstick was tested on the following scenarios: +-------------------------+---------+---------+---------+---------+ | Scenario | Apex | Compass | Fuel | Joid | @@ -613,10 +617,50 @@ Known Issues/Faults Corrected Faults ---------------- +Danube.3.1: + ++----------------------------+------------------------------------------------+ +| **JIRA REFERENCE** | **DESCRIPTION** | +| | | ++----------------------------+------------------------------------------------+ +| JIRA: YARDSTICK-714 | Add yardstick env influxdb/grafana command for | +| | CentOS | ++----------------------------+------------------------------------------------+ +| JIRA: YARDSTICK-655 | Monitor command in tc019 may not show the | +| | real nova-api service status | ++----------------------------+------------------------------------------------+ +| JIRA: YARDSTICK-397 | HA testing framework improvement | +| | | ++----------------------------+------------------------------------------------+ +| JIRA: YARDSTICK-660 | Improve monitor_process pass criteria | +| | | ++----------------------------+------------------------------------------------+ +| JIRA: YARDSTICK-657 | HA monitor_multi bug, | +| | KeyError: 'max_outage_time' | ++----------------------------+------------------------------------------------+ +| JIRA: YARDSTICK-647 | TC025 fault_type value is wrong when using | +| | baremetal pod scripts | ++----------------------------+------------------------------------------------+ +| JIRA: YARDSTICK-659 | Terminate openstack service process using kill | +| | command in HA test cases | ++----------------------------+------------------------------------------------+ +| JIRA: ARMBAND-275 | Yardstick TC005 fails with | +| | "Cannot map zero-fill pages" error | ++----------------------------+------------------------------------------------+ +| JIRA: YARDSTICK-561 | Bugfix: AttributeError: 'dict' object has no | +| | attribute 'split' if run sample/ping-hot.yaml | ++----------------------------+------------------------------------------------+ +| JIRA: ARMBAND-268 | ERROR No JSON object could be decoded from | +| | LMBENCH in TC010 | ++----------------------------+------------------------------------------------+ +| JIRA: YARDSTICK-680 | storperf test case tc074 do not get results | +| | | ++----------------------------+------------------------------------------------+ + Danube.2.0: +----------------------------+------------------------------------------------+ -| **JIRA REFERENCE** | **SLOGAN** | +| **JIRA REFERENCE** | **DESCRIPTION** | | | | +----------------------------+------------------------------------------------+ | JIRA: YARDSTICK-608 | Set work directory in Yardstick container | @@ -662,7 +706,7 @@ Danube.2.0: Danube.1.0: +----------------------------+------------------------------------------------+ -| **JIRA REFERENCE** | **SLOGAN** | +| **JIRA REFERENCE** | **DESCRIPTION** | | | | +----------------------------+------------------------------------------------+ | JIRA: YARDSTICK-599 | Could not load EntryPoint.parse when using | @@ -673,7 +717,7 @@ Danube.1.0: +----------------------------+------------------------------------------------+ -Danube 2.0 known restrictions/issues +Danube 3.1 known restrictions/issues ==================================== +-----------+-----------+----------------------------------------------+ | Installer | Scenario | Issue | @@ -695,7 +739,7 @@ Open JIRA tickets ================= +----------------------------+------------------------------------------------+ -| **JIRA REFERENCE** | **SLOGAN** | +| **JIRA REFERENCE** | **DESCRIPTION** | | | | +----------------------------+------------------------------------------------+ | JIRA: YARDSTICK-626 | Fio and Lmbench don't work in Ubuntu-arm64 | diff --git a/etc/yardstick/nodes/compass_sclab_virtual/pod.yaml b/etc/yardstick/nodes/compass_sclab_virtual/pod.yaml index 5c5574005..e306d0d94 100644 --- a/etc/yardstick/nodes/compass_sclab_virtual/pod.yaml +++ b/etc/yardstick/nodes/compass_sclab_virtual/pod.yaml @@ -20,30 +20,35 @@ nodes: - name: node1 + host_name: host1 role: Controller ip: 10.1.0.50 user: root password: root - name: node2 + host_name: host2 role: Controller ip: 10.1.0.51 user: root password: root - name: node3 + host_name: host3 role: Controller ip: 10.1.0.52 user: root password: root - name: node4 + host_name: host4 role: Compute ip: 10.1.0.53 user: root password: root - name: node5 + host_name: host5 role: Compute ip: 10.1.0.54 user: root diff --git a/etc/yardstick/yardstick.conf.sample b/etc/yardstick/yardstick.conf.sample index 572051186..1157b9d62 100644 --- a/etc/yardstick/yardstick.conf.sample +++ b/etc/yardstick/yardstick.conf.sample @@ -9,7 +9,7 @@ [DEFAULT] debug = False -dispatcher = http +dispatcher = http # setup multiple dipatcher with comma deperted e.g. file,http [dispatcher_http] timeout = 5 diff --git a/gui/Gruntfile.js b/gui/Gruntfile.js new file mode 100644 index 000000000..171d65add --- /dev/null +++ b/gui/Gruntfile.js @@ -0,0 +1,492 @@ +// Generated on 2017-05-31 using generator-angular 0.15.1 +'use strict'; + +// # Globbing +// for performance reasons we're only matching one level down: +// 'test/spec/{,*/}*.js' +// use this if you want to recursively match all subfolders: +// 'test/spec/**/*.js' + +module.exports = function(grunt) { + + // Time how long tasks take. Can help when optimizing build times + require('time-grunt')(grunt); + + // Automatically load required Grunt tasks + require('jit-grunt')(grunt, { + useminPrepare: 'grunt-usemin', + ngtemplates: 'grunt-angular-templates', + cdnify: 'grunt-google-cdn' + }); + + // Configurable paths for the application + var appConfig = { + app: require('./bower.json').appPath || 'app', + dist: 'dist' + }; + + // Define the configuration for all the tasks + grunt.initConfig({ + + // Project settings + yeoman: appConfig, + + // Watches files for changes and runs tasks based on the changed files + watch: { + bower: { + files: ['bower.json'], + tasks: ['wiredep'] + }, + js: { + files: ['<%= yeoman.app %>/scripts/{,*/}*.js'], + tasks: ['newer:jshint:all', 'newer:jscs:all'], + options: { + livereload: '<%= connect.options.livereload %>' + } + }, + jsTest: { + files: ['test/spec/{,*/}*.js'], + tasks: ['newer:jshint:test', 'newer:jscs:test', 'karma'] + }, + styles: { + files: ['<%= yeoman.app %>/styles/{,*/}*.css'], + tasks: ['newer:copy:styles', 'postcss'] + }, + gruntfile: { + files: ['Gruntfile.js'] + }, + livereload: { + options: { + livereload: '<%= connect.options.livereload %>' + }, + files: [ + '<%= yeoman.app %>/{,*/}*.html', + '.tmp/styles/{,*/}*.css', + '<%= yeoman.app %>/images/{,*/}*.{png,jpg,jpeg,gif,webp,svg}' + ] + } + }, + + // The actual grunt server settings + connect: { + options: { + port: 9099, + // Change this to '0.0.0.0' to access the server from outside. + hostname: 'localhost', + livereload: 35745 + }, + livereload: { + options: { + open: true, + middleware: function(connect) { + return [ + connect.static('.tmp'), + connect().use( + '/bower_components', + connect.static('./bower_components') + ), + connect().use( + '/app/styles', + connect.static('./app/styles') + ), + connect.static(appConfig.app) + ]; + } + } + }, + test: { + options: { + port: 9001, + middleware: function(connect) { + return [ + connect.static('.tmp'), + connect.static('test'), + connect().use( + '/bower_components', + connect.static('./bower_components') + ), + connect.static(appConfig.app) + ]; + } + } + }, + dist: { + options: { + open: true, + base: '<%= yeoman.dist %>' + } + } + }, + + // Make sure there are no obvious mistakes + jshint: { + options: { + jshintrc: '.jshintrc', + reporter: require('jshint-stylish') + }, + all: { + src: [ + 'Gruntfile.js', + '<%= yeoman.app %>/scripts/{,*/}*.js' + ] + }, + test: { + options: { + jshintrc: 'test/.jshintrc' + }, + src: ['test/spec/{,*/}*.js'] + } + }, + + // Make sure code styles are up to par + jscs: { + options: { + config: '.jscsrc', + verbose: true + }, + all: { + src: [ + 'Gruntfile.js', + '<%= yeoman.app %>/scripts/{,*/}*.js' + ] + }, + test: { + src: ['test/spec/{,*/}*.js'] + } + }, + + // Empties folders to start fresh + clean: { + dist: { + files: [{ + dot: true, + src: [ + '.tmp', + '<%= yeoman.dist %>/{,*/}*', + '!<%= yeoman.dist %>/.git{,*/}*' + ] + }] + }, + server: '.tmp' + }, + + // Add vendor prefixed styles + postcss: { + options: { + processors: [ + require('autoprefixer-core')({ browsers: ['last 1 version'] }) + ] + }, + server: { + options: { + map: true + }, + files: [{ + expand: true, + cwd: '.tmp/styles/', + src: '{,*/}*.css', + dest: '.tmp/styles/' + }] + }, + dist: { + files: [{ + expand: true, + cwd: '.tmp/styles/', + src: '{,*/}*.css', + dest: '.tmp/styles/' + }] + } + }, + + // Automatically inject Bower components into the app + wiredep: { + app: { + src: ['<%= yeoman.app %>/index.html'], + ignorePath: /\.\.\// + }, + test: { + devDependencies: true, + src: '<%= karma.unit.configFile %>', + ignorePath: /\.\.\//, + fileTypes: { + js: { + block: /(([\s\t]*)\/{2}\s*?bower:\s*?(\S*))(\n|\r|.)*?(\/{2}\s*endbower)/gi, + detect: { + js: /'(.*\.js)'/gi + }, + replace: { + js: '\'{{filePath}}\',' + } + } + } + } + }, + + // Renames files for browser caching purposes + filerev: { + dist: { + src: [ + '<%= yeoman.dist %>/scripts/{,*/}*.js', + '<%= yeoman.dist %>/styles/{,*/}*.css', + '<%= yeoman.dist %>/images/{,*/}*.{png,jpg,jpeg,gif,webp,svg}', + '<%= yeoman.dist %>/styles/fonts/*' + ] + } + }, + + // Reads HTML for usemin blocks to enable smart builds that automatically + // concat, minify and revision files. Creates configurations in memory so + // additional tasks can operate on them + useminPrepare: { + html: '<%= yeoman.app %>/index.html', + options: { + dest: '<%= yeoman.dist %>', + flow: { + html: { + steps: { + js: ['concat', 'uglifyjs'], + css: ['cssmin'] + }, + post: {} + } + } + } + }, + + // Performs rewrites based on filerev and the useminPrepare configuration + usemin: { + html: ['<%= yeoman.dist %>/{,*/}*.html'], + css: ['<%= yeoman.dist %>/styles/{,*/}*.css'], + js: ['<%= yeoman.dist %>/scripts/{,*/}*.js'], + options: { + assetsDirs: [ + '<%= yeoman.dist %>', + '<%= yeoman.dist %>/images', + '<%= yeoman.dist %>/styles' + ], + patterns: { + js: [ + [/(images\/[^''""]*\.(png|jpg|jpeg|gif|webp|svg))/g, 'Replacing references to images'] + ] + } + } + }, + + // The following *-min tasks will produce minified files in the dist folder + // By default, your `index.html`'s <!-- Usemin block --> will take care of + // minification. These next options are pre-configured if you do not wish + // to use the Usemin blocks. + // cssmin: { + // dist: { + // files: { + // '<%= yeoman.dist %>/styles/main.css': [ + // '.tmp/styles/{,*/}*.css' + // ] + // } + // } + // }, + // uglify: { + // dist: { + // files: { + // '<%= yeoman.dist %>/scripts/scripts.js': [ + // '<%= yeoman.dist %>/scripts/scripts.js' + // ] + // } + // } + // }, + // concat: { + // dist: {} + // }, + + imagemin: { + dist: { + files: [{ + expand: true, + cwd: '<%= yeoman.app %>/images', + src: '{,*/}*.{png,jpg,jpeg,gif}', + dest: '<%= yeoman.dist %>/images' + }] + } + }, + + svgmin: { + dist: { + files: [{ + expand: true, + cwd: '<%= yeoman.app %>/images', + src: '{,*/}*.svg', + dest: '<%= yeoman.dist %>/images' + }] + } + }, + + htmlmin: { + dist: { + options: { + collapseWhitespace: true, + conservativeCollapse: true, + collapseBooleanAttributes: true, + removeCommentsFromCDATA: true + }, + files: [{ + expand: true, + cwd: '<%= yeoman.dist %>', + src: ['*.html'], + dest: '<%= yeoman.dist %>' + }] + } + }, + + ngtemplates: { + dist: { + options: { + module: 'yardStickGui2App', + htmlmin: '<%= htmlmin.dist.options %>', + usemin: 'scripts/scripts.js' + }, + cwd: '<%= yeoman.app %>', + src: 'views/{,*/}*.html', + dest: '.tmp/templateCache.js' + } + }, + + // ng-annotate tries to make the code safe for minification automatically + // by using the Angular long form for dependency injection. + ngAnnotate: { + dist: { + files: [{ + expand: true, + cwd: '.tmp/concat/scripts', + src: '*.js', + dest: '.tmp/concat/scripts' + }] + } + }, + + // Replace Google CDN references + cdnify: { + dist: { + html: ['<%= yeoman.dist %>/*.html'] + } + }, + + // Copies remaining files to places other tasks can use + copy: { + dist: { + files: [{ + expand: true, + dot: true, + cwd: '<%= yeoman.app %>', + dest: '<%= yeoman.dist %>', + src: [ + '*.{ico,png,txt}', + '*.html', + 'images/{,*/}*.{webp}', + 'styles/fonts/{,*/}*.*' + ] + }, { + expand: true, + cwd: '.tmp/images', + dest: '<%= yeoman.dist %>/images', + src: ['generated/*'] + }, { + expand: true, + cwd: 'bower_components/bootstrap/dist', + src: 'fonts/*', + dest: '<%= yeoman.dist %>' + }, + { + expand: true, + cwd: 'bower_components/components-font-awesome', + src: 'fonts/*', + dest: '<%=yeoman.dist%>' + } + ] + }, + styles: { + expand: true, + cwd: '<%= yeoman.app %>/styles', + dest: '.tmp/styles/', + src: '{,*/}*.css' + } + }, + + // Run some tasks in parallel to speed up the build process + concurrent: { + server: [ + 'copy:styles' + ], + test: [ + 'copy:styles' + ], + dist: [ + 'copy:styles', + 'imagemin', + 'svgmin' + ] + }, + + // Test settings + karma: { + unit: { + configFile: 'test/karma.conf.js', + singleRun: true + } + } + }); + + + grunt.registerTask('serve', 'Compile then start a connect web server', function(target) { + if (target === 'dist') { + return grunt.task.run(['build', 'connect:dist:keepalive']); + } + + grunt.task.run([ + 'clean:server', + 'wiredep', + 'concurrent:server', + 'postcss:server', + 'connect:livereload', + 'watch' + ]); + }); + + grunt.registerTask('server', 'DEPRECATED TASK. Use the "serve" task instead', function(target) { + grunt.log.warn('The `server` task has been deprecated. Use `grunt serve` to start a server.'); + grunt.task.run(['serve:' + target]); + }); + + grunt.registerTask('test', [ + 'clean:server', + 'wiredep', + 'concurrent:test', + 'postcss', + 'connect:test', + 'karma' + ]); + + grunt.registerTask('build', [ + 'clean:dist', + 'wiredep', + 'useminPrepare', + 'concurrent:dist', + 'postcss', + 'ngtemplates', + 'concat', + 'ngAnnotate', + 'copy:dist', + // 'cdnify', + 'cssmin', + 'uglify', + 'filerev', + 'usemin', + 'htmlmin' + ]); + + grunt.registerTask('default', [ + 'newer:jshint', + 'newer:jscs', + 'test', + 'build' + ]); +}; diff --git a/gui/app/404.html b/gui/app/404.html new file mode 100644 index 000000000..899828a3c --- /dev/null +++ b/gui/app/404.html @@ -0,0 +1,152 @@ +<!doctype html> +<html lang="en"> + <head> + <meta charset="utf-8"> + <title>Page Not Found :(</title> + <style> + ::-moz-selection { + background: #b3d4fc; + text-shadow: none; + } + + ::selection { + background: #b3d4fc; + text-shadow: none; + } + + html { + padding: 30px 10px; + font-size: 20px; + line-height: 1.4; + color: #737373; + background: #f0f0f0; + -webkit-text-size-adjust: 100%; + -ms-text-size-adjust: 100%; + } + + html, + input { + font-family: "Helvetica Neue", Helvetica, Arial, sans-serif; + } + + body { + max-width: 500px; + padding: 30px 20px 50px; + border: 1px solid #b3b3b3; + border-radius: 4px; + margin: 0 auto; + box-shadow: 0 1px 10px #a7a7a7, inset 0 1px 0 #fff; + background: #fcfcfc; + } + + h1 { + margin: 0 10px; + font-size: 50px; + text-align: center; + } + + h1 span { + color: #bbb; + } + + h3 { + margin: 1.5em 0 0.5em; + } + + p { + margin: 1em 0; + } + + ul { + padding: 0 0 0 40px; + margin: 1em 0; + } + + .container { + max-width: 380px; + margin: 0 auto; + } + + /* google search */ + + #goog-fixurl ul { + list-style: none; + padding: 0; + margin: 0; + } + + #goog-fixurl form { + margin: 0; + } + + #goog-wm-qt, + #goog-wm-sb { + border: 1px solid #bbb; + font-size: 16px; + line-height: normal; + vertical-align: top; + color: #444; + border-radius: 2px; + } + + #goog-wm-qt { + width: 220px; + height: 20px; + padding: 5px; + margin: 5px 10px 0 0; + box-shadow: inset 0 1px 1px #ccc; + } + + #goog-wm-sb { + display: inline-block; + height: 32px; + padding: 0 10px; + margin: 5px 0 0; + white-space: nowrap; + cursor: pointer; + background-color: #f5f5f5; + background-image: -webkit-linear-gradient(rgba(255,255,255,0), #f1f1f1); + background-image: -moz-linear-gradient(rgba(255,255,255,0), #f1f1f1); + background-image: -ms-linear-gradient(rgba(255,255,255,0), #f1f1f1); + background-image: -o-linear-gradient(rgba(255,255,255,0), #f1f1f1); + -webkit-appearance: none; + -moz-appearance: none; + appearance: none; + } + + #goog-wm-sb:hover, + #goog-wm-sb:focus { + border-color: #aaa; + box-shadow: 0 1px 1px rgba(0, 0, 0, 0.1); + background-color: #f8f8f8; + } + + #goog-wm-qt:hover, + #goog-wm-qt:focus { + border-color: #105cb6; + outline: 0; + color: #222; + } + + input::-moz-focus-inner { + padding: 0; + border: 0; + } + </style> + </head> + <body> + <div class="container"> + <h1>Not found <span>:(</span></h1> + <p>Sorry, but the page you were trying to view does not exist.</p> + <p>It looks like this was the result of either:</p> + <ul> + <li>a mistyped address</li> + <li>an out-of-date link</li> + </ul> + <script> + var GOOG_FIXURL_LANG = (navigator.language || '').slice(0,2),GOOG_FIXURL_SITE = location.host; + </script> + <script src="//linkhelp.clients.google.com/tbproxy/lh/wm/fixurl.js"></script> + </div> + </body> +</html> diff --git a/gui/app/favicon.ico b/gui/app/favicon.ico Binary files differnew file mode 100644 index 000000000..652790530 --- /dev/null +++ b/gui/app/favicon.ico diff --git a/gui/app/images/back.png b/gui/app/images/back.png Binary files differnew file mode 100644 index 000000000..917c86edd --- /dev/null +++ b/gui/app/images/back.png diff --git a/gui/app/images/checkno.png b/gui/app/images/checkno.png Binary files differnew file mode 100644 index 000000000..7c6841930 --- /dev/null +++ b/gui/app/images/checkno.png diff --git a/gui/app/images/checkyes.png b/gui/app/images/checkyes.png Binary files differnew file mode 100644 index 000000000..ef6028310 --- /dev/null +++ b/gui/app/images/checkyes.png diff --git a/gui/app/images/close.png b/gui/app/images/close.png Binary files differnew file mode 100644 index 000000000..0d2c14252 --- /dev/null +++ b/gui/app/images/close.png diff --git a/gui/app/images/loading.gif b/gui/app/images/loading.gif Binary files differnew file mode 100644 index 000000000..b04dd11bc --- /dev/null +++ b/gui/app/images/loading.gif diff --git a/gui/app/images/loading2.gif b/gui/app/images/loading2.gif Binary files differnew file mode 100644 index 000000000..9d1534468 --- /dev/null +++ b/gui/app/images/loading2.gif diff --git a/gui/app/images/statusno.png b/gui/app/images/statusno.png Binary files differnew file mode 100644 index 000000000..ace4a454d --- /dev/null +++ b/gui/app/images/statusno.png diff --git a/gui/app/images/statusyes.png b/gui/app/images/statusyes.png Binary files differnew file mode 100644 index 000000000..d88a99ee8 --- /dev/null +++ b/gui/app/images/statusyes.png diff --git a/gui/app/images/url.json b/gui/app/images/url.json new file mode 100644 index 000000000..f16c4e0ac --- /dev/null +++ b/gui/app/images/url.json @@ -0,0 +1 @@ +{"url": "192.168.23.2:1948"}
\ No newline at end of file diff --git a/gui/app/images/yeoman.png b/gui/app/images/yeoman.png Binary files differnew file mode 100644 index 000000000..92497addf --- /dev/null +++ b/gui/app/images/yeoman.png diff --git a/gui/app/index.html b/gui/app/index.html new file mode 100644 index 000000000..5592656cc --- /dev/null +++ b/gui/app/index.html @@ -0,0 +1,111 @@ +<!doctype html> +<html> + +<head> + <meta charset="utf-8"> + <title></title> + <meta name="description" content=""> + <meta name="viewport" content="width=device-width"> + <!-- Place favicon.ico and apple-touch-icon.png in the root directory --> + <!-- build:css(.) styles/vendor.css --> + <!-- bower:css --> + <link rel="stylesheet" href="bower_components/bootstrap/dist/css/bootstrap.css" /> + <link rel="stylesheet" href="bower_components/angular-wizard/dist/angular-wizard.min.css" /> + <link rel="stylesheet" href="bower_components/AngularJS-Toaster/toaster.css" /> + <link rel="stylesheet" href="bower_components/ng-dialog/css/ngDialog.css" /> + <link rel="stylesheet" href="bower_components/ng-dialog/css/ngDialog-theme-default.css" /> + <link rel="stylesheet" href="bower_components/components-font-awesome/css/font-awesome.css" /> + <link rel="stylesheet" href="bower_components/v-accordion/dist/v-accordion.css" /> + <link rel="stylesheet" href="bower_components/angular-loading/angular-loading.css" /> + <!-- endbower --> + <!-- endbuild --> + <!-- build:css(.tmp) styles/main.css --> + <link rel="stylesheet" href="styles/main.css"> + + + <!-- endbuild --> +</head> + +<script> +// read file + + +</script> + +<body ng-app="yardStickGui2App"> + <!--[if lte IE 8]> + <p class="browsehappy">You are using an <strong>outdated</strong> browser. Please <a href="http://browsehappy.com/">upgrade your browser</a> to improve your experience.</p> + <![endif]--> + + + + + <div ui-view></div> + + + + + <!-- Google Analytics: change UA-XXXXX-X to be your site's ID --> + <!--<script> + ! function(A, n, g, u, l, a, r) { + A.GoogleAnalyticsObject = l, A[l] = A[l] || function() { + (A[l].q = A[l].q || []).push(arguments) + }, A[l].l = +new Date, a = n.createElement(g), + r = n.getElementsByTagName(g)[0], a.src = u, r.parentNode.insertBefore(a, r) + }(window, document, 'script', 'https://www.google-analytics.com/analytics.js', 'ga'); + + ga('create', 'UA-XXXXX-X'); + ga('send', 'pageview'); + </script>--> + + <!-- build:js(.) scripts/vendor.js --> + <!-- bower:js --> + <script src="bower_components/jquery/dist/jquery.js"></script> + <script src="bower_components/angular/angular.js"></script> + <script src="bower_components/bootstrap/dist/js/bootstrap.js"></script> + <script src="bower_components/angular-strap/dist/angular-strap.js"></script> + <script src="bower_components/angular-strap/dist/angular-strap.tpl.js"></script> + <script src="bower_components/angular-ui-router/release/angular-ui-router.js"></script> + <script src="bower_components/angular-animate/angular-animate.js"></script> + <script src="bower_components/angular-breadcrumb/release/angular-breadcrumb.js"></script> + <script src="bower_components/angular-wizard/dist/angular-wizard.min.js"></script> + <script src="bower_components/angular-resource/angular-resource.js"></script> + <script src="bower_components/ng-file-upload/ng-file-upload.js"></script> + <script src="bower_components/AngularJS-Toaster/toaster.js"></script> + <script src="bower_components/ng-dialog/js/ngDialog.js"></script> + <script src="bower_components/angularUtils-pagination/dirPagination.js"></script> + <script src="bower_components/ngstorage/ngStorage.js"></script> + <script src="bower_components/v-accordion/dist/v-accordion.js"></script> + <script src="bower_components/spin.js/spin.js"></script> + <script src="bower_components/angular-loading/angular-loading.js"></script> + <script src="bower_components/spin.js/spin.js"></script> + <script src="bower_components/angular-bootstrap/ui-bootstrap-tpls.js"></script> + <script src="bower_components/angular-sanitize/angular-sanitize.js"></script> + <!-- endbower --> + <!-- endbuild --> + + <!-- build:js({.tmp,app}) scripts/scripts.js --> + <script src="scripts/app.js"></script> + <script src="scripts/router.config.js"></script> + <script src="scripts/controllers/main.js"></script> + <script src="scripts/factory/main.factory.js"></script> + <script src="scripts/controllers/content.controller.js"></script> + <script src="scripts/controllers/detail.controller.js"></script> + <script src="scripts/controllers/image.controller.js"></script> + <script src="scripts/controllers/pod.controller.js"></script> + <script src="scripts/controllers/container.controller.js"></script> + <script src="scripts/controllers/testcase.controller.js"></script> + <script src="scripts/controllers/testcasedetail.controller.js"></script> + <script src="scripts/controllers/testsuit.controller.js"></script> + <script src="scripts/controllers/suitedetail.controller.js"></script> + <script src="scripts/controllers/suitecreate.controller.js"></script> + <script src="scripts/controllers/task.controller.js"></script> + <script src="scripts/controllers/report.controller.js"></script> + <script src="scripts/controllers/project.controller.js"></script> + <script src="scripts/controllers/projectDetail.controller.js"></script> + <script src="scripts/controllers/taskModify.controller.js"></script> + + <!-- endbuild --> +</body> + +</html> diff --git a/gui/app/robots.txt b/gui/app/robots.txt new file mode 100644 index 000000000..4d521f952 --- /dev/null +++ b/gui/app/robots.txt @@ -0,0 +1,4 @@ +# robotstxt.org + +User-agent: * +Disallow: diff --git a/gui/app/scripts/app.js b/gui/app/scripts/app.js new file mode 100644 index 000000000..ecb642c95 --- /dev/null +++ b/gui/app/scripts/app.js @@ -0,0 +1,30 @@ +'use strict'; + +/** + * @ngdoc overview + * @name yardStickGui2App + * @description + * # yardStickGui2App + * + * Main module of the application. + */ +angular + .module('yardStickGui2App', [ + 'ui.router', + 'ngAnimate', + 'ngSanitize', + 'mgcrea.ngStrap', + 'ncy-angular-breadcrumb', + 'mgo-angular-wizard', + 'ngResource', + 'ngFileUpload', + 'toaster', + 'ngDialog', + 'angularUtils.directives.dirPagination', + 'ngStorage', + 'vAccordion', + 'darthwade.dwLoading', + 'ui.bootstrap' + + + ]); diff --git a/gui/app/scripts/controllers/container.controller.js b/gui/app/scripts/controllers/container.controller.js new file mode 100644 index 000000000..6c2ccd8ff --- /dev/null +++ b/gui/app/scripts/controllers/container.controller.js @@ -0,0 +1,182 @@ +'use strict'; + +angular.module('yardStickGui2App') + .controller('ContainerController', ['$scope', '$state', '$stateParams', 'mainFactory', 'Upload', 'toaster', 'ngDialog', + function($scope, $state, $stateParams, mainFactory, Upload, toaster, ngDialog) { + + + init(); + $scope.showloading = false; + + $scope.displayContainerInfo = []; + $scope.containerList = [{ value: 'create_influxdb', name: "InfluxDB" }, { value: 'create_grafana', name: "Grafana" }] + + function init() { + + + $scope.uuid = $stateParams.uuid; + $scope.createContainer = createContainer; + $scope.openChooseContainnerDialog = openChooseContainnerDialog; + + + getItemIdDetail(); + + } + + function getItemIdDetail() { + $scope.displayContainerInfo = []; + mainFactory.ItemDetail().get({ + 'envId': $scope.uuid + }).$promise.then(function(response) { + if (response.status == 1) { + $scope.envName = response.result.environment.name; + $scope.containerId = response.result.environment.container_id; + if ($scope.containerId != null) { + + var keysArray = Object.keys($scope.containerId); + for (var k in $scope.containerId) { + getConDetail($scope.containerId[k]); + } + } else { + $scope.podData = null; + } + + } + }, function(error) { + toaster.pop({ + type: 'error', + title: 'fail', + body: 'unknow error', + timeout: 3000 + }); + }) + } + + function getConDetail(id) { + mainFactory.containerDetail().get({ + 'containerId': id + }).$promise.then(function(response) { + if (response.status == 1) { + // $scope.podData = response.result; + response.result.container['id'] = id; + $scope.displayContainerInfo.push(response.result.container); + + } + + }, function(error) { + toaster.pop({ + type: 'error', + title: 'fail', + body: 'unknow error', + timeout: 3000 + }); + }) + + } + + function createContainer() { + + $scope.showloading = true; + mainFactory.runAcontainer().post({ + 'action': $scope.selectContainer.value, + 'args': { + 'environment_id': $scope.uuid, + } + }).$promise.then(function(response) { + $scope.showloading = false; + if (response.status == 1) { + toaster.pop({ + type: 'success', + title: 'create container success', + body: 'you can go next step', + timeout: 3000 + }); + setTimeout(function() { + getItemIdDetail(); + }, 10000); + } else { + toaster.pop({ + type: 'error', + title: 'Wrong', + body: response.error_msg, + timeout: 3000 + }); + } + }, function(error) { + toaster.pop({ + type: 'error', + title: 'fail', + body: 'unknow error', + timeout: 3000 + }); + + }) + } + + function openChooseContainnerDialog() { + ngDialog.open({ + template: 'views/modal/chooseContainer.html', + scope: $scope, + className: 'ngdialog-theme-default', + width: 500, + showClose: true, + closeByDocument: false + }) + } + + function chooseResult(name) { + $scope.selectContainer = name; + } + $scope.goBack = function goBack() { + $state.go('app2.projectList'); + } + + $scope.openDeleteEnv = function openDeleteEnv(id, name) { + $scope.deleteName = name; + $scope.deleteId = id; + ngDialog.open({ + template: 'views/modal/deleteConfirm.html', + scope: $scope, + className: 'ngdialog-theme-default', + width: 500, + showClose: true, + closeByDocument: false + }) + + } + + $scope.deleteContainer = function deleteContainer() { + mainFactory.deleteContainer().delete({ 'containerId': $scope.deleteId }).$promise.then(function(response) { + if (response.status == 1) { + toaster.pop({ + type: 'success', + title: 'delete container success', + body: 'you can go next step', + timeout: 3000 + }); + ngDialog.close(); + getItemIdDetail(); + + } else { + toaster.pop({ + type: 'error', + title: 'Wrong', + body: response.error_msg, + timeout: 3000 + }); + } + + }, function(error) { + toaster.pop({ + type: 'error', + title: 'fail', + body: 'unknow error', + timeout: 3000 + }); + }) + } + + + + } + ]); diff --git a/gui/app/scripts/controllers/content.controller.js b/gui/app/scripts/controllers/content.controller.js new file mode 100644 index 000000000..d2bc19eea --- /dev/null +++ b/gui/app/scripts/controllers/content.controller.js @@ -0,0 +1,136 @@ +'use strict'; + +angular.module('yardStickGui2App') + .controller('ContentController', ['$scope', '$state', '$stateParams', 'mainFactory', 'Upload', 'toaster', '$location', '$localStorage', + function($scope, $state, $stateParams, mainFactory, Upload, toaster, $location, $localStorage) { + + + + + init(); + $scope.showEnvironment = false; + $scope.counldGoDetail = false; + $scope.activeStatus = 0; + + $scope.$watch(function() { + return location.hash + }, function(newvalue, oldvalue) { + if (location.hash.indexOf('project') > -1) { + $scope.projectShow = true; + $scope.taskShow = false; + $scope.reportShow = false; + } else if (location.hash.indexOf('task') > -1) { + $scope.taskShow = true; + $scope.projectShow = true; + } else if (location.hash.indexOf('report') > -1) { + $scope.reportShow = true; + $scope.taskShow = true; + $scope.projectShow = true; + } + + }) + + + function init() { + + + $scope.showEnvironments = showEnvironments; + $scope.showSteps = $location.path().indexOf('project'); + $scope.test = test; + $scope.gotoUploadPage = gotoUploadPage; + $scope.gotoOpenrcPage = gotoOpenrcPage; + $scope.gotoPodPage = gotoPodPage; + $scope.gotoContainerPage = gotoContainerPage; + $scope.gotoTestcase = gotoTestcase; + $scope.gotoEnviron = gotoEnviron; + $scope.gotoSuite = gotoSuite; + $scope.gotoProject = gotoProject; + $scope.gotoTask = gotoTask; + $scope.gotoReport = gotoReport; + $scope.stepsStatus = $localStorage.stepsStatus; + $scope.goBack = goBack; + + + } + + + + function showEnvironments() { + $scope.showEnvironment = true; + } + + function test() { + alert('test'); + } + + function gotoOpenrcPage() { + $scope.path = $location.path(); + $scope.uuid = $scope.path.split('/').pop(); + $state.go('app.environmentDetail', { uuid: $scope.uuid }) + } + + function gotoUploadPage() { + $scope.path = $location.path(); + $scope.uuid = $scope.path.split('/').pop(); + $state.go('app.uploadImage', { uuid: $scope.uuid }); + } + + function gotoPodPage() { + $scope.path = $location.path(); + $scope.uuid = $scope.path.split('/').pop(); + $state.go('app.podUpload', { uuid: $scope.uuid }); + } + + function gotoContainerPage() { + $scope.path = $location.path(); + $scope.uuid = $scope.path.split('/').pop(); + $state.go('app.container', { uuid: $scope.uuid }); + } + + function gotoTestcase() { + $state.go('app2.testcase'); + } + + function gotoEnviron() { + if ($location.path().indexOf('env') > -1 || $location.path().indexOf('environment') > -1) { + $scope.counldGoDetail = true; + } + $state.go('app2.environment'); + } + + function gotoSuite() { + $state.go('app2.testsuite'); + } + + function gotoProject() { + $state.go('app2.projectList'); + } + + function gotoTask() { + $state.go('app2.tasklist'); + } + + function gotoReport() { + $state.go('app2.report'); + } + + function goBack() { + if ($location.path().indexOf('main/environment')) { + return; + } else if ($location.path().indexOf('main/envDetail/') || $location.path().indexOf('main/imageDetail/') || + $location.path().indexOf('main/podupload/') || $location.path().indexOf('main/container/')) { + $state.go('app2.environment'); + return; + } else { + window.history.back(); + } + + } + + + + + + + } + ]);
\ No newline at end of file diff --git a/gui/app/scripts/controllers/detail.controller.js b/gui/app/scripts/controllers/detail.controller.js new file mode 100644 index 000000000..3e2eaa100 --- /dev/null +++ b/gui/app/scripts/controllers/detail.controller.js @@ -0,0 +1,384 @@ +'use strict'; + +angular.module('yardStickGui2App') + .controller('DetailController', ['$scope', '$state', '$stateParams', 'mainFactory', 'Upload', 'toaster', '$location', 'ngDialog', + function($scope, $state, $stateParams, mainFactory, Upload, toaster, $location, ngDialog) { + + + + + init(); + $scope.showEnvironment = false; + $scope.envInfo = []; + + function init() { + $scope.showEnvironments = showEnvironments; + // $scope.openrcID = $stateParams.uuid; + $scope.deleteEnvItem = deleteEnvItem; + $scope.addInfo = addInfo; + $scope.submitOpenRcFile = submitOpenRcFile; + $scope.uploadFiles = uploadFiles; + $scope.addEnvironment = addEnvironment; + + $scope.uuid = $stateParams.uuid; + $scope.openrcID = $stateParams.opercId; + $scope.imageID = $stateParams.imageId; + $scope.podID = $stateParams.podId; + $scope.containerId = $stateParams.containerId; + $scope.ifNew = $stateParams.ifNew; + + + getItemIdDetail(); + } + + + + function showEnvironments() { + $scope.showEnvironment = true; + } + + + function deleteEnvItem(index) { + $scope.envInfo.splice(index, 1); + } + + function addInfo() { + var tempKey = null; + var tempValue = null; + var temp = { + name: tempKey, + value: tempValue + } + $scope.envInfo.push(temp); + + } + + function submitOpenRcFile() { + $scope.showloading = true; + + var postData = {}; + postData['action'] = 'update_openrc'; + rebuildEnvInfo(); + postData['args'] = {}; + postData['args']['openrc'] = $scope.postEnvInfo; + postData['args']['environment_id'] = $scope.uuid; + + + mainFactory.postEnvironmentVariable().post(postData).$promise.then(function(response) { + $scope.showloading = false; + + if (response.status == 1) { + + $scope.openrcInfo = response.result; + toaster.pop({ + type: 'success', + title: 'create success', + body: 'you can go next step', + timeout: 3000 + }); + $scope.showEnvrionment = true; + getItemIdDetail(); + } else { + toaster.pop({ + type: 'error', + title: 'faile', + body: response.error_msg, + timeout: 3000 + }); + } + + }, function(error) { + toaster.pop({ + type: 'error', + title: 'fail', + body: 'unknow error', + timeout: 3000 + }); + }) + } + + //reconstruc EnvInfo + function rebuildEnvInfo() { + $scope.postEnvInfo = {}; + for (var i = 0; i < $scope.envInfo.length; i++) { + $scope.postEnvInfo[$scope.envInfo[i].name] = $scope.envInfo[i].value; + } + + } + + //buildtoEnvInfo + function buildToEnvInfo(object) { + var tempKeyArray = Object.keys(object); + + for (var i = 0; i < tempKeyArray.length; i++) { + var tempkey = tempKeyArray[i]; + var tempValue = object[tempKeyArray[i]]; + var temp = { + name: tempkey, + value: tempValue + }; + $scope.envInfo.push(temp); + } + } + + function uploadFiles($file, $invalidFiles) { + $scope.openrcInfo = {}; + $scope.loadingOPENrc = true; + + $scope.displayOpenrcFile = $file; + timeConstruct($scope.displayOpenrcFile.lastModified); + Upload.upload({ + url: Base_URL + '/api/v2/yardstick/openrcs', + data: { file: $file, 'environment_id': $scope.uuid, 'action': 'upload_openrc' } + }).then(function(response) { + + $scope.loadingOPENrc = false; + if (response.data.status == 1) { + toaster.pop({ + type: 'success', + title: 'upload success', + body: 'you can go next step', + timeout: 3000 + }); + $scope.openrcInfo = response.data.result; + getItemIdDetail(); + + } else { + toaster.pop({ + type: 'error', + title: 'faile', + body: response.error_msg, + timeout: 3000 + }); + } + + }, function(error) { + $scope.uploadfile = null; + toaster.pop({ + type: 'error', + title: 'fail', + body: 'unknow error', + timeout: 3000 + }); + }) + } + + function timeConstruct(array) { + var date = new Date(1398250549490); + var Y = date.getFullYear() + '-'; + var M = (date.getMonth() + 1 < 10 ? '0' + (date.getMonth() + 1) : date.getMonth() + 1) + '-'; + var D = date.getDate() + ' '; + var h = date.getHours() + ':'; + var m = date.getMinutes() + ':'; + var s = date.getSeconds(); + $scope.filelastModified = Y + M + D + h + m + s; + + } + + function addEnvironment() { + mainFactory.addEnvName().post({ + 'action': 'create_environment', + args: { + 'name': $scope.baseElementInfo.name + } + }).$promise.then(function(response) { + if (response.status == 1) { + toaster.pop({ + type: 'success', + title: 'create name success', + body: 'you can go next step', + timeout: 3000 + }); + $scope.uuid = response.result.uuid; + var path = $location.path(); + path = path + $scope.uuid; + $location.url(path); + getItemIdDetail(); + } + + }, function(error) { + toaster.pop({ + type: 'error', + title: 'fail', + body: 'unknow error', + timeout: 3000 + }); + }) + } + + function getItemIdDetail() { + + mainFactory.ItemDetail().get({ + 'envId': $scope.uuid + }).$promise.then(function(response) { + if (response.status == 1) { + $scope.baseElementInfo = response.result.environment; + + + if ($scope.ifNew != 'true') { + $scope.baseElementInfo = response.result.environment; + if ($scope.baseElementInfo.openrc_id != null) { + getOpenrcDetail($scope.baseElementInfo.openrc_id); + } + } + + } else { + toaster.pop({ + type: 'error', + title: 'fail', + body: response.error_msg, + timeout: 3000 + }); + + } + }, function(error) { + toaster.pop({ + type: 'error', + title: 'fail', + body: 'unknow error', + timeout: 3000 + }); + + }) + } + //getopenRcid + function getOpenrcDetail(openrcId) { + mainFactory.getEnvironmentDetail().get({ + 'openrc_id': openrcId + }).$promise.then(function(response) { + $scope.openrcInfo = response.result; + buildToEnvInfo($scope.openrcInfo.openrc) + }, function(response) { + + }) + } + + + //getImgDetail + function getImageDetail() { + mainFactory.ImageDetail().get({ + 'image_id': $scope.baseElementInfo.image_id + }).$promise.then(function(response) { + if (response.status == 1) { + $scope.imageDetail = response.result.image; + } + }, function(error) { + toaster.pop({ + type: 'error', + title: 'fail', + body: 'unknow error', + timeout: 3000 + }); + }) + } + + //getPodDetail + function getPodDetail() { + mainFactory.podDeatil().get({ + 'podId': $scope.baseElementInfo.pod_id + }).$promise.then(function(response) { + if (response.status == 1) { + $scope.podDetail = response.result.pod; + } + }, function(error) { + toaster.pop({ + type: 'error', + title: 'fail', + body: 'unknow error', + timeout: 3000 + }); + }) + } + //getContainerDetail + function getPodDetail(containerId) { + mainFactory.containerDetail().get({ + 'containerId': containerId + }).$promise.then(function(response) { + if (response.status == 1) { + $scope.podDetail = response.result.pod; + } else { + toaster.pop({ + type: 'error', + title: 'fail', + body: response.error_msg, + timeout: 3000 + }); + } + }, function(error) { + toaster.pop({ + type: 'error', + title: 'fail', + body: 'unknow error', + timeout: 3000 + }); + }) + } + $scope.goBack = function goBack() { + window.history.back(); + } + + $scope.goNext = function goNext() { + $scope.path = $location.path(); + $scope.uuid = $scope.path.split('/').pop(); + $state.go('app.uploadImage', { uuid: $scope.uuid }); + } + + $scope.openDeleteEnv = function openDeleteEnv(id, name) { + $scope.deleteName = name; + $scope.deleteId = id; + ngDialog.open({ + template: 'views/modal/deleteConfirm.html', + scope: $scope, + className: 'ngdialog-theme-default', + width: 500, + showClose: true, + closeByDocument: false + }) + + } + + $scope.deleteOpenRc = function deleteOpenRc() { + mainFactory.deleteOpenrc().delete({ 'openrc': $scope.baseElementInfo.openrc_id }).$promise.then(function(response) { + if (response.status == 1) { + toaster.pop({ + type: 'success', + title: 'delete openrc success', + body: 'you can go next step', + timeout: 3000 + }); + ngDialog.close(); + getItemIdDetail(); + $scope.openrcInfo = null; + $scope.envInfo = []; + $scope.displayOpenrcFile = null; + } else { + toaster.pop({ + type: 'error', + title: 'Wrong', + body: response.result, + timeout: 3000 + }); + } + + }, function(error) { + toaster.pop({ + type: 'error', + title: 'fail', + body: 'unknow error', + timeout: 3000 + }); + }) + } + + + + + + + + + + } + + + ]); diff --git a/gui/app/scripts/controllers/image.controller.js b/gui/app/scripts/controllers/image.controller.js new file mode 100644 index 000000000..53acff405 --- /dev/null +++ b/gui/app/scripts/controllers/image.controller.js @@ -0,0 +1,166 @@ +'use strict'; + +angular.module('yardStickGui2App') + .controller('ImageController', ['$scope', '$state', '$stateParams', 'mainFactory', 'Upload', 'toaster', '$location', '$interval', + function($scope, $state, $stateParams, mainFactory, Upload, toaster, $location, $interval) { + + + init(); + $scope.showloading = false; + $scope.ifshowStatus = 0; + + function init() { + + + $scope.uuid = $stateParams.uuid; + $scope.uploadImage = uploadImage; + getItemIdDetail(); + getImageListSimple(); + } + + function getItemIdDetail() { + mainFactory.ItemDetail().get({ + 'envId': $stateParams.uuid + }).$promise.then(function(response) { + if (response.status == 1) { + $scope.baseElementInfo = response.result.environment; + + + } else { + toaster.pop({ + type: 'error', + title: 'fail', + body: response.error_msg, + timeout: 3000 + }); + } + }, function(error) { + toaster.pop({ + type: 'error', + title: 'fail', + body: 'unknow error', + timeout: 3000 + }); + }) + } + + function getImageListSimple() { + + mainFactory.ImageList().get({}).$promise.then(function(response) { + if (response.status == 1) { + $scope.imageListData = response.result.images; + // $scope.imageStatus = response.result.status; + + } else { + toaster.pop({ + type: 'error', + title: 'get data failed', + body: 'please retry', + timeout: 3000 + }); + } + }, function(error) { + toaster.pop({ + type: 'error', + title: 'get data failed', + body: 'please retry', + timeout: 3000 + }); + }) + } + + + function getImageList() { + if ($scope.intervalImgae != undefined) { + $interval.cancel($scope.intervalImgae); + } + mainFactory.ImageList().get({}).$promise.then(function(response) { + if (response.status == 1) { + $scope.imageListData = response.result.images; + $scope.imageStatus = response.result.status; + + if ($scope.imageStatus == 0) { + $scope.intervalImgae = $interval(function() { + getImageList(); + }, 5000); + } else if ($scope.intervalImgae != undefined) { + $interval.cancel($scope.intervalImgae); + } + + } else { + toaster.pop({ + type: 'error', + title: 'get data failed', + body: 'please retry', + timeout: 3000 + }); + } + }, function(error) { + toaster.pop({ + type: 'error', + title: 'get data failed', + body: 'please retry', + timeout: 3000 + }); + }) + } + + function uploadImage() { + $scope.imageStatus = 0; + $interval.cancel($scope.intervalImgae); + $scope.ifshowStatus = 1; + $scope.showloading = true; + mainFactory.uploadImage().post({ + 'action': 'load_image', + 'args': { + 'environment_id': $scope.uuid + + } + }).$promise.then(function(response) { + $scope.showloading = false; + if (response.status == 1) { + toaster.pop({ + type: 'success', + title: 'create success', + body: 'you can go next step', + timeout: 3000 + }); + setTimeout(function() { + getImageList(); + }, 10000); + + } else { + toaster.pop({ + type: 'error', + title: 'failed', + body: 'something wrong', + timeout: 3000 + }); + + } + }, function(error) { + toaster.pop({ + type: 'error', + title: 'failed', + body: 'something wrong', + timeout: 3000 + }); + }) + } + + $scope.goBack = function goBack() { + $state.go('app2.projectList'); + } + + $scope.goNext = function goNext() { + $scope.path = $location.path(); + $scope.uuid = $scope.path.split('/').pop(); + $state.go('app.podUpload', { uuid: $scope.uuid }); + } + + + + + + } + ]); diff --git a/gui/app/scripts/controllers/main.js b/gui/app/scripts/controllers/main.js new file mode 100644 index 000000000..e3e880e62 --- /dev/null +++ b/gui/app/scripts/controllers/main.js @@ -0,0 +1,725 @@ +'use strict'; + +angular.module('yardStickGui2App') + .controller('MainCtrl', ['$scope', '$state', '$stateParams', 'mainFactory', 'Upload', 'toaster', 'ngDialog', '$localStorage', '$loading', '$interval', + function($scope, $state, $stateParams, mainFactory, Upload, toaster, ngDialog, $localStorage, $loading, $interval) { + + + init(); + $scope.project = 0; + $scope.showloading = false; + $scope.showEnvrionment = false; + $scope.loadingOPENrc = false; + $scope.uuidEnv = null; + $scope.showPod = null; + $scope.showImage = null; + $scope.showContainer = null; + $scope.showNextOpenRc = null; + $scope.showNextPod = null; + $scope.displayContainerInfo = []; + $scope.containerList = [{ value: 'create_influxdb', name: "InfluxDB" }, { value: 'create_grafana', name: "Grafana" }] + $scope.items = [ + 'The first choice!', + 'And another choice for you.', + 'but wait! A third!' + ]; + $scope.$on('$destroy', function() { + $interval.cancel($scope.intervalImgae) + }); + $scope.showImageStatus = 0; + + + + + + + function init() { + + + $scope.gotoProject = gotoProject; + $scope.gotoEnvironment = gotoEnvironment; + $scope.gotoTask = gotoTask; + $scope.gotoExcute = gotoExcute; + $scope.gotoReport = gotoReport; + $scope.deleteEnvItem = deleteEnvItem; + $scope.addInfo = addInfo; + $scope.submitOpenRcFile = submitOpenRcFile; + $scope.uploadFilesPod = uploadFilesPod; + $scope.uploadFiles = uploadFiles; + $scope.showEnvriomentStatus = showEnvriomentStatus; + $scope.openEnvironmentDialog = openEnvironmentDialog; + $scope.getEnvironmentList = getEnvironmentList; + $scope.gotoDetail = gotoDetail; + $scope.addEnvironment = addEnvironment; + $scope.createContainer = createContainer; + $scope.chooseResult = chooseResult; + + getEnvironmentList(); + // getImageList(); + + } + + function gotoProject() { + $scope.project = 1; + } + + function gotoEnvironment() { + $scope.project = 0; + } + + function gotoTask() { + $scope.project = 2; + } + + function gotoExcute() { + $scope.project = 3; + + } + + function gotoReport() { + $scope.project = 4; + } + $scope.skipPod = function skipPod() { + $scope.showContainer = 1; + + } + $scope.skipContainer = function skipContainer() { + getEnvironmentList(); + ngDialog.close(); + } + + $scope.goToImage = function goToImage() { + getImageListSimple(); + $scope.showImage = 1; + } + $scope.goToPod = function goToPod() { + $scope.showPod = 1; + } + $scope.goToPodPrev = function goToPodPrev() { + $scope.showImage = null; + + } + $scope.skipPodPrev = function skipPodPrev() { + $scope.showImage = 1; + $scope.showPod = null; + + } + $scope.skipContainerPrev = function skipContainerPrev() { + $scope.showPod = 1; + $scope.showContainer = null; + } + + $scope.envInfo = [ + { name: 'OS_USERNAME', value: '' }, + { name: 'OS_PASSWORD', value: '' }, + { name: 'OS_TENANT_NAME', value: '' }, + { name: 'EXTERNAL_NETWORK', value: '' } + ]; + + + function deleteEnvItem(index) { + $scope.envInfo.splice(index, 1); + } + + function addInfo() { + var tempKey = null; + var tempValue = null; + var temp = { + name: tempKey, + value: tempValue + } + $scope.envInfo.push(temp); + + } + + function submitOpenRcFile() { + $scope.showloading = true; + + var postData = {}; + postData['action'] = 'update_openrc'; + rebuildEnvInfo(); + postData['args'] = {}; + postData.args["openrc"] = $scope.postEnvInfo; + postData.args['environment_id'] = $scope.uuidEnv; + mainFactory.postEnvironmentVariable().post(postData).$promise.then(function(response) { + $scope.showloading = false; + + if (response.status == 1) { + + $scope.openrcInfo = response.result; + toaster.pop({ + type: 'success', + title: 'create success', + body: 'you can go next step', + timeout: 3000 + }); + $scope.showEnvrionment = true; + // $scope.showImage = response.status; + $scope.showNextOpenRc = 1; + } else { + toaster.pop({ + type: 'error', + title: 'fail', + body: response.error_msg, + timeout: 3000 + }); + } + + }, function(error) { + toaster.pop({ + type: 'error', + title: 'fail', + body: 'unknow error', + timeout: 3000 + }); + + }) + } + + function uploadFiles($file, $invalidFiles) { + $scope.openrcInfo = {}; + $scope.loadingOPENrc = true; + $scope.displayOpenrcFile = $file; + timeConstruct($scope.displayOpenrcFile.lastModified); + Upload.upload({ + url: Base_URL + '/api/v2/yardstick/openrcs', + data: { file: $file, 'environment_id': $scope.uuidEnv, 'action': 'upload_openrc' } + }).then(function(response) { + + $scope.loadingOPENrc = false; + if (response.data.status == 1) { + toaster.pop({ + type: 'success', + title: 'upload success', + body: 'you can go next step', + timeout: 3000 + }); + $scope.openrcInfo = response.data.result; + + getItemIdDetailforOpenrc(); + $scope.showNextOpenRc = 1; + } else { + toaster.pop({ + type: 'error', + title: 'fail', + body: response.error_msg, + timeout: 3000 + }); + } + + }, function(error) { + $scope.uploadfile = null; + toaster.pop({ + type: 'error', + title: 'fail', + body: 'unknow error', + timeout: 3000 + }); + }) + } + + //reconstruc EnvInfo + function rebuildEnvInfo() { + $scope.postEnvInfo = {}; + for (var i = 0; i < $scope.envInfo.length; i++) { + $scope.postEnvInfo[$scope.envInfo[i].name] = $scope.envInfo[i].value; + } + + } + function uploadFilesPod($file, $invalidFiles) { + $scope.loadingOPENrc = true; + + $scope.displayPodFile = $file; + timeConstruct($scope.displayPodFile.lastModified); + Upload.upload({ + url: Base_URL + '/api/v2/yardstick/pods', + data: { file: $file, 'environment_id': $scope.uuidEnv, 'action': 'upload_pod_file' } + }).then(function(response) { + + $scope.loadingOPENrc = false; + if (response.data.status == 1) { + toaster.pop({ + type: 'success', + title: 'upload success', + body: 'you can go next step', + timeout: 3000 + }); + + $scope.podData = response.data.result; + + + } else { + toaster.pop({ + type: 'error', + title: 'fail', + body: response.error_msg, + timeout: 3000 + }); + } + + }, function(error) { + $scope.uploadfile = null; + toaster.pop({ + type: 'error', + title: 'fail', + body: 'unknow error', + timeout: 3000 + }); + }) + } + + function timeConstruct(array) { + var date = new Date(1398250549490); + var Y = date.getFullYear() + '-'; + var M = (date.getMonth() + 1 < 10 ? '0' + (date.getMonth() + 1) : date.getMonth() + 1) + '-'; + var D = date.getDate() + ' '; + var h = date.getHours() + ':'; + var m = date.getMinutes() + ':'; + var s = date.getSeconds(); + $scope.filelastModified = Y + M + D + h + m + s; + + } + + //display environment + function showEnvriomentStatus() { + $scope.showEnvironment = true; + } + + //open Environment dialog + function openEnvironmentDialog() { + $scope.showEnvrionment = false; + $scope.loadingOPENrc = false; + $scope.uuidEnv = null; + $scope.showPod = null; + $scope.showImage = null; + $scope.showContainer = null; + $scope.showNextOpenRc = null; + $scope.showNextPod = null; + $scope.displayContainerInfo = []; + + $scope.displayPodFile = null; + $scope.name = null; + $scope.openrcInfo = null; + $scope.envInfo = [ + { name: 'OS_USERNAME', value: '' }, + { name: 'OS_PASSWORD', value: '' }, + { name: 'OS_TENANT_NAME', value: '' }, + { name: 'EXTERNAL_NETWORK', value: '' } + ]; + $scope.displayOpenrcFile = null; + $scope.podData = null; + $scope.displayContainerInfo = null; + ngDialog.open({ + preCloseCallback: function(value) { + getEnvironmentList(); + // getImageList(); + }, + template: 'views/modal/environmentDialog.html', + scope: $scope, + className: 'ngdialog-theme-default', + width: 950, + showClose: true, + closeByDocument: false + }) + } + + function getEnvironmentList() { + $loading.start('key'); + + mainFactory.getEnvironmentList().get().$promise.then(function(response) { + $scope.environmentList = response.result.environments; + $loading.finish('key'); + + }, function(error) { + $loading.finish('key'); + toaster.pop({ + type: 'error', + title: 'fail', + body: 'unknow error', + timeout: 3000 + }); + + }) + } + + //go to detail page + function gotoDetail(ifNew, uuid) { + + $state.go('app.environmentDetail', { uuid: uuid, ifNew: ifNew }); + } + + + function addEnvironment(name) { + mainFactory.addEnvName().post({ + 'action': 'create_environment', + args: { + 'name': name + } + }).$promise.then(function(response) { + if (response.status == 1) { + toaster.pop({ + type: 'success', + title: 'create name success', + body: 'you can go next step', + timeout: 3000 + }); + $scope.uuidEnv = response.result.uuid; + $scope.name = name; + + } + + }, function(error) { + toaster.pop({ + type: 'error', + title: 'fail', + body: 'unknow error', + timeout: 3000 + }); + }) + } + + + + $scope.goBack = function goBack() { + $state.go('app2.projectList'); + } + $scope.displayContainerInfo = []; + + function createContainer(selectContainer) { + + $scope.showloading = true; + mainFactory.runAcontainer().post({ + 'action': selectContainer.value, + 'args': { + 'environment_id': $scope.uuidEnv, + } + }).$promise.then(function(response) { + $scope.showloading = false; + if (response.status == 1) { + toaster.pop({ + type: 'success', + title: 'create container success', + body: 'you can go next step', + timeout: 3000 + }); + + setTimeout(function() { + getItemIdDetail(); + }, 10000); + $scope.ifskipOrClose = 1; + } else { + toaster.pop({ + type: 'error', + title: 'Wrong', + body: response.result, + timeout: 3000 + }); + } + + }, function(error) { + toaster.pop({ + type: 'error', + title: 'fail', + body: 'unknow error', + timeout: 3000 + }); + }) + } + + function getConDetail(id) { + mainFactory.containerDetail().get({ + 'containerId': id + }).$promise.then(function(response) { + if (response.status == 1) { + // $scope.podData = response.result; + $scope.displayContainerInfo.push(response.result.container); + + } + + }, function(error) { + toaster.pop({ + type: 'error', + title: 'fail', + body: 'unknow error', + timeout: 3000 + }); + }) + + } + + function chooseResult(name) { + $scope.selectContainer = name; + } + + function getItemIdDetail() { + $scope.displayContainerInfo = []; + mainFactory.ItemDetail().get({ + 'envId': $scope.uuidEnv + }).$promise.then(function(response) { + if (response.status == 1) { + $scope.envName = response.result.environment.name; + $scope.containerId = response.result.environment.container_id; + if ($scope.containerId != null) { + + var keysArray = Object.keys($scope.containerId); + for (var k in $scope.containerId) { + getConDetail($scope.containerId[k]); + + } + + + } else { + $scope.podData = null; + } + + } + }, function(error) { + toaster.pop({ + type: 'error', + title: 'fail', + body: 'unknow error', + timeout: 3000 + }); + }) + } + + $scope.uploadImage = function uploadImage() { + $scope.imageStatus = 0; + $scope.showImageStatus = 1; + $scope.showloading = true; + mainFactory.uploadImage().post({ + 'action': 'load_image', + 'args': { + 'environment_id': $scope.uuid + + } + }).$promise.then(function(response) { + $scope.showloading = false; + if (response.status == 1) { + toaster.pop({ + type: 'success', + title: 'create success', + body: 'you can go next step', + timeout: 3000 + }); + setTimeout(function() { + getImageList(); + }, 10000); + $scope.showNextPod = 1; + + } else { + toaster.pop({ + type: 'error', + title: 'failed', + body: 'something wrong', + timeout: 3000 + }); + + } + }, function(error) { + toaster.pop({ + type: 'error', + title: 'failed', + body: 'something wrong', + timeout: 3000 + }); + }) + } + + function getImageList() { + if ($scope.intervalImgae != undefined) { + $interval.cancel($scope.intervalImgae); + } + mainFactory.ImageList().get({}).$promise.then(function(response) { + if (response.status == 1) { + $scope.imageListData = response.result.images; + $scope.imageStatus = response.result.status; + + if ($scope.imageStatus == 0) { + $scope.intervalImgae = $interval(function() { + getImageList(); + }, 5000); + } else if ($scope.intervalImgae != undefined) { + $interval.cancel($scope.intervalImgae); + } + + } else { + toaster.pop({ + type: 'error', + title: 'get data failed', + body: 'please retry', + timeout: 3000 + }); + } + }, function(error) { + toaster.pop({ + type: 'error', + title: 'get data failed', + body: 'please retry', + timeout: 3000 + }); + }) + } + + function getImageListSimple() { + + mainFactory.ImageList().get({}).$promise.then(function(response) { + if (response.status == 1) { + $scope.imageListData = response.result.images; + $scope.imageStatus = response.result.status; + + } else { + toaster.pop({ + type: 'error', + title: 'get data failed', + body: 'please retry', + timeout: 3000 + }); + } + }, function(error) { + toaster.pop({ + type: 'error', + title: 'get data failed', + body: 'please retry', + timeout: 3000 + }); + }) + } + + $scope.openDeleteEnv = function openDeleteEnv(id, name) { + $scope.deleteName = name; + $scope.deleteId = id; + ngDialog.open({ + template: 'views/modal/deleteConfirm.html', + scope: $scope, + className: 'ngdialog-theme-default', + width: 500, + showClose: true, + closeByDocument: false + }) + + } + + $scope.deleteEnv = function deleteEnv() { + mainFactory.deleteEnv().delete({ 'env_id': $scope.deleteId }).$promise.then(function(response) { + if (response.status == 1) { + toaster.pop({ + type: 'success', + title: 'delete environment success', + body: 'you can go next step', + timeout: 3000 + }); + ngDialog.close(); + getEnvironmentList(); + } else { + toaster.pop({ + type: 'error', + title: 'Wrong', + body: response.result, + timeout: 3000 + }); + } + + }, function(error) { + toaster.pop({ + type: 'error', + title: 'fail', + body: 'unknow error', + timeout: 3000 + }); + }) + } + + + + + + + function getItemIdDetailforOpenrc() { + + mainFactory.ItemDetail().get({ + 'envId': $scope.uuidEnv + }).$promise.then(function(response) { + if (response.status == 1) { + $scope.baseElementInfo = response.result.environment; + + + if ($scope.ifNew != 'true') { + $scope.baseElementInfo = response.result.environment; + if ($scope.baseElementInfo.openrc_id != null) { + getOpenrcDetailForOpenrc($scope.baseElementInfo.openrc_id); + } + } + + } else { + toaster.pop({ + type: 'error', + title: 'fail', + body: response.error_msg, + timeout: 3000 + }); + + } + }, function(error) { + + toaster.pop({ + type: 'error', + title: 'fail', + body: 'unknow error', + timeout: 3000 + }); + + }) + } + + + + //getopenRcid + function getOpenrcDetailForOpenrc(openrcId) { + mainFactory.getEnvironmentDetail().get({ + 'openrc_id': openrcId + }).$promise.then(function(response) { + $scope.openrcInfo = response.result; + buildToEnvInfoOpenrc($scope.openrcInfo.openrc) + }, function(response) { + toaster.pop({ + type: 'error', + title: 'error', + body: 'unknow error', + timeout: 3000 + }); + }) + } + + //buildtoEnvInfo + function buildToEnvInfoOpenrc(object) { + var tempKeyArray = Object.keys(object); + $scope.envInfo = []; + + + for (var i = 0; i < tempKeyArray.length; i++) { + var tempkey = tempKeyArray[i]; + var tempValue = object[tempKeyArray[i]]; + var temp = { + name: tempkey, + value: tempValue + }; + $scope.envInfo.push(temp); + } + } + + + + + + + + + + + + + + + } + ]); diff --git a/gui/app/scripts/controllers/pod.controller.js b/gui/app/scripts/controllers/pod.controller.js new file mode 100644 index 000000000..3ef236854 --- /dev/null +++ b/gui/app/scripts/controllers/pod.controller.js @@ -0,0 +1,179 @@ +'use strict'; + +angular.module('yardStickGui2App') + .controller('PodController', ['$scope', '$state', '$stateParams', 'mainFactory', 'Upload', 'toaster', '$location', 'ngDialog', + function($scope, $state, $stateParams, mainFactory, Upload, toaster, $location, ngDialog) { + + + init(); + $scope.showloading = false; + $scope.loadingOPENrc = false; + + function init() { + + + $scope.uuid = $stateParams.uuid; + $scope.uploadFiles = uploadFiles; + getItemIdDetail(); + + } + + function getItemIdDetail() { + mainFactory.ItemDetail().get({ + 'envId': $scope.uuid + }).$promise.then(function(response) { + if (response.status == 1) { + $scope.name = response.result.environment.name; + $scope.podId = response.result.environment.pod_id; + if ($scope.podId != null) { + getPodDetail($scope.podId); + } else { + $scope.podData = null; + } + + } + }, function(error) { + toaster.pop({ + type: 'error', + title: 'fail', + body: 'unknow error', + timeout: 3000 + }); + }) + } + + function getPodDetail(id) { + mainFactory.getPodDetail().get({ + 'podId': id + }).$promise.then(function(response) { + if (response.status == 1) { + $scope.podData = response.result; + + } + }, function(error) { + toaster.pop({ + type: 'error', + title: 'fail', + body: 'unknow error', + timeout: 3000 + }); + }) + + } + + //upload pod file + function uploadFiles($file, $invalidFiles) { + $scope.loadingOPENrc = true; + + $scope.displayOpenrcFile = $file; + timeConstruct($scope.displayOpenrcFile.lastModified); + Upload.upload({ + url: Base_URL + '/api/v2/yardstick/pods', + data: { file: $file, 'environment_id': $scope.uuid, 'action': 'upload_pod_file' } + }).then(function(response) { + + $scope.loadingOPENrc = false; + if (response.data.status == 1) { + toaster.pop({ + type: 'success', + title: 'upload success', + body: 'you can go next step', + timeout: 3000 + }); + + $scope.podData = response.data.result; + + getItemIdDetail(); + + + } else { + + } + + }, function(error) { + $scope.uploadfile = null; + toaster.pop({ + type: 'error', + title: 'fail', + body: 'unknow error', + timeout: 3000 + }); + }) + } + + function timeConstruct(array) { + var date = new Date(1398250549490); + var Y = date.getFullYear() + '-'; + var M = (date.getMonth() + 1 < 10 ? '0' + (date.getMonth() + 1) : date.getMonth() + 1) + '-'; + var D = date.getDate() + ' '; + var h = date.getHours() + ':'; + var m = date.getMinutes() + ':'; + var s = date.getSeconds(); + $scope.filelastModified = Y + M + D + h + m + s; + + } + $scope.goBack = function goBack() { + $state.go('app2.projectList'); + } + + + $scope.goNext = function goNext() { + $scope.path = $location.path(); + $scope.uuid = $scope.path.split('/').pop(); + $state.go('app.container', { uuid: $scope.uuid }); + } + + $scope.openDeleteEnv = function openDeleteEnv(id, name) { + $scope.deleteName = name; + $scope.deleteId = id; + ngDialog.open({ + template: 'views/modal/deleteConfirm.html', + scope: $scope, + className: 'ngdialog-theme-default', + width: 500, + showClose: true, + closeByDocument: false + }) + + } + + $scope.deletePod = function deletePod() { + mainFactory.deletePod().delete({ 'podId': $scope.podId }).$promise.then(function(response) { + if (response.status == 1) { + toaster.pop({ + type: 'success', + title: 'delete pod success', + body: 'you can go next step', + timeout: 3000 + }); + ngDialog.close(); + $scope.uuid = $stateParams.uuid; + $scope.uploadFiles = uploadFiles; + $scope.displayOpenrcFile = null; + getItemIdDetail(); + } else { + toaster.pop({ + type: 'error', + title: 'Wrong', + body: response.result, + timeout: 3000 + }); + } + + }, function(error) { + toaster.pop({ + type: 'error', + title: 'fail', + body: 'unknow error', + timeout: 3000 + }); + + }) + } + + + + + + } + ]);
\ No newline at end of file diff --git a/gui/app/scripts/controllers/project.controller.js b/gui/app/scripts/controllers/project.controller.js new file mode 100644 index 000000000..0a7b8b932 --- /dev/null +++ b/gui/app/scripts/controllers/project.controller.js @@ -0,0 +1,160 @@ +'use strict'; + +angular.module('yardStickGui2App') + .controller('ProjectController', ['$scope', '$state', '$stateParams', 'mainFactory', 'Upload', 'toaster', 'ngDialog', '$loading', + function($scope, $state, $stateParams, mainFactory, Upload, toaster, ngDialog, $loading) { + + + init(); + + + function init() { + + + getProjectList(); + $scope.openCreateProject = openCreateProject; + $scope.createName = createName; + $scope.gotoDetail = gotoDetail; + + + } + + function getProjectList() { + $loading.start('key'); + mainFactory.projectList().get({}).$promise.then(function(response) { + $loading.finish('key'); + if (response.status == 1) { + $scope.projectListData = response.result.projects; + + + } else { + + } + }, function(error) { + $loading.finish('key'); + toaster.pop({ + type: 'error', + title: 'fail', + body: 'unknow error', + timeout: 3000 + }); + + }) + } + + function openCreateProject() { + + ngDialog.open({ + template: 'views/modal/projectCreate.html', + scope: $scope, + className: 'ngdialog-theme-default', + width: 400, + showClose: true, + closeByDocument: false + }) + } + + function createName(name) { + + mainFactory.createProjectName().post({ + 'action': 'create_project', + 'args': { + 'name': name, + } + }).$promise.then(function(response) { + if (response.status == 1) { + toaster.pop({ + type: 'success', + title: 'create project success', + body: 'you can go next step', + timeout: 3000 + }); + ngDialog.close(); + getProjectList(); + } else { + toaster.pop({ + type: 'error', + title: 'failed', + body: 'create project failed', + timeout: 3000 + }); + } + + }, function(error) { + toaster.pop({ + type: 'error', + title: 'failed', + body: 'Something Wrong', + timeout: 3000 + }); + }) + } + + function gotoDetail(id) { + $state.go('app2.projectdetail', { projectId: id }) + } + + + $scope.openDeleteEnv = function openDeleteEnv(id, name) { + $scope.deleteName = name; + $scope.deleteId = id; + ngDialog.open({ + template: 'views/modal/deleteConfirm.html', + scope: $scope, + className: 'ngdialog-theme-default', + width: 500, + showClose: true, + closeByDocument: false + }) + + } + + $scope.deleteProject = function deleteProject() { + mainFactory.deleteProject().delete({ 'project_id': $scope.deleteId }).$promise.then(function(response) { + if (response.status == 1) { + toaster.pop({ + type: 'success', + title: 'delete Project success', + body: 'you can go next step', + timeout: 3000 + }); + ngDialog.close(); + getProjectList(); + } else { + toaster.pop({ + type: 'error', + title: 'Wrong', + body: response.result, + timeout: 3000 + }); + } + + }, function(error) { + toaster.pop({ + type: 'error', + title: 'fail', + body: 'unknow error', + timeout: 3000 + }); + + }) + } + + + + + + + + + + + + + + + + + + } + ]); diff --git a/gui/app/scripts/controllers/projectDetail.controller.js b/gui/app/scripts/controllers/projectDetail.controller.js new file mode 100644 index 000000000..4ab4a055a --- /dev/null +++ b/gui/app/scripts/controllers/projectDetail.controller.js @@ -0,0 +1,690 @@ +'use strict'; + +angular.module('yardStickGui2App') + .controller('ProjectDetailController', ['$scope', '$state', '$stateParams', 'mainFactory', 'Upload', 'toaster', 'ngDialog', '$localStorage', '$loading', '$interval', + function($scope, $state, $stateParams, mainFactory, Upload, toaster, ngDialog, $localStorage, $loading, $interval) { + + + init(); + // $scope.taskListDisplay = []; + $scope.blisterPackTemplates = [{ id: 1, name: "Test Case" }, { id: 2, name: "Test Suite" }] + $scope.selectType = null; + $scope.ifHasEnv = false; + $scope.ifHasCase = false; + $scope.ifHasSuite = false; + $scope.$on('$destroy', function() { + $interval.cancel($scope.intervalCount) + }); + $scope.finalTaskListDisplay = []; + + + function init() { + + + getProjectDetail(); + + $scope.openCreate = openCreate; + $scope.createTask = createTask; + $scope.constructTestSuit = constructTestSuit; + $scope.addEnvToTask = addEnvToTask; + $scope.triggerContent = triggerContent; + $scope.constructTestCase = constructTestCase; + $scope.getTestDeatil = getTestDeatil; + $scope.confirmAddCaseOrSuite = confirmAddCaseOrSuite; + $scope.runAtask = runAtask; + $scope.gotoDetail = gotoDetail; + $scope.gotoReport = gotoReport; + $scope.gotoModify = gotoModify; + $scope.goBack = goBack; + $scope.goToExternal = goToExternal; + + + } + + function getProjectDetail() { + if ($scope.intervalCount != undefined) { + $interval.cancel($scope.intervalCount); + } + $loading.start('key'); + $scope.taskListDisplay = []; + $scope.finalTaskListDisplay = []; + mainFactory.getProjectDetail().get({ + project_id: $stateParams.projectId + }).$promise.then(function(response) { + $loading.finish('key'); + if (response.status == 1) { + + $scope.projectData = response.result.project; + if ($scope.projectData.tasks.length != 0) { + + + for (var i = 0; i < $scope.projectData.tasks.length; i++) { + getDetailTaskForList($scope.projectData.tasks[i]); + } + $scope.intervalCount = $interval(function() { + getDetailForEachTask(); + }, 10000); + } else { + + if ($scope.intervalCount != undefined) { + $interval.cancel($scope.intervalCount); + } + } + } else { + + } + }, function(error) { + toaster.pop({ + type: 'error', + title: 'fail', + body: 'unknow error', + timeout: 3000 + }); + + }) + } + + // function getProjectDetailSimple() { + // getDetailForEachTask(); + // } + + function openCreate() { + $scope.newUUID = null; + $scope.displayEnvName = null; + $scope.selectEnv = null; + $scope.selectCase = null; + $scope.selectType = null; + $scope.contentInfo = null; + $scope.ifHasEnv = false; + $scope.ifHasCase = false; + $scope.ifHasSuite = false; + + // getEnvironmentList(); + $scope.selectEnv = null; + ngDialog.open({ + template: 'views/modal/taskCreate.html', + scope: $scope, + className: 'ngdialog-theme-default', + width: 800, + showClose: true, + closeByDocument: false, + preCloseCallback: function(value) { + getProjectDetail(); + }, + }) + } + + function createTask(name) { + mainFactory.createTask().post({ + 'action': 'create_task', + 'args': { + 'name': name, + 'project_id': $stateParams.projectId + } + }).$promise.then(function(response) { + if (response.status == 1) { + toaster.pop({ + type: 'success', + title: 'create task success', + body: 'you can go next step', + timeout: 3000 + }); + $scope.newUUID = response.result.uuid; + getEnvironmentList(); + + } else { + toaster.pop({ + type: 'error', + title: 'create task wrong', + body: 'you can go next step', + timeout: 3000 + }); + } + + + + }, function(error) { + toaster.pop({ + type: 'error', + title: 'create task wrong', + body: 'you can go next step', + timeout: 3000 + }); + }) + } + + function getDetailTaskForList(id) { + + mainFactory.getTaskDetail().get({ + 'taskId': id + }).$promise.then(function(response) { + + if (response.status == 1) { + if (response.result.task.status == -1) { + response.result.task['stausWidth'] = '5%'; + } else if (response.result.task.status == 0) { + response.result.task['stausWidth'] = '50%'; + } else if (response.result.task.status == 1) { + response.result.task['stausWidth'] = '100%'; + } else if (response.result.task.status == 2) { + response.result.task['stausWidth'] = 'red'; + } + $scope.taskListDisplay.push(response.result.task); + console.log($scope.taskListDisplay); + + $scope.finalTaskListDisplay = $scope.taskListDisplay; + + } + }, function(error) { + toaster.pop({ + type: 'error', + title: 'fail', + body: 'unknow error', + timeout: 3000 + }); + + }) + } + + function getDetailTaskForListSimple(id, index) { + + mainFactory.getTaskDetail().get({ + 'taskId': id + }).$promise.then(function(response) { + + if (response.status == 1) { + if (response.result.task.status == -1) { + + $scope.finalTaskListDisplay[index].stausWidth = '5%'; + $scope.finalTaskListDisplay[index].status = response.result.task.status; + } else if (response.result.task.status == 0) { + + $scope.finalTaskListDisplay[index].stausWidth = '50%'; + $scope.finalTaskListDisplay[index].status = response.result.task.status; + } else if (response.result.task.status == 1) { + + $scope.finalTaskListDisplay[index].stausWidth = '100%'; + $scope.finalTaskListDisplay[index].status = response.result.task.status; + } else if (response.result.task.status == 2) { + + $scope.finalTaskListDisplay[index].stausWidth = 'red'; + $scope.finalTaskListDisplay[index].status = response.result.task.status; + } + + + } + }, function(error) { + toaster.pop({ + type: 'error', + title: 'fail', + body: 'unknow error', + timeout: 3000 + }); + + }) + } + + function getDetailForEachTask() { + for (var i = 0; i < $scope.finalTaskListDisplay.length; i++) { + if ($scope.finalTaskListDisplay[i].status != 1 && $scope.finalTaskListDisplay[i].status != -1) { + getDetailTaskForListSimple($scope.finalTaskListDisplay[i].uuid, i); + } + } + } + + function getEnvironmentList() { + mainFactory.getEnvironmentList().get().$promise.then(function(response) { + $scope.environmentList = response.result.environments; + }, function(error) { + toaster.pop({ + type: 'error', + title: 'fail', + body: 'unknow error', + timeout: 3000 + }); + + }) + } + + function constructTestSuit(id, name) { + $scope.displayEnvName = name; + $scope.selectEnv = id; + + } + + function constructTestCase(name) { + + $scope.selectCase = name; + if ($scope.selectType.name == 'Test Case') { + getCaseInfo(); + } else { + getSuiteInfo(); + } + + } + + + + + function addEnvToTask() { + mainFactory.taskAddEnv().put({ + 'taskId': $scope.newUUID, + 'action': 'add_environment', + 'args': { + 'task_id': $scope.newUUID, + 'environment_id': $scope.selectEnv + } + }).$promise.then(function(response) { + if (response.status == 1) { + toaster.pop({ + type: 'success', + title: 'add environment success', + body: 'you can go next step', + timeout: 3000 + }); + $scope.ifHasEnv = true; + + + } else { + toaster.pop({ + type: 'error', + title: 'create task wrong', + body: 'you can go next step', + timeout: 3000 + }); + } + + + + }, function(error) { + toaster.pop({ + type: 'error', + title: 'create task wrong', + body: 'you can go next step', + timeout: 3000 + }); + }) + } + + function triggerContent(name) { + $scope.selectCase = null; + $scope.displayTable = true; + + $scope.selectType = name; + if (name.name == 'Test Case') { + getTestcaseList(); + } else if (name.name == 'Test Suite') { + getsuiteList(); + } + } + + function getTestcaseList() { + mainFactory.getTestcaselist().get({ + + }).$promise.then(function(response) { + if (response.status == 1) { + $scope.testcaselist = response.result; + + + } + }, function(error) { + toaster.pop({ + type: 'error', + title: 'fail', + body: 'unknow error', + timeout: 3000 + }); + }) + } + + function getsuiteList() { + mainFactory.suiteList().get({ + + }).$promise.then(function(response) { + if (response.status == 1) { + $scope.testsuitlist = response.result.testsuites; + + } + }, function(error) { + toaster.pop({ + type: 'error', + title: 'fail', + body: 'unknow error', + timeout: 3000 + }); + }) + } + + function getTestDeatil() { + + + if ($scope.selectType.name == 'Test Case') { + getTestcaseDetail(); + } else { + getSuiteDetail(); + } + + } + + function getCaseInfo() { + + + + mainFactory.getTestcaseDetail().get({ + 'testcasename': $scope.selectCase + + }).$promise.then(function(response) { + if (response.status == 1) { + + $scope.contentInfo = response.result.testcase; + + } + }, function(error) { + toaster.pop({ + type: 'error', + title: 'fail', + body: 'unknow error', + timeout: 3000 + }); + }) + } + + function getSuiteInfo() { + mainFactory.suiteDetail().get({ + 'suiteName': $scope.selectCase.split('.')[0] + + }).$promise.then(function(response) { + if (response.status == 1) { + $scope.contentInfo = response.result.testsuite; + + } + }, function(error) { + toaster.pop({ + type: 'error', + title: 'fail', + body: 'unknow error', + timeout: 3000 + }); + }) + } + + + function getSuiteDetail() { + mainFactory.suiteDetail().get({ + 'suiteName': $scope.selectCase.split('.')[0] + + }).$promise.then(function(response) { + if (response.status == 1) { + $scope.displayTable = false; + $scope.contentInfo = response.result.testsuite; + + } + }, function(error) { + toaster.pop({ + type: 'error', + title: 'fail', + body: 'unknow error', + timeout: 3000 + }); + }) + } + + + function getTestcaseDetail() { + mainFactory.getTestcaseDetail().get({ + 'testcasename': $scope.selectCase + + }).$promise.then(function(response) { + if (response.status == 1) { + + $scope.displayTable = false; + $scope.contentInfo = response.result.testcase; + + } + }, function(error) { + toaster.pop({ + type: 'error', + title: 'fail', + body: 'unknow error', + timeout: 3000 + }); + }) + } + + function addCasetoTask(content) { + mainFactory.taskAddEnv().put({ + 'taskId': $scope.newUUID, + 'action': 'add_case', + 'args': { + 'task_id': $scope.newUUID, + 'case_name': $scope.selectCase, + 'case_content': content + } + }).$promise.then(function(response) { + if (response.status == 1) { + toaster.pop({ + type: 'success', + title: 'add test case success', + body: 'you can go next step', + timeout: 3000 + }); + $scope.ifHasCase = true; + + + } else { + toaster.pop({ + type: 'error', + title: 'create task wrong', + body: 'you can go next step', + timeout: 3000 + }); + } + }, function(error) { + toaster.pop({ + type: 'error', + title: 'create task wrong', + body: 'you can go next step', + timeout: 3000 + }); + }) + } + + function addSuitetoTask(content) { + mainFactory.taskAddEnv().put({ + 'taskId': $scope.newUUID, + 'action': 'add_suite', + 'args': { + 'task_id': $scope.newUUID, + 'suite_name': $scope.selectCase.split('.')[0], + 'suite_content': content + } + }).$promise.then(function(response) { + if (response.status == 1) { + toaster.pop({ + type: 'success', + title: 'add test suite success', + body: 'you can go next step', + timeout: 3000 + }); + $scope.ifHasSuite = true; + + + } else { + toaster.pop({ + type: 'error', + title: 'create task wrong', + body: 'wrong', + timeout: 3000 + }); + } + }, function(error) { + toaster.pop({ + type: 'error', + title: 'create task wrong', + body: 'something wrong', + timeout: 3000 + }); + }) + } + + function confirmAddCaseOrSuite(content) { + if ($scope.selectType.name == "Test Case") { + addCasetoTask(content); + } else { + addSuitetoTask(content); + } + } + + function runAtask(id) { + mainFactory.taskAddEnv().put({ + 'taskId': id, + 'action': 'run', + 'args': { + 'task_id': id + } + }).$promise.then(function(response) { + if (response.status == 1) { + toaster.pop({ + type: 'success', + title: 'run a task success', + body: 'you can go next step', + timeout: 3000 + }); + ngDialog.close(); + // getProjectDetail(); + } else { + + } + }, function(error) { + toaster.pop({ + type: 'error', + title: 'fail', + body: 'unknow error', + timeout: 3000 + }); + + }) + } + + $scope.runAtaskForTable = function runAtaskForTable(id) { + mainFactory.taskAddEnv().put({ + 'taskId': id, + 'action': 'run', + 'args': { + 'task_id': id + } + }).$promise.then(function(response) { + if (response.status == 1) { + // toaster.pop({ + // type: 'success', + // title: 'run a task success', + // body: 'you can go next step', + // timeout: 3000 + // }); + // ngDialog.close(); + getProjectDetail(); + } else { + toaster.pop({ + type: 'error', + title: 'fail', + body: response.result, + timeout: 3000 + }); + + } + }, function(error) { + toaster.pop({ + type: 'error', + title: 'fail', + body: 'unknow error', + timeout: 3000 + }); + + }) + } + + function gotoDetail(id) { + + + $state.go('app2.tasklist', { taskId: id }); + + } + + function gotoReport(id) { + $state.go('app2.report', { taskId: id }); + } + + function gotoModify(id) { + $state.go('app2.taskModify', { taskId: id }); + } + + function goBack() { + window.history.back(); + } + + function goToExternal() { + window.open(External_URL, '_blank'); + } + + $scope.openDeleteEnv = function openDeleteEnv(id, name) { + $scope.deleteName = name; + $scope.deleteId = id; + ngDialog.open({ + template: 'views/modal/deleteConfirm.html', + scope: $scope, + className: 'ngdialog-theme-default', + width: 500, + showClose: true, + closeByDocument: false + }) + + } + + $scope.deleteTask = function deleteTask() { + mainFactory.deleteTask().delete({ 'task_id': $scope.deleteId }).$promise.then(function(response) { + if (response.status == 1) { + toaster.pop({ + type: 'success', + title: 'delete Task success', + body: 'you can go next step', + timeout: 3000 + }); + ngDialog.close(); + getProjectDetail(); + } else { + toaster.pop({ + type: 'error', + title: 'Wrong', + body: response.result, + timeout: 3000 + }); + } + + }, function(error) { + toaster.pop({ + type: 'error', + title: 'fail', + body: 'unknow error', + timeout: 3000 + }); + + }) + } + + + + + + + + + + + + + + + + + } + ]);
\ No newline at end of file diff --git a/gui/app/scripts/controllers/report.controller.js b/gui/app/scripts/controllers/report.controller.js new file mode 100644 index 000000000..9b6b5958b --- /dev/null +++ b/gui/app/scripts/controllers/report.controller.js @@ -0,0 +1,115 @@ +'use strict'; + +angular.module('yardStickGui2App') + .controller('ReportController', ['$scope', '$state', '$stateParams', 'mainFactory', 'Upload', 'toaster', 'ngDialog', + function($scope, $state, $stateParams, mainFactory, Upload, toaster, ngDialog) { + + + init(); + + + function init() { + getDetailTaskForList(); + + + + } + $scope.goBack = function goBack() { + window.history.back(); + } + + function getDetailTaskForList(id) { + mainFactory.getTaskDetail().get({ + 'taskId': $stateParams.taskId + }).$promise.then(function(response) { + if (response.status == 1) { + if (response.result.task.status == -1) { + response.result.task['stausWidth'] = '5%'; + } else if (response.result.task.status == 0) { + response.result.task['stausWidth'] = '50%'; + } else if (response.result.task.status == 1) { + response.result.task['stausWidth'] = '100%'; + } else if (response.result.task.status == 2) { + response.result.task['stausWidth'] = 'red'; + } + $scope.result = response.result.task; + $scope.testcaseinfo = response.result.task.result.testcases; + var key = Object.keys($scope.testcaseinfo); + $scope.testcaseResult = $scope.testcaseinfo[key]; + + $scope.envIdForTask = response.result.task.environment_id; + getItemIdDetail(); + + + } + }, function(error) { + toaster.pop({ + type: 'error', + title: 'fail', + body: 'unknow error', + timeout: 3000 + }); + + }) + } + + $scope.goToExternal = function goToExternal(id) { + var url = External_URL + ':' + $scope.jumpPort + '/dashboard/db' + '/' + id; + + window.open(url, '_blank'); + } + + function getItemIdDetail() { + $scope.displayContainerInfo = []; + mainFactory.ItemDetail().get({ + 'envId': $scope.envIdForTask + }).$promise.then(function(response) { + if (response.status == 1) { + if (response.result.environment.container_id.grafana != null) { + getConDetail(response.result.environment.container_id.grafana); + + } else { + $scope.jumpPort = 3000; + } + + + } + }, function(error) { + toaster.pop({ + type: 'error', + title: 'fail', + body: 'unknow error', + timeout: 3000 + }); + }) + } + + function getConDetail(id) { + mainFactory.containerDetail().get({ + 'containerId': id + }).$promise.then(function(response) { + if (response.status == 1) { + // $scope.podData = response.result; + $scope.jumpPort = response.result.container.port; + + } + + }, function(error) { + toaster.pop({ + type: 'error', + title: 'fail', + body: 'unknow error', + timeout: 3000 + }); + }) + + } + + + + + + + + } + ]); diff --git a/gui/app/scripts/controllers/suitecreate.controller.js b/gui/app/scripts/controllers/suitecreate.controller.js new file mode 100644 index 000000000..4a7b6fe85 --- /dev/null +++ b/gui/app/scripts/controllers/suitecreate.controller.js @@ -0,0 +1,104 @@ +'use strict'; + +angular.module('yardStickGui2App') + .controller('suitcreateController', ['$scope', '$state', '$stateParams', 'mainFactory', 'Upload', 'toaster', 'ngDialog', + function($scope, $state, $stateParams, mainFactory, Upload, toaster, ngDialog) { + + + init(); + + + function init() { + + getTestcaseList(); + $scope.constructTestSuit = constructTestSuit; + $scope.openDialog = openDialog; + $scope.createSuite = createSuite; + + } + + function getTestcaseList() { + mainFactory.getTestcaselist().get({ + + }).$promise.then(function(response) { + if (response.status == 1) { + $scope.testcaselist = response.result; + + + } + }, function(error) { + toaster.pop({ + type: 'error', + title: 'fail', + body: 'unknow error', + timeout: 3000 + }); + }) + } + + $scope.testsuiteList = []; + $scope.suitReconstructList = []; + + function constructTestSuit(name) { + + var index = $scope.testsuiteList.indexOf(name); + if (index > -1) { + $scope.testsuiteList.splice(index, 1); + } else { + $scope.testsuiteList.push(name); + } + + + $scope.suitReconstructList = $scope.testsuiteList; + + } + + function createSuite(name) { + mainFactory.suiteCreate().post({ + 'action': 'create_suite', + 'args': { + 'name': name, + 'testcases': $scope.testsuiteList + } + }).$promise.then(function(response) { + if (response.status == 1) { + toaster.pop({ + type: 'success', + title: 'create suite success', + body: 'you can go next step', + timeout: 3000 + }); + ngDialog.close(); + } else { + + } + }, function(error) { + toaster.pop({ + type: 'error', + title: 'fail', + body: 'unknow error', + timeout: 3000 + }); + }) + } + + function openDialog() { + ngDialog.open({ + template: 'views/modal/suiteName.html', + className: 'ngdialog-theme-default', + scope: $scope, + width: 314, + showClose: true, + closeByDocument: false + }) + } + + + + + + + + + } + ]);
\ No newline at end of file diff --git a/gui/app/scripts/controllers/suitedetail.controller.js b/gui/app/scripts/controllers/suitedetail.controller.js new file mode 100644 index 000000000..0dd39c389 --- /dev/null +++ b/gui/app/scripts/controllers/suitedetail.controller.js @@ -0,0 +1,48 @@ +'use strict'; + +angular.module('yardStickGui2App') + .controller('suiteDetailController', ['$scope', '$state', '$stateParams', 'mainFactory', 'Upload', 'toaster', + function($scope, $state, $stateParams, mainFactory, Upload, toaster) { + + + init(); + + + function init() { + + getSuiteDetail(); + + } + + function getSuiteDetail() { + mainFactory.suiteDetail().get({ + 'suiteName': $stateParams.name + + }).$promise.then(function(response) { + if (response.status == 1) { + $scope.suiteinfo = response.result.testsuite; + + } + }, function(error) { + toaster.pop({ + type: 'error', + title: 'fail', + body: 'unknow error', + timeout: 3000 + }); + }) + } + $scope.goBack = function goBack() { + window.history.back(); + } + + + + + + + + + + } + ]); diff --git a/gui/app/scripts/controllers/task.controller.js b/gui/app/scripts/controllers/task.controller.js new file mode 100644 index 000000000..05546f9bf --- /dev/null +++ b/gui/app/scripts/controllers/task.controller.js @@ -0,0 +1,175 @@ +'use strict'; + +angular.module('yardStickGui2App') + .controller('TaskController', ['$scope', '$state', '$stateParams', 'mainFactory', 'Upload', 'toaster', 'ngDialog', + function($scope, $state, $stateParams, mainFactory, Upload, toaster, ngDialog) { + + + init(); + + + function init() { + getDetailTaskForList(); + + } + + function getDetailTaskForList() { + mainFactory.getTaskDetail().get({ + 'taskId': $stateParams.taskId + }).$promise.then(function(response) { + if (response.status == 1) { + if (response.result.task.status == -1) { + response.result.task['stausWidth'] = '5%'; + } else if (response.result.task.status == 0) { + response.result.task['stausWidth'] = '50%'; + } else if (response.result.task.status == 1) { + response.result.task['stausWidth'] = '100%'; + } else if (response.result.task.status == 2) { + response.result.task['stausWidth'] = 'red'; + } + + $scope.taskDetailData = response.result.task; + if ($scope.taskDetailData.environment_id != null) { + getItemIdDetail($scope.taskDetailData.environment_id); + } + + } + }, function(error) { + toaster.pop({ + type: 'error', + title: 'fail', + body: 'unknow error', + timeout: 3000 + }); + }) + } + + + function getItemIdDetail(id) { + mainFactory.ItemDetail().get({ + 'envId': id + }).$promise.then(function(response) { + if (response.status == 1) { + $scope.displayEnv = response.result.environment; + + if (response.result.environment.pod_id != null) { + getPodDetail(response.result.environment.pod_id); + } else if (response.result.environment.image_id != null) { + getImageDetail(response.result.environment.image_id); + } else if (response.result.environment.openrc_id != null) { + getOpenrcDetail(response.result.environment.openrc_id != null); + } else if (response.result.environment.container_id.length != 0) { + $scope.displayContainerDetail = []; + var containerArray = response.result.environment.container_id; + for (var i = 0; i < containerArray.length; i++) { + getContainerId(containerArray[i]); + } + + } + } else { + toaster.pop({ + type: 'error', + title: 'fail', + body: response.error_msg, + timeout: 3000 + }); + } + }, function(error) { + toaster.pop({ + type: 'error', + title: 'fail', + body: 'unknow error', + timeout: 3000 + }); + }) + } + + //getopenRcid + function getOpenrcDetail(openrcId) { + mainFactory.getEnvironmentDetail().get({ + 'openrc_id': openrcId + }).$promise.then(function(response) { + //openrc数据 + $scope.openrcInfo = response.result; + // buildToEnvInfo($scope.openrcInfo.openrc) + }, function(response) { + + }) + } + + + //getImgDetail + function getImageDetail(id) { + mainFactory.ImageDetail().get({ + 'image_id': id + }).$promise.then(function(response) { + if (response.status == 1) { + $scope.imageDetail = response.result.image; + + } + }, function(error) { + toaster.pop({ + type: 'error', + title: 'fail', + body: 'unknow error', + timeout: 3000 + }); + }) + } + + //getPodDetail + function getPodDetail(id) { + mainFactory.podDeatil().get({ + 'podId': id + }).$promise.then(function(response) { + if (response.status == 1) { + $scope.podDetail = response.result.pod; + } + }, function(error) { + toaster.pop({ + type: 'error', + title: 'fail', + body: 'unknow error', + timeout: 3000 + }); + }) + } + //getContainerDetail + function getContainerId(containerId) { + mainFactory.containerDetail().get({ + 'containerId': containerId + }).$promise.then(function(response) { + if (response.status == 1) { + $scope.container = response.result.container; + $scope.displayContainerDetail.push($scope.container); + + } else { + toaster.pop({ + type: 'error', + title: 'fail', + body: response.error_msg, + timeout: 3000 + }); + } + }, function(error) { + toaster.pop({ + type: 'error', + title: 'fail', + body: 'unknow error', + timeout: 3000 + }); + }) + } + $scope.goBack = function goBack() { + window.history.back(); + } + + + + + + + + + } + ]);
\ No newline at end of file diff --git a/gui/app/scripts/controllers/taskModify.controller.js b/gui/app/scripts/controllers/taskModify.controller.js new file mode 100644 index 000000000..757d65866 --- /dev/null +++ b/gui/app/scripts/controllers/taskModify.controller.js @@ -0,0 +1,533 @@ +'use strict'; + +angular.module('yardStickGui2App') + .controller('TaskModifyController', ['$scope', '$state', '$stateParams', 'mainFactory', 'Upload', 'toaster', + function($scope, $state, $stateParams, mainFactory, Upload, toaster) { + + + init(); + $scope.blisterPackTemplates = [{ id: 1, name: "Test Case" }, { id: 2, name: "Test Suite" }] + $scope.selectType = null; + + $scope.sourceShow = null; + + + + function init() { + getDetailTaskForList(); + getEnvironmentList(); + $scope.triggerContent = triggerContent; + $scope.constructTestSuit = constructTestSuit; + $scope.constructTestCase = constructTestCase; + $scope.getTestDeatil = getTestDeatil; + $scope.confirmToServer = confirmToServer; + $scope.addEnvToTask = addEnvToTask; + } + + function getDetailTaskForList() { + mainFactory.getTaskDetail().get({ + 'taskId': $stateParams.taskId + }).$promise.then(function(response) { + if (response.status == 1) { + if (response.result.task.status == -1) { + response.result.task['stausWidth'] = '5%'; + } else if (response.result.task.status == 0) { + response.result.task['stausWidth'] = '50%'; + } else if (response.result.task.status == 1) { + response.result.task['stausWidth'] = '100%'; + } else if (response.result.task.status == 2) { + response.result.task['stausWidth'] = 'red'; + } + + $scope.taskDetailData = response.result.task; + $scope.selectEnv = $scope.taskDetailData.environment_id; + + if ($scope.taskDetailData.environment_id != null) { + getItemIdDetail($scope.taskDetailData.environment_id); + } + + } + }, function(error) { + toaster.pop({ + type: 'error', + title: 'fail', + body: 'unknow error', + timeout: 3000 + }); + }) + } + + function getItemIdDetail(id) { + mainFactory.ItemDetail().get({ + 'envId': id + }).$promise.then(function(response) { + if (response.status == 1) { + $scope.envName = response.result.environment.name; + // $scope.selectEnv = $scope.envName; + } else { + alert('Something Wrong!'); + } + }, function(error) { + toaster.pop({ + type: 'error', + title: 'fail', + body: 'unknow error', + timeout: 3000 + }); + }) + } + + //getopenRcid + function getOpenrcDetail(openrcId) { + mainFactory.getEnvironmentDetail().get({ + 'openrc_id': openrcId + }).$promise.then(function(response) { + $scope.openrcInfo = response.result; + // buildToEnvInfo($scope.openrcInfo.openrc) + }, function(response) { + + }) + } + + + //getImgDetail + function getImageDetail(id) { + mainFactory.ImageDetail().get({ + 'image_id': id + }).$promise.then(function(response) { + if (response.status == 1) { + $scope.imageDetail = response.result.image; + + } + }, function(error) { + toaster.pop({ + type: 'error', + title: 'fail', + body: 'unknow error', + timeout: 3000 + }); + }) + } + + //getPodDetail + function getPodDetail(id) { + mainFactory.podDeatil().get({ + 'podId': id + }).$promise.then(function(response) { + if (response.status == 1) { + $scope.podDetail = response.result.pod; + } + }, function(error) { + toaster.pop({ + type: 'error', + title: 'fail', + body: 'unknow error', + timeout: 3000 + }); + }) + } + //getContainerDetail + function getContainerId(containerId) { + mainFactory.containerDetail().get({ + 'containerId': containerId + }).$promise.then(function(response) { + if (response.status == 1) { + $scope.container = response.result.container; + $scope.displayContainerDetail.push($scope.container); + + } else { + + } + }, function(error) { + toaster.pop({ + type: 'error', + title: 'fail', + body: 'unknow error', + timeout: 3000 + }); + }) + } + + function getEnvironmentList() { + mainFactory.getEnvironmentList().get().$promise.then(function(response) { + $scope.environmentList = response.result.environments; + }, function(error) { + toaster.pop({ + type: 'error', + title: 'fail', + body: 'unknow error', + timeout: 3000 + }); + }) + } + + + function triggerContent(name) { + $scope.selectCase = null; + $scope.displayTable = true; + + $scope.selectType = name; + if (name.name == 'Test Case') { + $scope.taskDetailData.suite = false; + getTestcaseList(); + } else if (name.name == 'Test Suite') { + $scope.taskDetailData.suite = true; + getsuiteList(); + } + } + + function getTestcaseList() { + mainFactory.getTestcaselist().get({ + + }).$promise.then(function(response) { + if (response.status == 1) { + $scope.testcaselist = response.result; + + + } + }, function(error) { + toaster.pop({ + type: 'error', + title: 'fail', + body: 'unknow error', + timeout: 3000 + }); + }) + } + + function getsuiteList() { + mainFactory.suiteList().get({ + + }).$promise.then(function(response) { + if (response.status == 1) { + $scope.testsuitlist = response.result.testsuites; + + } + }, function(error) { + toaster.pop({ + type: 'error', + title: 'fail', + body: 'unknow error', + timeout: 3000 + }); + }) + } + + + function constructTestSuit(id, name) { + + $scope.envName = name; + $scope.selectEnv = id; + + } + + function constructTestCase(name) { + + $scope.selectCase = name; + if ($scope.selectType.name == 'Test Case') { + getCaseInfo(); + } else { + getSuiteInfo(); + } + + } + + function getCaseInfo() { + + + + mainFactory.getTestcaseDetail().get({ + 'testcasename': $scope.selectCase + + }).$promise.then(function(response) { + if (response.status == 1) { + + $scope.contentInfo = response.result.testcase; + + } + }, function(error) { + toaster.pop({ + type: 'error', + title: 'fail', + body: 'unknow error', + timeout: 3000 + }); + }) + } + + function getSuiteInfo() { + mainFactory.suiteDetail().get({ + 'suiteName': $scope.selectCase.split('.')[0] + + }).$promise.then(function(response) { + if (response.status == 1) { + $scope.contentInfo = response.result.testsuite; + + } + }, function(error) { + toaster.pop({ + type: 'error', + title: 'fail', + body: 'unknow error', + timeout: 3000 + }); + }) + } + + + function getTestDeatil() { + + + if ($scope.selectType.name == 'Test Case') { + getTestcaseDetail(); + } else { + getSuiteDetail(); + } + + } + + function getSuiteDetail() { + mainFactory.suiteDetail().get({ + 'suiteName': $scope.selectCase.split('.')[0] + + }).$promise.then(function(response) { + if (response.status == 1) { + $scope.displayTable = false; + $scope.contentInfo = response.result.testsuite; + + } + }, function(error) { + toaster.pop({ + type: 'error', + title: 'fail', + body: 'unknow error', + timeout: 3000 + }); + }) + } + + + function getTestcaseDetail() { + mainFactory.getTestcaseDetail().get({ + 'testcasename': $scope.selectCase + + }).$promise.then(function(response) { + if (response.status == 1) { + + $scope.displayTable = false; + $scope.contentInfo = response.result.testcase; + + } + }, function(error) { + toaster.pop({ + type: 'error', + title: 'fail', + body: 'unknow error', + timeout: 3000 + }); + }) + } + + + + function addCasetoTask(content) { + mainFactory.taskAddEnv().put({ + 'taskId': $stateParams.taskId, + 'action': 'add_case', + 'args': { + 'task_id': $stateParams.taskId, + 'case_name': $scope.selectCase, + 'case_content': content + } + }).$promise.then(function(response) { + if (response.status == 1) { + toaster.pop({ + type: 'success', + title: 'add test case success', + body: 'you can go next step', + timeout: 3000 + }); + $scope.ifHasCase = true; + + + } else { + toaster.pop({ + type: 'error', + title: 'create task wrong', + body: '', + timeout: 3000 + }); + } + }, function(error) { + toaster.pop({ + type: 'error', + title: 'create task wrong', + body: '', + timeout: 3000 + }); + }) + } + + function addSuitetoTask(content) { + mainFactory.taskAddEnv().put({ + 'taskId': $stateParams.taskId, + 'action': 'add_suite', + 'args': { + 'task_id': $stateParams.taskId, + 'suite_name': $scope.selectCase.split('.')[0], + 'suite_content': content + } + }).$promise.then(function(response) { + if (response.status == 1) { + toaster.pop({ + type: 'success', + title: 'add test suite success', + body: 'you can go next step', + timeout: 3000 + }); + $scope.ifHasSuite = true; + + + } else { + toaster.pop({ + type: 'error', + title: 'create task wrong', + body: 'wrong', + timeout: 3000 + }); + } + }, function(error) { + toaster.pop({ + type: 'error', + title: 'create task wrong', + body: 'something wrong', + timeout: 3000 + }); + }) + } + $scope.changeStatussourceTrue = function changeStatussourceTrue() { + $scope.selectCase = null; + $scope.sourceShow = true; + } + + $scope.changeStatussourceFalse = function changeStatussourceFalse() { + $scope.sourceShow = false; + } + + function confirmToServer(content1, content2) { + + var content; + if ($scope.sourceShow == false) { + content = content2; + $scope.selectCase = $scope.taskDetailData.case_name; + } else if ($scope.sourceShow == true) { + content = content1; + } + if ($scope.selectCase == 'Test Case' || $scope.taskDetailData.suite == false) { + + addCasetoTask(content); + } else { + addSuitetoTask(content); + } + } + + + function addEnvToTask() { + + mainFactory.taskAddEnv().put({ + 'taskId': $stateParams.taskId, + 'action': 'add_environment', + 'args': { + 'task_id': $stateParams.taskId, + 'environment_id': $scope.selectEnv + } + }).$promise.then(function(response) { + if (response.status == 1) { + toaster.pop({ + type: 'success', + title: 'add environment success', + body: 'you can go next step', + timeout: 3000 + }); + $scope.ifHasEnv = true; + + + } else { + toaster.pop({ + type: 'error', + title: 'create task wrong', + body: 'you can go next step', + timeout: 3000 + }); + } + + + + }, function(error) { + toaster.pop({ + type: 'error', + title: 'create task wrong', + body: 'you can go next step', + timeout: 3000 + }); + }) + } + + $scope.goBack = function goBack() { + window.history.back(); + } + + $scope.runAtask = function runAtask() { + mainFactory.taskAddEnv().put({ + 'taskId': $stateParams.taskId, + 'action': 'run', + 'args': { + 'task_id': $stateParams.taskId + } + }).$promise.then(function(response) { + if (response.status == 1) { + toaster.pop({ + type: 'success', + title: 'run a task success', + body: 'go to task list page...', + timeout: 3000 + }); + setTimeout(function() { + window.history.back(); + }, 2000); + + + + } else { + toaster.pop({ + type: 'error', + title: 'fail', + body: response.error_msg, + timeout: 3000 + }); + } + }, function(error) { + toaster.pop({ + type: 'error', + title: 'fail', + body: 'unknow error', + timeout: 3000 + }); + }) + } + + + + + + + + + + + + + + + } + ]); diff --git a/gui/app/scripts/controllers/testcase.controller.js b/gui/app/scripts/controllers/testcase.controller.js new file mode 100644 index 000000000..616ceb4a8 --- /dev/null +++ b/gui/app/scripts/controllers/testcase.controller.js @@ -0,0 +1,154 @@ +'use strict'; + +angular.module('yardStickGui2App') + .controller('TestcaseController', ['$scope', '$state', '$stateParams', 'mainFactory', 'Upload', 'toaster', 'ngDialog', '$loading', + function($scope, $state, $stateParams, mainFactory, Upload, toaster, ngDialog, $loading) { + + + init(); + $scope.loadingOPENrc = false; + + + function init() { + $scope.testcaselist = []; + getTestcaseList(); + $scope.gotoDetail = gotoDetail; + $scope.uploadFiles = uploadFiles; + + + } + + function getTestcaseList() { + $loading.start('key'); + mainFactory.getTestcaselist().get({ + + }).$promise.then(function(response) { + $loading.finish('key'); + if (response.status == 1) { + $scope.testcaselist = response.result; + + + } + }, function(error) { + $loading.finish('key'); + toaster.pop({ + type: 'error', + title: 'fail', + body: 'unknow error', + timeout: 3000 + }); + }) + } + + function gotoDetail(name) { + $state.go('app2.testcasedetail', { name: name }); + } + + + function uploadFiles($file, $invalidFiles) { + $scope.loadingOPENrc = true; + + $scope.displayOpenrcFile = $file; + timeConstruct($scope.displayOpenrcFile.lastModified); + Upload.upload({ + url: Base_URL + '/api/v2/yardstick/testcases', + data: { file: $file, 'action': 'upload_case' } + }).then(function(response) { + + $scope.loadingOPENrc = false; + if (response.data.status == 1) { + toaster.pop({ + type: 'success', + title: 'upload success', + body: 'you can go next step', + timeout: 3000 + }); + + + + } else { + + } + + }, function(error) { + $scope.uploadfile = null; + toaster.pop({ + type: 'error', + title: 'fail', + body: 'unknow error', + timeout: 3000 + }); + }) + } + + function timeConstruct(array) { + var date = new Date(1398250549490); + var Y = date.getFullYear() + '-'; + var M = (date.getMonth() + 1 < 10 ? '0' + (date.getMonth() + 1) : date.getMonth() + 1) + '-'; + var D = date.getDate() + ' '; + var h = date.getHours() + ':'; + var m = date.getMinutes() + ':'; + var s = date.getSeconds(); + $scope.filelastModified = Y + M + D + h + m + s; + + } + $scope.goBack = function goBack() { + $state.go('app2.projectList'); + } + + $scope.openDeleteEnv = function openDeleteEnv(id, name) { + $scope.deleteName = name; + $scope.deleteId = id; + ngDialog.open({ + template: 'views/modal/deleteConfirm.html', + scope: $scope, + className: 'ngdialog-theme-default', + width: 500, + showClose: true, + closeByDocument: false + }) + + } + + $scope.deleteTestCase = function deleteTestCase() { + mainFactory.deleteTestCase().delete({ 'caseName': $scope.deleteId }).$promise.then(function(response) { + if (response.status == 1) { + toaster.pop({ + type: 'success', + title: 'delete Test Case success', + body: 'you can go next step', + timeout: 3000 + }); + ngDialog.close(); + getTestcaseList(); + } else { + toaster.pop({ + type: 'error', + title: 'Wrong', + body: response.result, + timeout: 3000 + }); + } + + }, function(error) { + toaster.pop({ + type: 'error', + title: 'fail', + body: 'unknow error', + timeout: 3000 + }); + + }) + } + + + + + + + + + + + } + ]);
\ No newline at end of file diff --git a/gui/app/scripts/controllers/testcasedetail.controller.js b/gui/app/scripts/controllers/testcasedetail.controller.js new file mode 100644 index 000000000..4e824ca85 --- /dev/null +++ b/gui/app/scripts/controllers/testcasedetail.controller.js @@ -0,0 +1,50 @@ +'use strict'; + +angular.module('yardStickGui2App') + .controller('testcaseDetailController', ['$scope', '$state', '$stateParams', 'mainFactory', 'Upload', 'toaster', + function($scope, $state, $stateParams, mainFactory, Upload, toaster) { + + + init(); + + + function init() { + + getTestcaseDetail(); + + + } + + function getTestcaseDetail() { + mainFactory.getTestcaseDetail().get({ + 'testcasename': $stateParams.name + + }).$promise.then(function(response) { + if (response.status == 1) { + $scope.testcaseInfo = response.result.testcase; + + } + }, function(error) { + toaster.pop({ + type: 'error', + title: 'fail', + body: 'unknow error', + timeout: 3000 + }); + }) + } + + $scope.goBack = function goBack() { + window.history.back(); + } + + + + + + + + + + } + ]);
\ No newline at end of file diff --git a/gui/app/scripts/controllers/testsuit.controller.js b/gui/app/scripts/controllers/testsuit.controller.js new file mode 100644 index 000000000..abc9095c7 --- /dev/null +++ b/gui/app/scripts/controllers/testsuit.controller.js @@ -0,0 +1,119 @@ +'use strict'; + +angular.module('yardStickGui2App') + .controller('SuiteListController', ['$scope', '$state', '$stateParams', 'mainFactory', 'Upload', 'toaster', 'ngDialog', '$loading', + function($scope, $state, $stateParams, mainFactory, Upload, toaster, ngDialog, $loading) { + + + init(); + + + function init() { + $scope.testsuitlist = []; + getsuiteList(); + $scope.gotoDetail = gotoDetail; + $scope.gotoCreateSuite = gotoCreateSuite; + + + } + + function getsuiteList() { + $loading.start('key'); + mainFactory.suiteList().get({ + + }).$promise.then(function(response) { + $loading.finish('key'); + if (response.status == 1) { + $scope.testsuitlist = response.result.testsuites; + + } + }, function(error) { + $loading.finish('key'); + toaster.pop({ + type: 'error', + title: 'fail', + body: 'unknow error', + timeout: 3000 + }); + }) + } + + function gotoDetail(name) { + var temp = name.split('.')[0]; + + $state.go('app2.suitedetail', { name: temp }) + + } + + function gotoCreateSuite() { + $state.go('app2.suitcreate'); + } + + $scope.goBack = function goBack() { + $state.go('app2.projectList'); + } + + + $scope.openDeleteEnv = function openDeleteEnv(id, name) { + $scope.deleteName = name; + $scope.deleteId = id.split('.')[0]; + ngDialog.open({ + template: 'views/modal/deleteConfirm.html', + scope: $scope, + className: 'ngdialog-theme-default', + width: 500, + showClose: true, + closeByDocument: false + }) + + } + + $scope.deleteSuite = function deleteSuite() { + mainFactory.deleteTestSuite().delete({ 'suite_name': $scope.deleteId }).$promise.then(function(response) { + if (response.status == 1) { + toaster.pop({ + type: 'success', + title: 'delete Test Suite success', + body: 'you can go next step', + timeout: 3000 + }); + ngDialog.close(); + getTestcaseList(); + } else { + toaster.pop({ + type: 'error', + title: 'Wrong', + body: response.result, + timeout: 3000 + }); + } + + }, function(error) { + toaster.pop({ + type: 'error', + title: 'fail', + body: 'unknow error', + timeout: 3000 + }); + }) + } + + + + + + + + + + + + + + + + + + + } + ]);
\ No newline at end of file diff --git a/gui/app/scripts/factory/main.factory.js b/gui/app/scripts/factory/main.factory.js new file mode 100644 index 000000000..f8e9df9a1 --- /dev/null +++ b/gui/app/scripts/factory/main.factory.js @@ -0,0 +1,247 @@ +'use strict'; + +/** + * get data factory + */ + + +var Base_URL; +var Grafana_URL; + +angular.module('yardStickGui2App') + .factory('mainFactory', ['$resource','$rootScope','$http', '$location',function($resource, $rootScope,$http,$location) { + + Base_URL = 'http://' + $location.host() + ':' + $location.port(); + Grafana_URL = 'http://' + $location.host(); + + return { + + postEnvironmentVariable: function() { + return $resource(Base_URL + '/api/v2/yardstick/openrcs', {}, { + 'post': { + method: 'POST' + } + }) + }, + uploadOpenrc: function() { + return $resource(Base_URL + '/ap/v2/yardstick/openrcs', {}, { + 'post': { + method: 'POST' + } + }) + }, + getEnvironmentList: function() { + return $resource(Base_URL+ '/api/v2/yardstick/environments', {}, { + 'get': { + method: 'GET' + } + }) + }, + getEnvironmentDetail: function() { + return $resource(Base_URL + '/api/v2/yardstick/openrcs/:openrc_id', { openrc_id: "@openrc_id" }, { + 'get': { + method: 'GET' + } + }) + }, + addEnvName: function() { + return $resource(Base_URL + '/api/v2/yardstick/environments', {}, { + 'post': { + method: 'POST' + } + }) + }, + ItemDetail: function() { + return $resource(Base_URL + '/api/v2/yardstick/environments/:envId', { envId: "@envId" }, { + 'get': { + method: 'GET' + } + }) + }, + ImageDetail: function() { + return $resource(Base_URL + '/api/v2/yardstick/images/:image_id', { image_id: "@image_id" }, { + 'get': { + method: 'GET' + } + }) + }, + podDeatil: function() { + return $resource(Base_URL + '/api/v2/yardstick/pods/:podId', { podId: "@podId" }, { + 'get': { + method: 'GET' + } + }) + }, + containerDetail: function() { + return $resource(Base_URL + '/api/v2/yardstick/containers/:containerId', { containerId: "@containerId" }, { + 'get': { + method: 'GET' + } + }) + }, + ImageList: function() { + return $resource(Base_URL + '/api/v2/yardstick/images', {}, { + 'get': { + method: 'GET' + } + }) + }, + uploadImage: function() { + return $resource(Base_URL + '/api/v2/yardstick/images', {}, { + 'post': { + method: 'POST' + } + }) + }, + getPodDetail: function() { + return $resource(Base_URL + '/api/v2/yardstick/pods/:podId', { podId: "@podId" }, { + 'get': { + method: 'GET' + } + }) + }, + runAcontainer: function() { + return $resource(Base_URL + '/api/v2/yardstick/containers', { podId: "@podId" }, { + 'post': { + method: 'POST' + } + }) + }, + getTestcaselist: function() { + return $resource(Base_URL + '/api/v2/yardstick/testcases', {}, { + 'get': { + method: 'GET' + } + }) + }, + getTestcaseDetail: function() { + return $resource(Base_URL + '/api/v2/yardstick/testcases/:testcasename', { testcasename: "@testcasename" }, { + 'get': { + method: 'GET' + } + }) + }, + suiteList: function() { + return $resource(Base_URL + '/api/v2/yardstick/testsuites', {}, { + 'get': { + method: 'GET' + } + }) + }, + suiteDetail: function() { + return $resource(Base_URL + '/api/v2/yardstick/testsuites/:suiteName', { suiteName: "@suiteName" }, { + 'get': { + method: 'GET' + } + }) + }, + suiteCreate: function() { + return $resource(Base_URL + '/api/v2/yardstick/testsuites', {}, { + 'post': { + method: 'POST' + } + }) + }, + projectList: function() { + return $resource(Base_URL + '/api/v2/yardstick/projects', {}, { + 'get': { + method: 'GET' + } + }) + }, + createProjectName: function() { + return $resource(Base_URL + '/api/v2/yardstick/projects', {}, { + 'post': { + method: 'POST' + } + }) + }, + getProjectDetail: function() { + return $resource(Base_URL + '/api/v2/yardstick/projects/:project_id', { project_id: "@project_id" }, { + 'post': { + method: 'POST' + } + }) + }, + createTask: function() { + return $resource(Base_URL + '/api/v2/yardstick/tasks', {}, { + 'post': { + method: 'POST' + } + }) + }, + getTaskDetail: function() { + return $resource(Base_URL + '/api/v2/yardstick/tasks/:taskId', { taskId: "@taskId" }, { + 'get': { + method: 'GET' + } + }) + }, + + taskAddEnv: function() { + return $resource(Base_URL + '/api/v2/yardstick/tasks/:taskId', { taskId: "@taskId" }, { + 'put': { + method: 'PUT' + } + }) + }, + //delete operate + deleteEnv: function() { + return $resource(Base_URL + '/api/v2/yardstick/environments/:env_id', { env_id: '@env_id' }, { + 'delete': { + method: 'DELETE' + } + }) + }, + deleteOpenrc: function() { + return $resource(Base_URL + '/api/v2/yardstick/openrcs/:openrc', { openrc: '@openrc' }, { + 'delete': { + method: 'DELETE' + } + }) + }, + deletePod: function() { + return $resource(Base_URL + '/api/v2/yardstick/pods/:podId', { podId: '@podId' }, { + 'delete': { + method: 'DELETE' + } + }) + }, + deleteContainer: function() { + return $resource(Base_URL + '/api/v2/yardstick/containers/:containerId', { containerId: '@containerId' }, { + 'delete': { + method: 'DELETE' + } + }) + }, + deleteTestCase: function() { + return $resource(Base_URL + '/api/v2/yardstick/testcases/:caseName', { caseName: '@caseName' }, { + 'delete': { + method: 'DELETE' + } + }) + }, + deleteTestSuite: function() { + return $resource(Base_URL + '/api/v2/yardstick/testsuites/:suite_name', { suite_name: '@suite_name' }, { + 'delete': { + method: 'DELETE' + } + }) + }, + deleteProject: function() { + return $resource(Base_URL + '/api/v2/yardstick/projects/:project_id', { project_id: '@project_id' }, { + 'delete': { + method: 'DELETE' + } + }) + }, + deleteTask: function() { + return $resource(Base_URL + '/api/v2/yardstick/tasks/:task_id', { task_id: '@task_id' }, { + 'delete': { + method: 'DELETE' + } + }) + } + + }; + }]); diff --git a/gui/app/scripts/router.config.js b/gui/app/scripts/router.config.js new file mode 100644 index 000000000..b42954272 --- /dev/null +++ b/gui/app/scripts/router.config.js @@ -0,0 +1,184 @@ +'use strict'; + +angular.module('yardStickGui2App') + .run( + ['$rootScope', '$state', '$stateParams', + function($rootScope, $state, $stateParams) { + $rootScope.$state = $state; + $rootScope.$stateParams = $stateParams; + + } + ] + ) + .config(['$stateProvider', '$urlRouterProvider', '$locationProvider', + function($stateProvider, $urlRouterProvider, $locationProvider) { + $urlRouterProvider + .otherwise('main/environment'); + + + + + $stateProvider + + .state('app2', { + url: "/main", + controller: 'ContentController', + templateUrl: "views/main2.html", + ncyBreadcrumb: { + label: 'Main' + } + }) + .state('app', { + url: "/main", + controller: 'ContentController', + templateUrl: "views/main.html", + ncyBreadcrumb: { + label: 'Main' + } + }) + + .state('app2.environment', { + url: '/environment', + templateUrl: 'views/environmentList.html', + controller: 'MainCtrl', + ncyBreadcrumb: { + label: 'Environment' + } + }) + .state('app2.testcase', { + url: '/testcase', + templateUrl: 'views/testcaselist.html', + controller: 'TestcaseController', + ncyBreadcrumb: { + label: 'Test Case' + } + }) + .state('app2.testsuite', { + url: '/suite', + templateUrl: 'views/suite.html', + controller: 'SuiteListController', + ncyBreadcrumb: { + label: 'Test Suite' + } + }) + .state('app2.suitcreate', { + url: '/suitcreate', + templateUrl: 'views/testcasechoose.html', + controller: 'suitcreateController', + ncyBreadcrumb: { + label: 'Suite Create' + } + }) + .state('app2.testcasedetail', { + url: '/testdetail/:name', + templateUrl: 'views/testcasedetail.html', + controller: 'testcaseDetailController', + ncyBreadcrumb: { + label: 'Test Case Detail' + }, + params: { name: null } + }) + .state('app2.suitedetail', { + url: '/suitedetail/:name', + templateUrl: 'views/suitedetail.html', + controller: 'suiteDetailController', + ncyBreadcrumb: { + label: 'Suite Detail' + }, + params: { name: null } + }) + .state('app.environmentDetail', { + url: '/envDetail/:uuid', + templateUrl: 'views/environmentDetail.html', + controller: 'DetailController', + params: { uuid: null, ifNew: null }, + ncyBreadcrumb: { + label: 'Environment Detail' + } + }) + .state('app.uploadImage', { + url: '/envimageDetail/:uuid', + templateUrl: 'views/uploadImage.html', + controller: 'ImageController', + params: { uuid: null }, + ncyBreadcrumb: { + label: 'Upload Image' + } + + }) + .state('app.podUpload', { + url: '/envpodupload/:uuid', + templateUrl: 'views/podupload.html', + controller: 'PodController', + params: { uuid: null }, + ncyBreadcrumb: { + label: 'Pod Upload' + } + }) + .state('app.container', { + url: '/envcontainer/:uuid', + templateUrl: 'views/container.html', + controller: 'ContainerController', + params: { uuid: null }, + ncyBreadcrumb: { + label: 'Container Manage' + } + }) + .state('app2.projectList', { + url: '/project', + templateUrl: 'views/projectList.html', + controller: 'ProjectController', + ncyBreadcrumb: { + label: 'Project' + } + + }) + .state('app2.tasklist', { + url: '/task/:taskId', + templateUrl: 'views/taskList.html', + controller: 'TaskController', + params: { taskId: null }, + ncyBreadcrumb: { + label: 'Task' + } + + }) + .state('app2.report', { + url: '/report/:taskId', + templateUrl: 'views/report.html', + controller: 'ReportController', + params: { taskId: null }, + ncyBreadcrumb: { + label: 'Report' + } + + }) + .state('app2.projectdetail', { + url: '/projectdetail/:projectId', + templateUrl: 'views/projectdetail.html', + controller: 'ProjectDetailController', + params: { projectId: null }, + ncyBreadcrumb: { + label: 'Project Detail' + } + + }) + .state('app2.taskModify', { + url: '/taskModify/:taskId', + templateUrl: 'views/taskmodify.html', + controller: 'TaskModifyController', + params: { taskId: null }, + ncyBreadcrumb: { + label: 'Modify Task' + } + + + }) + + + + + + } + ]) + .run(); diff --git a/gui/app/styles/main.css b/gui/app/styles/main.css new file mode 100644 index 000000000..e13a66bce --- /dev/null +++ b/gui/app/styles/main.css @@ -0,0 +1,208 @@ +.browsehappy { + margin: 0.2em 0; + background: #ccc; + color: #000; + padding: 0.2em 0; +} + +body { + padding: 0; +} + + +/* Everything but the jumbotron gets side spacing for mobile first views */ + +.header, +.marketing, +.footer { + /*padding-left: 15px; + padding-right: 15px;*/ +} + + +/* Custom page header */ + +.header { + border-bottom: 1px solid #e5e5e5; + margin-bottom: 10px; +} + + +/* Make the masthead heading the same height as the navigation */ + +.header h3 { + margin-top: 0; + margin-bottom: 0; + line-height: 40px; + padding-bottom: 19px; +} + + +/* Custom page footer */ + +.footer { + padding-top: 19px; + color: #777; + border-top: 1px solid #e5e5e5; +} + +.container-narrow>hr { + margin: 30px 0; +} + + +/* Main marketing message and sign up button */ + +.jumbotron { + text-align: center; + border-bottom: 1px solid #e5e5e5; +} + +.jumbotron .btn { + font-size: 21px; + padding: 14px 24px; +} + + +/* Supporting marketing content */ + +.marketing { + margin: 40px 0; +} + +.marketing p+h4 { + margin-top: 28px; +} + + +/* Responsive: Portrait tablets and up */ + +@media screen and (min-width: 768px) { + .container { + max-width: 730px; + } + /* Remove the padding we set earlier */ + .header, + .marketing, + .footer { + padding-left: 0; + padding-right: 0; + } + /* Space out the masthead */ + .header { + margin-bottom: 30px; + } + /* Remove the bottom border on the jumbotron for visual effect */ + .jumbotron { + border-bottom: 0; + } +} + +.jumbotron.ng-scope { + margin-top: 100px; +} + +.content { + /*margin-left: 300px;*/ + /*margin-left: 100px;*/ + position: relative; + margin-left: 25%; + width: 70%; + border: 1px solid #dfe3e4; + border-radius: 5px; + padding: 20px 20px 5px 20px; + height: 100%; + margin-right: 10px; + /*border-bottom: none;*/ + margin-bottom: 10px; + padding-bottom: 20px; + /* overflow: hidden; */ +} + +.ngdialog.ngdialog-theme-default .ngdialog-content { + background-color: #fff; +} + +.progree-parent { + width: 50%; + background-color: #dfe3e4; + height: 10px; + border-radius: 10px; +} + +.progree-child { + width: 50%; + background-color: #2ecc71; + /* background-color: white; */ + height: 10px; + border-radius: 5px; +} + +textarea { + width: 100%; + height: 400px; + border-radius: 5px; + border: 1px solid #e8e8e8; +} + +.naviSide { + position: fixed; + left: 0px; + top: 0px; + height: 150%; + background-color: #f8f8f8; + width: 210px; + padding: 30px 0 0 0; + border-radius: 10px; + border-right: 1px solid #e7e7e7; + z-index: 2; +} + +.panel-body { + border: none; +} + +.panel-group { + width: 210px; +} + +.panel-group { + margin-bottom: 0px; +} + +* { + border-radius: 0px ! important; +} + +.naviSide.ng-scope { + box-shadow: 1px 1px 2px #888888; +} + +.pagination>li>a, +.pagination>li>span { + color: #333; +} + +.pagination>.active>a, +.pagination>.active>span, +.pagination>.active>a:hover, +.pagination>.active>span:hover, +.pagination>.active>a:focus, +.pagination>.active>span:focus { + background-color: #f9f9f9; + color: #333; + border-color: #ddd; +} + +.ngdialog.ngdialog-theme-default .ngdialog-close{ + border: none; + background-color: #fff; +} + +button:focus {outline:0;} +input:focus{outline: 0} + +.ngdialog-content { + overflow: hidden; +} + diff --git a/gui/app/views/container.html b/gui/app/views/container.html new file mode 100644 index 000000000..b3d78bfb1 --- /dev/null +++ b/gui/app/views/container.html @@ -0,0 +1,134 @@ +<!--container management--> + +<div class="content"> + <div style="display:flex;flex-direction:row;"> + <div style="width:750px;"> + + <h3>{{envName}} -- Container + <!--<button class="btn btn-default" style="float:right">Go Next</button>--> + + </h3> + <!--<p>In this process, you can input your define openrc config or upload a openrc file</p>--> + + <hr/> + + <select ng-model="selectContainer" data-ng-options="container as container.name for container in containerList"> + <option value="">Choose...</option> + </select> + + <button class="btn btn-default" ng-click="createContainer()" ng-disabled="selectContainer==null"> + <div ng-show="!showloading">Create</div> + <img src="images/loading2.gif" width="25" height="25" ng-if="showloading" /> + </button> + + <hr/> + + <div> + <h4 ng-show="displayContainerInfo.length==0">No Container Data</h4> + <div ng-show="displayContainerInfo.length!=0"> + <h4>Current Container</h4> + <table class="table table-striped"> + + <tr> + <th>name</th> + <th>status</th> + <th>time</th> + <th>delete</th> + + </tr> + <tr ng-repeat="con in displayContainerInfo"> + <td>{{con.name}}</td> + <td>{{con.status}}</td> + <td>{{con.time}}</td> + <td> + <button class="btn btn-default btn-sm" ng-click="openDeleteEnv(con.id,'container')">Delete</button> + </td> + + + </tr> + + + + </table> + </div> + </div> + + + + + + + + + + + </div> + + + </div> + +</div> +<toaster-container></toaster-container> + +<style> + .form-control { + border-radius: 5px; + width: 200px; + margin-bottom: 10px; + } + + .uploadbutton { + background-color: #007ACC; + color: #fff; + border: 0px; + border-radius: 5px; + height: 27px; + } + + .edit-title { + border: 0px; + background-color: #ffffff; + margin-bottom: 5px; + font-size: 12px; + } + + .null-edit-title { + border: 1px solid #e5e6e7; + border-radius: 5px; + margin-bottom: 3px; + } + + .item-info { + display: flex; + flex-direction: row; + } + + .delete-img { + width: 15px; + height: 15px; + opacity: 0.8; + margin-left: -10px; + margin-top: -3px; + cursor: pointer; + } + + .nextButton { + margin-top: 30px; + border: none; + border-radius: 5px; + padding: 6px; + background-color: #339933; + color: #ffffff; + text-align: center; + /* margin-left: 300px; */ + } + + select { + height: 30px; + border-radius: 5px; + border: 1px solid #e8e8e8; + width: 135px; + margin-top: 20px; + margin-left: 20px; + } +</style> diff --git a/gui/app/views/content.html b/gui/app/views/content.html new file mode 100644 index 000000000..e69de29bb --- /dev/null +++ b/gui/app/views/content.html diff --git a/gui/app/views/environmentDetail.html b/gui/app/views/environmentDetail.html new file mode 100644 index 000000000..4d5f21c68 --- /dev/null +++ b/gui/app/views/environmentDetail.html @@ -0,0 +1,143 @@ +<!--environment detail page--> + +<div class="content" style="overflow-x: scroll;"> + <div style="display:flex;flex-direction:row;"> + <div> + + + <h3> {{baseElementInfo.name}} -- Openrc + <button class="btn btn-default" style="float:right" ng-click="goNext()">Next</button> + <button class="btn btn-default" style="float:right;margin-right:10px;" ng-click="openDeleteEnv(1,'openrc')">Delete</button> + </h3> + <!--<p>In this process, you can input your define openrc config or upload a openrc file</p>--> + + + + <div> + + <button style="display:inline;" class="btn btn-default" ng-click="addEnvironment()" ng-show="uuid==null">Add Name</button> + </div> + + + + <hr/> + <div bs-tabs style="width:600px;"> + <div data-title="Detail" bs-pane ng-if="openrcInfo.openrc!=null"> + + <h4> + You have already set up the openrc parameters + </h4> + <hr /> + <div ng-repeat="(key,value) in openrcInfo.openrc"> + <nobr> + <font style="font-weight:600;font-size:14px;">{{key}} : </font> + <font style="font-size:14px;">{{value}}</font> + </nobr> + </div> + + </div> + <div data-title="Update" bs-pane> + + <div style="margin-top:20px;"> + <button class="btn btn-default" ng-click="addInfo()" style="margin-bottom:20px;">Add</button> + <div style="height:300px;width:800px;display:flex;flex-direction:column;flex-wrap:wrap;margin-left:5px;"> + <div ng-repeat="info in envInfo"> + <!--<div> {{info.name}}</div>--> + + <input class="edit-title" ng-model="info.name" ng-class="{'null-edit-title':info.name==null}" ng-attr-type="{{info.name.indexOf('PASSWORD')>-1 ? password : text}}" /> + + <div class="item-info"> + <input class="form-control" type="text" ng-model="info.value" /> + <!--<button class="delete-button" ng-click="deleteEnvItem($index)">delete</button>--> + <img src="images/close.png" ng-click="deleteEnvItem($index)" class="delete-img" /> + </div> + + + + </div> + </div> + <button class="btn btn-default" ng-click="submitOpenRcFile()" style="margin-bottom:20px;"> + <div ng-if="!showloading">submit</div> + <img src="images/loading2.gif" width="25" height="25" ng-if="showloading" /> + </button> + + </div> + + </div> + <div data-title="Upload File" bs-pane> + <div style="margin-top:20px;height:405px;"> + <button class="btn btn-default" style="margin-bottom:20px;" ngf-select="uploadFiles($file, $invalidFiles)" ngf-max-size="5MB"> + <div ng-show="!loadingOPENrc">Upload</div> + <img src="images/loading2.gif" width="25" height="25" ng-if="loadingOPENrc" /> + </button> + + <!--<div ng-show="displayOpenrcFile!=null || displayOpenrcFile!=undefined"> + {{displayOpenrcFile.name}} last modified: {{filelastModified}} + </div>--> + </div> + </div> + </div> + + + + </div> + + + </div> + +</div> +<toaster-container></toaster-container> + +<style> + .form-control { + border-radius: 5px; + width: 200px; + margin-bottom: 10px; + } + + .uploadbutton { + background-color: #007ACC; + color: #fff; + border: 0px; + border-radius: 5px; + height: 27px; + } + + .edit-title { + border: 0px; + background-color: #ffffff; + margin-bottom: 5px; + font-size: 12px; + } + + .null-edit-title { + border: 1px solid #e5e6e7; + border-radius: 5px; + margin-bottom: 3px; + } + + .item-info { + display: flex; + flex-direction: row; + } + + .delete-img { + width: 15px; + height: 15px; + opacity: 0.8; + margin-left: -10px; + margin-top: -3px; + cursor: pointer; + } + + .nextButton { + margin-top: 30px; + border: none; + border-radius: 5px; + padding: 6px; + background-color: #339933; + color: #ffffff; + text-align: center; + /* margin-left: 300px; */ + } +</style> diff --git a/gui/app/views/environmentList.html b/gui/app/views/environmentList.html new file mode 100644 index 000000000..29273a724 --- /dev/null +++ b/gui/app/views/environmentList.html @@ -0,0 +1,155 @@ +<div class="content"> + + <!--environmentList--> + <i class="fa fa-arrow-left fa-1x" aria-hidden="true" style="color: #999;cursor:pointer" ng-click="goBack()">Back</i> + + <div> + + <h3>Environments + <button class="btn btn-default btn-sm" style="margin-left:30px;display:inline" ng-click="openEnvironmentDialog()">Add</button> + </h3> + <hr/> + + <!--<div ng-repeat="env in environmentList"> + {{env.name}} + </div>--> + <div dw-loading="key" dw-loading-options="{text:'loading'}"> + <div style="display:flex;flex-direction:row;justify-content:space-between;padding:8px;border-top: 1px solid #e9ecec;background-color: #f9f9f9;"> + <div style="font-weight:600">Name</div> + <div style="font-weight:600;margin-right:80px;">Action</div> + + </div> + + <div dir-paginate="env in environmentList | orderBy:'-id' | itemsPerPage: 10 "> + <div style="display:flex;flex-direction:row;justify-content:space-between;padding:8px;border-top: 1px solid #e9ecec;"> + <div> <a style="color:#e95420" ng-click="gotoDetail('false',env.uuid)">{{env.name}}</a></div> + <div> + + <!-- <button class="btn btn-default btn-sm" ng-click="openDeleteEnv(env.uuid,'environment')">Delete</button> --> + + <div class="btn-group" uib-dropdown is-open="status.isopen" style="margin-right:60px;"> + <button id="single-button" type="button" class="btn btn-default btn-sm" uib-dropdown-toggle> + Modify <span class="caret"></span> + </button> + <ul class="dropdown-menu" uib-dropdown-menu role="menu" aria-labelledby="single-button"> + <li role="menuitem"><a ng-click="openDeleteEnv(env.uuid,'environment')">Delete</a></li> + <!-- <li role="menuitem"><a href="#">Another action</a></li> + <li role="menuitem"><a href="#">Something else here</a></li> + <li class="divider"></li> + <li role="menuitem"><a href="#">Separated link</a></li> --> + </ul> + </div> + </div> + + + + </div> + + </div> + <center> + <dir-pagination-controls></dir-pagination-controls> + </center> + </div> + + + + + + + + </div> + + + + +</div> + +<toaster-container></toaster-container> + +<style> + .form-control { + border-radius: 5px; + width: 300px; + margin-bottom: 10px; + } + + .uploadbutton { + background-color: #007ACC; + color: #fff; + border: 0px; + border-radius: 5px; + height: 27px; + } + + .edit-title { + border: 0px; + background-color: #ffffff; + margin-bottom: 5px; + } + + .null-edit-title { + border: 1px solid #e5e6e7; + border-radius: 5px; + margin-bottom: 3px; + } + + .item-info { + display: flex; + flex-direction: row; + } + + .delete-img { + width: 19px; + height: 19px; + opacity: 0.8; + margin-left: 5px; + margin-top: 4px; + cursor: pointer; + } + + .deepColor { + background-color: #f9f9f9; + } + + .nextButton { + margin-top: 30px; + border: none; + border-radius: 5px; + padding: 6px; + background-color: #339933; + color: #ffffff; + text-align: center; + /* margin-left: 300px; */ + } + + .bs-sidenav { + margin-top: 40px; + margin-bottom: 20px; + width: 124px; + } + + .nav { + margin-bottom: 0; + padding-left: 0; + list-style: none; + } + + .nav>li { + position: relative; + display: block; + } + + li { + display: list-item; + text-align: -webkit-match-parent; + } + + a { + cursor: pointer; + } + + a.active { + background-color: #EEEEEE; + border-radius: 5px; + } +</style> diff --git a/gui/app/views/layout/footer.html b/gui/app/views/layout/footer.html new file mode 100644 index 000000000..cfdf74af3 --- /dev/null +++ b/gui/app/views/layout/footer.html @@ -0,0 +1,5 @@ +<div class="footer"> + <div class="container"> + <p></p> + </div> +</div>
\ No newline at end of file diff --git a/gui/app/views/layout/header.html b/gui/app/views/layout/header.html new file mode 100644 index 000000000..033322a62 --- /dev/null +++ b/gui/app/views/layout/header.html @@ -0,0 +1,43 @@ +<div class="header"> + <div class="navbar navbar-default" role="navigation"> + <div> + <div class="navbar-header"> + + + + <a class="navbar-brand" href="#/">Yardstick</a> + </div> + + + </div> + </div> +</div> +</div> + +<style> + .header { + position: fixed; + top: 0px; + width: 100%; + /*box-shadow: 3px 2px 5px #888888;*/ + z-index: 9; + } + + .navbar { + position: relative; + min-height: 50px; + margin-bottom: 0px; + border: none; + /* border: 1px solid transparent; */ + } + + .navbar { + border-radius: 0px; + background-color: #e95420; + color: #fff; + } + + .navbar-default .navbar-brand { + color: #fff; + } +</style> diff --git a/gui/app/views/layout/sideNav.html b/gui/app/views/layout/sideNav.html new file mode 100644 index 000000000..42dcbbc6e --- /dev/null +++ b/gui/app/views/layout/sideNav.html @@ -0,0 +1,141 @@ +<div class="naviSide"> + + + + + <ul class="nav bs-sidenav"> + + <div class="panel-group " role="tablist " aria-multiselectable="true " bs-collapse style="margin-bottom:0px; "> + <div class="panel panel-default "> + <div class="panel-heading " role="tab "> + <h4 class="panel-title "> + <a bs-collapse-toggle style=" text-decoration: none;" ng-click="gotoProject();"> + Project + </a> + </h4> + + </div> + + </div> + </div> + <div class="panel-group" role="tablist" aria-multiselectable="true" bs-collapse style="margin-bottom:0px;" ng-model="activeStatus"> + <div class="panel panel-default"> + <div class="panel-heading" role="tab"> + <h4 class="panel-title"> + <a bs-collapse-toggle style=" text-decoration: none;"> + <div style="display:inline;" ng-click="gotoEnviron()">Environment </div> + <i class="fa fa-sort-asc" aria-hidden="true" style="margin-left: 71px;display:inline" ng-show="activeStatus==0"></i> + <i class="fa fa-sort-desc" aria-hidden="true" style="margin-left: 71px;display:inline" ng-show="activeStatus==-1"></i> + </a> + </h4> + </div> + <div class="panel-collapse" role="tabpanel" bs-collapse-target> + <div class="panel-body" style="border-top: 2px solid grey;text-align: right;cursor:pointer" ng-click="gotoOpenrcPage()" ng-class="{active:$state.includes('app.environmentDetail')}"> + Openrc + </div> + <div class="panel-body " style="border:none;text-align: right;cursor:pointer" ng-click="gotoUploadPage()" ng-class="{active:$state.includes('app.uploadImage')}"> + Image + </div> + <div class="panel-body " style="border:none;text-align: right;cursor:pointer" ng-click="gotoPodPage()" ng-class="{active:$state.includes('app.podUpload')}"> + Pod File + </div> + <div class="panel-body " style="border:none;text-align: right;cursor:pointer" ng-click="gotoContainerPage()" ng-class="{active:$state.includes('app.container')}"> + Container + </div> + <div class="panel-body " style="border:none;text-align: right;"> + Others + </div> + </div> + </div> + </div> + + <div class="panel-group " role="tablist " aria-multiselectable="true " bs-collapse style="margin-bottom:0px; "> + <div class="panel panel-default "> + <div class="panel-heading " role="tab "> + <h4 class="panel-title "> + <a bs-collapse-toggle style=" text-decoration: none;" ng-click="gotoTestcase()"> + Test Case + </a> + </h4> + + </div> + + </div> + </div> + + <div class="panel-group " role="tablist " aria-multiselectable="true " bs-collapse style="margin-bottom:0px; "> + <div class="panel panel-default "> + <div class="panel-heading " role="tab "> + <h4 class="panel-title "> + <a bs-collapse-toggle style=" text-decoration: none;" ng-click="gotoSuite()"> + Test Suite + </a> + </h4> + + </div> + + </div> + </div> + + + + </ul> + + + + + +</div> + + +<style> + .bs-sidenav { + margin-top: 40px; + margin-bottom: 20px; + width: 124px; + } + + .naviSide { + height: 150%; + } + + .nav { + margin-bottom: 0; + padding-left: 0; + list-style: none; + } + + .nav>li { + position: relative; + display: block; + } + + li { + display: list-item; + text-align: -webkit-match-parent; + } + + a { + cursor: pointer; + } + + a.active { + background-color: #EEEEEE; + border-radius: 5px; + width: 165px; + } + /* + a:hover { + width: 165px; + }*/ + + .nav>li>a:hover, + .nav>li>a:focus { + text-decoration: underline; + background-color: transparent; + } + + .active.panel-body { + background-color: #dfe3e4; + } +</style> diff --git a/gui/app/views/layout/sideNav2.html b/gui/app/views/layout/sideNav2.html new file mode 100644 index 000000000..104a9c6cf --- /dev/null +++ b/gui/app/views/layout/sideNav2.html @@ -0,0 +1,108 @@ +<div class="naviSide"> + + + <ul class="nav bs-sidenav"> + + <div class="panel-group " role="tablist " aria-multiselectable="true " bs-collapse style="margin-bottom:0px; "> + <div class="panel panel-default "> + <div class="panel-heading " role="tab "> + <h4 class="panel-title "> + <a bs-collapse-toggle style=" text-decoration: none;" ng-click="gotoProject();"> + Project + </a> + </h4> + + </div> + + </div> + </div> + <div class="panel-group" role="tablist" aria-multiselectable="false" bs-collapse style="margin-bottom:0px;"> + <div class="panel panel-default"> + <div class="panel-heading" role="tab"> + <h4 class="panel-title"> + <a bs-collapse-toggle style=" text-decoration: none;"> + <div style="display:inline;" ng-click="gotoEnviron()">Environment </div> + <!--<i class="fa fa-sort-asc" aria-hidden="true" style="margin-left: 71px;display:inline"></i>--> + </a> + </h4> + </div> + + </div> + </div> + + <div class="panel-group " role="tablist " aria-multiselectable="true " bs-collapse style="margin-bottom:0px; "> + <div class="panel panel-default "> + <div class="panel-heading " role="tab "> + <h4 class="panel-title "> + <a bs-collapse-toggle style=" text-decoration: none;" ng-click="gotoTestcase()"> + Test Case + </a> + </h4> + + </div> + + </div> + </div> + + <div class="panel-group " role="tablist " aria-multiselectable="true " bs-collapse style="margin-bottom:0px; "> + <div class="panel panel-default "> + <div class="panel-heading " role="tab "> + <h4 class="panel-title "> + <a bs-collapse-toggle style=" text-decoration: none;" ng-click="gotoSuite()"> + Test Suite + </a> + </h4> + + </div> + + </div> + </div> + + + + </ul> + +</div> + +<style> + .bs-sidenav { + margin-top: 40px; + margin-bottom: 20px; + width: 124px; + } + + .nav { + margin-bottom: 0; + padding-left: 0; + list-style: none; + } + + .nav>li { + position: relative; + display: block; + } + + li { + display: list-item; + text-align: -webkit-match-parent; + } + + a { + cursor: pointer; + } + + a.active { + background-color: #EEEEEE; + border-radius: 5px; + width: 165px; + } + /*a:hover { + width: 165px; + }*/ + + .nav>li>a:hover, + .nav>li>a:focus { + text-decoration: underline; + background-color: transparent; + } +</style> diff --git a/gui/app/views/main.html b/gui/app/views/main.html new file mode 100644 index 000000000..d5f7a3af3 --- /dev/null +++ b/gui/app/views/main.html @@ -0,0 +1,174 @@ +<div> + <div ng-include="'views/layout/header.html'"></div> +</div> +<div ng-include="'views/layout/sideNav.html'"></div> + + +<div style="margin-top:80px;margin-left:100px;display:flex;flex-direction:row"> + <!--<div ncy-breadcrumb></div>--> + <div> + <ol class="progressDefine"> + <li data-step="1" ng-click="gotoProject();" style="cursor:pointer" ng-class="{'is-complete':projectShow}"> + Project + </li> + <li data-step="2" ng-class="{'is-complete':taskShow}"> + Task + </li> + + <li data-step="3" ng-class="{'progressDefine__last':reportShow}"> + Reporting + </li> + + </ol> + </div> + + +</div> + + + + + + + + + +<div ui-view></div> + + + +<style> + .stepsContent { + display: flex; + flex-direction: row; + justify-content: space-around; + margin-left: 120px; + margin-top: 100px; + } + + .stepItem { + display: flex; + flex-direction: column; + } + + .nextButton { + margin-left: 500px; + } + + .progressDefine { + list-style: none; + margin: 0; + padding: 0; + display: table; + table-layout: fixed; + width: 100%; + color: #849397; + } + + .progressDefine>li { + position: relative; + display: table-cell; + text-align: center; + font-size: 0.8em; + } + + .progressDefine>li:before { + content: attr(data-step); + display: block; + margin: 0 auto; + background: #DFE3E4; + width: 3em; + height: 3em; + text-align: center; + margin-bottom: 0.25em; + line-height: 3em; + border-radius: 100%; + position: relative; + z-index: 5; + } + + .progressDefine>li:after { + content: ''; + position: absolute; + display: block; + background: #DFE3E4; + width: 100%; + height: 0.5em; + top: 1.25em; + left: 50%; + margin-left: 1.5em\9; + z-index: -1; + } + + .progressDefine>li:last-child:after { + display: none; + } + + .progressDefine>li.is-complete { + color: #e95420; + } + + .progressDefine>li.is-complete:before, + .progressDefine>li.is-complete:after { + color: #FFF; + background: #e95420; + } + + .progressDefine>li.is-active { + color: #3498DB; + } + + .progressDefine>li.is-active:before { + color: #FFF; + background: #3498DB; + } + /** + * Needed for IE8 + */ + + .progressDefine__last:after { + display: none !important; + } + /** + * Size Extensions + */ + + .progressDefine--medium { + font-size: 1.5em; + } + + .progressDefine--large { + font-size: 2em; + } + /** + * Some Generic Stylings + */ + + *, + *:after, + *:before { + box-sizing: border-box; + } + + h1 { + margin-bottom: 1.5em; + } + + .progressDefine { + margin-bottom: 3em; + } + + a { + color: #3498DB; + text-decoration: none; + } + + a:hover { + text-decoration: underline; + } + /* + body { + text-align: center; + color: #444; + }*/ +</style> diff --git a/gui/app/views/main2.html b/gui/app/views/main2.html new file mode 100644 index 000000000..661d604c6 --- /dev/null +++ b/gui/app/views/main2.html @@ -0,0 +1,174 @@ +<div> + <div ng-include="'views/layout/header.html'"></div> +</div> +<div ng-include="'views/layout/sideNav2.html'"></div> + + +<div style="margin-top:80px;margin-left:220px;"> + <!--<div ncy-breadcrumb></div>--> + <div> + <ol class="progressDefine"> + <li data-step="1" ng-click="gotoProject();" style="cursor:pointer" ng-class="{'is-complete':projectShow}"> + Project + </li> + <li data-step="2" ng-class="{'is-complete':taskShow}"> + Task + </li> + + <li data-step="3" ng-class="{'is-complete':reportShow}"> + Reporting + </li> + + </ol> + </div> + + +</div> + + + + + + + + + +<div ui-view></div> + + + +<style> + .stepsContent { + display: flex; + flex-direction: row; + justify-content: space-around; + margin-left: 120px; + margin-top: 100px; + } + + .stepItem { + display: flex; + flex-direction: column; + } + + .nextButton { + margin-left: 500px; + } + + .progressDefine { + list-style: none; + margin: 0; + padding: 0; + display: table; + table-layout: fixed; + width: 100%; + color: #849397; + } + + .progressDefine>li { + position: relative; + display: table-cell; + text-align: center; + font-size: 0.8em; + } + + .progressDefine>li:before { + content: attr(data-step); + display: block; + margin: 0 auto; + background: #DFE3E4; + width: 3em; + height: 3em; + text-align: center; + margin-bottom: 0.25em; + line-height: 3em; + border-radius: 100%; + position: relative; + z-index: 5; + } + + .progressDefine>li:after { + content: ''; + position: absolute; + display: block; + background: #DFE3E4; + width: 100%; + height: 0.5em; + top: 1.25em; + left: 50%; + margin-left: 1.5em\9; + z-index: -1; + } + + .progressDefine>li:last-child:after { + display: none; + } + + .progressDefine>li.is-complete { + color: #e95420; + } + + .progressDefine>li.is-complete:before, + .progressDefine>li.is-complete:after { + color: #FFF; + background: #e95420; + } + + .progressDefine>li.is-active { + color: #3498DB; + } + + .progressDefine>li.is-active:before { + color: #FFF; + background: #3498DB; + } + /** + * Needed for IE8 + */ + + .progressDefine__last:after { + display: none !important; + } + /** + * Size Extensions + */ + + .progressDefine--medium { + font-size: 1.5em; + } + + .progressDefine--large { + font-size: 2em; + } + /** + * Some Generic Stylings + */ + + *, + *:after, + *:before { + box-sizing: border-box; + } + + h1 { + margin-bottom: 1.5em; + } + + .progressDefine { + margin-bottom: 3em; + } + + a { + color: #3498DB; + text-decoration: none; + } + + a:hover { + text-decoration: underline; + } + /* + body { + text-align: center; + color: #444; + }*/ +</style> diff --git a/gui/app/views/modal/chooseContainer.html b/gui/app/views/modal/chooseContainer.html new file mode 100644 index 000000000..4b857b22f --- /dev/null +++ b/gui/app/views/modal/chooseContainer.html @@ -0,0 +1,15 @@ +<h3>Choose Containers</h3> +<hr/> + + + +<style> + select { + height: 30px; + border-radius: 5px; + border: 1px solid #e8e8e8; + width: 135px; + margin-top: 20px; + margin-left: 20px; + } +</style>
\ No newline at end of file diff --git a/gui/app/views/modal/deleteConfirm.html b/gui/app/views/modal/deleteConfirm.html new file mode 100644 index 000000000..1659b884b --- /dev/null +++ b/gui/app/views/modal/deleteConfirm.html @@ -0,0 +1,19 @@ +<div>Confirm delete {{deleteName}} ?</div> + +<div style="display:flex;flex-direction:row; margin-left: 150px;margin-top: 30px;"> + <button class="btn btn-default" ng-click="deleteEnv()" ng-show="deleteName=='environment'">Confirm</button> + <button class="btn btn-default" ng-click="deleteProject()" ng-show="deleteName=='project'">Confirm</button> + <button class="btn btn-default" ng-click="deleteTask()" ng-show="deleteName=='task'">Confirm</button> + <button class="btn btn-default" ng-click="deleteTestCase()" ng-show="deleteName=='test case'">Confirm</button> + <button class="btn btn-default" ng-click="deleteSuite()" ng-show="deleteName=='test suite'">Confirm</button> + <button class="btn btn-default" ng-click="deleteContainer()" ng-show="deleteName=='container'">Confirm</button> + <button class="btn btn-default" ng-click="deletePod()" ng-show="deleteName=='pod'">Confirm</button> + <button class="btn btn-default" ng-click="deleteOpenRc()" ng-show="deleteName=='openrc'">Confirm</button> + + + + + + <button class="btn btn-default" style="margin-left:10px;" ng-click="closeThisDialog()">Cancel</button> + +</div>
\ No newline at end of file diff --git a/gui/app/views/modal/environmentDialog.html b/gui/app/views/modal/environmentDialog.html new file mode 100644 index 000000000..a5b88d240 --- /dev/null +++ b/gui/app/views/modal/environmentDialog.html @@ -0,0 +1,330 @@ +<!--environment input dialog--> + +<div> + <div ng-if="uuidEnv==null"> + <h4>Environment Name</h4> + <input type="text" ng-model="name" style="width:300px;" /> + + <div style="text-align:center;margin-top:20px;"> + <button class="btn btn-default" ng-disabled=" name==null || name==''" ng-click="addEnvironment(name)">Create</button> + </div> + </div> + + + <div style="display:flex;flex-direction:row;" ng-if="uuidEnv!=null&&showImage==null"> + <div> + <h3> {{name}} -- Openrc + <!--<button class="btn btn-default" style="float:right" ng-click="goNext()">Next</button>--> + <button class="btn btn-default" ng-click="goToImage()" style="margin-bottom:20px;float:right" ng-disabled="showNextOpenRc==null && showNextOpenRc==null "> + Next + </button> + </h3> + <!--<p>In this process, you can input your define openrc config or upload a openrc file</p>--> + <div> + + <!--<button style="display:inline;" class="btn btn-default" ng-click="addEnvironment()" ng-show="uuid==null">Add Name</button>--> + </div> + + <hr/> + <div bs-tabs style="width:750px;"> + <div data-title="Detail" bs-pane ng-if="openrcInfo.openrc!=null"> + + <h4> + You have already set up the openrc parameters + </h4> + <hr /> + <div ng-repeat="(key,value) in openrcInfo.openrc"> + <nobr> + <font style="font-weight:600;font-size:14px;">{{key}} : </font> + <font style="font-size:14px;">{{value}}</font> + </nobr> + </div> + + </div> + <div data-title="Update" bs-pane> + + <div style="margin-top:20px;"> + <button class="btn btn-default" ng-click="addInfo()" style="margin-bottom:20px;">Add</button> + <div style="height:300px;width:800px;display:flex;flex-direction:column;flex-wrap:wrap;margin-left:5px;overflow-x:scroll"> + <div ng-repeat="info in envInfo"> + <!--<div> {{info.name}}</div>--> + + <input class="edit-title" ng-model="info.name" ng-class="{'null-edit-title':info.name==null}" ng-attr-type="{{info.name.indexOf('PASSWORD')>-1 ? password : text}}" /> + + <div class="item-info"> + <input class="form-control" type="text" ng-model="info.value" /> + <!--<button class="delete-button" ng-click="deleteEnvItem($index)">delete</button>--> + <img src="images/close.png" ng-click="deleteEnvItem($index)" class="delete-img" /> + </div> + + + + </div> + </div> + <button class="btn btn-default" ng-click="submitOpenRcFile();" style="margin-bottom:20px;"> + <div ng-if="!showloading">Submit</div> + <img src="images/loading2.gif" width="25" height="25" ng-if="showloading" /> + </button> + + + </div> + + </div> + <div data-title="Upload File" bs-pane> + <div style="margin-top:20px;height:405px;"> + <button class="btn btn-default" style="margin-bottom:20px;" ngf-select="uploadFiles($file, $invalidFiles);" ngf-max-size="5MB"> + <div ng-show="!loadingOPENrc">Upload</div> + <img src="images/loading2.gif" width="25" height="25" ng-if="loadingOPENrc" /> + </button> + <!--<button class="btn btn-default" style="margin-bottom:20px;" ng-disabled="showNextOpenRc==null" ng-click="goToImage()"> + Next + </button>--> + + <!--<div ng-if="displayOpenrcFile!=null || displayOpenrcFile!=undefined"> + {{displayOpenrcFile.name}} last modified: {{filelastModified}} + </div>--> + </div> + </div> + </div> + + + + </div> + + + </div> + + <div ng-if="showImage==1&&showPod==null"> + <div style="display:flex;flex-direction:row;"> + <div style="width:750px;"> + + <h3>{{name}} -- Image + + <button class="btn btn-default" ng-click="goToPod()" ng-disabled="showNextPod==null" style="float:right"> + Next + </button> + <button class="btn btn-default" ng-click="goToPodPrev()" style="margin-right:5px;float:right"> + Previous + </button> + + </h3> + <!--<p>In this process, you can input your define openrc config or upload a openrc file</p>--> + + <hr/> + + <button class="btn btn-default" ng-click="uploadImage()"> + <div ng-if="!showloading">Load Image</div> + <img src="images/loading2.gif" width="25" height="25" ng-if="showloading" /> + + </button> + + <i class="fa fa-check" aria-hidden="true" style="margin-top:34px;margin-left:5px;color: #2ecc71;" ng-show="imageStatus==1&&showImageStatus==1">done</i> + <i class="fa fa-spinner" aria-hidden="true" style="margin-top:34px;margin-left:5px;color: #2ecc71;" ng-show="imageStatus==0&&showImageStatus==1">loading</i> + <i class="fa fa-exclamation-triangle" aria-hidden="true" style="margin-top:34px;margin-left:5px;color: red;" ng-show="imageStatus==2&&showImageStatus==1">error</i> + + + <!--<button class="btn btn-default" ng-click="goToPod()" ng-disabled="showNextPod==null"> + Next + </button>--> + <hr> + <h4>Current Images</h4> + + <div> + <table class="table table-striped"> + + <tr> + <th>name</th> + <th>size</th> + <th>status</th> + <th>time</th> + </tr> + <tr ng-repeat="image in imageListData"> + <td>{{image.name}}</td> + <td>{{image.size/1024}} mb</td> + <td>{{image.status}}</td> + <td>{{image.time}}</td> + + </tr> + + + + </table> + </div> + + + </div> + + + </div> + </div> + + <div ng-if="showPod==1&&showContainer==null"> + <div style="display:flex;flex-direction:row;"> + <div style="width:750px;"> + + + <h3>{{name}} -- Pod File + <div style="float:right"> + <button class="btn btn-default" ng-click="skipPodPrev()">Previous</button> + <button class="btn btn-default" ng-click="skipPod()" ng-show="podData==null">Skip</button> + <button class="btn btn-default" ng-click="skipPod()" ng-show="podData!=null">Next</button> + + </div> + + </h3> + + <hr/> + + <button class="btn btn-default" ngf-select="uploadFilesPod($file, $invalidFiles)" ngf-max-size="5MB"> + <div ng-show="!loadingOPENrc">Upload</div> + <img src="images/loading2.gif" width="25" height="25" ng-if="loadingOPENrc" /> + </button> + + + <hr/> + + <div> + <h4>Current Pod Configuration</h4> + <table class="table table-striped"> + + <tr> + <th>ip</th> + <th>name</th> + <th>password</th> + <th>role</th> + <th>user</th> + </tr> + <tr ng-repeat="pod in podData.pod.nodes"> + <td>{{pod.ip}}</td> + <td>{{pod.name}}</td> + <td>{{pod.password}}</td> + <td>{{pod.role}}</td> + <td>{{pod.user}}</td> + + </tr> + <tr ng-show="podData.length==0"> + <td>no data</td> + + </tr> + + + + </table> + </div> + + + + + + + + + + + </div> + + + </div> + + </div> + + <div ng-if="showContainer!=null"> + <div style="display:flex;flex-direction:row;"> + <div style="width:750px;"> + + <h3>{{name}} -- Container + <div style="float:right"> + <button class="btn btn-default" ng-click="skipContainerPrev()">Previous</button> + <button class="btn btn-default" ng-click="skipContainer()" ng-show="ifskipOrClose!=1"> + Skip + </button> + <button class="btn btn-default" ng-click="closeThisDialog(); getEnvironmentList();" ng-show="ifskipOrClose==1"> + Close + </button> + </div> + <!--<button class="btn btn-default" style="float:right">Go Next</button>--> + </h3> + <!--<p>In this process, you can input your define openrc config or upload a openrc file</p>--> + + <hr/> + + <select ng-model="selectContainer" data-ng-options="container as container.name for container in containerList"> + <option value="">Choose...</option> + </select> + + <button class="btn btn-default" ng-click="createContainer(selectContainer)" ng-disabled="selectContainer==null"> + <div ng-show="!showloading">Create</div> + <img src="images/loading2.gif" width="25" height="25" ng-if="showloading" /> + </button> + <!--<button class="btn btn-default" ng-click="skipContainer()" ng-show="ifskipOrClose!=1"> + Skip + </button> + <button class="btn btn-default" ng-click="closeThisDialog(); getEnvironmentList();" ng-show="ifskipOrClose==1"> + Close + </button>--> + + <hr/> + + <div> + <h4>Current Contain</h4> + <table class="table table-striped"> + + <tr> + <th>name</th> + <th>status</th> + <th>time</th> + + </tr> + <tr ng-repeat="con in displayContainerInfo"> + <td>{{con.name}}</td> + <td>{{con.status}}</td> + <td>{{con.time}}</td> + + + </tr> + + + + </table> + </div> + + + + + + + + + + + </div> + + + </div> + + </div> + + + + + + +</div> + + +<style> + input { + border-radius: 10px; + border: 1px solid #eeeeee; + width: 100%; + } + + select { + height: 30px; + border-radius: 5px; + border: 1px solid #e8e8e8; + width: 135px; + margin-top: 20px; + margin-left: 20px; + } +</style> diff --git a/gui/app/views/modal/projectCreate.html b/gui/app/views/modal/projectCreate.html new file mode 100644 index 000000000..74839e798 --- /dev/null +++ b/gui/app/views/modal/projectCreate.html @@ -0,0 +1,21 @@ +<div> + + <h4>Enter Project Name</h4> + <input type="text" ng-model="name" /> + + <div style="text-align:center;margin-top:20px;"> + <button class="btn btn-default" ng-disabled=" name==null || name==''" ng-click="createName(name)">Create</button> + </div> + + + +</div> + + +<style> + input { + border-radius: 10px; + border: 1px solid #eeeeee; + width: 100%; + } +</style> diff --git a/gui/app/views/modal/suiteName.html b/gui/app/views/modal/suiteName.html new file mode 100644 index 000000000..981d24210 --- /dev/null +++ b/gui/app/views/modal/suiteName.html @@ -0,0 +1,18 @@ +<h4>Enter Suite Name</h4> +<hr/> You have choose: +<div ng-repeat="selected in suitReconstructList">{{selected}}</div> +<hr/> +<input type="text" ng-model="name" /> + +<div style="text-align:center;margin-top:20px;"> + <button class="btn btn-default" ng-disabled="testsuiteList.length==0 || name==null || name==''" ng-click="createSuite(name)">Create</button> +</div> + + +<style> + input { + border-radius: 10px; + border: 1px solid #eeeeee; + width: 100%; + } +</style> diff --git a/gui/app/views/modal/taskCreate.html b/gui/app/views/modal/taskCreate.html new file mode 100644 index 000000000..e7812cf2b --- /dev/null +++ b/gui/app/views/modal/taskCreate.html @@ -0,0 +1,134 @@ + +<h4>Create Task</h4> +<hr/> +<div> + <div style="display:inline">Name <input type="text" ng-model="name" style="width:200px" /></div> + <button style="display:inline" class="btn btn-default" ng-disabled="name==null || name==''" ng-click="createTask(name)" ng-show="newUUID==null">Create</button> +</div> +<hr/> + +<div bs-tabs ng-show="newUUID!=null"> + <div data-title="Environment" bs-pane> + <div style="margin-top:10px" ng-show="displayEnvName!=null"> + <div style="display:inline">Choose Environment : {{displayEnvName}}</div> + <button class="btn btn-default" style="display:inline;float:right;margin-right:10px;margin-top: -4px;" ng-click="addEnvToTask()">confirm</button> + </div> + <hr /> + <div dir-paginate="env in environmentList | orderBy:'-id' | itemsPerPage: 10 "> + <div style="display:flex;flex-direction:row;justify-content:space-between;padding:8px;border-top: 1px solid #e9ecec;" ng-class="{deepColor: $index%2==0}"> + <div> {{env.name}}</div> + <!--<button class="btn btn-default btn-sm" ng-click="gotoDetail('false',env.uuid)">detail</button>--> + <img src="images/checkyes.png" style="height:18px;cursor:pointer" ng-click="constructTestSuit(env.uuid,env.name)" ng-show="selectEnv==env.uuid" /> + <img src="images/checkno.png" style="height:18px;cursor:pointer" ng-click="constructTestSuit(env.uuid,env.name)" ng-show="selectEnv!=env.uuid" /> + + </div> + <!--<hr style="margin-top:5px;margin-bottom:5px;" />--> + </div> + <center> + <dir-pagination-controls></dir-pagination-controls> + </center> + + </div> + <div data-title="Content" bs-pane> + <div style="display:flex;flex-direction:row"> + <div style="margin-top:20px;">Source of Content</div> + + + <select ng-model="selectType" ng-change="triggerContent(selectType)" data-ng-options="blisterPackTemplate as blisterPackTemplate.name for blisterPackTemplate in blisterPackTemplates"> + <option value="">Choose...</option> + </select> + + </div> + <div style="margin-top:10px" ng-show="selectCase!=null"> + <div style="display:inline">Choose Source: {{selectCase}}</div> + <button class="btn btn-default" style="display:inline;float:right;margin-right:10px;margin-top: -4px;" ng-click="confirmAddCaseOrSuite(contentInfo)">Confirm</button> + <button class="btn btn-default" style="display:inline;float:right;margin-right:10px;margin-top: -4px;" ng-click="getTestDeatil()">Edit</button> + </div> + <hr/> + + <div ng-show="displayTable==true"> + <div ng-show="testcaselist.testcases.length!=0 && selectType.name=='Test Case'"> + <div dir-paginate="test in testcaselist.testcases | itemsPerPage: 10" pagination-id="testcase"> + <div style="display:flex;flex-direction:row;justify-content:space-between;padding:8px;border-top: 1px solid #e9ecec;" ng-class="{deepColor: $index%2==0}"> + <div> {{test.Name}}</div> + <div style="font-size:10px;">{{test.Description}}</div> + <img src="images/checkyes.png" style="height:18px;cursor:pointer" ng-click="constructTestCase(test.Name)" ng-show="selectCase==test.Name" /> + <img src="images/checkno.png" style="height:18px;cursor:pointer" ng-click="constructTestCase(test.Name)" ng-show="selectCase!=test.Name" /> + + </div> + <!--<hr style="margin-top:5px;margin-bottom:5px;" />--> + </div> + <center> + <dir-pagination-controls pagination-id="testcase"></dir-pagination-controls> + </center> + </div> + + <div ng-show="testsuitlist.length!=0 && selectType.name=='Test Suite'"> + <div dir-paginate="suite in testsuitlist | itemsPerPage: 10" pagination-id="testsuite"> + <div style="display:flex;flex-direction:row;justify-content:space-between;padding:8px;border-top: 1px solid #e9ecec;" ng-class="{deepColor: $index%2==0}"> + <div> {{suite}}</div> + + <img src="images/checkyes.png" style="height:18px;cursor:pointer" ng-click="constructTestCase(suite)" ng-show="selectCase==suite" /> + <img src="images/checkno.png" style="height:18px;cursor:pointer" ng-click="constructTestCase(suite)" ng-show="selectCase!=suite" /> + + </div> + <!--<hr style="margin-top:5px;margin-bottom:5px;" />--> + </div> + <center> + <dir-pagination-controls pagination-id="testsuite"></dir-pagination-controls> + </center> + </div> + </div> + + <div ng-show="displayTable==false"> + <textarea ng-model="contentInfo" spellcheck="false"> + + + </textarea> + + + </div> + + + + + </div> +</div> + +<div style="text-align:center;margin-top:20px;"> + <button class="btn btn-default" ng-click="closeThisDialog()" ng-disabled="newUUID===null || ifHasEnv!=true || (ifHasCase!=true && ifHasSuite!=true)">Close</button> + <button class="btn btn-default" ng-disabled="newUUID===null || ifHasEnv!=true || (ifHasCase!=true && ifHasSuite!=true)" ng-click="runAtask(newUUID)">Run</button> +</div> + + +<style> + input { + border-radius: 10px; + border: 1px solid #eeeeee; + width: 100%; + } + + .deepColor { + background-color: #f9f9f9; + } + + select { + height: 30px; + border-radius: 5px; + border: 1px solid #e8e8e8; + width: 135px; + margin-top: 20px; + margin-left: 20px; + } + + textarea { + width: 100%; + height: 400px; + border-radius: 5px; + border: 1px solid #e8e8e8; + } + + .deepColor { + background-color: #f9f9f9; + } +</style> diff --git a/gui/app/views/podupload.html b/gui/app/views/podupload.html new file mode 100644 index 000000000..99e83aca2 --- /dev/null +++ b/gui/app/views/podupload.html @@ -0,0 +1,136 @@ +<!--pod file upload--> + +<div class="content"> + <div style="display:flex;flex-direction:row;"> + <div style="width:750px;"> + <!--<i class="fa fa-arrow-left fa-1x" aria-hidden="true" style="color: #999;cursor:pointer" ng-click="goBack()">Back</i>--> + + + <h3>{{name}} -- Pod File + <button class="btn btn-default" style="float:right" ng-click="goNext()">Next</button> + </h3> + <!--<p>In this process, you can input your define openrc config or upload a openrc file</p>--> + + <hr/> + + <button class="btn btn-default" ngf-select="uploadFiles($file, $invalidFiles)" ngf-max-size="5MB"> + <div ng-show="!loadingOPENrc">Upload</div> + <img src="images/loading2.gif" width="25" height="25" ng-if="loadingOPENrc" /> + </button> + <button class="btn btn-default" ng-click="openDeleteEnv(1,'pod')">Delete</button> + + <!--<div ng-show="displayOpenrcFile!=null || displayOpenrcFile!=undefined ||podData.pod.nodes!=null "> + {{displayOpenrcFile.name}} last modified: {{filelastModified}} + </div>--> + + <hr/> + + <div> + <h4 ng-show="podData.pod.nodes==null">No Pod Configuration</h4> + <div ng-show="podData.pod.nodes!=null"> + <h4>Current Pod Configuration</h4> + <table class="table table-striped"> + + <tr> + <th>ip</th> + <th>name</th> + <th>password</th> + <th>role</th> + <th>user</th> + </tr> + <tr ng-repeat="pod in podData.pod.nodes"> + <td>{{pod.ip}}</td> + <td>{{pod.name}}</td> + <td>{{pod.password}}</td> + <td>{{pod.role}}</td> + <td>{{pod.user}}</td> + + </tr> + + + + </table> + </div> + </div> + + + + + + + + + + + </div> + <!--<div style="margin-top:60px;margin-left:67px;"> + <h3>Openrc parameters</h3> + <div> + You have already set up the openrc parameters + </div> + <div ng-repeat="(key,value) in openrcInfo.openrc"> + <nobr> + <font style="font-weight:600;font-size:15px;">{{key}} : </font> + <font style="font-size:15px;">{{value}}</font> + </nobr> + </div> + </div>--> + + </div> + +</div> +<toaster-container></toaster-container> + +<style> + .form-control { + border-radius: 5px; + width: 200px; + margin-bottom: 10px; + } + + .uploadbutton { + background-color: #007ACC; + color: #fff; + border: 0px; + border-radius: 5px; + height: 27px; + } + + .edit-title { + border: 0px; + background-color: #ffffff; + margin-bottom: 5px; + font-size: 12px; + } + + .null-edit-title { + border: 1px solid #e5e6e7; + border-radius: 5px; + margin-bottom: 3px; + } + + .item-info { + display: flex; + flex-direction: row; + } + + .delete-img { + width: 15px; + height: 15px; + opacity: 0.8; + margin-left: -10px; + margin-top: -3px; + cursor: pointer; + } + + .nextButton { + margin-top: 30px; + border: none; + border-radius: 5px; + padding: 6px; + background-color: #339933; + color: #ffffff; + text-align: center; + /* margin-left: 300px; */ + } +</style> diff --git a/gui/app/views/projectList.html b/gui/app/views/projectList.html new file mode 100644 index 000000000..ea6e63d6b --- /dev/null +++ b/gui/app/views/projectList.html @@ -0,0 +1,57 @@ +<div class="content"> + + <h3>Projects + <button class="btn btn-default btn-sm" style="margin-left:30px;" ng-click="openCreateProject()">Create</button> + </h3> + + <hr/> + + + + <div dw-loading="key" dw-loading-options="{text:'loading'}"> + <div style="display:flex;flex-direction:row;justify-content:space-between;padding:8px;border-top: 1px solid #e9ecec;background-color:#f9f9f9;"> + <div style="font-weight:600">Name</div> + <div style="font-weight:600;margin-right:4px;">Action</div> + + </div> + + <div dir-paginate="project in projectListData | orderBy:'-id' | itemsPerPage: 10 "> + <div style="display:flex;flex-direction:row;justify-content:space-between;padding:8px;border-top: 1px solid #e9ecec;"> + <div> + <a ng-click="gotoDetail(project.uuid)" style="color:#e95420"> {{project.name}}</a> + </div> + <div> + <!-- <button class="btn btn-default btn-sm" ng-click="gotoDetail(project.uuid)">Detail</button> --> + <!--<button class="btn btn-default btn-sm" ng-click="openDeleteEnv(project.uuid,'project')">Delete</button>--> + <div class="btn-group" uib-dropdown is-open="status.isopen" style="margin-right:20px;"> + <button id="single-button" type="button" class="btn btn-default btn-sm" uib-dropdown-toggle> + Modify <span class="caret"></span> + </button> + <ul class="dropdown-menu" uib-dropdown-menu role="menu" aria-labelledby="single-button"> + <li role="menuitem" ng-show="task.status!=0"><a ng-click="openDeleteEnv(project.uuid,'project')">delete</a></li> + + + </ul> + </div> + </div> + + </div> + <!--<hr style="margin-top:5px;margin-bottom:5px;" />--> + </div> + <center> + <dir-pagination-controls></dir-pagination-controls> + </center> + + </div> + + +</div> + +<toaster-container></toaster-container> + + +<style> + .deepColor { + background-color: #f9f9f9; + } +</style> diff --git a/gui/app/views/projectdetail.html b/gui/app/views/projectdetail.html new file mode 100644 index 000000000..ff61c5fed --- /dev/null +++ b/gui/app/views/projectdetail.html @@ -0,0 +1,97 @@ +<div class="content"> + <i class="fa fa-arrow-left fa-1x" aria-hidden="true" style="color: #999;cursor:pointer" ng-click="goBack()">Back</i> + + <h3>Project -- Task + + </h3> + + <hr/> + + <div> + + <h4>{{projectData.name}}</h4> + <h5>{{projectData.time}}</h5> + <hr/> + <h4>Tasks + <button class="btn btn-default btn-sm" style="margin-left:30px;" ng-click="openCreate()">Create</button> + </h4> + <div ng-show="projectData.tasks.length==0">No task in this project</div> + <table class="table " width="100%" dw-loading="key" dw-loading-options="{text:'loading'}"> + <tr style="background-color:#f9f9f9"> + <td style="font-weight:700">Name</td> + <td style="font-weight:700"> Status</td> + <td style="font-weight:700">Action</td> + </tr> + <tr dir-paginate="task in finalTaskListDisplay | orderBy:'-id' | itemsPerPage: 6 " pagination-id="table"> + + <td width="10%"> <a ng-click="gotoDetail(task.uuid)" style="color:#e95420"> {{task.name}} </a></td> + <td width="40%"> + <div class="progree-parent" ng-show="task.status!=2"> + <div class="progree-child" ng-style="{'width':task.stausWidth}"> + </div> + </div> + <div class="progree-parent" ng-show="task.status==2" style="background-color:red"> + <div class="progree-child" style="width:0"> + </div> + </div> + </td> + + + <td width="50%"> + + <div class="btn-group" uib-dropdown is-open="status.isopen" style="margin-right:20px;"> + <button id="single-button" type="button" class="btn btn-default btn-sm" uib-dropdown-toggle> + Modify <span class="caret"></span> + </button> + <ul class="dropdown-menu" uib-dropdown-menu role="menu" aria-labelledby="single-button"> + <li role="menuitem" ng-show="task.status!=0"><a ng-click="runAtaskForTable(task.uuid)">run</a></li> + + <li role="menuitem" ng-show="task.status!=0"><a ng-click="gotoModify(task.uuid)">modify</a></li> + <li role="menuitem" ng-show="task.status!=-1 && task.status!=0"><a ng-click="gotoReport(task.uuid)" style="color:#2ecc71">reporting</a></li> + <li role="menuitem"><a ng-click="openDeleteEnv(task.uuid,'task')">delete</a></li> + + + </ul> + </div> + <!-- <button class="btn btn-default btn-sm" ng-click="runAtask(task.uuid)" ng-disabled="task.status!=-1">run</button> + <button class="btn btn-default btn-sm" ng-click="gotoDetail(task.uuid)">detail</button> + <button class="btn btn-default btn-sm" ng-click="gotoModify(task.uuid)" ng-disabled="task.status==0">modify</button> + <button class="btn btn-default btn-sm" ng-click="gotoReport(task.uuid)" style="color:#2ecc71" ng-disabled="task.status==-1 || task.status==0">reporting</button> + <button class="btn btn-default btn-sm" ng-click="openDeleteEnv(task.uuid,'task')">delete</button> --> + + </td> + + </tr> + </table> + + + + </div> + <center> + <dir-pagination-controls pagination-id="table"></dir-pagination-controls> + </center> + +</div> + + + +</div> + +<toaster-container></toaster-container> + +<style> + .progree-parent { + width: 50%; + background-color: #dfe3e4; + height: 10px; + border-radius: 10px; + } + + .progree-child { + width: 50%; + background-color: #2ecc71; + /* background-color: white; */ + height: 10px; + border-radius: 5px; + } +</style> diff --git a/gui/app/views/report.html b/gui/app/views/report.html new file mode 100644 index 000000000..78ac6a0c9 --- /dev/null +++ b/gui/app/views/report.html @@ -0,0 +1,56 @@ +<div class="content"> + <i class="fa fa-arrow-left fa-1x" aria-hidden="true" style="color: #999;cursor:pointer" ng-click="goBack()">Back</i> + <h3>Yardstick Report </h3> + <hr/> + <div> + + <div>Task ID : {{result.result.task_id}} </div> + <div style="margin-top:5px;">Criteria : + <font style="color:#2ECC71" ng-show="result.result.criteria=='PASS'"> {{result.result.criteria}}</font> + <font style="color:red" ng-show="result.result.criteria=='FAIL'"> {{result.result.criteria}}</font> + </div> + <hr/> + <caption>Information</caption> + <table class="table table-striped"> + <tr> + <th>#</th> + <th>key</th> + <th>value</th> + </tr> + <tbody> + <tr ng-repeat="(key,value) in result.result.info"> + <td>{{$index}}</td> + <td>{{key}}</td> + <td>{{value}}</td> + </tr> + + </tbody> + </table> + <hr/> + + <caption>Test Cases</caption> + <table class="table table-striped"> + <tr> + <th>#</th> + <th>key</th> + + <th>value</th> + <th>grafana</th> + </tr> + <tbody> + <tr ng-repeat="(key,value) in result.result.testcases"> + <td>{{$index}}</td> + <td>{{key}}</td> + + <td>{{value.criteria}}</td> + <td> <button class="btn btn-default btn-sm" ng-click="goToExternal(key)"> grafana</button></td> + </tr> + </tbody> + </table> + + </div> +</div> + + + +</div> diff --git a/gui/app/views/suite.html b/gui/app/views/suite.html new file mode 100644 index 000000000..8e1348333 --- /dev/null +++ b/gui/app/views/suite.html @@ -0,0 +1,149 @@ +<div class="content"> + <!--suitelist--> + <i class="fa fa-arrow-left fa-1x" aria-hidden="true" style="color: #999;cursor:pointer" ng-click="goBack()">Back</i> + + <div> + Test Suites + <button class="btn btn-default" style="margin-left:20px;" ng-click="gotoCreateSuite()"> + Create + + </button> + + <!--<div ng-show="displayOpenrcFile!=null || displayOpenrcFile!=undefined"> + {{displayOpenrcFile.name}} last modified: {{filelastModified}} + </div>--> + <hr/> + + <!--<div ng-repeat="env in environmentList"> + {{env.name}} + </div>--> + <div dw-loading="key" dw-loading-options="{text:'loading'}"> + <div style="display:flex;flex-direction:row;justify-content:space-between;padding:8px;border-top: 1px solid #e9ecec;background-color: #f9f9f9;" > + <div style="font-weight:600">Name</div> + <div style="font-weight:600;margin-right:4px;">Operate</div> + + </div> + + + <div dir-paginate="suite in testsuitlist | itemsPerPage: 10"> + <div style="display:flex;flex-direction:row;justify-content:space-between;padding:8px;border-top: 1px solid #e9ecec;"> + <div> + <a style="color:#e95420" ng-click="gotoDetail(suite)"> {{suite}} + </a> + </div> + <div> + <!-- <button class="btn btn-default btn-sm" ng-click="gotoDetail(suite)">Detail</button> --> + <button class="btn btn-default btn-sm" ng-click="openDeleteEnv(suite,'test suite')">Delete</button> + </div> + + </div> + + </div> + <center> + <dir-pagination-controls></dir-pagination-controls> + </center> + </div> + + + + + + + + + </div> + + + + +</div> + +<toaster-container></toaster-container> + +<style> + .deepColor { + background-color: #f9f9f9; + } + + .form-control { + border-radius: 5px; + width: 300px; + margin-bottom: 10px; + } + + .uploadbutton { + background-color: #007ACC; + color: #fff; + border: 0px; + border-radius: 5px; + height: 27px; + } + + .edit-title { + border: 0px; + background-color: #ffffff; + margin-bottom: 5px; + } + + .null-edit-title { + border: 1px solid #e5e6e7; + border-radius: 5px; + margin-bottom: 3px; + } + + .item-info { + display: flex; + flex-direction: row; + } + + .delete-img { + width: 19px; + height: 19px; + opacity: 0.8; + margin-left: 5px; + margin-top: 4px; + cursor: pointer; + } + + .nextButton { + margin-top: 30px; + border: none; + border-radius: 5px; + padding: 6px; + background-color: #339933; + color: #ffffff; + text-align: center; + /* margin-left: 300px; */ + } + + .bs-sidenav { + margin-top: 40px; + margin-bottom: 20px; + width: 124px; + } + + .nav { + margin-bottom: 0; + padding-left: 0; + list-style: none; + } + + .nav>li { + position: relative; + display: block; + } + + li { + display: list-item; + text-align: -webkit-match-parent; + } + + a { + cursor: pointer; + } + + a.active { + background-color: #EEEEEE; + border-radius: 5px; + } +</style> diff --git a/gui/app/views/suitedetail.html b/gui/app/views/suitedetail.html new file mode 100644 index 000000000..6122f6560 --- /dev/null +++ b/gui/app/views/suitedetail.html @@ -0,0 +1,110 @@ +<div class="content"> + <!--testcaselist--> + <div> + <i class="fa fa-arrow-left fa-1x" aria-hidden="true" style="color: #999;cursor:pointer" ng-click="goBack()">Back</i> + + <h3>Detail</h3> + <hr/> + + <textarea ng-model="suiteinfo" spellcheck="false"> + + </textarea> + + + + + + + + </div> + + + + +</div> + +<toaster-container></toaster-container> + +<style> + .form-control { + border-radius: 5px; + width: 300px; + margin-bottom: 10px; + } + + .uploadbutton { + background-color: #007ACC; + color: #fff; + border: 0px; + border-radius: 5px; + height: 27px; + } + + .edit-title { + border: 0px; + background-color: #ffffff; + margin-bottom: 5px; + } + + .null-edit-title { + border: 1px solid #e5e6e7; + border-radius: 5px; + margin-bottom: 3px; + } + + .item-info { + display: flex; + flex-direction: row; + } + + .delete-img { + width: 19px; + height: 19px; + opacity: 0.8; + margin-left: 5px; + margin-top: 4px; + cursor: pointer; + } + + .nextButton { + margin-top: 30px; + border: none; + border-radius: 5px; + padding: 6px; + background-color: #339933; + color: #ffffff; + text-align: center; + /* margin-left: 300px; */ + } + + .bs-sidenav { + margin-top: 40px; + margin-bottom: 20px; + width: 124px; + } + + .nav { + margin-bottom: 0; + padding-left: 0; + list-style: none; + } + + .nav>li { + position: relative; + display: block; + } + + li { + display: list-item; + text-align: -webkit-match-parent; + } + + a { + cursor: pointer; + } + + a.active { + background-color: #EEEEEE; + border-radius: 5px; + } +</style> diff --git a/gui/app/views/taskList.html b/gui/app/views/taskList.html new file mode 100644 index 000000000..159fed5c9 --- /dev/null +++ b/gui/app/views/taskList.html @@ -0,0 +1,62 @@ +<div class="content"> + <i class="fa fa-arrow-left fa-1x" aria-hidden="true" style="color: #999;cursor:pointer" ng-click="goBack()">Back</i> + <h3>Detail</h3> + <hr/> + <div style="display:flex;flex-direction:row"> + <div> + <h4>{{taskDetailData.name}}</h4> + <div style="margin-top:5px;">{{taskDetailData.time}}</div> + </div> + <div class="progree-parent" ng-show="taskDetailData.status!=2" style="margin-top:34px;margin-left:30px;"> + <div class="progree-child" ng-style="{'width':taskDetailData.stausWidth}"> + </div> + + </div> + <div class="progree-parent" ng-show="taskDetailData.status==2" style="background-color:red;margin-top:34px;margin-left:30px;"> + <div class="progree-child" style="width:0"> + </div> + </div> + <i class="fa fa-check" aria-hidden="true" style="margin-top:34px;margin-left:5px;color: #2ecc71;" ng-show="taskDetailData.status==1">finish</i> + <i class="fa fa-spinner" aria-hidden="true" style="margin-top:34px;margin-left:5px;color: #2ecc71;" ng-show="taskDetailData.status==0">runing</i> + <i class="fa fa-exclamation-triangle" aria-hidden="true" style="margin-top:34px;margin-left:5px;color: red;" ng-show="taskDetailData.status==2">failed</i> + </div> + + <div style="margin-top:5px;">Environment : {{displayEnv.name}} </div> + <div ng-show="taskDetailData.case_name!=false" style="margin-top:5px;margin-bottom:5px;"> Name : {{taskDetailData.case_name}}</div> + <textarea ng-model="taskDetailData.content" spellcheck="false"> + + </textarea> + + <div style="text-align:center;margin-top:20px;"> + <button class="btn btn-default" ng-click="createTask(name)" ng-show="">Run</button> + </div> +</div> + + +<style> + input { + border-radius: 10px; + border: 1px solid #eeeeee; + width: 100%; + } + + select { + height: 30px; + border-radius: 5px; + border: 1px solid #e8e8e8; + width: 135px; + margin-top: 20px; + margin-left: 20px; + } + + textarea { + width: 100%; + height: 350px; + border-radius: 5px; + border: 1px solid #e8e8e8; + } + + .content { + height: 90%; + } +</style> diff --git a/gui/app/views/taskmodify.html b/gui/app/views/taskmodify.html new file mode 100644 index 000000000..a4593f745 --- /dev/null +++ b/gui/app/views/taskmodify.html @@ -0,0 +1,162 @@ +<div class="content"> + <i class="fa fa-arrow-left fa-1x" aria-hidden="true" style="color: #999;cursor:pointer" ng-click="goBack()">Back</i> + <h4>Modify </h4> + + <hr/> + + <div> + <div style="display:inline">Name <input type="text" ng-model="taskDetailData.name" style="width:200px" /></div> + + <button class="btn btn-default" ng-click="runAtask()" style="float:right;margin-right:10px;">Run</button> + </div> + <hr/> + + <div bs-tabs> + <div data-title="Environment" bs-pane> + <div style="margin-top:10px"> + <div style="display:inline">Choose Environment : {{envName}}</div> + <button class="btn btn-default" style="display:inline;float:right;margin-right:10px;margin-top: -4px;" ng-click="addEnvToTask()">Confirm</button> + </div> + <hr /> + <div dir-paginate="env in environmentList | orderBy:'-id' | itemsPerPage: 10 "> + <div style="display:flex;flex-direction:row;justify-content:space-between;padding:8px;border-top: 1px solid #e9ecec;" ng-class="{deepColor: $index%2==0}"> + <div> {{env.name}}</div> + <!--<button class="btn btn-default btn-sm" ng-click="gotoDetail('false',env.uuid)">detail</button>--> + <img src="images/checkyes.png" style="height:18px;cursor:pointer" ng-click="constructTestSuit(env.uuid,env.name)" ng-show="selectEnv==env.uuid" /> + <img src="images/checkno.png" style="height:18px;cursor:pointer" ng-click="constructTestSuit(env.uuid,env.name)" ng-show="selectEnv!=env.uuid" /> + + </div> + <!--<hr style="margin-top:5px;margin-bottom:5px;" />--> + </div> + <center> + <dir-pagination-controls></dir-pagination-controls> + </center> + + </div> + <div data-title="Content" bs-pane> + <div style="margin-top:10px;"> + <button class="btn btn-default" ng-click="changeStatussourceFalse()">Modify Content</button> + <button class="btn btn-default" ng-click="changeStatussourceTrue()">Modify Source</button> + <div class="label-type" ng-show="taskDetailData.suite==false"> Test Case</div> + <div class="label-type" ng-show="taskDetailData.suite==true"> Test Suite</div> + <button class="btn btn-default" style="float:right" ng-disabled="sourceShow==null" ng-click="confirmToServer(contentInfo,taskDetailData.content)">Confirm</button> + </div> + + + <textarea ng-model="taskDetailData.content" ng-show="sourceShow==false" style="margin-top:5px;" spellcheck="false"> + + + </textarea> + + <div ng-show="sourceShow==true"> + <div style="display:flex;flex-direction:row"> + <div style="margin-top:20px;">Source of Content</div> + + + <select ng-model="selectType" ng-change="triggerContent(selectType)" data-ng-options="blisterPackTemplate as blisterPackTemplate.name for blisterPackTemplate in blisterPackTemplates"> + <option value="">Choose...</option> + </select> + + </div> + + <div style="margin-top:10px" ng-show="selectCase!=null "> + <div style="display:inline">Choose Source : {{selectCase}}</div> + <!--<button class="btn btn-default" style="display:inline;float:right;margin-right:10px;margin-top: -4px;" ng-click="confirmAddCaseOrSuite(contentInfo)">Confirm</button>--> + <button class="btn btn-default" style="display:inline;float:right;margin-right:10px;margin-top: -4px;" ng-click="getTestDeatil()">Edit</button> + </div> + <hr/> + + <div ng-show="displayTable==true"> + <div ng-show="testcaselist.testcases.length!=0 && selectType.name=='Test Case'"> + <div dir-paginate="test in testcaselist.testcases | itemsPerPage: 10" pagination-id="testcase"> + <div style="display:flex;flex-direction:row;justify-content:space-between;padding:8px;border-top: 1px solid #e9ecec;" ng-class="{deepColor: $index%2==0}"> + <div> {{test.Name}}</div> + <div style="font-size:10px;">{{test.Description}}</div> + <img src="images/checkyes.png" style="height:18px;cursor:pointer" ng-click="constructTestCase(test.Name)" ng-show="selectCase==test.Name" /> + <img src="images/checkno.png" style="height:18px;cursor:pointer" ng-click="constructTestCase(test.Name)" ng-show="selectCase!=test.Name" /> + + </div> + <!--<hr style="margin-top:5px;margin-bottom:5px;" />--> + </div> + <center> + <dir-pagination-controls pagination-id="testcase"></dir-pagination-controls> + </center> + </div> + + <div ng-show="testsuitlist.length!=0 && selectType.name=='Test Suite'"> + <div dir-paginate="suite in testsuitlist | itemsPerPage: 10" pagination-id="testsuite"> + <div style="display:flex;flex-direction:row;justify-content:space-between;padding:8px;border-top: 1px solid #e9ecec;" ng-class="{deepColor: $index%2==0}"> + <div> {{suite}}</div> + + <img src="images/checkyes.png" style="height:18px;cursor:pointer" ng-click="constructTestCase(suite)" ng-show="selectCase==suite" /> + <img src="images/checkno.png" style="height:18px;cursor:pointer" ng-click="constructTestCase(suite)" ng-show="selectCase!=suite" /> + + </div> + <!--<hr style="margin-top:5px;margin-bottom:5px;" />--> + </div> + <center> + <dir-pagination-controls pagination-id="testsuite"></dir-pagination-controls> + </center> + </div> + </div> + + <div ng-show="displayTable==false"> + <textarea ng-model="contentInfo" spellcheck="false"> + </textarea> + + + </div> + </div> + + + + + </div> + </div> + + +</div> +<toaster-container></toaster-container> + + +<style> + input { + border-radius: 10px; + border: 1px solid #eeeeee; + width: 100%; + padding: 5px; + } + + .deepColor { + background-color: #f9f9f9; + } + + select { + height: 30px; + border-radius: 5px; + border: 1px solid #e8e8e8; + width: 135px; + margin-top: 20px; + margin-left: 20px; + } + + textarea { + width: 100%; + height: 350px; + border-radius: 5px; + border: 1px solid #e8e8e8; + } + + .label-type { + display: inline; + background-color: #2ecc71; + color: #fff; + border-radius: 5px; + padding: 3px; + font-size: 10px; + } + + .content { + height: auto; + } +</style> diff --git a/gui/app/views/testcasechoose.html b/gui/app/views/testcasechoose.html new file mode 100644 index 000000000..12bdb834f --- /dev/null +++ b/gui/app/views/testcasechoose.html @@ -0,0 +1,48 @@ +<div class="content"> + + <div> + Test case list + <button class="btn btn-default" style="margin-left:20px;" ng-click="openDialog()"> + <div ng-show="!loadingOPENrc">Create </div> + <img src="images/loading2.gif" width="25" height="25" ng-if="loadingOPENrc" /> + </button> + + + <hr/> You have choose : + <div ng-repeat="selected in suitReconstructList" style="display:inline;" class="item">{{selected}}</div> + <hr/> + + <!--<div ng-repeat="env in environmentList"> + {{env.name}} + </div>--> + <div dir-paginate="test in testcaselist.testcases | itemsPerPage: 10"> + <div style="display:flex;flex-direction:row;"> + <img src="images/checkyes.png" style="height:12px;cursor:pointer" ng-click="constructTestSuit(test.Name)" ng-show="testsuiteList.indexOf(test.Name)>-1" /> + <img src="images/checkno.png" style="height:12px;cursor:pointer" ng-click="constructTestSuit(test.Name)" ng-show="testsuiteList.indexOf(test.Name)==-1" /> + <div style="margin-left:50px;"> {{test.Name}}</div> + <div style="font-size:10px;margin-left:100px">{{test.Description}}</div> + + </div> + <hr style="margin-top:5px;margin-bottom:5px;" /> + </div> + <center> + <dir-pagination-controls></dir-pagination-controls> + </center> + + + + </div> + <toaster-container></toaster-container> + + <style> + .item { + background-color: #3498db; + color: #fff; + width: 150px; + border-radius: 5px; + padding-left: 10px; + margin-left: 2px; + margin-top: 3px; + padding: 4px; + } + </style> diff --git a/gui/app/views/testcasedetail.html b/gui/app/views/testcasedetail.html new file mode 100644 index 000000000..43a51537f --- /dev/null +++ b/gui/app/views/testcasedetail.html @@ -0,0 +1,110 @@ +<div class="content"> + <!--testcaselist--> + <div> + <i class="fa fa-arrow-left fa-1x" aria-hidden="true" style="color: #999;cursor:pointer" ng-click="goBack()">Back</i> + + <h4>Detail</h4> + <hr/> + + <textarea ng-model="testcaseInfo" spellcheck="false"> + + </textarea> + + + + + + + + </div> + + + + +</div> + +<toaster-container></toaster-container> + +<style> + .form-control { + border-radius: 5px; + width: 300px; + margin-bottom: 10px; + } + + .uploadbutton { + background-color: #007ACC; + color: #fff; + border: 0px; + border-radius: 5px; + height: 27px; + } + + .edit-title { + border: 0px; + background-color: #ffffff; + margin-bottom: 5px; + } + + .null-edit-title { + border: 1px solid #e5e6e7; + border-radius: 5px; + margin-bottom: 3px; + } + + .item-info { + display: flex; + flex-direction: row; + } + + .delete-img { + width: 19px; + height: 19px; + opacity: 0.8; + margin-left: 5px; + margin-top: 4px; + cursor: pointer; + } + + .nextButton { + margin-top: 30px; + border: none; + border-radius: 5px; + padding: 6px; + background-color: #339933; + color: #ffffff; + text-align: center; + /* margin-left: 300px; */ + } + + .bs-sidenav { + margin-top: 40px; + margin-bottom: 20px; + width: 124px; + } + + .nav { + margin-bottom: 0; + padding-left: 0; + list-style: none; + } + + .nav>li { + position: relative; + display: block; + } + + li { + display: list-item; + text-align: -webkit-match-parent; + } + + a { + cursor: pointer; + } + + a.active { + background-color: #EEEEEE; + border-radius: 5px; + } +</style> diff --git a/gui/app/views/testcaselist.html b/gui/app/views/testcaselist.html new file mode 100644 index 000000000..3e8cfccf9 --- /dev/null +++ b/gui/app/views/testcaselist.html @@ -0,0 +1,150 @@ +<div class="content"> + <!--testcaselist--> + <i class="fa fa-arrow-left fa-1x" aria-hidden="true" style="color: #999;cursor:pointer" ng-click="goBack()">Back</i> + + <div> + Test Cases + <button class="btn btn-default" style="margin-left:20px;" ngf-select="uploadFiles($file, $invalidFiles)" ngf-max-size="5MB"> + <div ng-show="!loadingOPENrc">Upload</div> + <img src="images/loading2.gif" width="25" height="25" ng-if="loadingOPENrc" /> + </button> + + <!--<div ng-show="displayOpenrcFile!=null || displayOpenrcFile!=undefined"> + {{displayOpenrcFile.name}} last modified: {{filelastModified}} + </div>--> + <hr/> + + <!--<div ng-repeat="env in environmentList"> + {{env.name}} + </div>--> + <div dw-loading="key" dw-loading-options="{text:'loading'}"> + <div style="display:flex;flex-direction:row;justify-content:space-between;padding:8px;border-top: 1px solid #e9ecec;background-color: #f9f9f9"> + <div style="font-weight:600">Name</div> + <div style="font-weight:600;margin-right:4px;">Operate</div> + + </div> + + <div dir-paginate="test in testcaselist.testcases | itemsPerPage: 10"> + <div style="display:flex;flex-direction:row;justify-content:space-between;padding:8px;border-top: 1px solid #e9ecec;"> + <div> + + <a style="color:#e95420" ng-click="gotoDetail(test.Name)"> + {{test.Name}} + </a> + </div> + <div style="font-size:10px;">{{test.Description}}</div> + <div> + <!-- <button class="btn btn-default btn-sm" ng-click="gotoDetail(test.Name)">Detail</button> --> + <button class="btn btn-default btn-sm" ng-click="openDeleteEnv(test.Name,'test case')">Delete</button> + </div> + + </div> + + </div> + <center> + <dir-pagination-controls></dir-pagination-controls> + </center> + </div> + + + + + + + + </div> + + + + +</div> + +<toaster-container></toaster-container> + +<style> + .deepColor { + background-color: #f9f9f9; + } + + .form-control { + border-radius: 5px; + width: 300px; + margin-bottom: 10px; + } + + .uploadbutton { + background-color: #007ACC; + color: #fff; + border: 0px; + border-radius: 5px; + height: 27px; + } + + .edit-title { + border: 0px; + background-color: #ffffff; + margin-bottom: 5px; + } + + .null-edit-title { + border: 1px solid #e5e6e7; + border-radius: 5px; + margin-bottom: 3px; + } + + .item-info { + display: flex; + flex-direction: row; + } + + .delete-img { + width: 19px; + height: 19px; + opacity: 0.8; + margin-left: 5px; + margin-top: 4px; + cursor: pointer; + } + + .nextButton { + margin-top: 30px; + border: none; + border-radius: 5px; + padding: 6px; + background-color: #339933; + color: #ffffff; + text-align: center; + /* margin-left: 300px; */ + } + + .bs-sidenav { + margin-top: 40px; + margin-bottom: 20px; + width: 124px; + } + + .nav { + margin-bottom: 0; + padding-left: 0; + list-style: none; + } + + .nav>li { + position: relative; + display: block; + } + + li { + display: list-item; + text-align: -webkit-match-parent; + } + + a { + cursor: pointer; + } + + a.active { + background-color: #EEEEEE; + border-radius: 5px; + } +</style> diff --git a/gui/app/views/uploadImage.html b/gui/app/views/uploadImage.html new file mode 100644 index 000000000..17ccfdb8b --- /dev/null +++ b/gui/app/views/uploadImage.html @@ -0,0 +1,145 @@ +<!--upload image page--> + +<div class="content"> + <div style="display:flex;flex-direction:row;"> + <div style="width:750px;"> + + <h3>{{baseElementInfo.name}} -- Image + <button class="btn btn-default" style="float:right" ng-click="goNext()">Next</button> + </h3> + <!--<p>In this process, you can input your define openrc config or upload a openrc file</p>--> + + <hr/> + <button class="btn btn-default" ng-click="uploadImage()"> + <div ng-if="!showloading">Load Image</div> + <img src="images/loading2.gif" width="25" height="25" ng-if="showloading" /> + </button> + <i class="fa fa-check" aria-hidden="true" style="margin-top:34px;margin-left:5px;color: #2ecc71;" ng-show="imageStatus==1&&ifshowStatus==1">done</i> + <i class="fa fa-spinner" aria-hidden="true" style="margin-top:34px;margin-left:5px;color: #2ecc71;" ng-show="imageStatus==0&&ifshowStatus==1">loading</i> + <i class="fa fa-exclamation-triangle" aria-hidden="true" style="margin-top:34px;margin-left:5px;color: red;" ng-show="imageStatus==2&&ifshowStatus==1">error</i> + + <hr> + <h4>Current Images</h4> + + <div> + <table class="table table-striped"> + + <tr> + <th>name</th> + <th>size</th> + <th>status</th> + <th>time</th> + </tr> + <tr ng-repeat="image in imageListData"> + <td>{{image.name}}</td> + <td>{{image.size/1024}} MB</td> + <td>{{image.status}}</td> + <td>{{image.time}}</td> + + </tr> + + + + </table> + </div> + + + + + + + + + + </div> + + + </div> + +</div> +<toaster-container></toaster-container> + +<style> + .form-control { + border-radius: 5px; + width: 200px; + margin-bottom: 10px; + } + + .uploadbutton { + background-color: #007ACC; + color: #fff; + border: 0px; + border-radius: 5px; + height: 27px; + } + + .edit-title { + border: 0px; + background-color: #ffffff; + margin-bottom: 5px; + font-size: 12px; + } + + .null-edit-title { + border: 1px solid #e5e6e7; + border-radius: 5px; + margin-bottom: 3px; + } + + .item-info { + display: flex; + flex-direction: row; + } + + .delete-img { + width: 15px; + height: 15px; + opacity: 0.8; + margin-left: -10px; + margin-top: -3px; + cursor: pointer; + } + + .nextButton { + margin-top: 30px; + border: none; + border-radius: 5px; + padding: 6px; + background-color: #339933; + color: #ffffff; + text-align: center; + /* margin-left: 300px; */ + } + + .bs-sidenav { + margin-top: 40px; + margin-bottom: 20px; + width: 124px; + } + + .nav { + margin-bottom: 0; + padding-left: 0; + list-style: none; + } + + .nav>li { + position: relative; + display: block; + } + + li { + display: list-item; + text-align: -webkit-match-parent; + } + + a { + cursor: pointer; + } + + a.active { + background-color: #EEEEEE; + border-radius: 5px; + } +</style> diff --git a/gui/bower.json b/gui/bower.json new file mode 100644 index 000000000..6da3bee3c --- /dev/null +++ b/gui/bower.json @@ -0,0 +1,45 @@ +{ + "name": "yard-stick-gui2", + "version": "0.0.0", + "dependencies": { + "angular": "^1.4.0", + "bootstrap": "^3.2.0", + "angular-strap": "^2.3.12", + "angular-ui-router": "^1.0.3", + "angular-animate": "^1.6.4", + "angular-breadcrumb": "^0.5.0", + "angular-wizard": "^0.10.0", + "angular-resource": "^1.6.4", + "ng-file-upload": "^12.2.13", + "AngularJS-Toaster": "angularjs-toaster#^2.1.0", + "ng-dialog": "^1.3.0", + "angularUtils-pagination": "angular-utils-pagination#^0.11.1", + "components-font-awesome": "^4.7.0", + "ngstorage": "^0.3.11", + "v-accordion": "^1.6.0", + "angular-loading": "^0.1.4", + "angular-bootstrap": "^2.5.0", + "angular-sanitize": "^1.6.5" + }, + "devDependencies": { + "angular-mocks": "^1.4.0" + }, + "appPath": "app", + "moduleName": "yardStickGui2App", + "overrides": { + "bootstrap": { + "main": [ + "less/bootstrap.less", + "dist/css/bootstrap.css", + "dist/js/bootstrap.js" + ] + }, + "angular-loading": { + "main": [ + "angular-loading.css", + "angular-loading.js", + "../spin.js/spin.js" + ] + } + } +} diff --git a/gui/gui.sh b/gui/gui.sh new file mode 100755 index 000000000..12a14923e --- /dev/null +++ b/gui/gui.sh @@ -0,0 +1,8 @@ +apt-get install -y nodejs +apt-get install -y npm +ln -s /usr/bin/nodejs /usr/bin/node +npm install +npm install -g grunt +npm install -g bower +bower install --force --allow-root +grunt build diff --git a/gui/package.json b/gui/package.json new file mode 100644 index 000000000..b85c75469 --- /dev/null +++ b/gui/package.json @@ -0,0 +1,43 @@ +{ + "name": "yardstickgui2", + "private": true, + "devDependencies": { + "autoprefixer-core": "^5.2.1", + "grunt": "^0.4.5", + "grunt-angular-templates": "^0.5.7", + "grunt-concurrent": "^1.0.0", + "grunt-contrib-clean": "^0.6.0", + "grunt-contrib-concat": "^0.5.0", + "grunt-contrib-connect": "^0.9.0", + "grunt-contrib-copy": "^0.7.0", + "grunt-contrib-cssmin": "^0.12.0", + "grunt-contrib-htmlmin": "^0.4.0", + "grunt-contrib-imagemin": "^1.0.0", + "grunt-contrib-jshint": "^0.11.0", + "grunt-contrib-uglify": "^0.7.0", + "grunt-contrib-watch": "^0.6.1", + "grunt-filerev": "^2.1.2", + "grunt-google-cdn": "^0.4.3", + "grunt-jscs": "^1.8.0", + "grunt-newer": "^1.1.0", + "grunt-ng-annotate": "^0.9.2", + "grunt-postcss": "^0.5.5", + "grunt-svgmin": "^2.0.0", + "grunt-usemin": "^3.0.0", + "grunt-wiredep": "^2.0.0", + "jasmine-core": "^2.6.2", + "jit-grunt": "^0.9.1", + "jshint-stylish": "^1.0.0", + "karma": "^1.7.0", + "karma-jasmine": "^1.1.0", + "karma-phantomjs-launcher": "^1.0.4", + "phantomjs-prebuilt": "^2.1.14", + "time-grunt": "^1.0.0" + }, + "engines": { + "node": ">=0.10.0" + }, + "scripts": { + "test": "karma start test\\karma.conf.js" + } +} diff --git a/gui/test/.jshintrc b/gui/test/.jshintrc new file mode 100644 index 000000000..b2ce4eff4 --- /dev/null +++ b/gui/test/.jshintrc @@ -0,0 +1,18 @@ +{ + "bitwise": true, + "browser": true, + "curly": true, + "eqeqeq": true, + "esnext": true, + "jasmine": true, + "latedef": true, + "noarg": true, + "node": true, + "strict": true, + "undef": true, + "unused": true, + "globals": { + "angular": false, + "inject": false + } +} diff --git a/gui/test/karma.conf.js b/gui/test/karma.conf.js new file mode 100644 index 000000000..a9ab3a824 --- /dev/null +++ b/gui/test/karma.conf.js @@ -0,0 +1,93 @@ +// Karma configuration +// Generated on 2017-05-31 + +module.exports = function(config) { + 'use strict'; + + config.set({ + // enable / disable watching file and executing tests whenever any file changes + autoWatch: true, + + // base path, that will be used to resolve files and exclude + basePath: '../', + + // testing framework to use (jasmine/mocha/qunit/...) + // as well as any additional frameworks (requirejs/chai/sinon/...) + frameworks: [ + 'jasmine' + ], + + // list of files / patterns to load in the browser + files: [ + // bower:js + 'bower_components/jquery/dist/jquery.js', + 'bower_components/angular/angular.js', + 'bower_components/bootstrap/dist/js/bootstrap.js', + 'bower_components/angular-strap/dist/angular-strap.js', + 'bower_components/angular-strap/dist/angular-strap.tpl.js', + 'bower_components/angular-ui-router/release/angular-ui-router.js', + 'bower_components/angular-animate/angular-animate.js', + 'bower_components/angular-breadcrumb/release/angular-breadcrumb.js', + 'bower_components/angular-wizard/dist/angular-wizard.min.js', + 'bower_components/angular-resource/angular-resource.js', + 'bower_components/ng-file-upload/ng-file-upload.js', + 'bower_components/AngularJS-Toaster/toaster.js', + 'bower_components/ng-dialog/js/ngDialog.js', + 'bower_components/angularUtils-pagination/dirPagination.js', + 'bower_components/ngstorage/ngStorage.js', + 'bower_components/v-accordion/dist/v-accordion.js', + 'bower_components/spin.js/spin.js', + 'bower_components/angular-loading/angular-loading.js', + 'bower_components/spin.js/spin.js', + 'bower_components/angular-bootstrap/ui-bootstrap-tpls.js', + 'bower_components/angular-sanitize/angular-sanitize.js', + 'bower_components/angular-mocks/angular-mocks.js', + // endbower + 'app/scripts/**/*.js', + 'test/mock/**/*.js', + 'test/spec/**/*.js' + ], + + // list of files / patterns to exclude + exclude: [ + ], + + // web server port + port: 8080, + + // Start these browsers, currently available: + // - Chrome + // - ChromeCanary + // - Firefox + // - Opera + // - Safari (only Mac) + // - PhantomJS + // - IE (only Windows) + browsers: [ + 'PhantomJS' + ], + + // Which plugins to enable + plugins: [ + 'karma-phantomjs-launcher', + 'karma-jasmine' + ], + + // Continuous Integration mode + // if true, it capture browsers, run tests and exit + singleRun: false, + + colors: true, + + // level of logging + // possible values: LOG_DISABLE || LOG_ERROR || LOG_WARN || LOG_INFO || LOG_DEBUG + logLevel: config.LOG_INFO, + + // Uncomment the following lines if you are using grunt's server to run the tests + // proxies: { + // '/': 'http://localhost:9000/' + // }, + // URL root prevent conflicts with the site root + // urlRoot: '_karma_' + }); +}; diff --git a/gui/test/spec/controllers/main.js b/gui/test/spec/controllers/main.js new file mode 100644 index 000000000..27e0a5ad3 --- /dev/null +++ b/gui/test/spec/controllers/main.js @@ -0,0 +1,23 @@ +'use strict'; + +describe('Controller: MainCtrl', function () { + + // load the controller's module + beforeEach(module('yardStickGui2App')); + + var MainCtrl, + scope; + + // Initialize the controller and a mock scope + beforeEach(inject(function ($controller, $rootScope) { + scope = $rootScope.$new(); + MainCtrl = $controller('MainCtrl', { + $scope: scope + // place here mocked dependencies + }); + })); + + it('should attach a list of awesomeThings to the scope', function () { + expect(MainCtrl.awesomeThings.length).toBe(3); + }); +}); diff --git a/install.sh b/install.sh index ad14b8e0b..e82ae0233 100755 --- a/install.sh +++ b/install.sh @@ -86,7 +86,10 @@ easy_install -U pip pip install -r requirements.txt pip install -e . -/bin/bash "$(pwd)/api/api-prepare.sh" +/bin/bash "${PWD}/docker/uwsgi.sh" +/bin/bash "${PWD}/docker/nginx.sh" +cd "${PWD}/gui" && /bin/bash gui.sh +mv dist /etc/nginx/yardstick/gui service nginx restart uwsgi -i /etc/yardstick/yardstick.ini diff --git a/plugin/CI/storperf.yaml b/plugin/CI/storperf.yaml index e144dd150..70915f661 100644 --- a/plugin/CI/storperf.yaml +++ b/plugin/CI/storperf.yaml @@ -7,7 +7,7 @@ # http://www.apache.org/licenses/LICENSE-2.0 ############################################################################## --- -# StorPerf plugin configration file for huawei-pod1 +# StorPerf plugin configration file for compass pod in CI # Used for integration StorPerf into Yardstick as a plugin schema: "yardstick:plugin:0.1" @@ -16,6 +16,6 @@ plugins: name: storperf deployment: - ip: 192.168.10.6 + ip: local user: root password: root diff --git a/requirements.txt b/requirements.txt index f283b9921..2bcc4dfa7 100644 --- a/requirements.txt +++ b/requirements.txt @@ -14,7 +14,7 @@ PyYAML==3.12 SQLAlchemy==1.1.4 ansible==2.2.2.0 appdirs==1.4.3 -backport-ipaddress==0.1 +backport-ipaddress==0.1; python_version <= '2.7' chainmap==1.0.2 cliff==2.4.0 cmd2==0.6.9 @@ -39,6 +39,7 @@ jsonpatch==1.15 jsonpointer==1.10 jsonschema==2.5.1 keystoneauth1==2.18.0 +kubernetes==3.0.0a1 linecache2==1.0.0 lxml==3.7.2 mccabe==0.4.0 @@ -58,6 +59,7 @@ oslo.utils==3.22.0 paramiko==2.1.1 pbr==1.10.0 pep8==1.7.0 +ping==0.2; python_version <= '2.7' pika==0.10.0 positional==1.1.1 prettytable==0.7.2 diff --git a/samples/ping_k8s.yaml b/samples/ping_k8s.yaml new file mode 100644 index 000000000..503fe6a45 --- /dev/null +++ b/samples/ping_k8s.yaml @@ -0,0 +1,46 @@ +############################################################################## +# Copyright (c) 2017 Huawei AB and others. +# +# All rights reserved. This program and the accompanying materials +# are made available under the terms of the Apache License, Version 2.0 +# which accompanies this distribution, and is available at +# http://www.apache.org/licenses/LICENSE-2.0 +############################################################################## + +--- +# Sample benchmark task config file +# measure network latency using ping in container + +schema: "yardstick:task:0.1" + +scenarios: +- + type: Ping + options: + packetsize: 200 + + host: host-k8s + target: target-k8s + + runner: + type: Duration + duration: 60 + interval: 1 + + sla: + max_rtt: 10 + action: monitor + +context: + type: Kubernetes + name: k8s + + servers: + host: + image: openretriever/yardstick + command: /bin/bash + args: ['-c', 'chmod 700 ~/.ssh; chmod 600 ~/.ssh/*; service ssh restart;while true ; do sleep 10000; done'] + target: + image: openretriever/yardstick + command: /bin/bash + args: ['-c', 'chmod 700 ~/.ssh; chmod 600 ~/.ssh/*; service ssh restart;while true ; do sleep 10000; done'] diff --git a/samples/storperf.yaml b/samples/storperf.yaml index 5000759e0..2ea022173 100644 --- a/samples/storperf.yaml +++ b/samples/storperf.yaml @@ -18,6 +18,7 @@ scenarios: options: agent_count: 1 agent_image: "Ubuntu-16.04" + agent_flavor: "storperf" public_network: "ext-net" volume_size: 2 # target: diff --git a/samples/vnf_samples/nsut/ping/tc_ping_ovs_dpdk_context.yaml b/samples/vnf_samples/nsut/ping/tc_ping_ovs_dpdk_context.yaml new file mode 100644 index 000000000..7654b0f96 --- /dev/null +++ b/samples/vnf_samples/nsut/ping/tc_ping_ovs_dpdk_context.yaml @@ -0,0 +1,42 @@ +# Copyright (c) 2016-2017 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. + +--- +schema: "yardstick:task:0.1" + +scenarios: +- + type: NSPerf + traffic_profile: ../../traffic_profiles/fixed.yaml + topology: ping_tg_topology.yaml # TODO: look in relative path where the tc.yaml is found + + nodes: # This section is copied from pod.xml or resolved via Heat + tg__1: trafficgen_1.yardstick + vnf__1: vnf.yardstick + + vnf_options: + tg__1: + target_ip: pingvnf__1.xe0.local_ip # TODO: resolve to config vars + vnf__1: + target_ip: pinggen__1.xe1.local_ip # TODO: resolve to config vars + runner: + type: Duration + duration: 10 + +context: + type: Standalone + name: yardstick + nfvi_type: Ovsdpdk + vm_deploy: True + file: /etc/yardstick/nodes/pod_ovs.yaml diff --git a/tests/ci/prepare_env.sh b/tests/ci/prepare_env.sh index 3d9cc298f..c3ee4c76b 100755 --- a/tests/ci/prepare_env.sh +++ b/tests/ci/prepare_env.sh @@ -11,7 +11,7 @@ # Perepare the environment to run yardstick ci : ${DEPLOY_TYPE:='bm'} # Can be any of 'bm' (Bare Metal) or 'virt' (Virtual) - +: ${INSTALLER_TYPE:='unknown'} : ${NODE_NAME:='unknown'} : ${EXTERNAL_NETWORK:='admin_floating_net'} @@ -61,9 +61,9 @@ export EXTERNAL_NETWORK INSTALLER_TYPE DEPLOY_TYPE NODE_NAME # Prepare a admin-rc file for StorPerf integration $YARDSTICK_REPO_DIR/tests/ci/prepare_storperf_admin-rc.sh -# copy a admin-rc file for StorPerf integration to the deployment location -if [ "$NODE_NAME" == "huawei-pod1" ]; then - bash $YARDSTICK_REPO_DIR/tests/ci/scp_storperf_files.sh +# copy Storperf related files to the deployment location +if [ "$INSTALLER_TYPE" == "compass" ]; then + source $YARDSTICK_REPO_DIR/tests/ci/scp_storperf_files.sh fi # Fetching id_rsa file from jump_server..." diff --git a/tests/ci/scp_storperf_files.sh b/tests/ci/scp_storperf_files.sh index 234032cf1..71306eb80 100644 --- a/tests/ci/scp_storperf_files.sh +++ b/tests/ci/scp_storperf_files.sh @@ -12,9 +12,26 @@ # Copy storperf_admin-rc to deployment location. ssh_options="-o UserKnownHostsFile=/dev/null -o StrictHostKeyChecking=no" -sshpass -p root scp 2>/dev/null $ssh_options ~/storperf_admin-rc \ - root@192.168.10.6:/root/ &> /dev/null -sshpass -p root scp 2>/dev/null $ssh_options /home/opnfv/repos/storperf/docker-compose/docker-compose.yaml \ - root@192.168.10.6:/root/ &> /dev/null -sshpass -p root scp 2>/dev/null $ssh_options /home/opnfv/repos/storperf/docker-compose/nginx.conf \ - root@192.168.10.6:/root/ &> /dev/null + +scp_files(){ + export JUMP_HOST_IP + sshpass -p root scp 2>/dev/null $ssh_options ~/storperf_admin-rc \ + root@${JUMP_HOST_IP}:/root/ &> /dev/null + sshpass -p root scp 2>/dev/null $ssh_options /home/opnfv/repos/storperf/docker-compose/docker-compose.yaml \ + root@${JUMP_HOST_IP}:/root/ &> /dev/null +} + +case "$NODE_NAME" in + "huawei-pod1") + JUMP_HOST_IP='192.168.10.6' + scp_files + ;; + "huawei-pod2") + JUMP_HOST_IP='192.168.11.2' + scp_files + ;; + *) + # no node name, exit + echo "storperf test case will not run on this pod, skipping scp files..." + ;; +esac diff --git a/tests/ci/yardstick-verify b/tests/ci/yardstick-verify index 096ea534f..16598df7b 100755 --- a/tests/ci/yardstick-verify +++ b/tests/ci/yardstick-verify @@ -99,8 +99,8 @@ set -o pipefail install_storperf() { - # Install Storper on huawei-pod1 - if [ "$NODE_NAME" == "huawei-pod1" ]; then + # Install Storper on huawei-pod1 and huawei-pod2 + if [ "$NODE_NAME" == "huawei-pod1" -o "$NODE_NAME" == "huawei-pod2" ]; then echo echo "========== Installing storperf ==========" @@ -114,8 +114,8 @@ install_storperf() remove_storperf() { - # remove Storper from huawei-pod1 - if [ "$NODE_NAME" == "huawei-pod1" ]; then + # remove Storper from huawei-pod1 and huawei-pod2 + if [ "$NODE_NAME" == "huawei-pod1" -o "$NODE_NAME" == "huawei-pod2" ]; then echo echo "========== Removing storperf ==========" @@ -293,8 +293,13 @@ main() echo # check OpenStack services + if [[ $OS_INSECURE ]] && [[ "$(echo $OS_INSECURE | tr '[:upper:]' '[:lower:]')" = "true" ]]; then + SECURE="--insecure" + else + SECURE="" + fi echo "Checking OpenStack services:" - for cmd in "openstack image list" "openstack server list" "openstack stack list"; do + for cmd in "openstack ${SECURE} image list" "openstack ${SECURE} server list" "openstack ${SECURE} stack list"; do echo " checking ${cmd} ..." if ! $cmd >/dev/null; then echo "error: command \"$cmd\" failed" diff --git a/tests/opnfv/test_cases/opnfv_yardstick_tc008.yaml b/tests/opnfv/test_cases/opnfv_yardstick_tc008.yaml index 4c7fdab90..f5ccb255a 100644 --- a/tests/opnfv/test_cases/opnfv_yardstick_tc008.yaml +++ b/tests/opnfv/test_cases/opnfv_yardstick_tc008.yaml @@ -29,12 +29,21 @@ scenarios: packetsize: {{pkt_size}} number_of_ports: {{num_ports}} duration: 20 + # choose vnic name: default to eth0 + # vnic_name: 'ens3' + # turn on multiqueue inside VM + # multiqueue: True + # choose starting pps: default 1M; + # works with binary search runner Dynamictp to find max throughput per sla + # pps: 3000000 host: demeter.yardstick-TC008 target: poseidon.yardstick-TC008 runner: type: Iteration + # binary search runner + # type: Dynamictp iterations: 10 interval: 1 diff --git a/tests/opnfv/test_cases/opnfv_yardstick_tc023.yaml b/tests/opnfv/test_cases/opnfv_yardstick_tc023.yaml new file mode 100644 index 000000000..2804f25a2 --- /dev/null +++ b/tests/opnfv/test_cases/opnfv_yardstick_tc023.yaml @@ -0,0 +1,176 @@ +############################################################################## +# Copyright (c) 2017 Huawei Technologies Co.,Ltd and others. +# +# All rights reserved. This program and the accompanying materials +# are made available under the terms of the Apache License, Version 2.0 +# which accompanies this distribution, and is available at +# http://www.apache.org/licenses/LICENSE-2.0 +############################################################################## +--- + +schema: "yardstick:task:0.1" + +{% set file = file or "etc/yardstick/nodes/compass_sclab_virtual/pod.yaml" %} +{% set cpu_set = cpu_set or "0,1,2,3" %} +{% set memory_load = memory_load or 0 %} + +{% set flavor = flavor or "yardstick-migrate-flavor" %} +{% set ram = ram or "2048" %} +{% set vcpus = vcpus or "2" %} +{% set disk = disk or "3" %} + +scenarios: +- + type: GetServer + + output: status server + + host: server.migrate + + runner: + type: Iteration + iteration: 1 +- + type: GetNumaInfo + + options: + server: $server + file: {{ file }} + + output: origin_numa_info + + host: server.migrate + + runner: + type: Iteration + iteration: 1 +- + type: GetMigrateTargetHost + + options: + server: $server + output: target_host + + runner: + type: Iteration + iteration: 1 +- + type: GetServerIp + + options: + server: $server + + output: server_ip + + runner: + type: Iteration + iteration: 1 +- + type: AddMemoryLoad + + options: + memory_load: {{ memory_load }} + + host: server.migrate + + runner: + type: Iteration + iteration: 1 +- + type: Migrate + + options: + server: $server + host: $target_host + server_ip: $server_ip + + output: status migrate_time1 downtime1 + + runner: + type: Iteration + iteration: 1 +- + type: CheckValue + + options: + value1: $status + value2: 0 + operator: eq + + runner: + type: Iteration + iteration: 1 +- + type: GetServer + + output: status server + + host: server.migrate + + runner: + type: Iteration + iteration: 1 +- + type: GetNumaInfo + + options: + server: $server + file: {{ file }} + + output: new_numa_info + + host: server.migrate + + runner: + type: Iteration + iteration: 1 +- + type: CheckNumaInfo + + options: + info1: $origin_numa_info + info2: $new_numa_info + cpu_set: {{ cpu_set }} + + output: status + + runner: + type: Iteration + iteration: 1 +- + type: CheckValue + + options: + value1: $status + value2: true + operator: eq + + runner: + type: Iteration + iteration: 1 + + +contexts: +- + type: Node + name: env-prepare + file: {{ file }} + + env: + type: ansible + setup: migrate_pinning_setup.yaml -e "flavor={{ flavor }} ram={{ ram }} vcpus={{ vcpus }} disk={{ disk }} cpu_set={{ cpu_set }}" + teardown: migrate_pinning_teardown.yaml -e "flavor={{ flavor }}" + +- + name: migrate + image: yardstick-image + flavor: {{ flavor }} + user: ubuntu + + servers: + server: + floating_ip: true + + networks: + test: + cidr: '10.0.1.0/24' diff --git a/tests/opnfv/test_cases/opnfv_yardstick_tc074.yaml b/tests/opnfv/test_cases/opnfv_yardstick_tc074.yaml index 5bd3f676f..ef4f02c9e 100644 --- a/tests/opnfv/test_cases/opnfv_yardstick_tc074.yaml +++ b/tests/opnfv/test_cases/opnfv_yardstick_tc074.yaml @@ -14,13 +14,14 @@ description: > StorPerf is a tool to measure block and object storage performance in an NFVI. {% set public_network = public_network or "ext-net" %} -{% set StorPerf_ip = StorPerf_ip or "192.168.10.6" %} +{% set StorPerf_ip = StorPerf_ip or "192.168.200.1" %} scenarios: - type: StorPerf options: agent_count: 1 agent_image: "Ubuntu-16.04" + agent_flavor: "storperf" public_network: {{public_network}} volume_size: 4 block_sizes: "4096" diff --git a/tests/opnfv/test_suites/opnfv_os-nosdn-nofeature-ha_daily.yaml b/tests/opnfv/test_suites/opnfv_os-nosdn-nofeature-ha_daily.yaml index ba1a93cec..dea44c8b3 100644 --- a/tests/opnfv/test_suites/opnfv_os-nosdn-nofeature-ha_daily.yaml +++ b/tests/opnfv/test_suites/opnfv_os-nosdn-nofeature-ha_daily.yaml @@ -134,10 +134,12 @@ test_cases: file_name: opnfv_yardstick_tc074.yaml constraint: installer: compass - pod: huawei-pod1 + pod: huawei-pod1, huawei-pod2 task_args: huawei-pod1: '{"public_network": "ext-net", - "StorPerf_ip": "192.168.200.1"}' + "StorPerf_ip": "192.168.10.6"}' + huawei-pod2: '{"public_network": "ext-net", + "StorPerf_ip": "192.168.11.2"}' - file_name: opnfv_yardstick_tc075.yaml constraint: diff --git a/tests/unit/apiserver/utils/test_common.py b/tests/unit/apiserver/utils/test_common.py deleted file mode 100644 index ad81cb76b..000000000 --- a/tests/unit/apiserver/utils/test_common.py +++ /dev/null @@ -1,41 +0,0 @@ -############################################################################## -# Copyright (c) 2016 Huawei Technologies Co.,Ltd and others. -# -# All rights reserved. This program and the accompanying materials -# are made available under the terms of the Apache License, Version 2.0 -# which accompanies this distribution, and is available at -# http://www.apache.org/licenses/LICENSE-2.0 -############################################################################## -from __future__ import absolute_import -import unittest - -from api.utils import common - - -class TranslateToStrTestCase(unittest.TestCase): - - def test_translate_to_str_unicode(self): - input_str = u'hello' - output_str = common.translate_to_str(input_str) - - result = 'hello' - self.assertEqual(result, output_str) - - def test_translate_to_str_dict_list_unicode(self): - input_str = { - u'hello': {u'hello': [u'world']} - } - output_str = common.translate_to_str(input_str) - - result = { - 'hello': {'hello': ['world']} - } - self.assertEqual(result, output_str) - - -def main(): - unittest.main() - - -if __name__ == '__main__': - main() diff --git a/tests/unit/benchmark/contexts/nodes_duplicate_sample_new.yaml b/tests/unit/benchmark/contexts/nodes_duplicate_sample_new.yaml new file mode 100644 index 000000000..306915ca1 --- /dev/null +++ b/tests/unit/benchmark/contexts/nodes_duplicate_sample_new.yaml @@ -0,0 +1,32 @@ +nodes: +- + name: sriov + role: Sriov1 + ip: 10.123.123.122 + user: root + auth_type: password + password: password + vf_macs: + - "00:00:00:00:00:00" + - "00:00:00:00:00:00" + phy_ports: # Physical ports to configure sriov + - "0000:06:00.0" + - "0000:06:00.1" + phy_driver: i40e # kernel driver + images: "/var/lib/libvirt/images/ubuntu1.img" +- + name: sriov + role: Sriov1 + ip: 10.123.123.111 + user: root + auth_type: password + password: password + vf_macs: + - "00:00:00:00:00:00" + - "00:00:00:00:00:00" + phy_ports: # Physical ports to configure sriov + - "0000:06:00.0" + - "0000:06:00.1" + phy_driver: i40e # kernel driver + images: "/var/lib/libvirt/images/ubuntu1.img" + diff --git a/tests/unit/benchmark/contexts/nodes_duplicate_sample_ovs.yaml b/tests/unit/benchmark/contexts/nodes_duplicate_sample_ovs.yaml new file mode 100644 index 000000000..65449c91c --- /dev/null +++ b/tests/unit/benchmark/contexts/nodes_duplicate_sample_ovs.yaml @@ -0,0 +1,63 @@ +# Copyright (c) 2016 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. +nodes: +- + name: ovs + role: test + ip: 10.223.197.222 + user: root + auth_type: password + password: intel123 + vpath: "/usr/local/" + vports: + - dpdkvhostuser0 + - dpdkvhostuser1 + vports_mac: + - "00:00:00:00:00:03" + - "00:00:00:00:00:04" + phy_ports: # Physical ports to configure ovs + - "0000:06:00.0" + - "0000:06:00.1" + flow: + - ovs-ofctl add-flow br0 in_port=1,action=output:3 + - ovs-ofctl add-flow br0 in_port=3,action=output:1 + - ovs-ofctl add-flow br0 in_port=4,action=output:2 + - ovs-ofctl add-flow br0 in_port=2,action=output:4 + phy_driver: i40e # kernel driver + images: "/var/lib/libvirt/images/ubuntu1.img" +- + name: ovs + role: test + ip: 10.223.197.112 + user: root + auth_type: password + password: intel123 + vpath: "/usr/local/" + vports: + - dpdkvhostuser0 + - dpdkvhostuser1 + vports_mac: + - "00:00:00:00:00:03" + - "00:00:00:00:00:04" + phy_ports: # Physical ports to configure ovs + - "0000:06:00.0" + - "0000:06:00.1" + flow: + - ovs-ofctl add-flow br0 in_port=1,action=output:3 + - ovs-ofctl add-flow br0 in_port=3,action=output:1 + - ovs-ofctl add-flow br0 in_port=4,action=output:2 + - ovs-ofctl add-flow br0 in_port=2,action=output:4 + phy_driver: i40e # kernel driver + images: "/var/lib/libvirt/images/ubuntu1.img" + diff --git a/tests/unit/benchmark/contexts/nodes_sample_new.yaml b/tests/unit/benchmark/contexts/nodes_sample_new.yaml new file mode 100644 index 000000000..a400bec03 --- /dev/null +++ b/tests/unit/benchmark/contexts/nodes_sample_new.yaml @@ -0,0 +1,96 @@ +# Copyright (c) 2016-2017 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. + +nodes: +- + name: trafficgen_1 + role: TrafficGen + ip: 10.123.123.123 + user: root + auth_type: password + password: password + interfaces: + xe0: # logical name from topology.yaml and vnfd.yaml + vpci: "0000:03:00.0" + driver: ixgbe + dpdk_port_num: 0 + local_ip: "152.16.100.20" + netmask: "255.255.255.0" + local_mac: "00:00:00:00:00:00" + xe1: # logical name from topology.yaml and vnfd.yaml + vpci: "0000:03:00.1" + driver: ixgbe + dpdk_port_num: 1 + local_ip: "152.16.100.21" + netmask: "255.255.255.0" + local_mac: "00:00:00:00:00:00" +- + name: sriov + role: Sriov + ip: 10.123.123.122 + user: root + auth_type: password + password: password + vf_macs: + - "00:00:00:00:00:00" + - "00:00:00:00:00:00" + phy_ports: # Physical ports to configure sriov + - "0000:06:00.0" + - "0000:06:00.1" + phy_driver: i40e # kernel driver + images: "/var/lib/libvirt/images/ubuntu1.img" + +- + name: vnf + role: vnf + ip: 10.123.123.121 + user: root + auth_type: password + password: password + host: 10.123.123.121 #BM host == ip, SRIOV & ovs-dpdk host == compute node. + interfaces: + xe0: # logical name from topology.yaml and vnfd.yaml + vpci: "0000:06:00.0" + driver: i40e + dpdk_port_num: 0 + local_ip: "152.16.100.19" + netmask: "255.255.255.0" + local_mac: "00:00:00:00:00:00" + + xe1: # logical name from topology.yaml and vnfd.yaml + vpci: "0000:06:00.1" + driver: i40e + dpdk_port_num: 1 + local_ip: "152.16.40.19" + netmask: "255.255.255.0" + local_mac: "00:00:00:00:00:00" + routing_table: + - network: "152.16.100.20" + netmask: "255.255.255.0" + gateway: "152.16.100.20" + if: "xe0" + - network: "152.16.40.20" + netmask: "255.255.255.0" + gateway: "152.16.40.20" + if: "xe1" + nd_route_tbl: + - network: "0064:ff9b:0:0:0:0:9810:6414" + netmask: "112" + gateway: "0064:ff9b:0:0:0:0:9810:6414" + if: "xe0" + - network: "0064:ff9b:0:0:0:0:9810:2814" + netmask: "112" + gateway: "0064:ff9b:0:0:0:0:9810:2814" + if: "xe1" + diff --git a/tests/unit/benchmark/contexts/nodes_sample_new_sriov.yaml b/tests/unit/benchmark/contexts/nodes_sample_new_sriov.yaml new file mode 100644 index 000000000..55ff2e778 --- /dev/null +++ b/tests/unit/benchmark/contexts/nodes_sample_new_sriov.yaml @@ -0,0 +1,82 @@ +nodes: +- + name: trafficgen_1 + role: TrafficGen + ip: 10.123.123.123 + user: root + auth_type: password + password: password + interfaces: + xe0: # logical name from topology.yaml and vnfd.yaml + vpci: "0000:03:00.0" + driver: ixgbe + dpdk_port_num: 0 + local_ip: "152.16.100.20" + netmask: "255.255.255.0" + local_mac: "00:00:00:00:00:00" + xe1: # logical name from topology.yaml and vnfd.yaml + vpci: "0000:03:00.1" + driver: ixgbe + dpdk_port_num: 1 + local_ip: "152.16.100.21" + netmask: "255.255.255.0" + local_mac: "00:00:00:00:00:00" +- + name: sriov + role: Sriov1 + ip: 10.123.123.122 + user: root + auth_type: password + password: password + vf_macs: + - "00:00:00:00:00:00" + - "00:00:00:00:00:00" + phy_ports: # Physical ports to configure sriov + - "0000:06:00.0" + - "0000:06:00.1" + phy_driver: i40e # kernel driver + images: "/var/lib/libvirt/images/ubuntu1.img" + +- + name: vnf + role: vnf + ip: 10.123.123.121 + user: root + auth_type: password + password: password + host: 10.123.123.121 #BM host == ip, SRIOV & ovs-dpdk host == compute node. + interfaces: + xe0: # logical name from topology.yaml and vnfd.yaml + vpci: "0000:06:00.0" + driver: i40e + dpdk_port_num: 0 + local_ip: "152.16.100.19" + netmask: "255.255.255.0" + local_mac: "00:00:00:00:00:00" + + xe1: # logical name from topology.yaml and vnfd.yaml + vpci: "0000:06:00.1" + driver: i40e + dpdk_port_num: 1 + local_ip: "152.16.40.19" + netmask: "255.255.255.0" + local_mac: "00:00:00:00:00:00" + routing_table: + - network: "152.16.100.20" + netmask: "255.255.255.0" + gateway: "152.16.100.20" + if: "xe0" + - network: "152.16.40.20" + netmask: "255.255.255.0" + gateway: "152.16.40.20" + if: "xe1" + nd_route_tbl: + - network: "0064:ff9b:0:0:0:0:9810:6414" + netmask: "112" + gateway: "0064:ff9b:0:0:0:0:9810:6414" + if: "xe0" + - network: "0064:ff9b:0:0:0:0:9810:2814" + netmask: "112" + gateway: "0064:ff9b:0:0:0:0:9810:2814" + if: "xe1" + diff --git a/tests/unit/benchmark/contexts/nodes_sample_ovs.yaml b/tests/unit/benchmark/contexts/nodes_sample_ovs.yaml new file mode 100644 index 000000000..b1da1ea9f --- /dev/null +++ b/tests/unit/benchmark/contexts/nodes_sample_ovs.yaml @@ -0,0 +1,104 @@ +# Copyright (c) 2016 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. + +nodes: +- + name: trafficgen_1 + role: TrafficGen + ip: 10.223.197.182 + user: root + auth_type: password + password: intel123 + interfaces: + xe0: # logical name from topology.yaml and vnfd.yaml + vpci: "0000:03:00.0" + driver: ixgbe + dpdk_port_num: 0 + local_ip: "152.16.100.20" + netmask: "255.255.255.0" + local_mac: "90:e2:ba:77:ce:68" + xe1: # logical name from topology.yaml and vnfd.yaml + vpci: "0000:03:00.1" + driver: ixgbe + dpdk_port_num: 1 + local_ip: "152.16.100.21" + netmask: "255.255.255.0" + local_mac: "90:e2:ba:77:ce:69" +- + name: ovs + role: Ovsdpdk + ip: 10.223.197.222 + user: root + auth_type: password + password: intel123 + vpath: "/usr/local/" + vports: + - dpdkvhostuser0 + - dpdkvhostuser1 + vports_mac: + - "00:00:00:00:00:03" + - "00:00:00:00:00:04" + phy_ports: # Physical ports to configure ovs + - "0000:06:00.0" + - "0000:06:00.1" + flow: + - ovs-ofctl add-flow br0 in_port=1,action=output:3 + - ovs-ofctl add-flow br0 in_port=3,action=output:1 + - ovs-ofctl add-flow br0 in_port=4,action=output:2 + - ovs-ofctl add-flow br0 in_port=2,action=output:4 + phy_driver: i40e # kernel driver + images: "/var/lib/libvirt/images/ubuntu1.img" + +- + name: vnf + role: vnf + ip: 10.223.197.155 + user: root + auth_type: password + password: intel123 + host: 10.223.197.140 + interfaces: + xe0: # logical name from topology.yaml and vnfd.yaml + vpci: "0000:00:04.0" + driver: virtio-pci + dpdk_port_num: 0 + local_ip: "152.16.100.19" + netmask: "255.255.255.0" + local_mac: "00:00:00:00:00:03" + + xe1: # logical name from topology.yaml and vnfd.yaml + vpci: "0000:00:05.0" + driver: virtio-pci + dpdk_port_num: 1 + local_ip: "152.16.40.19" + netmask: "255.255.255.0" + local_mac: "00:00:00:00:00:04" + routing_table: + - network: "152.16.100.20" + netmask: "255.255.255.0" + gateway: "152.16.100.20" + if: "xe0" + - network: "152.16.40.20" + netmask: "255.255.255.0" + gateway: "152.16.40.20" + if: "xe1" + nd_route_tbl: + - network: "0064:ff9b:0:0:0:0:9810:6414" + netmask: "112" + gateway: "0064:ff9b:0:0:0:0:9810:6414" + if: "xe0" + - network: "0064:ff9b:0:0:0:0:9810:2814" + netmask: "112" + gateway: "0064:ff9b:0:0:0:0:9810:2814" + if: "xe1" diff --git a/tests/unit/benchmark/contexts/nodes_sample_ovsdpdk.yaml b/tests/unit/benchmark/contexts/nodes_sample_ovsdpdk.yaml new file mode 100644 index 000000000..c02849a05 --- /dev/null +++ b/tests/unit/benchmark/contexts/nodes_sample_ovsdpdk.yaml @@ -0,0 +1,104 @@ +# Copyright (c) 2016 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. + +nodes: +- + name: trafficgen_1 + role: TrafficGen + ip: 10.223.197.182 + user: root + auth_type: password + password: intel123 + interfaces: + xe0: # logical name from topology.yaml and vnfd.yaml + vpci: "0000:03:00.0" + driver: ixgbe + dpdk_port_num: 0 + local_ip: "152.16.100.20" + netmask: "255.255.255.0" + local_mac: "90:e2:ba:77:ce:68" + xe1: # logical name from topology.yaml and vnfd.yaml + vpci: "0000:03:00.1" + driver: ixgbe + dpdk_port_num: 1 + local_ip: "152.16.100.21" + netmask: "255.255.255.0" + local_mac: "90:e2:ba:77:ce:69" +- + name: ovs + role: Ovsdpdk1 + ip: 10.223.197.222 + user: root + auth_type: password + password: intel123 + vpath: "/usr/local/" + vports: + - dpdkvhostuser0 + - dpdkvhostuser1 + vports_mac: + - "00:00:00:00:00:03" + - "00:00:00:00:00:04" + phy_ports: # Physical ports to configure ovs + - "0000:06:00.0" + - "0000:06:00.1" + flow: + - ovs-ofctl add-flow br0 in_port=1,action=output:3 + - ovs-ofctl add-flow br0 in_port=3,action=output:1 + - ovs-ofctl add-flow br0 in_port=4,action=output:2 + - ovs-ofctl add-flow br0 in_port=2,action=output:4 + phy_driver: i40e # kernel driver + images: "/var/lib/libvirt/images/ubuntu1.img" + +- + name: vnf + role: vnf + ip: 10.223.197.155 + user: root + auth_type: password + password: intel123 + host: 10.223.197.140 + interfaces: + xe0: # logical name from topology.yaml and vnfd.yaml + vpci: "0000:00:04.0" + driver: virtio-pci + dpdk_port_num: 0 + local_ip: "152.16.100.19" + netmask: "255.255.255.0" + local_mac: "00:00:00:00:00:03" + + xe1: # logical name from topology.yaml and vnfd.yaml + vpci: "0000:00:05.0" + driver: virtio-pci + dpdk_port_num: 1 + local_ip: "152.16.40.19" + netmask: "255.255.255.0" + local_mac: "00:00:00:00:00:04" + routing_table: + - network: "152.16.100.20" + netmask: "255.255.255.0" + gateway: "152.16.100.20" + if: "xe0" + - network: "152.16.40.20" + netmask: "255.255.255.0" + gateway: "152.16.40.20" + if: "xe1" + nd_route_tbl: + - network: "0064:ff9b:0:0:0:0:9810:6414" + netmask: "112" + gateway: "0064:ff9b:0:0:0:0:9810:6414" + if: "xe0" + - network: "0064:ff9b:0:0:0:0:9810:2814" + netmask: "112" + gateway: "0064:ff9b:0:0:0:0:9810:2814" + if: "xe1" diff --git a/tests/unit/benchmark/contexts/ovs_sample_password.yaml b/tests/unit/benchmark/contexts/ovs_sample_password.yaml new file mode 100644 index 000000000..b1da1ea9f --- /dev/null +++ b/tests/unit/benchmark/contexts/ovs_sample_password.yaml @@ -0,0 +1,104 @@ +# Copyright (c) 2016 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. + +nodes: +- + name: trafficgen_1 + role: TrafficGen + ip: 10.223.197.182 + user: root + auth_type: password + password: intel123 + interfaces: + xe0: # logical name from topology.yaml and vnfd.yaml + vpci: "0000:03:00.0" + driver: ixgbe + dpdk_port_num: 0 + local_ip: "152.16.100.20" + netmask: "255.255.255.0" + local_mac: "90:e2:ba:77:ce:68" + xe1: # logical name from topology.yaml and vnfd.yaml + vpci: "0000:03:00.1" + driver: ixgbe + dpdk_port_num: 1 + local_ip: "152.16.100.21" + netmask: "255.255.255.0" + local_mac: "90:e2:ba:77:ce:69" +- + name: ovs + role: Ovsdpdk + ip: 10.223.197.222 + user: root + auth_type: password + password: intel123 + vpath: "/usr/local/" + vports: + - dpdkvhostuser0 + - dpdkvhostuser1 + vports_mac: + - "00:00:00:00:00:03" + - "00:00:00:00:00:04" + phy_ports: # Physical ports to configure ovs + - "0000:06:00.0" + - "0000:06:00.1" + flow: + - ovs-ofctl add-flow br0 in_port=1,action=output:3 + - ovs-ofctl add-flow br0 in_port=3,action=output:1 + - ovs-ofctl add-flow br0 in_port=4,action=output:2 + - ovs-ofctl add-flow br0 in_port=2,action=output:4 + phy_driver: i40e # kernel driver + images: "/var/lib/libvirt/images/ubuntu1.img" + +- + name: vnf + role: vnf + ip: 10.223.197.155 + user: root + auth_type: password + password: intel123 + host: 10.223.197.140 + interfaces: + xe0: # logical name from topology.yaml and vnfd.yaml + vpci: "0000:00:04.0" + driver: virtio-pci + dpdk_port_num: 0 + local_ip: "152.16.100.19" + netmask: "255.255.255.0" + local_mac: "00:00:00:00:00:03" + + xe1: # logical name from topology.yaml and vnfd.yaml + vpci: "0000:00:05.0" + driver: virtio-pci + dpdk_port_num: 1 + local_ip: "152.16.40.19" + netmask: "255.255.255.0" + local_mac: "00:00:00:00:00:04" + routing_table: + - network: "152.16.100.20" + netmask: "255.255.255.0" + gateway: "152.16.100.20" + if: "xe0" + - network: "152.16.40.20" + netmask: "255.255.255.0" + gateway: "152.16.40.20" + if: "xe1" + nd_route_tbl: + - network: "0064:ff9b:0:0:0:0:9810:6414" + netmask: "112" + gateway: "0064:ff9b:0:0:0:0:9810:6414" + if: "xe0" + - network: "0064:ff9b:0:0:0:0:9810:2814" + netmask: "112" + gateway: "0064:ff9b:0:0:0:0:9810:2814" + if: "xe1" diff --git a/tests/unit/benchmark/contexts/ovs_sample_ssh_key.yaml b/tests/unit/benchmark/contexts/ovs_sample_ssh_key.yaml new file mode 100644 index 000000000..896ec33bb --- /dev/null +++ b/tests/unit/benchmark/contexts/ovs_sample_ssh_key.yaml @@ -0,0 +1,69 @@ +############################################################################## +# Copyright (c) 2015 Huawei Technologies Co.,Ltd and others. +# +# All rights reserved. This program and the accompanying materials +# are made available under the terms of the Apache License, Version 2.0 +# which accompanies this distribution, and is available at +# http://www.apache.org/licenses/LICENSE-2.0 +############################################################################## +--- +# Sample config file about the POD information, including the +# name/IP/user/ssh key of Bare Metal and Controllers/Computes +# +# The options of this config file include: +# name: the name of this node +# role: node's role, support role: Master/Controller/Comupte/BareMetal +# ip: the node's IP address +# user: the username for login +# key_filename:the path of the private key file for login + +nodes: +- + name: trafficgen_1 + role: TrafficGen + ip: 10.10.10.10 + auth_type: ssh_key + user: root + ssh_port: 22 + key_filename: /root/.ssh/id_rsa + interfaces: + xe0: # logical name from topology.yaml and vnfd.yaml + vpci: "0000:03:00.0" + driver: ixgbe + dpdk_port_num: 0 + local_ip: "152.16.100.20" + netmask: "255.255.255.0" + local_mac: "90:e2:ba:77:ce:68" + xe1: # logical name from topology.yaml and vnfd.yaml + vpci: "0000:03:00.1" + driver: ixgbe + dpdk_port_num: 1 + local_ip: "152.16.100.21" + netmask: "255.255.255.0" + local_mac: "90:e2:ba:77:ce:69" +- + name: ovs + role: Ovsdpdk + ip: 10.223.197.222 + auth_type: ssh_key + user: root + ssh_port: 22 + key_filename: /root/.ssh/id_rsa + vpath: "/usr/local/" + vports: + - dpdkvhostuser0 + - dpdkvhostuser1 + vports_mac: + - "00:00:00:00:00:03" + - "00:00:00:00:00:04" + phy_ports: # Physical ports to configure ovs + - "0000:06:00.0" + - "0000:06:00.1" + flow: + - ovs-ofctl add-flow br0 in_port=1,action=output:3 + - ovs-ofctl add-flow br0 in_port=3,action=output:1 + - ovs-ofctl add-flow br0 in_port=4,action=output:2 + - ovs-ofctl add-flow br0 in_port=2,action=output:4 + phy_driver: i40e # kernel driver + images: "/var/lib/libvirt/images/ubuntu1.img" + diff --git a/tests/unit/benchmark/contexts/ovs_sample_write_to_file.txt b/tests/unit/benchmark/contexts/ovs_sample_write_to_file.txt new file mode 100644 index 000000000..f0eec86f6 --- /dev/null +++ b/tests/unit/benchmark/contexts/ovs_sample_write_to_file.txt @@ -0,0 +1 @@ +some content
\ No newline at end of file diff --git a/tests/unit/benchmark/contexts/sriov_sample_password.yaml b/tests/unit/benchmark/contexts/sriov_sample_password.yaml new file mode 100644 index 000000000..4f60e46d5 --- /dev/null +++ b/tests/unit/benchmark/contexts/sriov_sample_password.yaml @@ -0,0 +1,52 @@ +# Copyright (c) 2016-2017 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. + +nodes: +- + name: trafficgen_1 + role: TrafficGen + ip: 10.10.10.10 + auth_type: password + user: root + password: password + interfaces: + xe0: # logical name from topology.yaml and vnfd.yaml + vpci: "0000:03:00.0" + driver: ixgbe + dpdk_port_num: 0 + local_ip: "152.16.100.20" + netmask: "255.255.255.0" + local_mac: "90:e2:ba:77:ce:68" + xe1: # logical name from topology.yaml and vnfd.yaml + vpci: "0000:03:00.1" + driver: ixgbe + dpdk_port_num: 1 + local_ip: "152.16.100.21" + netmask: "255.255.255.0" + local_mac: "90:e2:ba:77:ce:69" +- + name: sriov + role: Sriov + ip: 10.10.10.11 + auth_type: password + user: root + password: password + vf_macs: + - "00:00:00:71:7d:25" + - "00:00:00:71:7d:26" + phy_ports: # Physical ports to configure sriov + - "0000:06:00.0" + - "0000:06:00.1" + phy_driver: i40e # kernel driver + images: "/var/lib/libvirt/images/ubuntu1.img" diff --git a/tests/unit/benchmark/contexts/sriov_sample_ssh_key.yaml b/tests/unit/benchmark/contexts/sriov_sample_ssh_key.yaml new file mode 100644 index 000000000..faa496771 --- /dev/null +++ b/tests/unit/benchmark/contexts/sriov_sample_ssh_key.yaml @@ -0,0 +1,54 @@ +# Copyright (c) 2016-2017 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. + +nodes: +- + name: trafficgen_1 + role: TrafficGen + ip: 10.10.10.10 + auth_type: ssh_key + user: root + ssh_port: 22 + key_filename: /root/.ssh/id_rsa + interfaces: + xe0: # logical name from topology.yaml and vnfd.yaml + vpci: "0000:03:00.0" + driver: ixgbe + dpdk_port_num: 0 + local_ip: "152.16.100.20" + netmask: "255.255.255.0" + local_mac: "90:e2:ba:77:ce:68" + xe1: # logical name from topology.yaml and vnfd.yaml + vpci: "0000:03:00.1" + driver: ixgbe + dpdk_port_num: 1 + local_ip: "152.16.100.21" + netmask: "255.255.255.0" + local_mac: "90:e2:ba:77:ce:69" +- + name: sriov + role: Sriov + ip: 10.10.10.11 + auth_type: ssh_key + user: root + ssh_port: 22 + key_filename: /root/.ssh/id_rsa + vf_macs: + - "00:00:00:71:7d:25" + - "00:00:00:71:7d:26" + phy_ports: # Physical ports to configure sriov + - "0000:06:00.0" + - "0000:06:00.1" + phy_driver: i40e # kernel driver + images: "/var/lib/libvirt/images/ubuntu1.img" diff --git a/tests/unit/benchmark/contexts/sriov_sample_write_to_file.txt b/tests/unit/benchmark/contexts/sriov_sample_write_to_file.txt new file mode 100644 index 000000000..f0eec86f6 --- /dev/null +++ b/tests/unit/benchmark/contexts/sriov_sample_write_to_file.txt @@ -0,0 +1 @@ +some content
\ No newline at end of file diff --git a/tests/unit/benchmark/contexts/test_heat.py b/tests/unit/benchmark/contexts/test_heat.py index 3dadd48eb..ae57402c0 100644 --- a/tests/unit/benchmark/contexts/test_heat.py +++ b/tests/unit/benchmark/contexts/test_heat.py @@ -13,6 +13,7 @@ from __future__ import absolute_import +import ipaddress import logging import os import unittest @@ -120,7 +121,8 @@ class HeatContextTestCase(unittest.TestCase): mock_template.add_router_interface.assert_called_with("bar-fool-network-router-if0", "bar-fool-network-router", "bar-fool-network-subnet") @mock.patch('yardstick.benchmark.contexts.heat.HeatTemplate') - def test_deploy(self, mock_template): + @mock.patch('yardstick.benchmark.contexts.heat.get_neutron_client') + def test_deploy(self, mock_neutron, mock_template): self.test_context.name = 'foo' self.test_context.template_file = '/bar/baz/some-heat-file' @@ -133,6 +135,59 @@ class HeatContextTestCase(unittest.TestCase): self.test_context.heat_parameters) self.assertIsNotNone(self.test_context.stack) + def test_add_server_port(self): + network1 = mock.MagicMock() + network1.vld_id = 'vld111' + network2 = mock.MagicMock() + network2.vld_id = 'vld777' + self.test_context.name = 'foo' + self.test_context.stack = mock.MagicMock() + self.test_context.networks = { + 'a': network1, + 'c': network2, + } + self.test_context.stack.outputs = { + u'b': u'10.20.30.45', + u'b-subnet_id': 1, + u'foo-a-subnet-cidr': u'10.20.0.0/15', + u'foo-a-subnet-gateway_ip': u'10.20.30.1', + u'b-mac_address': u'00:01', + u'b-device_id': u'dev21', + u'b-network_id': u'net789', + u'd': u'40.30.20.15', + u'd-subnet_id': 2, + u'foo-c-subnet-cidr': u'40.30.0.0/18', + u'foo-c-subnet-gateway_ip': u'40.30.20.254', + u'd-mac_address': u'00:10', + u'd-device_id': u'dev43', + u'd-network_id': u'net987', + } + server = mock.MagicMock() + server.ports = OrderedDict([ + ('a', {'stack_name': 'b'}), + ('c', {'stack_name': 'd'}), + ]) + + expected = { + "private_ip": '10.20.30.45', + "subnet_id": 1, + "subnet_cidr": '10.20.0.0/15', + "network": '10.20.0.0', + "netmask": '255.254.0.0', + "gateway_ip": '10.20.30.1', + "mac_address": '00:01', + "device_id": 'dev21', + "network_id": 'net789', + "network_name": 'a', + "local_mac": '00:01', + "local_ip": '10.20.30.45', + "vld_id": 'vld111', + } + self.test_context.add_server_port(server) + self.assertEqual(server.private_ip, '10.20.30.45') + self.assertEqual(len(server.interfaces), 2) + self.assertDictEqual(server.interfaces['a'], expected) + @mock.patch('yardstick.benchmark.contexts.heat.HeatTemplate') def test_undeploy(self, mock_template): @@ -155,3 +210,57 @@ class HeatContextTestCase(unittest.TestCase): self.assertEqual(result['ip'], '127.0.0.1') self.assertEqual(result['private_ip'], '10.0.0.1') + + def test__get_network(self): + network1 = mock.MagicMock() + network1.name = 'net_1' + network1.vld_id = 'vld111' + network1.segmentation_id = 'seg54' + network1.network_type = 'type_a' + network1.physical_network = 'phys' + + network2 = mock.MagicMock() + network2.name = 'net_2' + network2.vld_id = 'vld999' + network2.segmentation_id = 'seg45' + network2.network_type = 'type_b' + network2.physical_network = 'virt' + + self.test_context.networks = { + 'a': network1, + 'b': network2, + } + + attr_name = None + self.assertIsNone(self.test_context._get_network(attr_name)) + + attr_name = {} + self.assertIsNone(self.test_context._get_network(attr_name)) + + attr_name = {'vld_id': 'vld777'} + self.assertIsNone(self.test_context._get_network(attr_name)) + + attr_name = 'vld777' + self.assertIsNone(self.test_context._get_network(attr_name)) + + attr_name = {'vld_id': 'vld999'} + expected = { + "name": 'net_2', + "vld_id": 'vld999', + "segmentation_id": 'seg45', + "network_type": 'type_b', + "physical_network": 'virt', + } + result = self.test_context._get_network(attr_name) + self.assertDictEqual(result, expected) + + attr_name = 'a' + expected = { + "name": 'net_1', + "vld_id": 'vld111', + "segmentation_id": 'seg54', + "network_type": 'type_a', + "physical_network": 'phys', + } + result = self.test_context._get_network(attr_name) + self.assertDictEqual(result, expected) diff --git a/tests/unit/benchmark/contexts/test_kubernetes.py b/tests/unit/benchmark/contexts/test_kubernetes.py new file mode 100644 index 000000000..f47c07a67 --- /dev/null +++ b/tests/unit/benchmark/contexts/test_kubernetes.py @@ -0,0 +1,165 @@ +#!/usr/bin/env python + +############################################################################## +# Copyright (c) 2015 Huawei Technologies Co.,Ltd and others. +# +# All rights reserved. This program and the accompanying materials +# are made available under the terms of the Apache License, Version 2.0 +# which accompanies this distribution, and is available at +# http://www.apache.org/licenses/LICENSE-2.0 +############################################################################## + +# Unittest for yardstick.benchmark.contexts.kubernetes + +from __future__ import absolute_import +import unittest +import mock + +from yardstick.benchmark.contexts.kubernetes import KubernetesContext + + +context_cfg = { + 'type': 'Kubernetes', + 'name': 'k8s', + 'servers': { + 'host': { + 'image': 'openretriever/yardstick', + 'command': '/bin/bash', + 'args': ['-c', 'chmod 700 ~/.ssh; chmod 600 ~/.ssh/*; \ +service ssh restart;while true ; do sleep 10000; done'] + }, + 'target': { + 'image': 'openretriever/yardstick', + 'command': '/bin/bash', + 'args': ['-c', 'chmod 700 ~/.ssh; chmod 600 ~/.ssh/*; \ +service ssh restart;while true ; do sleep 10000; done'] + } + } +} + +prefix = 'yardstick.benchmark.contexts.kubernetes' + + +class UndeployTestCase(unittest.TestCase): + + @mock.patch('{}.KubernetesContext._delete_ssh_key'.format(prefix)) + @mock.patch('{}.KubernetesContext._delete_rcs'.format(prefix)) + @mock.patch('{}.KubernetesContext._delete_pods'.format(prefix)) + def test_undeploy(self, + mock_delete_pods, + mock_delete_rcs, + mock_delete_ssh): + + k8s_context = KubernetesContext() + k8s_context.init(context_cfg) + k8s_context.undeploy() + self.assertTrue(mock_delete_ssh.called) + self.assertTrue(mock_delete_rcs.called) + self.assertTrue(mock_delete_pods.called) + + +class DeployTestCase(unittest.TestCase): + + @mock.patch('{}.KubernetesContext._wait_until_running'.format(prefix)) + @mock.patch('{}.KubernetesTemplate.get_rc_pods'.format(prefix)) + @mock.patch('{}.KubernetesContext._create_rcs'.format(prefix)) + @mock.patch('{}.KubernetesContext._set_ssh_key'.format(prefix)) + def test_deploy(self, + mock_set_ssh_key, + mock_create_rcs, + mock_get_rc_pods, + mock_wait_until_running): + + k8s_context = KubernetesContext() + k8s_context.init(context_cfg) + k8s_context.deploy() + self.assertTrue(mock_set_ssh_key.called) + self.assertTrue(mock_create_rcs.called) + self.assertTrue(mock_get_rc_pods.called) + self.assertTrue(mock_wait_until_running.called) + + +class SSHKeyTestCase(unittest.TestCase): + + @mock.patch('{}.k8s_utils.delete_config_map'.format(prefix)) + @mock.patch('{}.k8s_utils.create_config_map'.format(prefix)) + def test_ssh_key(self, mock_create, mock_delete): + + k8s_context = KubernetesContext() + k8s_context.init(context_cfg) + k8s_context._set_ssh_key() + k8s_context._delete_ssh_key() + self.assertTrue(mock_create.called) + self.assertTrue(mock_delete.called) + + +class WaitUntilRunningTestCase(unittest.TestCase): + + @mock.patch('{}.k8s_utils.read_pod_status'.format(prefix)) + def test_wait_until_running(self, mock_read_pod_status): + + k8s_context = KubernetesContext() + k8s_context.init(context_cfg) + k8s_context.template.pods = ['server'] + mock_read_pod_status.return_value = 'Running' + k8s_context._wait_until_running() + + +class GetServerTestCase(unittest.TestCase): + + @mock.patch('{}.k8s_utils.get_pod_list'.format(prefix)) + def test_get_server(self, mock_get_pod_list): + k8s_context = KubernetesContext() + k8s_context.init(context_cfg) + + mock_get_pod_list.return_value.items = [] + server = k8s_context._get_server('server') + self.assertIsNone(server) + + +class CreateRcsTestCase(unittest.TestCase): + + @mock.patch('{}.KubernetesContext._create_rc'.format(prefix)) + def test_create_rcs(self, mock_create_rc): + k8s_context = KubernetesContext() + k8s_context.init(context_cfg) + k8s_context._create_rcs() + self.assertTrue(mock_create_rc.called) + + +class CreateRcTestCase(unittest.TestCase): + + @mock.patch('{}.k8s_utils.create_replication_controller'.format(prefix)) + def test_create_rc(self, mock_create_replication_controller): + k8s_context = KubernetesContext() + k8s_context.init(context_cfg) + k8s_context._create_rc({}) + self.assertTrue(mock_create_replication_controller.called) + + +class DeleteRcsTestCases(unittest.TestCase): + + @mock.patch('{}.KubernetesContext._delete_rc'.format(prefix)) + def test_delete_rcs(self, mock_delete_rc): + k8s_context = KubernetesContext() + k8s_context.init(context_cfg) + k8s_context._delete_rcs() + self.assertTrue(mock_delete_rc.called) + + +class DeleteRcTestCase(unittest.TestCase): + + @mock.patch('{}.k8s_utils.delete_replication_controller'.format(prefix)) + def test_delete_rc(self, mock_delete_replication_controller): + k8s_context = KubernetesContext() + k8s_context.init(context_cfg) + k8s_context._delete_rc({}) + self.assertTrue(mock_delete_replication_controller.called) + + +def main(): + unittest.main() + + +if __name__ == '__main__': + main() diff --git a/tests/unit/benchmark/contexts/test_model.py b/tests/unit/benchmark/contexts/test_model.py index 3fb186b9b..1ce550306 100644 --- a/tests/unit/benchmark/contexts/test_model.py +++ b/tests/unit/benchmark/contexts/test_model.py @@ -161,6 +161,23 @@ class NetworkTestCase(unittest.TestCase): self.assertEqual(model.Network.find_external_network(), 'ext_net') + def test_construct_gateway_ip_is_null(self): + + attrs = {'gateway_ip': 'null'} + test_network = model.Network('foo', self.mock_context, attrs) + self.assertEqual(test_network.gateway_ip, 'null') + + def test_construct_gateway_ip_is_none(self): + + attrs = {'gateway_ip': None} + test_network = model.Network('foo', self.mock_context, attrs) + self.assertEqual(test_network.gateway_ip, 'null') + + def test_construct_gateway_ip_is_absent(self): + + attrs = {} + test_network = model.Network('foo', self.mock_context, attrs) + self.assertIsNone(test_network.gateway_ip) class ServerTestCase(unittest.TestCase): @@ -214,11 +231,12 @@ class ServerTestCase(unittest.TestCase): attrs = {'image': 'some-image', 'flavor': 'some-flavor', 'floating_ip': '192.168.1.10', 'floating_ip_assoc': 'some-vm'} test_server = model.Server('foo', self.mock_context, attrs) - self.mock_context.flavors = ['flavor1', 'flavor2', 'some-flavor'] + self.mock_context.flavors = ['flavor1', 'flavor2', 'some-flavor'] mock_network = mock.Mock() mock_network.name = 'some-network' mock_network.stack_name = 'some-network-stack' + mock_network.allowed_address_pairs = ["1", "2"] mock_network.subnet_stack_name = 'some-network-stack-subnet' mock_network.provider = 'sriov' mock_network.external_network = 'ext_net' @@ -232,7 +250,8 @@ class ServerTestCase(unittest.TestCase): mock_network.stack_name, mock_network.subnet_stack_name, sec_group_id=self.mock_context.secgroup_name, - provider=mock_network.provider) + provider=mock_network.provider, + allowed_address_pairs=mock_network.allowed_address_pairs) mock_template.add_floating_ip.assert_called_with( 'some-server-fip', @@ -290,11 +309,12 @@ class ServerTestCase(unittest.TestCase): } test_server = model.Server('ServerFlavor-2', self.mock_context, attrs) - self.mock_context.flavors = ['flavor2'] + self.mock_context.flavors = ['flavor2'] mock_network = mock.Mock() - mock_network.configure_mock(name='some-network', stack_name= 'some-network-stack', - subnet_stack_name = 'some-network-stack-subnet', - provider = 'some-provider') + mock_network.allowed_address_pairs = ["1", "2"] + mock_network.configure_mock(name='some-network', stack_name='some-network-stack', + subnet_stack_name='some-network-stack-subnet', + provider='some-provider') test_server._add_instance(mock_template, 'ServerFlavor-2', [mock_network], 'hints') @@ -304,7 +324,8 @@ class ServerTestCase(unittest.TestCase): mock_network.stack_name, mock_network.subnet_stack_name, provider=mock_network.provider, - sec_group_id=self.mock_context.secgroup_name) + sec_group_id=self.mock_context.secgroup_name, + allowed_address_pairs=mock_network.allowed_address_pairs) mock_template.add_server.assert_called_with( 'ServerFlavor-2', 'some-image', diff --git a/tests/unit/benchmark/contexts/test_node.py b/tests/unit/benchmark/contexts/test_node.py index 4b35ca421..d5ce8c5cb 100644 --- a/tests/unit/benchmark/contexts/test_node.py +++ b/tests/unit/benchmark/contexts/test_node.py @@ -208,6 +208,50 @@ class NodeContextTestCase(unittest.TestCase): obj._get_client(node_name_args) self.assertTrue(wait_mock.called) + def test__get_network(self): + network1 = { + 'name': 'net_1', + 'vld_id': 'vld111', + 'segmentation_id': 'seg54', + 'network_type': 'type_a', + 'physical_network': 'phys', + } + network2 = { + 'name': 'net_2', + 'vld_id': 'vld999', + } + self.test_context.networks = { + 'a': network1, + 'b': network2, + } + + attr_name = {} + self.assertIsNone(self.test_context._get_network(attr_name)) + + attr_name = {'vld_id': 'vld777'} + self.assertIsNone(self.test_context._get_network(attr_name)) + + self.assertIsNone(self.test_context._get_network(None)) + + attr_name = 'vld777' + self.assertIsNone(self.test_context._get_network(attr_name)) + + attr_name = {'vld_id': 'vld999'} + expected = { + "name": 'net_2', + "vld_id": 'vld999', + "segmentation_id": None, + "network_type": None, + "physical_network": None, + } + result = self.test_context._get_network(attr_name) + self.assertDictEqual(result, expected) + + attr_name = 'a' + expected = network1 + result = self.test_context._get_network(attr_name) + self.assertDictEqual(result, expected) + def main(): unittest.main() diff --git a/tests/unit/benchmark/contexts/test_ovsdpdk.py b/tests/unit/benchmark/contexts/test_ovsdpdk.py new file mode 100644 index 000000000..ac25ec877 --- /dev/null +++ b/tests/unit/benchmark/contexts/test_ovsdpdk.py @@ -0,0 +1,325 @@ +# Copyright (c) 2016-2017 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. + +from __future__ import absolute_import +import os +import mock +import unittest + +from yardstick.benchmark.contexts import ovsdpdk + +NIC_INPUT = { + 'interface': {}, + 'vports_mac': ['00:00:00:71:7d:25', '00:00:00:71:7d:26'], + 'pci': ['0000:06:00.0', '0000:06:00.1'], + 'phy_driver': 'i40e'} +DRIVER = "i40e" +NIC_DETAILS = { + 'interface': {0: 'enp6s0f0', 1: 'enp6s0f1'}, + 'vports_mac': ['00:00:00:71:7d:25', '00:00:00:71:7d:26'], + 'pci': ['0000:06:00.0', '0000:06:00.1'], + 'phy_driver': 'i40e'} + +CORRECT_FILE_PATH = "/etc/yardstick/nodes/pod_ovs.yaml" +WRONG_FILE_PATH = "/etc/yardstick/wrong.yaml" +SAMPLE_FILE = "ovs_sample_write_to_file.txt" + +OVS = [{ + 'auth_type': 'ssh_key', + 'name': 'ovs', + 'ssh_port': 22, + 'ip': '10.10.10.11', + 'key_filename': '/root/.ssh/id_rsa', + 'vports_mac': ['00:00:00:00:00:03', '00:00:00:00:00:04'], + 'vpath': '/usr/local/', + 'role': 'Ovsdpdk', + 'user': 'root', + 'images': '/var/lib/libvirt/images/ubuntu1.img', + 'flow': ['ovs-ofctl add-flow br0 in_port=1,action=output:3', + 'ovs-ofctl add-flow br0 in_port=3,action=output:1', + 'ovs-ofctl add-flow br0 in_port=4,action=output:2', + 'ovs-ofctl add-flow br0 in_port=2,action=output:4'], + 'phy_driver': 'i40e', + 'phy_ports': ['0000:06:00.0', '0000:06:00.1']}] + +OVS_PASSWORD = [{ + 'auth_type': 'password', + 'name': 'ovs', + 'vports_mac': ['00:00:00:00:00:03', '00:00:00:00:00:04'], + 'ip': '10.10.10.11', + 'role': 'Ovsdpdk', + 'user': 'root', + 'vpath': '/usr/local/', + 'images': '/var/lib/libvirt/images/ubuntu1.img', + 'flow': ['ovs-ofctl add-flow br0 in_port=1,action=output:3', + 'ovs-ofctl add-flow br0 in_port=3,action=output:1', + 'ovs-ofctl add-flow br0 in_port=4,action=output:2', + 'ovs-ofctl add-flow br0 in_port=2,action=output:4'], + 'phy_driver': 'i40e', + 'password': 'password', + 'phy_ports': ['0000:06:00.0', '0000:06:00.1']}] + +#vfnic = "i40evf" +PCIS = ['0000:06:00.0', '0000:06:00.1'] + + +class OvsdpdkTestCase(unittest.TestCase): + + NODES_SAMPLE_SSH = "ovs_sample_ssh_key.yaml" + NODES_SAMPLE_PASSWORD = "ovs_sample_password.yaml" + + def setUp(self): + self.test_context = ovsdpdk.Ovsdpdk() + + def test_construct(self): + self.assertIsNone(self.test_context.name) + self.assertIsNone(self.test_context.file_path) + self.assertEqual(self.test_context.nodes, []) + self.assertEqual(self.test_context.ovs, []) + self.assertFalse(self.test_context.vm_deploy) + self.assertTrue(self.test_context.first_run) + self.assertEqual(self.test_context.user, "") + self.assertEqual(self.test_context.ssh_ip, "") + self.assertEqual(self.test_context.passwd, "") + self.assertEqual(self.test_context.ssh_port, "") + self.assertEqual(self.test_context.auth_type, "") + + def test_init(self): + self.test_context.parse_pod_and_get_data = mock.Mock() + self.test_context.file_path = CORRECT_FILE_PATH + self.test_context.init() + self.assertIsNone(self.test_context.init()) + + def test_successful_init_with_ssh(self): + CORRECT_FILE_PATH = self._get_file_abspath(self.NODES_SAMPLE_SSH) + self.test_context.parse_pod_and_get_data(CORRECT_FILE_PATH) + + def test_successful_init_with_password(self): + CORRECT_FILE_PATH = self._get_file_abspath(self.NODES_SAMPLE_PASSWORD) + self.test_context.parse_pod_and_get_data(CORRECT_FILE_PATH) + + def test_unsuccessful_init(self): + self.assertRaises( + IOError, + lambda: self.test_context.parse_pod_and_get_data(WRONG_FILE_PATH)) + + def test_ssh_connection(self): + with mock.patch("yardstick.ssh.SSH") as ssh: + ssh_mock = mock.Mock(autospec=ssh.SSH) + ssh_mock.execute = \ + mock.Mock(return_value=(0, {}, "")) + ssh.return_value = ssh_mock + + @mock.patch('yardstick.network_services.utils.provision_tool', return_value="b") + def test_ssh_connection(self, mock_prov): + with mock.patch("yardstick.ssh.SSH") as ssh: + ssh_mock = mock.Mock(autospec=ssh.SSH) + ssh_mock.execute = \ + mock.Mock(return_value=(1, "b", "")) + ssh.return_value = ssh_mock + mock_prov.provision_tool = mock.Mock() + ovs_obj = ovsdpdk.Ovsdpdk() + ovs_obj.connection = ssh_mock + ovs_obj.ovs = OVS_PASSWORD + self.assertIsNone(ovs_obj.ssh_remote_machine()) + + @mock.patch('yardstick.network_services.utils.provision_tool', return_value="b") + def test_ssh_connection_ssh_key(self, mock_prov): + with mock.patch("yardstick.ssh.SSH") as ssh: + ssh_mock = mock.Mock(autospec=ssh.SSH) + ssh_mock.execute = \ + mock.Mock(return_value=(1, "b", "")) + ssh.return_value = ssh_mock + mock_prov.provision_tool = mock.Mock() + ovs_obj = ovsdpdk.Ovsdpdk() + ovs_obj.connection = ssh_mock + ovs_obj.ovs = OVS + ovs_obj.key_filename = '/root/.ssh/id_rsa' + self.assertIsNone(ovs_obj.ssh_remote_machine()) + + def test_get_nic_details(self): + with mock.patch("yardstick.ssh.SSH") as ssh: + ssh_mock = mock.Mock(autospec=ssh.SSH) + ssh_mock.execute = \ + mock.Mock(return_value=(0, "eth0 eth1", "")) + ssh.return_value = ssh_mock + ovs_obj = ovsdpdk.Ovsdpdk() + ovs_obj.ovs = OVS + ovs_obj.connection = ssh_mock + self.assertIsNotNone(ovs_obj.get_nic_details()) + + def test_install_req_libs(self): + with mock.patch("yardstick.ssh.SSH") as ssh: + ssh_mock = mock.Mock(autospec=ssh.SSH) + ssh_mock.execute = \ + mock.Mock(return_value=(0, {}, "")) + ssh.return_value = ssh_mock + ovs_obj = ovsdpdk.Ovsdpdk() + ovs_obj.first_run = True + ovs_obj.connection = ssh_mock + self.assertIsNone(ovs_obj.install_req_libs()) + + def test_setup_ovs(self): + with mock.patch("yardstick.ssh.SSH") as ssh: + ssh_mock = mock.Mock(autospec=ssh.SSH) + ssh_mock.execute = \ + mock.Mock(return_value=(0, {}, "")) + ssh.return_value = ssh_mock + ovs_obj = ovsdpdk.Ovsdpdk() + ovs_obj.connection = ssh_mock + ovs_obj.ovs = OVS + self.assertIsNone(ovs_obj.setup_ovs({"eth0 eth1"})) + + def test_start_ovs_serverswitch(self): + with mock.patch("yardstick.ssh.SSH") as ssh: + ssh_mock = mock.Mock(autospec=ssh.SSH) + ssh_mock.execute = \ + mock.Mock(return_value=(0, {}, "")) + ssh.return_value = ssh_mock + ovs_obj = ovsdpdk.Ovsdpdk() + ovs_obj.connection = ssh_mock + ovs_obj.ovs = OVS + self.assertIsNone(ovs_obj.start_ovs_serverswitch()) + + def test_setup_ovs_bridge(self): + with mock.patch("yardstick.ssh.SSH") as ssh: + ssh_mock = mock.Mock(autospec=ssh.SSH) + ssh_mock.execute = \ + mock.Mock(return_value=(0, {}, "")) + ssh.return_value = ssh_mock + ovs_obj = ovsdpdk.Ovsdpdk() + ovs_obj.connection = ssh_mock + ovs_obj.ovs = OVS + self.assertIsNone(ovs_obj.setup_ovs_bridge()) + + def test_add_oflows(self): + with mock.patch("yardstick.ssh.SSH") as ssh: + ssh_mock = mock.Mock(autospec=ssh.SSH) + ssh_mock.execute = \ + mock.Mock(return_value=(0, {}, "")) + ssh.return_value = ssh_mock + ovs_obj = ovsdpdk.Ovsdpdk() + ovs_obj.connection = ssh_mock + ovs_obj.ovs = OVS + self.assertIsNone(ovs_obj.add_oflows()) + + def test_setup_ovs_context_vm_already_present(self): + with mock.patch("yardstick.ssh.SSH") as ssh: + ssh_mock = mock.Mock(autospec=ssh.SSH) + ssh_mock.execute = \ + mock.Mock(return_value=(0, {}, "")) + ssh.return_value = ssh_mock + ovs_obj = ovsdpdk.Ovsdpdk() + ovs_obj.connection = ssh_mock + ovs_obj.ovs = OVS + mock_ovs = mock.Mock() + ssh_mock.put = mock.Mock() + ovs_obj.check_output = mock.Mock(return_value=(0, "vm1")) + with mock.patch("yardstick.benchmark.contexts.ovsdpdk.time"): + self.assertIsNone(ovs_obj.setup_ovs_context(PCIS, NIC_DETAILS, DRIVER)) + + @mock.patch( + 'yardstick.benchmark.contexts.ovsdpdk', + return_value="Domain vm1 created from /tmp/vm_ovs.xml") + def test_is_vm_created(self, NIC_INPUT): + with mock.patch("yardstick.ssh.SSH") as ssh: + ssh_mock = mock.Mock(autospec=ssh.SSH) + ssh_mock.execute = \ + mock.Mock(return_value=(0, {}, "")) + ssh_mock.put = \ + mock.Mock(return_value=(0, {}, "")) + ssh.return_value = ssh_mock + mock_ovs = mock.Mock() + ret_create = mock.Mock() + pcis = NIC_DETAILS['pci'] + driver = NIC_DETAILS['phy_driver'] + self.assertIsNotNone( + mock_ovs.ovs_obj.setup_ovs_context( + pcis, + NIC_DETAILS, + driver)) + + def test_check_output(self): + with mock.patch("yardstick.ssh.SSH") as ssh: + cmd = "command" + ssh_mock = mock.Mock(autospec=ssh.SSH) + ssh_mock.execute = \ + mock.Mock(return_value=(0, {}, "")) + ssh.return_value = ssh_mock + ovs_obj = ovsdpdk.Ovsdpdk() + ovs_obj.connection = ssh_mock + self.assertIsNotNone(ovs_obj.check_output(cmd, None)) + + def test_split_cpu_list_available(self): + with mock.patch("itertools.chain") as iter1: + iter1 = mock.Mock() + print("{0}".format(iter1)) + ovs_obj = ovsdpdk.Ovsdpdk() + self.assertIsNotNone(ovs_obj.split_cpu_list('0,5')) + + def test_split_cpu_list_null(self): + with mock.patch("itertools.chain") as iter1: + iter1 = mock.Mock() + print("{0}".format(iter1)) + ovs_obj = ovsdpdk.Ovsdpdk() + self.assertEqual(ovs_obj.split_cpu_list([]), []) + + def test_destroy_vm_successful(self): + with mock.patch("yardstick.ssh.SSH") as ssh: + ssh_mock = mock.Mock(autospec=ssh.SSH) + ssh_mock.execute = \ + mock.Mock(return_value=(0, {}, "")) + ssh.return_value = ssh_mock + ovs_obj = ovsdpdk.Ovsdpdk() + ovs_obj.connection = ssh_mock + ovs_obj.ovs = OVS + ovs_obj.check_output = mock.Mock(return_value=(0, "vm1")) + ssh_mock.execute = \ + mock.Mock(return_value=(0, {}, "")) + ssh_mock.execute = \ + mock.Mock(return_value=(0, {}, "")) + ssh_mock.execute = \ + mock.Mock(return_value=(0, "0 i40e")) + ssh_mock.execute = \ + mock.Mock(return_value=(0, "0 i40e")) + self.assertIsNone(ovs_obj.destroy_vm()) + + def test_destroy_vm_unsuccessful(self): + with mock.patch("yardstick.ssh.SSH") as ssh: + ssh_mock = mock.Mock(autospec=ssh.SSH) + ssh_mock.execute = \ + mock.Mock(return_value=(0, {}, "")) + ssh.return_value = ssh_mock + ovs_obj = ovsdpdk.Ovsdpdk() + ovs_obj.connection = ssh_mock + ovs_obj.ovs = OVS + ovs_obj.check_output = mock.Mock(return_value=(1, {})) + self.assertIsNone(ovs_obj.destroy_vm()) + + def test_read_from_file(self): + CORRECT_FILE_PATH = self._get_file_abspath(self.NODES_SAMPLE_PASSWORD) + ovs_obj = ovsdpdk.Ovsdpdk() + self.assertIsNotNone(ovs_obj.read_from_file(CORRECT_FILE_PATH)) + + def test_write_to_file(self): + ovs_obj = ovsdpdk.Ovsdpdk() + self.assertIsNone(ovs_obj.write_to_file(SAMPLE_FILE, "some content")) + + def _get_file_abspath(self, filename): + curr_path = os.path.dirname(os.path.abspath(__file__)) + file_path = os.path.join(curr_path, filename) + return file_path + +if __name__ == '__main__': + unittest.main() diff --git a/tests/unit/benchmark/contexts/test_sriov.py b/tests/unit/benchmark/contexts/test_sriov.py new file mode 100644 index 000000000..a8641a2eb --- /dev/null +++ b/tests/unit/benchmark/contexts/test_sriov.py @@ -0,0 +1,421 @@ +# Copyright (c) 2016-2017 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. + +from __future__ import absolute_import +import os +import mock +import unittest + +from yardstick.benchmark.contexts import sriov + +NIC_INPUT = { + 'interface': {}, + 'vf_macs': ['00:00:00:71:7d:25', '00:00:00:71:7d:26'], + 'pci': ['0000:06:00.0', '0000:06:00.1'], + 'phy_driver': 'i40e'} +DRIVER = "i40e" +NIC_DETAILS = { + 'interface': {0: 'enp6s0f0', 1: 'enp6s0f1'}, + 'vf_macs': ['00:00:00:71:7d:25', '00:00:00:71:7d:26'], + 'pci': ['0000:06:00.0', '0000:06:00.1'], + 'phy_driver': 'i40e'} + +CORRECT_FILE_PATH = "/etc/yardstick/nodes/pod_sriov.yaml" +WRONG_FILE_PATH = "/etc/yardstick/wrong.yaml" +SAMPLE_FILE = "sriov_sample_write_to_file.txt" + +SRIOV = [{ + 'auth_type': 'ssh_key', + 'name': 'sriov', + 'ssh_port': 22, + 'ip': '10.10.10.11', + 'key_filename': '/root/.ssh/id_rsa', + 'vf_macs': ['00:00:00:71:7d:25', '00:00:00:71:7d:26'], + 'role': 'Sriov', + 'user': 'root', + 'images': '/var/lib/libvirt/images/ubuntu1.img', + 'phy_driver': 'i40e', + 'phy_ports': ['0000:06:00.0', '0000:06:00.1']}] + +SRIOV_PASSWORD = [{ + 'auth_type': 'password', + 'name': 'sriov', + 'vf_macs': ['00:00:00:71:7d:25', '00:00:00:71:7d:26'], + 'ip': '10.10.10.11', + 'role': 'Sriov', + 'user': 'root', + 'images': '/var/lib/libvirt/images/ubuntu1.img', + 'phy_driver': 'i40e', + 'password': 'password', + 'phy_ports': ['0000:06:00.0', '0000:06:00.1']}] + +vfnic = "i40evf" +PCIS = ['0000:06:00.0', '0000:06:00.1'] + + +class SriovTestCase(unittest.TestCase): + + NODES_SAMPLE_SSH = "sriov_sample_ssh_key.yaml" + NODES_SAMPLE_PASSWORD = "sriov_sample_password.yaml" + + def setUp(self): + self.test_context = sriov.Sriov() + + def test_construct(self): + self.assertIsNone(self.test_context.name) + self.assertIsNone(self.test_context.file_path) + self.assertEqual(self.test_context.nodes, []) + self.assertEqual(self.test_context.sriov, []) + self.assertFalse(self.test_context.vm_deploy) + self.assertTrue(self.test_context.first_run) + self.assertEqual(self.test_context.user, "") + self.assertEqual(self.test_context.ssh_ip, "") + self.assertEqual(self.test_context.passwd, "") + self.assertEqual(self.test_context.ssh_port, "") + self.assertEqual(self.test_context.auth_type, "") + + def test_init(self): + self.test_context.parse_pod_and_get_data = mock.Mock() + self.test_context.file_path = CORRECT_FILE_PATH + self.test_context.init() + self.assertIsNone(self.test_context.init()) + + def test_successful_init_with_ssh(self): + CORRECT_FILE_PATH = self._get_file_abspath(self.NODES_SAMPLE_SSH) + self.test_context.parse_pod_and_get_data(CORRECT_FILE_PATH) + + def test_successful_init_with_password(self): + CORRECT_FILE_PATH = self._get_file_abspath(self.NODES_SAMPLE_PASSWORD) + self.test_context.parse_pod_and_get_data(CORRECT_FILE_PATH) + + def test_unsuccessful_init(self): + self.assertRaises( + IOError, + lambda: self.test_context.parse_pod_and_get_data(WRONG_FILE_PATH)) + + @mock.patch('yardstick.network_services.utils.provision_tool', return_value="a") + def test_ssh_connection(self, mock_prov): + with mock.patch("yardstick.ssh.SSH") as ssh: + ssh_mock = mock.Mock(autospec=ssh.SSH) + ssh_mock.execute = \ + mock.Mock(return_value=(1, "a", "")) + ssh.return_value = ssh_mock + mock_prov.provision_tool = mock.Mock() + sriov_obj = sriov.Sriov() + sriov_obj.connection = ssh_mock + sriov_obj.sriov = SRIOV_PASSWORD + self.assertIsNone(sriov_obj.ssh_remote_machine()) + + @mock.patch('yardstick.network_services.utils.provision_tool', return_value="a") + def test_ssh_connection_ssh_key(self, mock_prov): + with mock.patch("yardstick.ssh.SSH") as ssh: + ssh_mock = mock.Mock(autospec=ssh.SSH) + ssh_mock.execute = \ + mock.Mock(return_value=(1, "a", "")) + ssh.return_value = ssh_mock + mock_prov.provision_tool = mock.Mock() + sriov_obj = sriov.Sriov() + sriov_obj.connection = ssh_mock + sriov_obj.sriov = SRIOV + sriov_obj.key_filename = '/root/.ssh/id_rsa' + self.assertIsNone(sriov_obj.ssh_remote_machine()) + + def test_get_nic_details(self): + with mock.patch("yardstick.ssh.SSH") as ssh: + ssh_mock = mock.Mock(autospec=ssh.SSH) + ssh_mock.execute = \ + mock.Mock(return_value=(0, "eth0 eth1", "")) + ssh.return_value = ssh_mock + sriov_obj = sriov.Sriov() + sriov_obj.sriov = SRIOV + sriov_obj.connection = ssh_mock + self.assertIsNotNone(sriov_obj.get_nic_details()) + + def test_install_req_libs(self): + with mock.patch("yardstick.ssh.SSH") as ssh: + ssh_mock = mock.Mock(autospec=ssh.SSH) + ssh_mock.execute = \ + mock.Mock(return_value=(0, {}, "")) + ssh.return_value = ssh_mock + sriov_obj = sriov.Sriov() + sriov_obj.first_run = True + sriov_obj.connection = ssh_mock + self.assertIsNone(sriov_obj.install_req_libs()) + + def test_configure_nics_for_sriov(self): + with mock.patch("yardstick.ssh.SSH") as ssh: + nic_details = { + 'interface': {0: 'enp6s0f0', 1: 'enp6s0f1'}, + 'vf_macs': ['00:00:00:71:7d:25', '00:00:00:71:7d:26'], + 'pci': ['0000:06:00.0', '0000:06:00.1'], + 'phy_driver': 'i40e', + 'vf_pci': [{}, {}]} + ssh_mock = mock.Mock(autospec=ssh.SSH) + ssh_mock.execute = \ + mock.Mock((DRIVER), return_value=(0, "0 driver", "")) + ssh.return_value = ssh_mock + ssh_mock.execute = \ + mock.Mock(return_value=(0, {}, "")) + ssh.return_value = ssh_mock + for i in range(len(NIC_DETAILS['pci'])): + ssh_mock.execute = \ + mock.Mock(return_value=(0, {}, "")) + ssh_mock.execute = \ + mock.Mock(return_value=(0, {}, "")) + sriov_obj = sriov.Sriov() + sriov_obj.connection = ssh_mock + ssh_mock.execute = \ + mock.Mock(return_value=( + 0, + "{'0':'06:02:00','1':'06:06:00'}", + "")) + sriov_obj.get_vf_datas = mock.Mock(return_value={ + '0000:06:00.0': '0000:06:02.0'}) + nic_details['vf_pci'][i] = sriov_obj.get_vf_datas.return_value + vf_pci = [[], []] + vf_pci[i] = sriov_obj.get_vf_datas.return_value + with mock.patch("yardstick.benchmark.contexts.sriov.time"): + self.assertIsNotNone(sriov_obj.configure_nics_for_sriov(DRIVER, NIC_DETAILS)) + + def test_setup_sriov_context(self): + with mock.patch("yardstick.ssh.SSH") as ssh: + nic_details = { + 'interface': {0: 'enp6s0f0', 1: 'enp6s0f1'}, + 'vf_macs': ['00:00:00:71:7d:25', '00:00:00:71:7d:26'], + 'pci': ['0000:06:00.0', '0000:06:00.1'], + 'phy_driver': 'i40e', + 'vf_pci': [{'vf_pci': '06:02.00'}, {'vf_pci': '06:06.00'}]} + vf = [{'vf_pci': '06:02.00'}, {'vf_pci': '06:06.00'}] + ssh_mock = mock.Mock(autospec=ssh.SSH) + ssh_mock.execute = \ + mock.Mock(return_value=(0, {}, "")) + ssh.return_value = ssh_mock + sriov_obj = sriov.Sriov() + sriov_obj.connection = ssh_mock + sriov_obj.sriov = SRIOV + blacklist = "/etc/modprobe.d/blacklist.conf" + self.assertEqual(vfnic, "i40evf") + mock_sriov = mock.Mock() + mock_sriov.sriov_obj.read_from_file(blacklist) + sriov_obj.read_from_file = mock.Mock( + return_value="some random text") + ssh_mock.execute = \ + mock.Mock(return_value=(0, {}, "")) + sriov_obj.configure_nics_for_sriov = mock.Mock( + return_value=nic_details) + nic_details = sriov_obj.configure_nics_for_sriov.return_value + self.assertEqual(vf, nic_details['vf_pci']) + vf = [ + {'vf_pci': '06:02.00', 'mac': '00:00:00:00:00:0a'}, + {'vf_pci': '06:06.00', 'mac': '00:00:00:00:00:0b'}] + sriov_obj.add_sriov_interface = mock.Mock() + ssh_mock.execute = \ + mock.Mock(return_value=(0, {}, "")) + ssh_mock.put = mock.Mock() + sriov_obj.check_output = mock.Mock(return_value=(1, {})) + with mock.patch("yardstick.benchmark.contexts.sriov.time"): + self.assertIsNone(sriov_obj.setup_sriov_context(PCIS, nic_details, DRIVER)) + + def test_setup_sriov_context_vm_already_present(self): + with mock.patch("yardstick.ssh.SSH") as ssh: + nic_details = { + 'interface': {0: 'enp6s0f0', 1: 'enp6s0f1'}, + 'vf_macs': ['00:00:00:71:7d:25', '00:00:00:71:7d:26'], + 'pci': ['0000:06:00.0', '0000:06:00.1'], + 'phy_driver': 'i40e', + 'vf_pci': [{'vf_pci': '06:02.00'}, {'vf_pci': '06:06.00'}]} + vf = [{'vf_pci': '06:02.00'}, {'vf_pci': '06:06.00'}] + ssh_mock = mock.Mock(autospec=ssh.SSH) + ssh_mock.execute = \ + mock.Mock(return_value=(0, {}, "")) + ssh.return_value = ssh_mock + sriov_obj = sriov.Sriov() + sriov_obj.connection = ssh_mock + sriov_obj.sriov = SRIOV + blacklist = "/etc/modprobe.d/blacklist.conf" + self.assertEqual(vfnic, "i40evf") + mock_sriov = mock.Mock() + mock_sriov.sriov_obj.read_from_file(blacklist) + sriov_obj.read_from_file = mock.Mock( + return_value="some random text") + ssh_mock.execute = \ + mock.Mock(return_value=(0, {}, "")) + sriov_obj.configure_nics_for_sriov = mock.Mock( + return_value=nic_details) + nic_details = sriov_obj.configure_nics_for_sriov.return_value + self.assertEqual(vf, nic_details['vf_pci']) + vf = [ + {'vf_pci': '06:02.00', 'mac': '00:00:00:00:00:0a'}, + {'vf_pci': '06:06.00', 'mac': '00:00:00:00:00:0b'}] + sriov_obj.add_sriov_interface = mock.Mock() + ssh_mock.execute = \ + mock.Mock(return_value=(0, {}, "")) + ssh_mock.put = mock.Mock() + sriov_obj.check_output = mock.Mock(return_value=(0, "vm1")) + with mock.patch("yardstick.benchmark.contexts.sriov.time"): + self.assertIsNone(sriov_obj.setup_sriov_context(PCIS, nic_details, DRIVER)) + + @mock.patch( + 'yardstick.benchmark.contexts.sriov', + return_value="Domain vm1 created from /tmp/vm_sriov.xml") + def test_is_vm_created(self, NIC_INPUT): + with mock.patch("yardstick.ssh.SSH") as ssh: + ssh_mock = mock.Mock(autospec=ssh.SSH) + ssh_mock.execute = \ + mock.Mock(return_value=(0, {}, "")) + ssh.return_value = ssh_mock + mock_sriov = mock.Mock() + pcis = NIC_DETAILS['pci'] + driver = NIC_DETAILS['phy_driver'] + self.assertIsNotNone( + mock_sriov.sriov_obj.setup_sriov_context( + pcis, + NIC_DETAILS, + driver)) + + def test_add_sriov_interface(self): + with mock.patch("yardstick.ssh.SSH") as ssh: + ssh_mock = mock.Mock(autospec=ssh.SSH) + ssh_mock.execute = \ + mock.Mock(return_value=(0, {}, "")) + ssh.return_value = ssh_mock + sriov_obj = sriov.Sriov() + sriov_obj.connection = ssh_mock + with mock.patch("xml.etree.ElementTree.parse") as parse: + with mock.patch("re.search") as re: + with mock.patch("xml.etree.ElementTree.SubElement") \ + as elem: + parse = mock.Mock(return_value="root") + re = mock.Mock() + elem = mock.Mock() + print("{0} {1} {2}".format(parse, re, elem)) + self.assertIsNone(sriov_obj.add_sriov_interface( + 0, + "0000:06:02.0", + "00:00:00:00:00:0a", + "/tmp/vm_sriov.xml")) + + def test_get_virtual_devices(self): + with mock.patch("yardstick.ssh.SSH") as ssh: + ssh_mock = mock.Mock(autospec=ssh.SSH) + ssh_mock.execute = \ + mock.Mock(return_value=(0, {}, "")) + ssh.return_value = ssh_mock + sriov_obj = sriov.Sriov() + sriov_obj.connection = ssh_mock + pci_out = " \ + PCI_CLASS=20000 \ + PCI_ID=8086:154C \ + PCI_SUBSYS_ID=8086:0000 \ + PCI_SLOT_NAME=0000:06:02.0 \ + MODALIAS= \ + pci:v00008086d0000154Csv00008086sd00000000bc02sc00i00" + pci = "0000:06:00.0" + sriov_obj.check_output = mock.Mock(return_value=(0, pci_out)) + with mock.patch("re.search") as re: + re = mock.Mock(return_value="a") + print("{0}".format(re)) + self.assertIsNotNone(sriov_obj.get_virtual_devices(pci)) + + def test_get_vf_datas(self): + with mock.patch("yardstick.ssh.SSH") as ssh: + ssh_mock = mock.Mock(autospec=ssh.SSH) + ssh_mock.execute = \ + mock.Mock(return_value=(0, {}, "")) + ssh.return_value = ssh_mock + sriov_obj = sriov.Sriov() + sriov_obj.connection = ssh_mock + sriov_obj.get_virtual_devices = mock.Mock( + return_value={'0000:06:00.0': '0000:06:02.0'}) + with mock.patch("re.search") as re: + re = mock.Mock() + print("{0}".format(re)) + self.assertIsNotNone(sriov_obj.get_vf_datas( + 'vf_pci', + {'0000:06:00.0': '0000:06:02.0'}, + "00:00:00:00:00:0a")) + + def test_check_output(self): + with mock.patch("yardstick.ssh.SSH") as ssh: + cmd = "command" + ssh_mock = mock.Mock(autospec=ssh.SSH) + ssh_mock.execute = \ + mock.Mock(return_value=(0, {}, "")) + ssh.return_value = ssh_mock + sriov_obj = sriov.Sriov() + sriov_obj.connection = ssh_mock + self.assertIsNotNone(sriov_obj.check_output(cmd, None)) + + def test_split_cpu_list_available(self): + with mock.patch("itertools.chain") as iter1: + iter1 = mock.Mock() + print("{0}".format(iter1)) + sriov_obj = sriov.Sriov() + self.assertIsNotNone(sriov_obj.split_cpu_list('0,5')) + + def test_split_cpu_list_null(self): + with mock.patch("itertools.chain") as iter1: + iter1 = mock.Mock() + print("{0}".format(iter1)) + sriov_obj = sriov.Sriov() + self.assertEqual(sriov_obj.split_cpu_list([]), []) + + def test_destroy_vm_successful(self): + with mock.patch("yardstick.ssh.SSH") as ssh: + ssh_mock = mock.Mock(autospec=ssh.SSH) + ssh_mock.execute = \ + mock.Mock(return_value=(0, {}, "")) + ssh.return_value = ssh_mock + sriov_obj = sriov.Sriov() + sriov_obj.connection = ssh_mock + sriov_obj.sriov = SRIOV + sriov_obj.check_output = mock.Mock(return_value=(0, "vm1")) + ssh_mock.execute = \ + mock.Mock(return_value=(0, {}, "")) + ssh_mock.execute = \ + mock.Mock(return_value=(0, {}, "")) + ssh_mock.execute = \ + mock.Mock(return_value=(0, "0 i40e")) + ssh_mock.execute = \ + mock.Mock(return_value=(0, "0 i40e")) + self.assertIsNone(sriov_obj.destroy_vm()) + + def test_destroy_vm_unsuccessful(self): + with mock.patch("yardstick.ssh.SSH") as ssh: + ssh_mock = mock.Mock(autospec=ssh.SSH) + ssh_mock.execute = \ + mock.Mock(return_value=(0, {}, "")) + ssh.return_value = ssh_mock + sriov_obj = sriov.Sriov() + sriov_obj.connection = ssh_mock + sriov_obj.sriov = SRIOV + sriov_obj.check_output = mock.Mock(return_value=(1, {})) + self.assertIsNone(sriov_obj.destroy_vm()) + + def test_read_from_file(self): + CORRECT_FILE_PATH = self._get_file_abspath(self.NODES_SAMPLE_PASSWORD) + sriov_obj = sriov.Sriov() + self.assertIsNotNone(sriov_obj.read_from_file(CORRECT_FILE_PATH)) + + def test_write_to_file(self): + sriov_obj = sriov.Sriov() + self.assertIsNone(sriov_obj.write_to_file(SAMPLE_FILE, "some content")) + + def _get_file_abspath(self, filename): + curr_path = os.path.dirname(os.path.abspath(__file__)) + file_path = os.path.join(curr_path, filename) + return file_path + +if __name__ == '__main__': + unittest.main() diff --git a/tests/unit/benchmark/contexts/test_standalone.py b/tests/unit/benchmark/contexts/test_standalone.py index 687ef7305..1fc740393 100644 --- a/tests/unit/benchmark/contexts/test_standalone.py +++ b/tests/unit/benchmark/contexts/test_standalone.py @@ -20,112 +20,667 @@ from __future__ import absolute_import import os import unittest +import mock from yardstick.benchmark.contexts import standalone +from yardstick.benchmark.contexts import sriov +from yardstick.benchmark.contexts import ovsdpdk +MOCKS = { + 'yardstick.benchmark.contexts': mock.MagicMock(), + 'yardstick.benchmark.contexts.sriov': mock.MagicMock(), + 'yardstick.benchmark.contexts.ovsdpdk': mock.MagicMock(), + 'yardstick.benchmark.contexts.standalone': mock.MagicMock(), +} + +@mock.patch('yardstick.benchmark.contexts.ovsdpdk.time') +@mock.patch('yardstick.benchmark.contexts.standalone.time') +@mock.patch('yardstick.benchmark.contexts.sriov.time') class StandaloneContextTestCase(unittest.TestCase): + NODES_SAMPLE = "nodes_sample_new.yaml" + NODES_SAMPLE_SRIOV = "nodes_sample_new_sriov.yaml" + NODES_DUPLICATE_SAMPLE = "nodes_duplicate_sample_new.yaml" - NODES_SAMPLE = "standalone_sample.yaml" - NODES_DUPLICATE_SAMPLE = "standalone_duplicate_sample.yaml" + NODES_SAMPLE_OVSDPDK = "nodes_sample_ovs.yaml" + NODES_SAMPLE_OVSDPDK_ROLE = "nodes_sample_ovsdpdk.yaml" + NODES_DUPLICATE_OVSDPDK = "nodes_duplicate_sample_ovs.yaml" def setUp(self): self.test_context = standalone.StandaloneContext() - def test_construct(self): - + def test_construct(self, mock_sriov_time, mock_standlalone_time, mock_ovsdpdk_time): self.assertIsNone(self.test_context.name) self.assertIsNone(self.test_context.file_path) self.assertEqual(self.test_context.nodes, []) self.assertEqual(self.test_context.nfvi_node, []) - def test_unsuccessful_init(self): - + def test_unsuccessful_init(self, mock_sriov_time, mock_standlalone_time, mock_ovsdpdk_time): attrs = { 'name': 'foo', 'file': self._get_file_abspath("error_file") } - self.assertRaises(IOError, self.test_context.init, attrs) - def test_successful_init(self): - - attrs = { + def test_successful_init_sriov(self, mock_sriov_time, mock_standlalone_time, + mock_ovsdpdk_time): + attrs_sriov = { + 'name': 'sriov', + 'file': self._get_file_abspath(self.NODES_SAMPLE) + } + self.test_context.nfvi_node = [{ + 'name': 'sriov', + 'vf_macs': ['00:00:00:71:7d:25', '00:00:00:71:7d:26'], + 'ip': '10.223.197.140', + 'role': 'Sriov', + 'user': 'root', + 'images': '/var/lib/libvirt/images/ubuntu1.img', + 'phy_driver': 'i40e', + 'password': 'intel123', + 'phy_ports': ['0000:06:00.0', '0000:06:00.1']}] + self.test_context.get_nfvi_obj = mock.Mock() + self.test_context.init(attrs_sriov) + self.assertEqual(self.test_context.name, "sriov") + self.assertEqual(len(self.test_context.nodes), 2) + self.assertEqual(len(self.test_context.nfvi_node), 2) + self.assertEqual(self.test_context.nfvi_node[0]["name"], "sriov") + + def test_successful_init_ovs(self, mock_sriov_time, mock_standlalone_time, mock_ovsdpdk_time): + attrs_ovs = { + 'name': 'ovs', + 'file': self._get_file_abspath(self.NODES_SAMPLE_OVSDPDK) + } + self.test_context.nfvi_node = [{ + 'name': 'ovs', + 'vports_mac': ['00:00:00:00:00:03', '00:00:00:00:00:04'], + 'ip': '10.223.197.140', + 'role': 'Ovsdpdk', + 'user': 'root', + 'vpath': '/usr/local/', + 'images': '/var/lib/libvirt/images/ubuntu1.img', + 'phy_driver': 'i40e', + 'password': 'password', + 'phy_ports': ['0000:06:00.0', '0000:06:00.1']}] + self.test_context.get_nfvi_obj = mock.Mock() + self.test_context.init(attrs_ovs) + self.assertEqual(self.test_context.name, "ovs") + self.assertEqual(len(self.test_context.nodes), 2) + self.assertEqual(len(self.test_context.nfvi_node), 2) + self.assertEqual(self.test_context.nfvi_node[0]["name"], "ovs") + + def test__get_server_with_dic_attr_name_sriov(self, mock_sriov_time, mock_standlalone_time, + mock_ovsdpdk_time): + attrs_sriov = { 'name': 'foo', 'file': self._get_file_abspath(self.NODES_SAMPLE) } + self.test_context.nfvi_node = [{ + 'name': 'sriov', + 'vf_macs': ['00:00:00:71:7d:25', '00:00:00:71:7d:26'], + 'ip': '10.223.197.140', + 'role': 'Sriov', + 'user': 'root', + 'images': '/var/lib/libvirt/images/ubuntu1.img', + 'phy_driver': 'i40e', + 'password': 'intel123', + 'phy_ports': ['0000:06:00.0', '0000:06:00.1']}] + self.test_context.init(attrs_sriov) + attr_name = {'name': 'foo.bar'} + result = self.test_context._get_server(attr_name) + self.assertEqual(result, None) - self.test_context.init(attrs) - - self.assertEqual(self.test_context.name, "foo") - self.assertEqual(len(self.test_context.nodes), 3) - self.assertEqual(len(self.test_context.nfvi_node), 1) - self.assertEqual(self.test_context.nfvi_node[0]["name"], "node2") - - def test__get_server_with_dic_attr_name(self): + def test__get_server_with_dic_attr_name_ovs(self, mock_sriov_time, mock_standlalone_time, + mock_ovsdpdk_time): + attrs_ovs = { + 'name': 'foo', + 'file': self._get_file_abspath(self.NODES_SAMPLE_OVSDPDK) + } + self.test_context.nfvi_node = [{ + 'name': 'ovs', + 'vports_mac': ['00:00:00:00:00:03', '00:00:00:00:00:04'], + 'ip': '10.223.197.140', + 'role': 'Ovsdpdk', + 'user': 'root', + 'vpath': '/usr/local/', + 'images': '/var/lib/libvirt/images/ubuntu1.img', + 'phy_driver': 'i40e', + 'password': 'intel123', + 'phy_ports': ['0000:06:00.0', '0000:06:00.1']}] + self.test_context.init(attrs_ovs) + attr_name = {'name': 'foo.bar'} + result = self.test_context._get_server(attr_name) + self.assertEqual(result, None) + def test__get_server_not_found_sriov(self, mock_sriov_time, mock_standlalone_time, + mock_ovsdpdk_time): attrs = { 'name': 'foo', 'file': self._get_file_abspath(self.NODES_SAMPLE) } - + self.test_context.nfvi_node = [{ + 'name': 'sriov', + 'vf_macs': ['00:00:00:71:7d:25', '00:00:00:71:7d:26'], + 'ip': '10.223.197.140', + 'role': 'Sriov', + 'user': 'root', + 'images': '/var/lib/libvirt/images/ubuntu1.img', + 'phy_driver': 'i40e', + 'password': 'password', + 'phy_ports': ['0000:06:00.0', '0000:06:00.1']}] self.test_context.init(attrs) - - attr_name = {'name': 'foo.bar'} + attr_name = 'bar.foo' result = self.test_context._get_server(attr_name) - self.assertEqual(result, None) - def test__get_server_not_found(self): - + def test__get_server_not_found_ovs(self, mock_sriov_time, mock_standlalone_time, + mock_ovsdpdk_time): attrs = { 'name': 'foo', - 'file': self._get_file_abspath(self.NODES_SAMPLE) + 'file': self._get_file_abspath(self.NODES_SAMPLE_OVSDPDK) } - + self.test_context.nfvi_node = [{ + 'name': 'ovs', + 'vports_mac': ['00:00:00:00:00:03', '00:00:00:00:00:04'], + 'ip': '10.223.197.140', + 'role': 'Ovsdpdk', + 'user': 'root', + 'vpath': '/usr/local/', + 'images': '/var/lib/libvirt/images/ubuntu1.img', + 'phy_driver': 'i40e', + 'password': 'password', + 'phy_ports': ['0000:06:00.0', '0000:06:00.1']}] self.test_context.init(attrs) - attr_name = 'bar.foo' result = self.test_context._get_server(attr_name) - self.assertEqual(result, None) - def test__get_server_duplicate(self): + + def test__get_server_duplicate_sriov(self, mock_sriov_time, mock_standlalone_time, + mock_ovsdpdk_time): attrs = { 'name': 'foo', 'file': self._get_file_abspath(self.NODES_DUPLICATE_SAMPLE) } + self.test_context.nfvi_node = [{ + 'name': 'sriov', + 'vf_macs': ['00:00:00:71:7d:25', '00:00:00:71:7d:26'], + 'ip': '10.223.197.140', + 'role': 'Sriov', + 'user': 'root', + 'images': '/var/lib/libvirt/images/ubuntu1.img', + 'phy_driver': 'i40e', + 'password': 'password', + 'phy_ports': ['0000:06:00.0', '0000:06:00.1']}] + self.test_context.get_nfvi_obj = mock.Mock(return_value="sriov") + self.test_context.init(attrs) + attr_name = 'sriov.foo' + # self.test_context.name = "sriov" + self.assertRaises(ValueError, self.test_context._get_server, attr_name) + def test__get_server_duplicate_ovs(self, mock_sriov_time, mock_standlalone_time, + mock_ovsdpdk_time): + attrs = { + 'name': 'foo', + 'file': self._get_file_abspath(self.NODES_DUPLICATE_OVSDPDK) + } + self.test_context.nfvi_node = [{ + 'name': 'ovs', + 'vports_mac': ['00:00:00:00:00:03', '00:00:00:00:00:04'], + 'ip': '10.223.197.140', + 'role': 'Ovsdpdk', + 'user': 'root', + 'vpath': '/usr/local/', + 'images': '/var/lib/libvirt/images/ubuntu1.img', + 'phy_driver': 'i40e', + 'password': 'intel123', + 'phy_ports': ['0000:06:00.0', '0000:06:00.1']}] + + self.test_context.get_nfvi_obj = mock.Mock(return_value="OvsDpdk") self.test_context.init(attrs) - attr_name = 'node2.foo' + attr_name = 'ovs.foo' + self.assertRaises( + ValueError, + self.test_context._get_server, + attr_name) + def test__get_server_found_sriov(self, mock_sriov_time, mock_standlalone_time, + mock_ovsdpdk_time): + attrs = { + 'name': 'foo', + 'file': self._get_file_abspath(self.NODES_SAMPLE_SRIOV) + } + self.test_context.nfvi_node = [{ + 'name': 'sriov', + 'vf_macs': ['00:00:00:71:7d:25', '00:00:00:71:7d:26'], + 'ip': '10.223.197.140', + 'role': 'Sriov', + 'user': 'root', + 'images': '/var/lib/libvirt/images/ubuntu1.img', + 'phy_driver': 'i40e', + 'password': 'intel123', + 'phy_ports': ['0000:06:00.0', '0000:06:00.1']}] + + self.test_context.get_nfvi_obj = mock.Mock(return_value="OvsDpdk") + self.test_context.init(attrs) + attr_name = 'sriov.foo' + result = self.test_context._get_server(attr_name) + self.assertEqual(result['ip'], '10.123.123.122') + self.assertEqual(result['name'], 'sriov.foo') + self.assertEqual(result['user'], 'root') - self.assertRaises(ValueError, self.test_context._get_server, attr_name) + def test__get_server_found_ovs(self, mock_sriov_time, mock_standlalone_time, + mock_ovsdpdk_time): + attrs = { + 'name': 'foo', + 'file': self._get_file_abspath(self.NODES_SAMPLE_OVSDPDK_ROLE) + } + self.test_context.nfvi_node = [{ + 'name': 'ovs', + 'vports_mac': ['00:00:00:00:00:03', '00:00:00:00:00:04'], + 'ip': '10.223.197.140', + 'role': 'Ovsdpdk', + 'user': 'root', + 'vpath': '/usr/local/', + 'images': '/var/lib/libvirt/images/ubuntu1.img', + 'phy_driver': 'i40e', + 'password': 'password', + 'phy_ports': ['0000:06:00.0', '0000:06:00.1']}] + self.test_context.get_nfvi_obj = mock.Mock(return_value="OvsDpdk") + self.test_context.init(attrs) + attr_name = 'ovs.foo' + result = self.test_context._get_server(attr_name) + self.assertEqual(result['ip'], '10.223.197.222') + self.assertEqual(result['name'], 'ovs.foo') + self.assertEqual(result['user'], 'root') + + def test__deploy_unsuccessful(self, mock_sriov_time, mock_standlalone_time, mock_ovsdpdk_time): + self.test_context.vm_deploy = False - def test__get_server_found(self): + def test__deploy_sriov_firsttime(self, mock_sriov_time, mock_standlalone_time, + mock_ovsdpdk_time): + attrs = { + 'name': 'foo', + 'file': self._get_file_abspath(self.NODES_SAMPLE) + } + self.test_context.nfvi_node = [{ + 'name': 'sriov', + 'vf_macs': ['00:00:00:71:7d:25', '00:00:00:71:7d:26'], + 'ip': '10.223.197.140', + 'role': 'Sriov', + 'user': 'root', + 'images': '/var/lib/libvirt/images/ubuntu1.img', + 'phy_driver': 'i40e', + 'password': 'intel123', + 'phy_ports': ['0000:06:00.0', '0000:06:00.1']}] + + MYSRIOV = [{ + 'name': 'sriov', + 'vf_macs': ['00:00:00:71:7d:25', '00:00:00:71:7d:26'], + 'ip': '10.223.197.140', + 'role': 'Sriov', + 'user': 'root', + 'images': '/var/lib/libvirt/images/ubuntu1.img', + 'phy_driver': 'i40e', + 'password': 'intel123', + 'phy_ports': ['0000:06:00.0', '0000:06:00.1']}] + + self.test_context.vm_deploy = True + + self.test_context.get_nfvi_obj = mock.MagicMock() + self.test_context.init(attrs) + self.test_context.nfvi_obj.sriov = MYSRIOV + self.test_context.nfvi_obj.ssh_remote_machine = mock.Mock() + self.test_context.nfvi_obj.first_run = True + self.test_context.nfvi_obj.install_req_libs() + self.test_context.nfvi_obj.get_nic_details = mock.Mock() + PORTS = ['0000:06:00.0', '0000:06:00.1'] + NIC_DETAILS = { + 'interface': {0: 'enp6s0f0', 1: 'enp6s0f1'}, + 'vf_macs': ['00:00:00:71:7d:25', '00:00:00:71:7d:26'], + 'pci': ['0000:06:00.0', '0000:06:00.1'], + 'phy_driver': 'i40e'} + DRIVER = 'i40e' + result = self.test_context.nfvi_obj.setup_sriov_context( + PORTS, + NIC_DETAILS, + DRIVER) + print("{0}".format(result)) + self.assertIsNone(self.test_context.deploy()) + def test__deploy_sriov_notfirsttime(self, mock_sriov_time, mock_standlalone_time, + mock_ovsdpdk_time): attrs = { 'name': 'foo', 'file': self._get_file_abspath(self.NODES_SAMPLE) } + self.test_context.nfvi_node = [{ + 'name': 'sriov', + 'vf_macs': ['00:00:00:71:7d:25', '00:00:00:71:7d:26'], + 'ip': '10.223.197.140', + 'role': 'Sriov', + 'user': 'root', + 'images': '/var/lib/libvirt/images/ubuntu1.img', + 'phy_driver': 'i40e', + 'password': 'intel123', + 'phy_ports': ['0000:06:00.0', '0000:06:00.1']}] + MYSRIOV = [{ + 'name': 'sriov', + 'vf_macs': ['00:00:00:71:7d:25', '00:00:00:71:7d:26'], + 'ip': '10.223.197.140', + 'role': 'Sriov', + 'user': 'root', + 'images': '/var/lib/libvirt/images/ubuntu1.img', + 'phy_driver': 'i40e', + 'password': 'intel123', + 'phy_ports': ['0000:06:00.0', '0000:06:00.1']}] + + self.test_context.vm_deploy = True + self.test_context.get_nfvi_obj = mock.MagicMock() self.test_context.init(attrs) + self.test_context.nfvi_obj.sriov = MYSRIOV + self.test_context.nfvi_obj.ssh_remote_machine = mock.Mock() + self.test_context.nfvi_obj.first_run = False + self.test_context.nfvi_obj.get_nic_details = mock.Mock() + PORTS = ['0000:06:00.0', '0000:06:00.1'] + NIC_DETAILS = { + 'interface': {0: 'enp6s0f0', 1: 'enp6s0f1'}, + 'vf_macs': ['00:00:00:71:7d:25', '00:00:00:71:7d:26'], + 'pci': ['0000:06:00.0', '0000:06:00.1'], + 'phy_driver': 'i40e'} + DRIVER = 'i40e' + result = self.test_context.nfvi_obj.setup_sriov_context( + PORTS, + NIC_DETAILS, + DRIVER) + print("{0}".format(result)) + self.assertIsNone(self.test_context.deploy()) - attr_name = 'node1.foo' - result = self.test_context._get_server(attr_name) + def test__deploy_ovs_firsttime(self, mock_sriov_time, mock_standlalone_time, + mock_ovsdpdk_time): + attrs = { + 'name': 'foo', + 'file': self._get_file_abspath(self.NODES_SAMPLE_OVSDPDK) + } - self.assertEqual(result['ip'], '1.1.1.1') - self.assertEqual(result['name'], 'node1.foo') - self.assertEqual(result['user'], 'root') + self.test_context.nfvi_node = [{ + 'name': 'ovs', + 'vports_mac': ['00:00:00:00:00:03', '00:00:00:00:00:04'], + 'ip': '10.223.197.140', + 'role': 'Ovsdpdk', + 'user': 'root', + 'vpath': '/usr/local/', + 'images': '/var/lib/libvirt/images/ubuntu1.img', + 'phy_driver': 'i40e', + 'password': 'password', + 'phy_ports': ['0000:06:00.0', '0000:06:00.1']}] + + MYOVS = [{ + 'name': 'ovs', + 'vports_mac': ['00:00:00:00:00:03', '00:00:00:00:00:04'], + 'ip': '10.223.197.140', + 'role': 'Ovsdpdk', + 'user': 'root', + 'vpath': '/usr/local/', + 'images': '/var/lib/libvirt/images/ubuntu1.img', + 'phy_driver': 'i40e', + 'password': 'password', + 'flow': ['ovs-ofctl add-flow br0 in_port=1,action=output:3', + 'ovs-ofctl add-flow br0 in_port=3,action=output:1' + 'ovs-ofctl add-flow br0 in_port=4,action=output:2' + 'ovs-ofctl add-flow br0 in_port=2,action=output:4'], + 'phy_ports': ['0000:06:00.0', '0000:06:00.1']}] + + self.test_context.vm_deploy = True + self.test_context.get_nfvi_obj = mock.MagicMock() + self.test_context.init(attrs) + self.test_context.ovs = MYOVS + self.test_context.nfvi_obj.ssh_remote_machine = mock.Mock() + self.test_context.nfvi_obj.first_run = True + self.test_context.nfvi_obj.install_req_libs() + self.test_context.nfvi_obj.get_nic_details = mock.Mock() + PORTS = ['0000:06:00.0', '0000:06:00.1'] + NIC_DETAILS = { + 'interface': {0: 'enp6s0f0', 1: 'enp6s0f1'}, + 'vports_mac': ['00:00:00:00:00:05', '00:00:00:00:00:06'], + 'pci': ['0000:06:00.0', '0000:06:00.1'], + 'phy_driver': 'i40e'} + DRIVER = 'i40e' + + self.test_context.nfvi_obj.setup_ovs = mock.Mock() + self.test_context.nfvi_obj.start_ovs_serverswitch = mock.Mock() + self.test_context.nfvi_obj.setup_ovs_bridge = mock.Mock() + self.test_context.nfvi_obj.add_oflows = mock.Mock() + + # self.test_context.nfvi_obj.setup_ovs(PORTS) + # self.test_context.nfvi_obj.start_ovs_serverswitch() + # self.test_context.nfvi_obj.setup_ovs_bridge() + # self.test_context.nfvi_obj.add_oflows() + + result = self.test_context.nfvi_obj.setup_ovs_context( + PORTS, + NIC_DETAILS, + DRIVER) + print("{0}".format(result)) + self.assertIsNone(self.test_context.deploy()) - def test_deploy(self): + def test__deploy_ovs_notfirsttime(self, mock_sriov_time, mock_standlalone_time, + mock_ovsdpdk_time): + attrs = { + 'name': 'foo', + 'file': self._get_file_abspath(self.NODES_SAMPLE_OVSDPDK) + } + self.test_context.nfvi_node = [{ + 'name': 'ovs', + 'vports_mac': ['00:00:00:00:00:03', '00:00:00:00:00:04'], + 'ip': '10.223.197.140', + 'role': 'Ovsdpdk', + 'user': 'root', + 'vpath': '/usr/local/', + 'images': '/var/lib/libvirt/images/ubuntu1.img', + 'phy_driver': 'i40e', + 'password': 'password', + 'phy_ports': ['0000:06:00.0', '0000:06:00.1']}] + + MYOVS = [{ + 'name': 'ovs', + 'vports_mac': ['00:00:00:00:00:03', '00:00:00:00:00:04'], + 'ip': '10.223.197.140', + 'role': 'Ovsdpdk', + 'user': 'root', + 'vpath': '/usr/local/', + 'images': '/var/lib/libvirt/images/ubuntu1.img', + 'phy_driver': 'i40e', + 'password': 'password', + 'flow': ['ovs-ofctl add-flow br0 in_port=1,action=output:3', + 'ovs-ofctl add-flow br0 in_port=3,action=output:1' + 'ovs-ofctl add-flow br0 in_port=4,action=output:2' + 'ovs-ofctl add-flow br0 in_port=2,action=output:4'], + 'phy_ports': ['0000:06:00.0', '0000:06:00.1']}] + + self.test_context.vm_deploy = True + self.test_context.get_nfvi_obj = mock.MagicMock() + self.test_context.init(attrs) + self.test_context.ovs = MYOVS + self.test_context.nfvi_obj.ssh_remote_machine = mock.Mock() + self.test_context.nfvi_obj.first_run = False + self.test_context.nfvi_obj.get_nic_details = mock.Mock() + PORTS = ['0000:06:00.0', '0000:06:00.1'] + NIC_DETAILS = { + 'interface': {0: 'enp6s0f0', 1: 'enp6s0f1'}, + 'vports_mac': ['00:00:00:00:00:05', '00:00:00:00:00:06'], + 'pci': ['0000:06:00.0', '0000:06:00.1'], + 'phy_driver': 'i40e'} + DRIVER = 'i40e' + + self.test_context.nfvi_obj.setup_ovs(PORTS) + self.test_context.nfvi_obj.start_ovs_serverswitch() + self.test_context.nfvi_obj.setup_ovs_bridge() + self.test_context.nfvi_obj.add_oflows() + + result = self.test_context.nfvi_obj.setup_ovs_context( + PORTS, + NIC_DETAILS, + DRIVER) + print("{0}".format(result)) self.assertIsNone(self.test_context.deploy()) - def test_undeploy(self): + def test_undeploy_sriov(self, mock_sriov_time, mock_standlalone_time, mock_ovsdpdk_time): + attrs = { + 'name': 'foo', + 'file': self._get_file_abspath(self.NODES_SAMPLE) + } + self.test_context.nfvi_node = [{ + 'name': 'sriov', + 'vf_macs': ['00:00:00:71:7d:25', '00:00:00:71:7d:26'], + 'ip': '10.223.197.140', + 'role': 'Sriov', + 'user': 'root', + 'images': '/var/lib/libvirt/images/ubuntu1.img', + 'phy_driver': 'i40e', + 'password': 'intel123', + 'phy_ports': ['0000:06:00.0', '0000:06:00.1']}] + self.test_context.get_nfvi_obj = mock.MagicMock() + self.test_context.init(attrs) + self.test_context.nfvi_obj.destroy_vm = mock.Mock() + self.assertIsNone(self.test_context.undeploy()) + + def test_undeploy_ovs(self, mock_sriov_time, mock_standlalone_time, mock_ovsdpdk_time): + attrs = { + 'name': 'foo', + 'file': self._get_file_abspath(self.NODES_SAMPLE_OVSDPDK) + } + + self.test_context.nfvi_node = [{ + 'name': 'ovs', + 'vports_mac': ['00:00:00:00:00:03', '00:00:00:00:00:04'], + 'ip': '10.223.197.140', + 'role': 'Ovsdpdk', + 'user': 'root', + 'vpath': '/usr/local/', + 'images': '/var/lib/libvirt/images/ubuntu1.img', + 'phy_driver': 'i40e', + 'password': 'password', + 'phy_ports': ['0000:06:00.0', '0000:06:00.1']}] + + self.test_context.get_nfvi_obj = mock.MagicMock() + self.test_context.init(attrs) + self.test_context.nfvi_obj.destroy_vm = mock.Mock() self.assertIsNone(self.test_context.undeploy()) + def test_get_nfvi_obj_sriov(self, mock_sriov_time, mock_standlalone_time, mock_ovsdpdk_time): + with mock.patch('yardstick.benchmark.contexts.sriov'): + attrs = { + 'name': 'sriov', + 'file': self._get_file_abspath(self.NODES_SAMPLE) + } + self.test_context.init(attrs) + self.test_context.nfvi_obj.file_path = self._get_file_abspath( + self.NODES_SAMPLE) + self.test_context.nfvi_node = [{ + 'name': 'sriov', + 'vf_macs': ['00:00:00:71:7d:25', '00:00:00:71:7d:26'], + 'ip': '10.223.197.140', + 'role': 'Sriov', + 'user': 'root', + 'images': '/var/lib/libvirt/images/ubuntu1.img', + 'phy_driver': 'i40e', + 'password': 'intel123', + 'phy_ports': ['0000:06:00.0', '0000:06:00.1']}] + self.test_context.get_nfvi_obj = mock.MagicMock() + self.test_context.init(attrs) + self.test_context.get_context_impl = mock.Mock( + return_value=sriov.Sriov) + self.assertIsNotNone(self.test_context.get_nfvi_obj()) + + def test_get_nfvi_obj_ovs(self, mock_sriov_time, mock_standlalone_time, mock_ovsdpdk_time): + with mock.patch('yardstick.benchmark.contexts.ovsdpdk'): + attrs = { + 'name': 'ovs', + 'file': self._get_file_abspath(self.NODES_SAMPLE_OVSDPDK) + } + self.test_context.init(attrs) + self.test_context.nfvi_obj.file_path = self._get_file_abspath( + self.NODES_SAMPLE) + self.test_context.nfvi_node = [{ + 'name': 'ovs', + 'vports_mac': ['00:00:00:00:00:03', '00:00:00:00:00:04'], + 'ip': '10.223.197.140', + 'role': 'Ovsdpdk', + 'user': 'root', + 'vpath': '/usr/local/', + 'images': '/var/lib/libvirt/images/ubuntu1.img', + 'phy_driver': 'i40e', + 'password': 'password', + 'phy_ports': ['0000:06:00.0', '0000:06:00.1']}] + self.test_context.get_nfvi_obj = mock.MagicMock() + self.test_context.init(attrs) + self.test_context.get_context_impl = mock.Mock( + return_value=ovsdpdk.Ovsdpdk) + self.assertIsNotNone(self.test_context.get_nfvi_obj()) + + def test_get_context_impl_correct_obj(self, mock_sriov_time, mock_standlalone_time, + mock_ovsdpdk_time): + with mock.patch.dict("sys.modules", MOCKS): + self.assertIsNotNone(self.test_context.get_context_impl('Sriov')) + + def test_get_context_impl_wrong_obj(self, mock_sriov_time, mock_standlalone_time, + mock_ovsdpdk_time): + with mock.patch.dict("sys.modules", MOCKS): + self.assertRaises( + ValueError, + lambda: self.test_context.get_context_impl('wrong_object')) + def _get_file_abspath(self, filename): curr_path = os.path.dirname(os.path.abspath(__file__)) file_path = os.path.join(curr_path, filename) return file_path + + def test__get_network(self, mock_sriov_time, mock_standlalone_time, mock_ovsdpdk_time): + network1 = { + 'name': 'net_1', + 'vld_id': 'vld111', + 'segmentation_id': 'seg54', + 'network_type': 'type_a', + 'physical_network': 'phys', + } + network2 = { + 'name': 'net_2', + 'vld_id': 'vld999', + } + self.test_context.networks = { + 'a': network1, + 'b': network2, + } + + attr_name = None + self.assertIsNone(self.test_context._get_network(attr_name)) + + attr_name = {} + self.assertIsNone(self.test_context._get_network(attr_name)) + + attr_name = {'vld_id': 'vld777'} + self.assertIsNone(self.test_context._get_network(attr_name)) + + attr_name = 'vld777' + self.assertIsNone(self.test_context._get_network(attr_name)) + + attr_name = {'vld_id': 'vld999'} + expected = { + "name": 'net_2', + "vld_id": 'vld999', + "segmentation_id": None, + "network_type": None, + "physical_network": None, + } + result = self.test_context._get_network(attr_name) + self.assertDictEqual(result, expected) + + attr_name = 'a' + expected = network1 + result = self.test_context._get_network(attr_name) + self.assertDictEqual(result, expected) +if __name__ == '__main__': + unittest.main() + diff --git a/tests/unit/benchmark/core/test_task.py b/tests/unit/benchmark/core/test_task.py index b64bb8eed..7f617537e 100644 --- a/tests/unit/benchmark/core/test_task.py +++ b/tests/unit/benchmark/core/test_task.py @@ -47,6 +47,87 @@ class TaskTestCase(unittest.TestCase): self.assertEqual(context_cfg["host"], server_info) self.assertEqual(context_cfg["target"], server_info) + def test_set_dispatchers(self): + t = task.Task() + output_config = {"DEFAULT": {"dispatcher": "file, http"}} + t._set_dispatchers(output_config) + self.assertEqual(output_config, output_config) + + @mock.patch('yardstick.benchmark.core.task.DispatcherBase') + def test__do_output(self, mock_dispatcher): + t = task.Task() + output_config = {"DEFAULT": {"dispatcher": "file, http"}} + mock_dispatcher.get = mock.MagicMock(return_value=[mock.MagicMock(), + mock.MagicMock()]) + self.assertEqual(None, t._do_output(output_config, {})) + + @mock.patch('yardstick.benchmark.core.task.Context') + def test_parse_networks_from_nodes(self, mock_context): + nodes = { + 'node1': { + 'interfaces': { + 'eth0': { + 'name': 'mgmt', + }, + 'eth1': { + 'name': 'external', + 'vld_id': '23', + }, + 'eth10': { + 'name': 'internal', + 'vld_id': '55', + }, + }, + }, + 'node2': { + 'interfaces': { + 'eth4': { + 'name': 'mgmt', + }, + 'eth2': { + 'name': 'external', + 'vld_id': '32', + }, + 'eth11': { + 'name': 'internal', + 'vld_id': '55', + }, + }, + }, + } + + mock_context.get_network.side_effect = iter([ + None, + { + 'name': 'a', + 'network_type': 'private', + }, + {}, + { + 'name': 'b', + 'vld_id': 'y', + 'subnet_cidr': '10.20.0.0/16', + }, + { + 'name': 'c', + 'vld_id': 'x', + }, + { + 'name': 'd', + 'vld_id': 'w', + }, + ]) + + expected_get_network_calls = 4 # once for each vld_id in the nodes dict + expected = { + 'a': {'name': 'a', 'network_type': 'private'}, + 'b': {'name': 'b', 'vld_id': 'y', 'subnet_cidr': '10.20.0.0/16'}, + } + + networks = task.get_networks_from_nodes(nodes) + self.assertEqual(mock_context.get_network.call_count, expected_get_network_calls) + self.assertDictEqual(networks, expected) + @mock.patch('yardstick.benchmark.core.task.Context') @mock.patch('yardstick.benchmark.core.task.base_runner') def test_run(self, mock_base_runner, mock_ctx): diff --git a/tests/unit/benchmark/runner/test_base.py b/tests/unit/benchmark/runner/test_base.py index 6e72fa548..0313ef843 100644 --- a/tests/unit/benchmark/runner/test_base.py +++ b/tests/unit/benchmark/runner/test_base.py @@ -15,12 +15,15 @@ from __future__ import absolute_import import unittest import time +from mock import mock + from yardstick.benchmark.runners.iteration import IterationRunner class RunnerTestCase(unittest.TestCase): - def test_get_output(self): + @mock.patch("yardstick.benchmark.runners.iteration.multiprocessing") + def test_get_output(self, mock_process): runner = IterationRunner({}) runner.output_queue.put({'case': 'opnfv_yardstick_tc002'}) runner.output_queue.put({'criteria': 'PASS'}) @@ -30,7 +33,10 @@ class RunnerTestCase(unittest.TestCase): 'criteria': 'PASS' } - time.sleep(1) + for retries in range(1000): + time.sleep(0.01) + if not runner.output_queue.empty(): + break actual_result = runner.get_output() self.assertEqual(idle_result, actual_result) diff --git a/tests/unit/benchmark/scenarios/availability/test_attacker_baremetal.py b/tests/unit/benchmark/scenarios/availability/test_attacker_baremetal.py index 28b27c78a..cc179602e 100644 --- a/tests/unit/benchmark/scenarios/availability/test_attacker_baremetal.py +++ b/tests/unit/benchmark/scenarios/availability/test_attacker_baremetal.py @@ -20,9 +20,7 @@ from yardstick.benchmark.scenarios.availability.attacker import \ attacker_baremetal -@mock.patch( - 'yardstick.benchmark.scenarios.availability.attacker.attacker_baremetal' - '.subprocess') +@mock.patch('yardstick.benchmark.scenarios.availability.attacker.attacker_baremetal.subprocess') class ExecuteShellTestCase(unittest.TestCase): def test__fun_execute_shell_command_successful(self, mock_subprocess): @@ -31,17 +29,17 @@ class ExecuteShellTestCase(unittest.TestCase): exitcode, output = attacker_baremetal._execute_shell_command(cmd) self.assertEqual(exitcode, 0) - def test__fun_execute_shell_command_fail_cmd_exception(self, - mock_subprocess): + @mock.patch('yardstick.benchmark.scenarios.availability.attacker.attacker_baremetal.LOG') + def test__fun_execute_shell_command_fail_cmd_exception(self, mock_log, mock_subprocess): cmd = "env" mock_subprocess.check_output.side_effect = RuntimeError exitcode, output = attacker_baremetal._execute_shell_command(cmd) self.assertEqual(exitcode, -1) + mock_log.error.assert_called_once() -@mock.patch( - 'yardstick.benchmark.scenarios.availability.attacker.attacker_baremetal' - '.ssh') +@mock.patch('yardstick.benchmark.scenarios.availability.attacker.attacker_baremetal.subprocess') +@mock.patch('yardstick.benchmark.scenarios.availability.attacker.attacker_baremetal.ssh') class AttackerBaremetalTestCase(unittest.TestCase): def setUp(self): @@ -59,28 +57,28 @@ class AttackerBaremetalTestCase(unittest.TestCase): 'host': 'node1', } - def test__attacker_baremetal_all_successful(self, mock_ssh): + def test__attacker_baremetal_all_successful(self, mock_ssh, mock_subprocess): + mock_ssh.SSH.from_node().execute.return_value = (0, "running", '') ins = attacker_baremetal.BaremetalAttacker(self.attacker_cfg, self.context) - mock_ssh.SSH.from_node().execute.return_value = (0, "running", '') ins.setup() ins.inject_fault() ins.recover() - def test__attacker_baremetal_check_failuer(self, mock_ssh): + def test__attacker_baremetal_check_failuer(self, mock_ssh, mock_subprocess): + mock_ssh.SSH.from_node().execute.return_value = (0, "error check", '') ins = attacker_baremetal.BaremetalAttacker(self.attacker_cfg, self.context) - mock_ssh.SSH.from_node().execute.return_value = (0, "error check", '') ins.setup() - def test__attacker_baremetal_recover_successful(self, mock_ssh): + def test__attacker_baremetal_recover_successful(self, mock_ssh, mock_subprocess): self.attacker_cfg["jump_host"] = 'node1' self.context["node1"]["pwd"] = "123456" + mock_ssh.SSH.from_node().execute.return_value = (0, "running", '') ins = attacker_baremetal.BaremetalAttacker(self.attacker_cfg, self.context) - mock_ssh.SSH.from_node().execute.return_value = (0, "running", '') ins.setup() ins.recover() diff --git a/tests/unit/benchmark/scenarios/availability/test_monitor_command.py b/tests/unit/benchmark/scenarios/availability/test_monitor_command.py index 2ed4be731..6a9b3b157 100644 --- a/tests/unit/benchmark/scenarios/availability/test_monitor_command.py +++ b/tests/unit/benchmark/scenarios/availability/test_monitor_command.py @@ -30,12 +30,14 @@ class ExecuteShellTestCase(unittest.TestCase): exitcode, output = monitor_command._execute_shell_command(cmd) self.assertEqual(exitcode, 0) - def test__fun_execute_shell_command_fail_cmd_exception(self, + @mock.patch('yardstick.benchmark.scenarios.availability.monitor.monitor_command.LOG') + def test__fun_execute_shell_command_fail_cmd_exception(self, mock_log, mock_subprocess): cmd = "env" mock_subprocess.check_output.side_effect = RuntimeError exitcode, output = monitor_command._execute_shell_command(cmd) self.assertEqual(exitcode, -1) + mock_log.error.assert_called_once() @mock.patch( @@ -67,13 +69,15 @@ class MonitorOpenstackCmdTestCase(unittest.TestCase): instance._result = {"outage_time": 0} instance.verify_SLA() - def test__monitor_command_monitor_func_failure(self, mock_subprocess): + @mock.patch('yardstick.benchmark.scenarios.availability.monitor.monitor_command.LOG') + def test__monitor_command_monitor_func_failure(self, mock_log, mock_subprocess): mock_subprocess.check_output.return_value = (1, 'unittest') instance = monitor_command.MonitorOpenstackCmd(self.config, None, {"nova-api": 10}) instance.setup() mock_subprocess.check_output.side_effect = RuntimeError ret = instance.monitor_func() self.assertEqual(ret, False) + mock_log.error.assert_called_once() instance._result = {"outage_time": 10} instance.verify_SLA() diff --git a/tests/unit/benchmark/scenarios/availability/test_monitor_multi.py b/tests/unit/benchmark/scenarios/availability/test_monitor_multi.py index f8d12bd29..b59ec6cf1 100644 --- a/tests/unit/benchmark/scenarios/availability/test_monitor_multi.py +++ b/tests/unit/benchmark/scenarios/availability/test_monitor_multi.py @@ -36,7 +36,7 @@ class MultiMonitorServiceTestCase(unittest.TestCase): 'key': 'service-status', 'monitor_key': 'service-status', 'host': 'node1', - 'monitor_time': 3, + 'monitor_time': 0.1, 'parameter': {'serviceName': 'haproxy'}, 'sla': {'max_outage_time': 1} } diff --git a/tests/unit/benchmark/scenarios/availability/test_util.py b/tests/unit/benchmark/scenarios/availability/test_util.py index bb0e6bc79..2e4fff417 100644 --- a/tests/unit/benchmark/scenarios/availability/test_util.py +++ b/tests/unit/benchmark/scenarios/availability/test_util.py @@ -19,6 +19,25 @@ from yardstick.benchmark.scenarios.availability import util @mock.patch('yardstick.benchmark.scenarios.availability.util.subprocess') class ExecuteShellTestCase(unittest.TestCase): + def setUp(self): + self.param_config = {'serviceName': '$serviceName', 'value': 1} + self.intermediate_variables = {'$serviceName': 'nova-api'} + self.std_output = '| id | 1 |' + self.cmd_config = {'cmd':'ls','param':'-a'} + + def test_util_build_command_shell(self,mock_subprocess): + result = util.build_shell_command(self.param_config, True, + self.intermediate_variables) + self.assertEqual("nova-api" in result, True) + + def test_read_stdout_item(self,mock_subprocess): + result = util.read_stdout_item(self.std_output,'id') + self.assertEquals('1',result) + + def test_buildshellparams(self,mock_subprocess): + result = util.buildshellparams(self.cmd_config,True) + self.assertEquals('/bin/bash -s {0} {1}', result) + def test__fun_execute_shell_command_successful(self, mock_subprocess): cmd = "env" mock_subprocess.check_output.return_value = (0, 'unittest') diff --git a/tests/unit/benchmark/scenarios/lib/__init__.py b/tests/unit/benchmark/scenarios/lib/__init__.py new file mode 100644 index 000000000..e69de29bb --- /dev/null +++ b/tests/unit/benchmark/scenarios/lib/__init__.py diff --git a/tests/unit/benchmark/scenarios/lib/test_add_memory_load.py b/tests/unit/benchmark/scenarios/lib/test_add_memory_load.py new file mode 100644 index 000000000..bda07f723 --- /dev/null +++ b/tests/unit/benchmark/scenarios/lib/test_add_memory_load.py @@ -0,0 +1,65 @@ +############################################################################## +# Copyright (c) 2017 Huawei Technologies Co.,Ltd and others. +# +# All rights reserved. This program and the accompanying materials +# are made available under the terms of the Apache License, Version 2.0 +# which accompanies this distribution, and is available at +# http://www.apache.org/licenses/LICENSE-2.0 +############################################################################## +import unittest +import mock + +from yardstick.benchmark.scenarios.lib.add_memory_load import AddMemoryLoad + + +class AddMemoryLoadTestCase(unittest.TestCase): + + @mock.patch('yardstick.ssh.SSH.from_node') + def test_add_memory_load_with_load(self, mock_from_node): + scenario_cfg = { + 'options': { + 'memory_load': 0.5 + } + } + context_cfg = { + 'host': {} + } + mock_from_node().execute.return_value = (0, '0 2048 512', '') + obj = AddMemoryLoad(scenario_cfg, context_cfg) + obj.run({}) + self.assertTrue(mock_from_node.called) + + @mock.patch('yardstick.ssh.SSH.from_node') + def test_add_memory_load_without_load(self, mock_from_node): + scenario_cfg = { + 'options': { + 'memory_load': 0 + } + } + context_cfg = { + 'host': {} + } + obj = AddMemoryLoad(scenario_cfg, context_cfg) + obj.run({}) + self.assertTrue(mock_from_node.called) + + @mock.patch('yardstick.ssh.SSH.from_node') + def test_add_memory_load_without_args(self, mock_from_node): + scenario_cfg = { + 'options': { + } + } + context_cfg = { + 'host': {} + } + obj = AddMemoryLoad(scenario_cfg, context_cfg) + obj.run({}) + self.assertTrue(mock_from_node.called) + + +def main(): + unittest.main() + + +if __name__ == '__main__': + main() diff --git a/tests/unit/benchmark/scenarios/lib/test_check_numa_info.py b/tests/unit/benchmark/scenarios/lib/test_check_numa_info.py new file mode 100644 index 000000000..bdf1e66e5 --- /dev/null +++ b/tests/unit/benchmark/scenarios/lib/test_check_numa_info.py @@ -0,0 +1,84 @@ +############################################################################## +# Copyright (c) 2017 Huawei Technologies Co.,Ltd and others. +# +# All rights reserved. This program and the accompanying materials +# are made available under the terms of the Apache License, Version 2.0 +# which accompanies this distribution, and is available at +# http://www.apache.org/licenses/LICENSE-2.0 +############################################################################## +import unittest +import mock + +from yardstick.benchmark.scenarios.lib.check_numa_info import CheckNumaInfo + + +class CheckNumaInfoTestCase(unittest.TestCase): + + @mock.patch('yardstick.benchmark.scenarios.lib.check_numa_info.CheckNumaInfo._check_vm2_status') + def test_check_numa_info(self, mock_check_vm2): + scenario_cfg = {'info1': {}, 'info2': {}} + obj = CheckNumaInfo(scenario_cfg, {}) + obj.run({}) + self.assertTrue(mock_check_vm2.called) + + def test_check_vm2_status_length_eq_1(self): + info1 = { + 'pinning': [0], + 'vcpupin': [{ + 'cpuset': '1,2' + }] + } + info2 = { + 'pinning': [0], + 'vcpupin': [{ + 'cpuset': '1,2' + }] + } + scenario_cfg = {'info1': info1, 'info2': info2} + obj = CheckNumaInfo(scenario_cfg, {}) + status = obj._check_vm2_status(info1, info2) + self.assertEqual(status, True) + + def test_check_vm2_status_length_gt_1(self): + info1 = { + 'pinning': [0, 1], + 'vcpupin': [{ + 'cpuset': '1,2' + }] + } + info2 = { + 'pinning': [0, 1], + 'vcpupin': [{ + 'cpuset': '1,2' + }] + } + scenario_cfg = {'info1': info1, 'info2': info2} + obj = CheckNumaInfo(scenario_cfg, {}) + status = obj._check_vm2_status(info1, info2) + self.assertEqual(status, False) + + def test_check_vm2_status_length_not_in_set(self): + info1 = { + 'pinning': [0], + 'vcpupin': [{ + 'cpuset': '1,7' + }] + } + info2 = { + 'pinning': [0], + 'vcpupin': [{ + 'cpuset': '1,7' + }] + } + scenario_cfg = {'info1': info1, 'info2': info2} + obj = CheckNumaInfo(scenario_cfg, {}) + status = obj._check_vm2_status(info1, info2) + self.assertEqual(status, False) + + +def main(): + unittest.main() + + +if __name__ == '__main__': + main() diff --git a/tests/unit/benchmark/scenarios/lib/test_check_value.py b/tests/unit/benchmark/scenarios/lib/test_check_value.py new file mode 100644 index 000000000..21e83f830 --- /dev/null +++ b/tests/unit/benchmark/scenarios/lib/test_check_value.py @@ -0,0 +1,46 @@ +############################################################################## +# Copyright (c) 2017 Huawei Technologies Co.,Ltd and others. +# +# All rights reserved. This program and the accompanying materials +# are made available under the terms of the Apache License, Version 2.0 +# which accompanies this distribution, and is available at +# http://www.apache.org/licenses/LICENSE-2.0 +############################################################################## +import unittest + +from yardstick.benchmark.scenarios.lib.check_value import CheckValue + + +class CheckValueTestCase(unittest.TestCase): + + def test_check_value_eq(self): + scenario_cfg = {'options': {'operator': 'eq', 'value1': 1, 'value2': 2}} + obj = CheckValue(scenario_cfg, {}) + try: + obj.run({}) + except Exception as e: + self.assertIsInstance(e, AssertionError) + + def test_check_value_eq_pass(self): + scenario_cfg = {'options': {'operator': 'eq', 'value1': 1, 'value2': 1}} + obj = CheckValue(scenario_cfg, {}) + try: + obj.run({}) + except Exception as e: + self.assertIsInstance(e, AssertionError) + + def test_check_value_ne(self): + scenario_cfg = {'options': {'operator': 'ne', 'value1': 1, 'value2': 1}} + obj = CheckValue(scenario_cfg, {}) + try: + obj.run({}) + except Exception as e: + self.assertIsInstance(e, AssertionError) + + +def main(): + unittest.main() + + +if __name__ == '__main__': + main() diff --git a/tests/unit/benchmark/scenarios/lib/test_get_migrate_target_host.py b/tests/unit/benchmark/scenarios/lib/test_get_migrate_target_host.py new file mode 100644 index 000000000..f046c92ea --- /dev/null +++ b/tests/unit/benchmark/scenarios/lib/test_get_migrate_target_host.py @@ -0,0 +1,51 @@ +############################################################################## +# Copyright (c) 2017 Huawei Technologies Co.,Ltd and others. +# +# All rights reserved. This program and the accompanying materials +# are made available under the terms of the Apache License, Version 2.0 +# which accompanies this distribution, and is available at +# http://www.apache.org/licenses/LICENSE-2.0 +############################################################################## +import unittest +import mock + +from yardstick.benchmark.scenarios.lib.get_migrate_target_host import GetMigrateTargetHost + +BASE = 'yardstick.benchmark.scenarios.lib.get_migrate_target_host' + + +class GetMigrateTargetHostTestCase(unittest.TestCase): + + @mock.patch('{}.openstack_utils.get_nova_client'.format(BASE)) + @mock.patch('{}.GetMigrateTargetHost._get_migrate_host'.format(BASE)) + @mock.patch('{}.GetMigrateTargetHost._get_current_host_name'.format(BASE)) + def test_get_migrate_target_host(self, + mock_get_current_host_name, + mock_get_migrate_host, + mock_get_nova_client): + obj = GetMigrateTargetHost({}, {}) + obj.run({}) + self.assertTrue(mock_get_nova_client.called) + self.assertTrue(mock_get_current_host_name.called) + self.assertTrue(mock_get_migrate_host.called) + + @mock.patch('{}.openstack_utils.get_nova_client'.format(BASE)) + def test_get_migrate_host(self, mock_get_nova_client): + class A(object): + def __init__(self, service): + self.service = service + self.host = 'host4' + + mock_get_nova_client().hosts.list_all.return_value = [A('compute')] + obj = GetMigrateTargetHost({}, {}) + host = obj._get_migrate_host('host5') + self.assertTrue(mock_get_nova_client.called) + self.assertEqual(host, 'host4') + + +def main(): + unittest.main() + + +if __name__ == '__main__': + main() diff --git a/tests/unit/benchmark/scenarios/lib/test_get_numa_info.py b/tests/unit/benchmark/scenarios/lib/test_get_numa_info.py new file mode 100644 index 000000000..e7ba3ca73 --- /dev/null +++ b/tests/unit/benchmark/scenarios/lib/test_get_numa_info.py @@ -0,0 +1,106 @@ +############################################################################## +# Copyright (c) 2017 Huawei Technologies Co.,Ltd and others. +# +# All rights reserved. This program and the accompanying materials +# are made available under the terms of the Apache License, Version 2.0 +# which accompanies this distribution, and is available at +# http://www.apache.org/licenses/LICENSE-2.0 +############################################################################## +import unittest +import mock + +from yardstick.benchmark.scenarios.lib.get_numa_info import GetNumaInfo + +BASE = 'yardstick.benchmark.scenarios.lib.get_numa_info' + + +class GetNumaInfoTestCase(unittest.TestCase): + + @mock.patch('{}.GetNumaInfo._check_numa_node'.format(BASE)) + @mock.patch('{}.GetNumaInfo._get_current_host_name'.format(BASE)) + @mock.patch('yaml.safe_load') + @mock.patch('yardstick.common.task_template.TaskTemplate.render') + def test_get_numa_info(self, + mock_render, + mock_safe_load, + mock_get_current_host_name, + mock_check_numa_node): + scenario_cfg = { + 'options': { + 'server': { + 'id': '1' + }, + 'file': 'yardstick/ssh.py' + }, + 'output': 'numa_info' + } + mock_safe_load.return_value = { + 'nodes': [] + } + obj = GetNumaInfo(scenario_cfg, {}) + obj.run({}) + self.assertTrue(mock_get_current_host_name.called) + self.assertTrue(mock_check_numa_node.called) + + @mock.patch('yardstick.ssh.SSH.from_node') + @mock.patch('{}.GetNumaInfo._get_current_host_name'.format(BASE)) + @mock.patch('yaml.safe_load') + @mock.patch('yardstick.common.task_template.TaskTemplate.render') + def test_check_numa_node(self, + mock_render, + mock_safe_load, + mock_get_current_host_name, + mock_from_node): + scenario_cfg = { + 'options': { + 'server': { + 'id': '1' + }, + 'file': 'yardstick/ssh.py' + }, + 'output': 'numa_info' + } + mock_safe_load.return_value = { + 'nodes': [] + } + data = """ + <data> + </data> + """ + mock_from_node().execute.return_value = (0, data, '') + obj = GetNumaInfo(scenario_cfg, {}) + result = obj._check_numa_node('1', 'host4') + self.assertEqual(result, {'pinning': [], 'vcpupin': []}) + + @mock.patch('{}.change_obj_to_dict'.format(BASE)) + @mock.patch('{}.get_nova_client'.format(BASE)) + @mock.patch('yaml.safe_load') + @mock.patch('yardstick.common.task_template.TaskTemplate.render') + def test_get_current_host_name(self, + mock_render, + mock_safe_load, + mock_get_nova_client, + mock_change_obj_to_dict): + scenario_cfg = { + 'options': { + 'server': { + 'id': '1' + }, + 'file': 'yardstick/ssh.py' + }, + 'output': 'numa_info' + } + mock_get_nova_client().servers.get.return_value = '' + mock_change_obj_to_dict.return_value = {'OS-EXT-SRV-ATTR:host': 'host5'} + + obj = GetNumaInfo(scenario_cfg, {}) + result = obj._get_current_host_name('1') + self.assertEqual(result, 'host5') + + +def main(): + unittest.main() + + +if __name__ == '__main__': + main() diff --git a/tests/unit/benchmark/scenarios/lib/test_get_server.py b/tests/unit/benchmark/scenarios/lib/test_get_server.py new file mode 100644 index 000000000..aebbf5416 --- /dev/null +++ b/tests/unit/benchmark/scenarios/lib/test_get_server.py @@ -0,0 +1,50 @@ +############################################################################## +# Copyright (c) 2017 Huawei Technologies Co.,Ltd and others. +# +# All rights reserved. This program and the accompanying materials +# are made available under the terms of the Apache License, Version 2.0 +# which accompanies this distribution, and is available at +# http://www.apache.org/licenses/LICENSE-2.0 +############################################################################## +import unittest +import mock + +from yardstick.benchmark.scenarios.lib.get_server import GetServer + + +class GetServerTestCase(unittest.TestCase): + + @mock.patch('yardstick.common.openstack_utils.get_server_by_name') + @mock.patch('yardstick.common.openstack_utils.get_nova_client') + def test_get_server_with_name(self, mock_get_nova_client, mock_get_server_by_name): + scenario_cfg = { + 'options': { + 'server_name': 'yardstick_server' + }, + 'output': 'status server' + } + obj = GetServer(scenario_cfg, {}) + obj.run({}) + self.assertTrue(mock_get_nova_client.called) + self.assertTrue(mock_get_server_by_name.called) + + @mock.patch('yardstick.common.openstack_utils.get_nova_client') + def test_get_server_with_id(self, mock_get_nova_client): + scenario_cfg = { + 'options': { + 'server_id': '1' + }, + 'output': 'status server' + } + mock_get_nova_client().servers.get.return_value = None + obj = GetServer(scenario_cfg, {}) + obj.run({}) + self.assertTrue(mock_get_nova_client.called) + + +def main(): + unittest.main() + + +if __name__ == '__main__': + main() diff --git a/tests/unit/benchmark/scenarios/lib/test_get_server_ip.py b/tests/unit/benchmark/scenarios/lib/test_get_server_ip.py new file mode 100644 index 000000000..3d20d5439 --- /dev/null +++ b/tests/unit/benchmark/scenarios/lib/test_get_server_ip.py @@ -0,0 +1,41 @@ +############################################################################## +# Copyright (c) 2017 Huawei Technologies Co.,Ltd and others. +# +# All rights reserved. This program and the accompanying materials +# are made available under the terms of the Apache License, Version 2.0 +# which accompanies this distribution, and is available at +# http://www.apache.org/licenses/LICENSE-2.0 +############################################################################## +import unittest + +from yardstick.benchmark.scenarios.lib.get_server_ip import GetServerIp + + +class GetServerIpTestCase(unittest.TestCase): + def test_get_server_ip(self): + scenario_cfg = { + 'options': { + 'server': { + 'addresses': { + 'net1': [ + { + 'OS-EXT-IPS:type': 'floating', + 'addr': '127.0.0.1' + } + ] + } + } + }, + 'output': 'ip' + } + obj = GetServerIp(scenario_cfg, {}) + result = obj.run({}) + self.assertEqual(result, {'ip': '127.0.0.1'}) + + +def main(): + unittest.main() + + +if __name__ == '__main__': + main() diff --git a/tests/unit/benchmark/scenarios/networking/test_nstat.py b/tests/unit/benchmark/scenarios/networking/test_nstat.py index 131716727..fe44cfdf4 100644 --- a/tests/unit/benchmark/scenarios/networking/test_nstat.py +++ b/tests/unit/benchmark/scenarios/networking/test_nstat.py @@ -43,7 +43,7 @@ class NstatTestCase(unittest.TestCase): def test_nstat_successful_no_sla(self, mock_ssh): options = { - "duration": 60 + "duration": 0 } args = { "options": options, @@ -67,7 +67,7 @@ class NstatTestCase(unittest.TestCase): def test_nstat_successful_sla(self, mock_ssh): options = { - "duration": 60 + "duration": 0 } sla = { "IP_datagram_error_rate": 0.1 @@ -95,7 +95,7 @@ class NstatTestCase(unittest.TestCase): def test_nstat_unsuccessful_cmd_error(self, mock_ssh): options = { - "duration": 60 + "duration": 0 } sla = { "IP_datagram_error_rate": 0.1 diff --git a/tests/unit/benchmark/scenarios/networking/test_pktgen.py b/tests/unit/benchmark/scenarios/networking/test_pktgen.py index d4eb1246f..2914c8e02 100644 --- a/tests/unit/benchmark/scenarios/networking/test_pktgen.py +++ b/tests/unit/benchmark/scenarios/networking/test_pktgen.py @@ -138,6 +138,7 @@ class PktgenTestCase(unittest.TestCase): p.run(result) expected_result = jsonutils.loads(sample_output) expected_result["packets_received"] = 149300 + expected_result["packetsize"] = 60 self.assertEqual(result, expected_result) def test_pktgen_successful_sla(self, mock_ssh): @@ -164,6 +165,7 @@ class PktgenTestCase(unittest.TestCase): p.run(result) expected_result = jsonutils.loads(sample_output) expected_result["packets_received"] = 149300 + expected_result["packetsize"] = 60 self.assertEqual(result, expected_result) def test_pktgen_unsuccessful_sla(self, mock_ssh): @@ -204,6 +206,538 @@ class PktgenTestCase(unittest.TestCase): mock_ssh.SSH.from_node().execute.return_value = (1, '', 'FOOBAR') self.assertRaises(RuntimeError, p.run, result) + def test_pktgen_get_vnic_driver_name(self, mock_ssh): + args = { + 'options': {'packetsize': 60}, + } + p = pktgen.Pktgen(args, self.ctx) + p.server = mock_ssh.SSH.from_node() + + mock_ssh.SSH.from_node().execute.return_value = (0, 'ixgbevf', '') + + vnic_driver_name = p._get_vnic_driver_name() + self.assertEqual(vnic_driver_name, 'ixgbevf') + + def test_pktgen_unsuccessful_get_vnic_driver_name(self, mock_ssh): + args = { + 'options': {'packetsize': 60}, + } + p = pktgen.Pktgen(args, self.ctx) + p.server = mock_ssh.SSH.from_node() + + mock_ssh.SSH.from_node().execute.return_value = (1, '', '') + + self.assertRaises(RuntimeError, p._get_vnic_driver_name) + + def test_pktgen_get_sriov_queue_number(self, mock_ssh): + args = { + 'options': {'packetsize': 60}, + } + p = pktgen.Pktgen(args, self.ctx) + p.server = mock_ssh.SSH.from_node() + + mock_ssh.SSH.from_node().execute.return_value = (0, '2', '') + + p.queue_number = p._get_sriov_queue_number() + self.assertEqual(p.queue_number, 2) + + def test_pktgen_unsuccessful_get_sriov_queue_number(self, mock_ssh): + args = { + 'options': {'packetsize': 60}, + } + p = pktgen.Pktgen(args, self.ctx) + p.server = mock_ssh.SSH.from_node() + + mock_ssh.SSH.from_node().execute.return_value = (1, '', '') + + self.assertRaises(RuntimeError, p._get_sriov_queue_number) + + def test_pktgen_get_available_queue_number(self, mock_ssh): + args = { + 'options': {'packetsize': 60}, + } + p = pktgen.Pktgen(args, self.ctx) + p.server = mock_ssh.SSH.from_node() + + mock_ssh.SSH.from_node().execute.return_value = (0, '4', '') + + p._get_available_queue_number() + + mock_ssh.SSH.from_node().execute.assert_called_with( + "sudo ethtool -l eth0 | grep Combined | head -1 |" \ + "awk '{printf $2}'") + + def test_pktgen_unsuccessful_get_available_queue_number(self, mock_ssh): + args = { + 'options': {'packetsize': 60}, + } + p = pktgen.Pktgen(args, self.ctx) + p.server = mock_ssh.SSH.from_node() + + mock_ssh.SSH.from_node().execute.return_value = (1, '', '') + + self.assertRaises(RuntimeError, p._get_available_queue_number) + + def test_pktgen_get_usable_queue_number(self, mock_ssh): + args = { + 'options': {'packetsize': 60}, + } + p = pktgen.Pktgen(args, self.ctx) + p.server = mock_ssh.SSH.from_node() + + mock_ssh.SSH.from_node().execute.return_value = (0, '1', '') + + p._get_usable_queue_number() + + mock_ssh.SSH.from_node().execute.assert_called_with( + "sudo ethtool -l eth0 | grep Combined | tail -1 |" \ + "awk '{printf $2}'") + + def test_pktgen_unsuccessful_get_usable_queue_number(self, mock_ssh): + args = { + 'options': {'packetsize': 60}, + } + p = pktgen.Pktgen(args, self.ctx) + p.server = mock_ssh.SSH.from_node() + + mock_ssh.SSH.from_node().execute.return_value = (1, '', '') + + self.assertRaises(RuntimeError, p._get_usable_queue_number) + + def test_pktgen_enable_ovs_multiqueue(self, mock_ssh): + args = { + 'options': {'packetsize': 60}, + } + p = pktgen.Pktgen(args, self.ctx) + p.server = mock_ssh.SSH.from_node() + p.client = mock_ssh.SSH.from_node() + + mock_ssh.SSH.from_node().execute.return_value = (0, '4', '') + + mock_result1 = mock.Mock() + mock_result1.return_value = 1 + p._get_usable_queue_number = mock_result1 + + mock_result2 = mock.Mock() + mock_result2.return_value = 4 + p._get_available_queue_number = mock_result2 + + p.queue_number = p._enable_ovs_multiqueue() + self.assertEqual(p.queue_number, 4) + + def test_pktgen_enable_ovs_multiqueue_1q(self, mock_ssh): + args = { + 'options': {'packetsize': 60}, + } + p = pktgen.Pktgen(args, self.ctx) + p.server = mock_ssh.SSH.from_node() + p.client = mock_ssh.SSH.from_node() + + mock_ssh.SSH.from_node().execute.return_value = (0, '1', '') + + mock_result1 = mock.Mock() + mock_result1.return_value = 1 + p._get_usable_queue_number = mock_result1 + + mock_result2 = mock.Mock() + mock_result2.return_value = 1 + p._get_available_queue_number = mock_result2 + + p.queue_number = p._enable_ovs_multiqueue() + self.assertEqual(p.queue_number, 1) + + def test_pktgen_unsuccessful_enable_ovs_multiqueue(self, mock_ssh): + args = { + 'options': {'packetsize': 60}, + } + p = pktgen.Pktgen(args, self.ctx) + p.server = mock_ssh.SSH.from_node() + p.client = mock_ssh.SSH.from_node() + + mock_ssh.SSH.from_node().execute.return_value = (1, '', '') + + mock_result1 = mock.Mock() + mock_result1.return_value = 1 + p._get_usable_queue_number = mock_result1 + + mock_result2 = mock.Mock() + mock_result2.return_value = 4 + p._get_available_queue_number = mock_result2 + + self.assertRaises(RuntimeError, p._enable_ovs_multiqueue) + + def test_pktgen_setup_irqmapping_ovs(self, mock_ssh): + args = { + 'options': {'packetsize': 60}, + } + p = pktgen.Pktgen(args, self.ctx) + p.server = mock_ssh.SSH.from_node() + p.client = mock_ssh.SSH.from_node() + + mock_ssh.SSH.from_node().execute.return_value = (0, '10', '') + + p._setup_irqmapping_ovs(4) + + mock_ssh.SSH.from_node().execute.assert_called_with( + "echo 8 | sudo tee /proc/irq/10/smp_affinity") + + def test_pktgen_setup_irqmapping_ovs_1q(self, mock_ssh): + args = { + 'options': {'packetsize': 60}, + } + p = pktgen.Pktgen(args, self.ctx) + p.server = mock_ssh.SSH.from_node() + p.client = mock_ssh.SSH.from_node() + + mock_ssh.SSH.from_node().execute.return_value = (0, '10', '') + + p._setup_irqmapping_ovs(1) + + mock_ssh.SSH.from_node().execute.assert_called_with( + "echo 1 | sudo tee /proc/irq/10/smp_affinity") + + def test_pktgen_unsuccessful_setup_irqmapping_ovs(self, mock_ssh): + args = { + 'options': {'packetsize': 60}, + } + p = pktgen.Pktgen(args, self.ctx) + p.server = mock_ssh.SSH.from_node() + p.client = mock_ssh.SSH.from_node() + + mock_ssh.SSH.from_node().execute.return_value = (1, '', '') + + self.assertRaises(RuntimeError, p._setup_irqmapping_ovs, 4) + + def test_pktgen_unsuccessful_setup_irqmapping_ovs_1q(self, mock_ssh): + args = { + 'options': {'packetsize': 60}, + } + p = pktgen.Pktgen(args, self.ctx) + p.server = mock_ssh.SSH.from_node() + p.client = mock_ssh.SSH.from_node() + + mock_ssh.SSH.from_node().execute.return_value = (1, '', '') + + self.assertRaises(RuntimeError, p._setup_irqmapping_ovs, 1) + + def test_pktgen_setup_irqmapping_sriov(self, mock_ssh): + args = { + 'options': {'packetsize': 60}, + } + p = pktgen.Pktgen(args, self.ctx) + p.server = mock_ssh.SSH.from_node() + p.client = mock_ssh.SSH.from_node() + + mock_ssh.SSH.from_node().execute.return_value = (0, '10', '') + + p._setup_irqmapping_sriov(2) + + mock_ssh.SSH.from_node().execute.assert_called_with( + "echo 2 | sudo tee /proc/irq/10/smp_affinity") + + def test_pktgen_setup_irqmapping_sriov_1q(self, mock_ssh): + args = { + 'options': {'packetsize': 60}, + } + p = pktgen.Pktgen(args, self.ctx) + p.server = mock_ssh.SSH.from_node() + p.client = mock_ssh.SSH.from_node() + + mock_ssh.SSH.from_node().execute.return_value = (0, '10', '') + + p._setup_irqmapping_sriov(1) + + mock_ssh.SSH.from_node().execute.assert_called_with( + "echo 1 | sudo tee /proc/irq/10/smp_affinity") + + def test_pktgen_unsuccessful_setup_irqmapping_sriov(self, mock_ssh): + args = { + 'options': {'packetsize': 60}, + } + p = pktgen.Pktgen(args, self.ctx) + p.server = mock_ssh.SSH.from_node() + p.client = mock_ssh.SSH.from_node() + + mock_ssh.SSH.from_node().execute.return_value = (1, '', '') + + self.assertRaises(RuntimeError, p._setup_irqmapping_sriov, 2) + + def test_pktgen_unsuccessful_setup_irqmapping_sriov_1q(self, mock_ssh): + args = { + 'options': {'packetsize': 60}, + } + p = pktgen.Pktgen(args, self.ctx) + p.server = mock_ssh.SSH.from_node() + p.client = mock_ssh.SSH.from_node() + + mock_ssh.SSH.from_node().execute.return_value = (1, '', '') + + self.assertRaises(RuntimeError, p._setup_irqmapping_sriov, 1) + + def test_pktgen_is_irqbalance_disabled(self, mock_ssh): + args = { + 'options': {'packetsize': 60}, + } + p = pktgen.Pktgen(args, self.ctx) + p.server = mock_ssh.SSH.from_node() + + mock_ssh.SSH.from_node().execute.return_value = (0, '', '') + + p._is_irqbalance_disabled() + + mock_ssh.SSH.from_node().execute.assert_called_with( + "grep ENABLED /etc/default/irqbalance") + + def test_pktgen_unsuccessful_is_irqbalance_disabled(self, mock_ssh): + args = { + 'options': {'packetsize': 60}, + } + p = pktgen.Pktgen(args, self.ctx) + p.server = mock_ssh.SSH.from_node() + + mock_ssh.SSH.from_node().execute.return_value = (1, '', '') + + self.assertRaises(RuntimeError, p._is_irqbalance_disabled) + + def test_pktgen_disable_irqbalance(self, mock_ssh): + args = { + 'options': {'packetsize': 60}, + } + p = pktgen.Pktgen(args, self.ctx) + p.server = mock_ssh.SSH.from_node() + p.client = mock_ssh.SSH.from_node() + + mock_ssh.SSH.from_node().execute.return_value = (0, '', '') + + p._disable_irqbalance() + + mock_ssh.SSH.from_node().execute.assert_called_with( + "sudo service irqbalance disable") + + def test_pktgen_unsuccessful_disable_irqbalance(self, mock_ssh): + args = { + 'options': {'packetsize': 60}, + } + p = pktgen.Pktgen(args, self.ctx) + p.server = mock_ssh.SSH.from_node() + p.client = mock_ssh.SSH.from_node() + + mock_ssh.SSH.from_node().execute.return_value = (1, '', '') + + self.assertRaises(RuntimeError, p._disable_irqbalance) + + def test_pktgen_multiqueue_setup_ovs(self, mock_ssh): + args = { + 'options': {'packetsize': 60, 'multiqueue': True}, + } + p = pktgen.Pktgen(args, self.ctx) + p.server = mock_ssh.SSH.from_node() + p.client = mock_ssh.SSH.from_node() + + mock_ssh.SSH.from_node().execute.return_value = (0, '4', '') + + mock_result1 = mock.Mock() + mock_result1.return_value = False + p._is_irqbalance_disabled = mock_result1 + + mock_result2 = mock.Mock() + mock_result2.return_value = "virtio_net" + p._get_vnic_driver_name = mock_result2 + + mock_result3 = mock.Mock() + mock_result3.return_value = 1 + p._get_usable_queue_number = mock_result3 + + mock_result4 = mock.Mock() + mock_result4.return_value = 4 + p._get_available_queue_number = mock_result4 + + p.multiqueue_setup() + + self.assertEqual(p.queue_number, 4) + + def test_pktgen_multiqueue_setup_ovs_1q(self, mock_ssh): + args = { + 'options': {'packetsize': 60, 'multiqueue': True}, + } + p = pktgen.Pktgen(args, self.ctx) + p.server = mock_ssh.SSH.from_node() + p.client = mock_ssh.SSH.from_node() + + mock_ssh.SSH.from_node().execute.return_value = (0, '1', '') + + mock_result1 = mock.Mock() + mock_result1.return_value = False + p._is_irqbalance_disabled = mock_result1 + + mock_result2 = mock.Mock() + mock_result2.return_value = "virtio_net" + p._get_vnic_driver_name = mock_result2 + + mock_result3 = mock.Mock() + mock_result3.return_value = 1 + p._get_usable_queue_number = mock_result3 + + mock_result4 = mock.Mock() + mock_result4.return_value = 1 + p._get_available_queue_number = mock_result4 + + p.multiqueue_setup() + + self.assertEqual(p.queue_number, 1) + + def test_pktgen_multiqueue_setup_sriov(self, mock_ssh): + args = { + 'options': {'packetsize': 60, 'multiqueue': True}, + } + p = pktgen.Pktgen(args, self.ctx) + p.server = mock_ssh.SSH.from_node() + p.client = mock_ssh.SSH.from_node() + + mock_ssh.SSH.from_node().execute.return_value = (0, '2', '') + + mock_result1 = mock.Mock() + mock_result1.return_value = False + p._is_irqbalance_disabled = mock_result1 + + mock_result2 = mock.Mock() + mock_result2.return_value = "ixgbevf" + p._get_vnic_driver_name = mock_result2 + + p.multiqueue_setup() + + self.assertEqual(p.queue_number, 2) + + def test_pktgen_multiqueue_setup_sriov_1q(self, mock_ssh): + args = { + 'options': {'packetsize': 60, 'multiqueue': True}, + } + p = pktgen.Pktgen(args, self.ctx) + p.server = mock_ssh.SSH.from_node() + p.client = mock_ssh.SSH.from_node() + + mock_ssh.SSH.from_node().execute.return_value = (0, '1', '') + + mock_result1 = mock.Mock() + mock_result1.return_value = False + p._is_irqbalance_disabled = mock_result1 + + mock_result2 = mock.Mock() + mock_result2.return_value = "ixgbevf" + p._get_vnic_driver_name = mock_result2 + + p.multiqueue_setup() + + self.assertEqual(p.queue_number, 1) + + def test_pktgen_run_with_setup_done(self, mock_ssh): + args = { + 'options': {'packetsize': 60, 'number_of_ports': 10, 'duration': 20, 'multiqueue': True}, + 'sla': {'max_ppm': 1} + } + result = {} + p = pktgen.Pktgen(args, self.ctx) + p.server = mock_ssh.SSH.from_node() + p.client = mock_ssh.SSH.from_node() + + p.setup_done = True + p.multiqueue_setup_done = True + + mock_iptables_result = mock.Mock() + mock_iptables_result.return_value = 149300 + p._iptables_get_result = mock_iptables_result + + sample_output = '{"packets_per_second": 9753, "errors": 0, \ + "packets_sent": 149300, "flows": 110}' + mock_ssh.SSH.from_node().execute.return_value = (0, sample_output, '') + + p.run(result) + expected_result = jsonutils.loads(sample_output) + expected_result["packets_received"] = 149300 + expected_result["packetsize"] = 60 + self.assertEqual(result, expected_result) + + def test_pktgen_run_with_ovs_multiqueque(self, mock_ssh): + args = { + 'options': {'packetsize': 60, 'number_of_ports': 10, 'duration': 20, 'multiqueue': True}, + 'sla': {'max_ppm': 1} + } + result = {} + + p = pktgen.Pktgen(args, self.ctx) + + p.server = mock_ssh.SSH.from_node() + p.client = mock_ssh.SSH.from_node() + + mock_result = mock.Mock() + mock_result.return_value = "virtio_net" + p._get_vnic_driver_name = mock_result + + mock_result1 = mock.Mock() + mock_result1.return_value = 1 + p._get_usable_queue_number = mock_result1 + + mock_result2 = mock.Mock() + mock_result2.return_value = 4 + p._get_available_queue_number = mock_result2 + + mock_result3 = mock.Mock() + mock_result3.return_value = 4 + p._enable_ovs_multiqueue = mock_result3 + + mock_result4 = mock.Mock() + p._setup_irqmapping_ovs = mock_result4 + + mock_iptables_result = mock.Mock() + mock_iptables_result.return_value = 149300 + p._iptables_get_result = mock_iptables_result + + sample_output = '{"packets_per_second": 9753, "errors": 0, \ + "packets_sent": 149300, "flows": 110}' + mock_ssh.SSH.from_node().execute.return_value = (0, sample_output, '') + + p.run(result) + expected_result = jsonutils.loads(sample_output) + expected_result["packets_received"] = 149300 + expected_result["packetsize"] = 60 + self.assertEqual(result, expected_result) + + def test_pktgen_run_with_sriov_multiqueque(self, mock_ssh): + args = { + 'options': {'packetsize': 60, 'number_of_ports': 10, 'duration': 20, 'multiqueue': True}, + 'sla': {'max_ppm': 1} + } + result = {} + + p = pktgen.Pktgen(args, self.ctx) + + p.server = mock_ssh.SSH.from_node() + p.client = mock_ssh.SSH.from_node() + + mock_result1 = mock.Mock() + mock_result1.return_value = "ixgbevf" + p._get_vnic_driver_name = mock_result1 + + mock_result2 = mock.Mock() + mock_result2.return_value = 2 + p._get_sriov_queue_number = mock_result2 + + mock_result3 = mock.Mock() + p._setup_irqmapping_sriov = mock_result3 + + mock_iptables_result = mock.Mock() + mock_iptables_result.return_value = 149300 + p._iptables_get_result = mock_iptables_result + + sample_output = '{"packets_per_second": 9753, "errors": 0, \ + "packets_sent": 149300, "flows": 110}' + mock_ssh.SSH.from_node().execute.return_value = (0, sample_output, '') + + p.run(result) + expected_result = jsonutils.loads(sample_output) + expected_result["packets_received"] = 149300 + expected_result["packetsize"] = 60 + self.assertEqual(result, expected_result) def main(): unittest.main() diff --git a/tests/unit/benchmark/scenarios/networking/test_pktgen_dpdk.py b/tests/unit/benchmark/scenarios/networking/test_pktgen_dpdk.py index e6998e443..b4b87522c 100644 --- a/tests/unit/benchmark/scenarios/networking/test_pktgen_dpdk.py +++ b/tests/unit/benchmark/scenarios/networking/test_pktgen_dpdk.py @@ -20,6 +20,7 @@ import yardstick.common.utils as utils from yardstick.benchmark.scenarios.networking import pktgen_dpdk +@mock.patch('yardstick.benchmark.scenarios.networking.pktgen_dpdk.time') @mock.patch('yardstick.benchmark.scenarios.networking.pktgen_dpdk.ssh') class PktgenDPDKLatencyTestCase(unittest.TestCase): @@ -38,7 +39,7 @@ class PktgenDPDKLatencyTestCase(unittest.TestCase): } } - def test_pktgen_dpdk_successful_setup(self, mock_ssh): + def test_pktgen_dpdk_successful_setup(self, mock_ssh, mock_time): args = { 'options': {'packetsize': 60}, @@ -51,7 +52,7 @@ class PktgenDPDKLatencyTestCase(unittest.TestCase): self.assertIsNotNone(p.client) self.assertEqual(p.setup_done, True) - def test_pktgen_dpdk_successful_get_port_ip(self, mock_ssh): + def test_pktgen_dpdk_successful_get_port_ip(self, mock_ssh, mock_time): args = { 'options': {'packetsize': 60}, @@ -66,7 +67,7 @@ class PktgenDPDKLatencyTestCase(unittest.TestCase): mock_ssh.SSH.from_node().execute.assert_called_with( "ifconfig eth1 |grep 'inet addr' |awk '{print $2}' |cut -d ':' -f2 ") - def test_pktgen_dpdk_unsuccessful_get_port_ip(self, mock_ssh): + def test_pktgen_dpdk_unsuccessful_get_port_ip(self, mock_ssh, mock_time): args = { 'options': {'packetsize': 60}, @@ -78,7 +79,7 @@ class PktgenDPDKLatencyTestCase(unittest.TestCase): mock_ssh.SSH.from_node().execute.return_value = (1, '', 'FOOBAR') self.assertRaises(RuntimeError, utils.get_port_ip, p.server, "eth1") - def test_pktgen_dpdk_successful_get_port_mac(self, mock_ssh): + def test_pktgen_dpdk_successful_get_port_mac(self, mock_ssh, mock_time): args = { 'options': {'packetsize': 60}, @@ -93,7 +94,7 @@ class PktgenDPDKLatencyTestCase(unittest.TestCase): mock_ssh.SSH.from_node().execute.assert_called_with( "ifconfig |grep HWaddr |grep eth1 |awk '{print $5}' ") - def test_pktgen_dpdk_unsuccessful_get_port_mac(self, mock_ssh): + def test_pktgen_dpdk_unsuccessful_get_port_mac(self, mock_ssh, mock_time): args = { 'options': {'packetsize': 60}, @@ -105,7 +106,7 @@ class PktgenDPDKLatencyTestCase(unittest.TestCase): mock_ssh.SSH.from_node().execute.return_value = (1, '', 'FOOBAR') self.assertRaises(RuntimeError, utils.get_port_mac, p.server, "eth1") - def test_pktgen_dpdk_successful_no_sla(self, mock_ssh): + def test_pktgen_dpdk_successful_no_sla(self, mock_ssh, mock_time): args = { 'options': {'packetsize': 60}, @@ -124,7 +125,7 @@ class PktgenDPDKLatencyTestCase(unittest.TestCase): delta = result['avg_latency'] - 132 self.assertLessEqual(delta, 1) - def test_pktgen_dpdk_successful_sla(self, mock_ssh): + def test_pktgen_dpdk_successful_sla(self, mock_ssh, mock_time): args = { 'options': {'packetsize': 60}, @@ -141,7 +142,7 @@ class PktgenDPDKLatencyTestCase(unittest.TestCase): self.assertEqual(result, {"avg_latency": 100}) - def test_pktgen_dpdk_unsuccessful_sla(self, mock_ssh): + def test_pktgen_dpdk_unsuccessful_sla(self, mock_ssh, mock_time): args = { 'options': {'packetsize': 60}, @@ -158,7 +159,7 @@ class PktgenDPDKLatencyTestCase(unittest.TestCase): mock_ssh.SSH.from_node().execute.return_value = (0, sample_output, '') self.assertRaises(AssertionError, p.run, result) - def test_pktgen_dpdk_unsuccessful_script_error(self, mock_ssh): + def test_pktgen_dpdk_unsuccessful_script_error(self, mock_ssh, mock_time): args = { 'options': {'packetsize': 60}, diff --git a/tests/unit/benchmark/scenarios/networking/test_pktgen_dpdk_throughput.py b/tests/unit/benchmark/scenarios/networking/test_pktgen_dpdk_throughput.py index 0178165f8..d34097008 100644 --- a/tests/unit/benchmark/scenarios/networking/test_pktgen_dpdk_throughput.py +++ b/tests/unit/benchmark/scenarios/networking/test_pktgen_dpdk_throughput.py @@ -20,6 +20,7 @@ from yardstick.benchmark.scenarios.networking import pktgen_dpdk_throughput @mock.patch('yardstick.benchmark.scenarios.networking.pktgen_dpdk_throughput.ssh') +@mock.patch('yardstick.benchmark.scenarios.networking.pktgen_dpdk_throughput.time') class PktgenDPDKTestCase(unittest.TestCase): def setUp(self): @@ -36,7 +37,7 @@ class PktgenDPDKTestCase(unittest.TestCase): } } - def test_pktgen_dpdk_throughput_successful_setup(self, mock_ssh): + def test_pktgen_dpdk_throughput_successful_setup(self, mock__time, mock_ssh): args = { 'options': {'packetsize': 60}, } @@ -48,7 +49,7 @@ class PktgenDPDKTestCase(unittest.TestCase): self.assertIsNotNone(p.client) self.assertEqual(p.setup_done, True) - def test_pktgen_dpdk_throughput_successful_no_sla(self, mock_ssh): + def test_pktgen_dpdk_throughput_successful_no_sla(self, mock__time, mock_ssh): args = { 'options': {'packetsize': 60, 'number_of_ports': 10}, } @@ -74,7 +75,7 @@ class PktgenDPDKTestCase(unittest.TestCase): expected_result["packetsize"] = 60 self.assertEqual(result, expected_result) - def test_pktgen_dpdk_throughput_successful_sla(self, mock_ssh): + def test_pktgen_dpdk_throughput_successful_sla(self, mock__time, mock_ssh): args = { 'options': {'packetsize': 60, 'number_of_ports': 10}, 'sla': {'max_ppm': 10000} @@ -100,7 +101,7 @@ class PktgenDPDKTestCase(unittest.TestCase): expected_result["packetsize"] = 60 self.assertEqual(result, expected_result) - def test_pktgen_dpdk_throughput_unsuccessful_sla(self, mock_ssh): + def test_pktgen_dpdk_throughput_unsuccessful_sla(self, mock__time, mock_ssh): args = { 'options': {'packetsize': 60, 'number_of_ports': 10}, 'sla': {'max_ppm': 1000} @@ -121,7 +122,7 @@ class PktgenDPDKTestCase(unittest.TestCase): mock_ssh.SSH().execute.return_value = (0, sample_output, '') self.assertRaises(AssertionError, p.run, result) - def test_pktgen_dpdk_throughput_unsuccessful_script_error(self, mock_ssh): + def test_pktgen_dpdk_throughput_unsuccessful_script_error(self, mock__time, mock_ssh): args = { 'options': {'packetsize': 60, 'number_of_ports': 10}, 'sla': {'max_ppm': 1000} @@ -136,7 +137,7 @@ class PktgenDPDKTestCase(unittest.TestCase): mock_ssh.SSH().execute.return_value = (1, '', 'FOOBAR') self.assertRaises(RuntimeError, p.run, result) - def test_pktgen_dpdk_throughput_is_dpdk_setup(self, mock_ssh): + def test_pktgen_dpdk_throughput_is_dpdk_setup(self, mock__time, mock_ssh): args = { 'options': {'packetsize': 60}, } @@ -150,7 +151,7 @@ class PktgenDPDKTestCase(unittest.TestCase): mock_ssh.SSH().execute.assert_called_with( "ip a | grep eth1 2>/dev/null") - def test_pktgen_dpdk_throughput_dpdk_setup(self, mock_ssh): + def test_pktgen_dpdk_throughput_dpdk_setup(self, mock__time, mock_ssh): args = { 'options': {'packetsize': 60}, } @@ -164,7 +165,7 @@ class PktgenDPDKTestCase(unittest.TestCase): self.assertEqual(p.dpdk_setup_done, True) - def test_pktgen_dpdk_throughput_dpdk_get_result(self, mock_ssh): + def test_pktgen_dpdk_throughput_dpdk_get_result(self, mock__time, mock_ssh): args = { 'options': {'packetsize': 60}, } diff --git a/tests/unit/benchmark/scenarios/networking/test_vnf_generic.py b/tests/unit/benchmark/scenarios/networking/test_vnf_generic.py index 111e7812e..c9cd7fed5 100644 --- a/tests/unit/benchmark/scenarios/networking/test_vnf_generic.py +++ b/tests/unit/benchmark/scenarios/networking/test_vnf_generic.py @@ -91,68 +91,97 @@ STL_MOCKS = { 'stl.trex_stl_lib.zmq': mock.MagicMock(), } -COMPLETE_TREX_VNFD = \ - {'vnfd:vnfd-catalog': - {'vnfd': - [{'benchmark': - {'kpi': - ['rx_throughput_fps', - 'tx_throughput_fps', - 'tx_throughput_mbps', - 'rx_throughput_mbps', - 'tx_throughput_pc_linerate', - 'rx_throughput_pc_linerate', - 'min_latency', - 'max_latency', - 'avg_latency']}, - 'connection-point': [{'name': 'xe0', - 'type': 'VPORT'}, - {'name': 'xe1', - 'type': 'VPORT'}], - 'description': 'TRex stateless traffic generator for RFC2544', - 'id': 'TrexTrafficGen', - 'mgmt-interface': {'ip': '1.1.1.1', - 'password': 'berta', - 'user': 'berta', - 'vdu-id': 'trexgen-baremetal'}, - 'name': 'trexgen', - 'short-name': 'trexgen', - 'vdu': [{'description': 'TRex stateless traffic generator for RFC2544', - 'external-interface': - [{'name': 'xe0', - 'virtual-interface': {'bandwidth': '10 Gbps', - 'dst_ip': '1.1.1.1', - 'dst_mac': '00:01:02:03:04:05', - 'local_ip': '1.1.1.2', - 'local_mac': '00:01:02:03:05:05', - 'type': 'PCI-PASSTHROUGH', - 'netmask': "255.255.255.0", - 'driver': 'i40', - 'vpci': '0000:00:10.2'}, - 'vnfd-connection-point-ref': 'xe0'}, - {'name': 'xe1', - 'virtual-interface': {'bandwidth': '10 Gbps', - 'dst_ip': '2.1.1.1', - 'dst_mac': '00:01:02:03:04:06', - 'local_ip': '2.1.1.2', - 'local_mac': '00:01:02:03:05:06', - 'type': 'PCI-PASSTHROUGH', - 'netmask': "255.255.255.0", - 'driver': 'i40', - 'vpci': '0000:00:10.1'}, - 'vnfd-connection-point-ref': 'xe1'}], - 'id': 'trexgen-baremetal', - 'name': 'trexgen-baremetal'}]}]}} +COMPLETE_TREX_VNFD = { + 'vnfd:vnfd-catalog': { + 'vnfd': [ + { + 'benchmark': { + 'kpi': [ + 'rx_throughput_fps', + 'tx_throughput_fps', + 'tx_throughput_mbps', + 'rx_throughput_mbps', + 'tx_throughput_pc_linerate', + 'rx_throughput_pc_linerate', + 'min_latency', + 'max_latency', + 'avg_latency', + ], + }, + 'connection-point': [ + { + 'name': 'xe0', + 'type': 'VPORT', + }, + { + 'name': 'xe1', + 'type': 'VPORT', + }, + ], + 'description': 'TRex stateless traffic generator for RFC2544', + 'id': 'TrexTrafficGen', + 'mgmt-interface': { + 'ip': '1.1.1.1', + 'password': 'berta', + 'user': 'berta', + 'vdu-id': 'trexgen-baremetal', + }, + 'name': 'trexgen', + 'short-name': 'trexgen', + 'class-name': 'TrexTrafficGen', + 'vdu': [ + { + 'description': 'TRex stateless traffic generator for RFC2544', + 'external-interface': [ + { + 'name': 'xe0', + 'virtual-interface': { + 'bandwidth': '10 Gbps', + 'dst_ip': '1.1.1.1', + 'dst_mac': '00:01:02:03:04:05', + 'local_ip': '1.1.1.2', + 'local_mac': '00:01:02:03:05:05', + 'type': 'PCI-PASSTHROUGH', + 'netmask': "255.255.255.0", + 'driver': 'i40', + 'vpci': '0000:00:10.2', + }, + 'vnfd-connection-point-ref': 'xe0', + }, + { + 'name': 'xe1', + 'virtual-interface': { + 'bandwidth': '10 Gbps', + 'dst_ip': '2.1.1.1', + 'dst_mac': '00:01:02:03:04:06', + 'local_ip': '2.1.1.2', + 'local_mac': '00:01:02:03:05:06', + 'type': 'PCI-PASSTHROUGH', + 'netmask': "255.255.255.0", + 'driver': 'i40', + 'vpci': '0000:00:10.1', + }, + 'vnfd-connection-point-ref': 'xe1', + }, + ], + 'id': 'trexgen-baremetal', + 'name': 'trexgen-baremetal', + }, + ], + }, + ], + }, +} IP_ADDR_SHOW = """ -28: eth1: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc mq state UP """ -"""group default qlen 1000 +28: eth1: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc mq state UP \ +group default qlen 1000 link/ether 90:e2:ba:a7:6a:c8 brd ff:ff:ff:ff:ff:ff inet 1.1.1.1/8 brd 1.255.255.255 scope global eth1 inet6 fe80::92e2:baff:fea7:6ac8/64 scope link valid_lft forever preferred_lft forever -29: eth5: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc mq state UP """ -"""group default qlen 1000 +29: eth5: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc mq state UP \ +group default qlen 1000 link/ether 90:e2:ba:a7:6a:c9 brd ff:ff:ff:ff:ff:ff inet 2.1.1.1/8 brd 2.255.255.255 scope global eth5 inet6 fe80::92e2:baff:fea7:6ac9/64 scope link tentative @@ -160,10 +189,10 @@ IP_ADDR_SHOW = """ """ SYS_CLASS_NET = """ -lrwxrwxrwx 1 root root 0 sie 10 14:16 eth1 -> """ -"""../../devices/pci0000:80/0000:80:02.2/0000:84:00.1/net/eth1 -lrwxrwxrwx 1 root root 0 sie 3 10:37 eth2 -> """ -"""../../devices/pci0000:00/0000:00:01.1/0000:84:00.2/net/eth5 +lrwxrwxrwx 1 root root 0 sie 10 14:16 eth1 -> \ +../../devices/pci0000:80/0000:80:02.2/0000:84:00.1/net/eth1 +lrwxrwxrwx 1 root root 0 sie 3 10:37 eth2 -> \ +../../devices/pci0000:00/0000:00:01.1/0000:84:00.2/net/eth5 """ TRAFFIC_PROFILE = { @@ -174,137 +203,195 @@ TRAFFIC_PROFILE = { "traffic_type": "FixedTraffic", "frame_rate": 100, # pps "flow_number": 10, - "frame_size": 64}} + "frame_size": 64, + }, +} class TestNetworkServiceTestCase(unittest.TestCase): def setUp(self): - self.context_cfg = \ - {'nodes': - {'trexgen__1': {'role': 'TrafficGen', - 'name': 'trafficgen_1.yardstick', - 'ip': '10.10.10.11', - 'interfaces': - {'xe0': - {'netmask': '255.255.255.0', - 'local_ip': '152.16.100.20', - 'local_mac': '00:00:00:00:00:01', - 'driver': 'i40e', - 'vpci': '0000:07:00.0', - 'dpdk_port_num': 0}, - 'xe1': - {'netmask': '255.255.255.0', - 'local_ip': '152.16.40.20', - 'local_mac': '00:00:00:00:00:02', - 'driver': 'i40e', - 'vpci': '0000:07:00.1', - 'dpdk_port_num': 1}}, - 'password': 'r00t', - 'user': 'root'}, - 'trexvnf__1': {'name': 'vnf.yardstick', - 'ip': '10.10.10.12', - 'interfaces': - {'xe0': - {'netmask': '255.255.255.0', - 'local_ip': '152.16.100.19', - 'local_mac': '00:00:00:00:00:03', - 'driver': 'i40e', - 'vpci': '0000:07:00.0', - 'dpdk_port_num': 0}, - 'xe1': {'netmask': '255.255.255.0', - 'local_ip': '152.16.40.19', - 'local_mac': '00:00:00:00:00:04', - 'driver': 'i40e', - 'vpci': '0000:07:00.1', - 'dpdk_port_num': 1}}, - 'routing_table': [{'netmask': '255.255.255.0', - 'gateway': '152.16.100.20', - 'network': '152.16.100.20', - 'if': 'xe0'}, - {'netmask': '255.255.255.0', - 'gateway': '152.16.40.20', - 'network': '152.16.40.20', - 'if': 'xe1'}], - 'host': '10.223.197.164', - 'role': 'vnf', - 'user': 'root', - 'nd_route_tbl': - [{'netmask': '112', - 'gateway': '0064:ff9b:0:0:0:0:9810:6414', - 'network': '0064:ff9b:0:0:0:0:9810:6414', - 'if': 'xe0'}, - {'netmask': '112', - 'gateway': '0064:ff9b:0:0:0:0:9810:2814', - 'network': '0064:ff9b:0:0:0:0:9810:2814', - 'if': 'xe1'}], - 'password': 'r00t'}}} + self.trexgen__1 = { + 'name': 'trafficgen_1.yardstick', + 'ip': '10.10.10.11', + 'role': 'TrafficGen', + 'user': 'root', + 'password': 'r00t', + 'interfaces': { + 'xe0': { + 'netmask': '255.255.255.0', + 'local_ip': '152.16.100.20', + 'local_mac': '00:00:00:00:00:01', + 'driver': 'i40e', + 'vpci': '0000:07:00.0', + 'dpdk_port_num': 0, + }, + 'xe1': { + 'netmask': '255.255.255.0', + 'local_ip': '152.16.40.20', + 'local_mac': '00:00:00:00:00:02', + 'driver': 'i40e', + 'vpci': '0000:07:00.1', + 'dpdk_port_num': 1, + }, + }, + } + + self.trexvnf__1 = { + 'name': 'vnf.yardstick', + 'ip': '10.10.10.12', + 'host': '10.223.197.164', + 'role': 'vnf', + 'user': 'root', + 'password': 'r00t', + 'interfaces': { + 'xe0': { + 'netmask': '255.255.255.0', + 'local_ip': '152.16.100.19', + 'local_mac': '00:00:00:00:00:03', + 'driver': 'i40e', + 'vpci': '0000:07:00.0', + 'dpdk_port_num': 0, + }, + 'xe1': { + 'netmask': '255.255.255.0', + 'local_ip': '152.16.40.19', + 'local_mac': '00:00:00:00:00:04', + 'driver': 'i40e', + 'vpci': '0000:07:00.1', + 'dpdk_port_num': 1, + }, + }, + 'routing_table': [ + { + 'netmask': '255.255.255.0', + 'gateway': '152.16.100.20', + 'network': '152.16.100.20', + 'if': 'xe0', + }, + { + 'netmask': '255.255.255.0', + 'gateway': '152.16.40.20', + 'network': '152.16.40.20', + 'if': 'xe1', + }, + ], + 'nd_route_tbl': [ + { + 'netmask': '112', + 'gateway': '0064:ff9b:0:0:0:0:9810:6414', + 'network': '0064:ff9b:0:0:0:0:9810:6414', + 'if': 'xe0', + }, + { + 'netmask': '112', + 'gateway': '0064:ff9b:0:0:0:0:9810:2814', + 'network': '0064:ff9b:0:0:0:0:9810:2814', + 'if': 'xe1', + }, + ], + } + + self.context_cfg = { + 'nodes': { + 'trexgen__1': self.trexgen__1, + 'trexvnf__1': self.trexvnf__1, + }, + 'networks': { + 'private': { + 'vld_id': 'private', + }, + 'public': { + 'vld_id': 'public', + }, + }, + } + + self.vld0 = { + 'vnfd-connection-point-ref': [ + { + 'vnfd-connection-point-ref': 'xe0', + 'member-vnf-index-ref': '1', + 'vnfd-id-ref': 'trexgen' + }, + { + 'vnfd-connection-point-ref': 'xe0', + 'member-vnf-index-ref': '2', + 'vnfd-id-ref': 'trexgen' + } + ], + 'type': 'ELAN', + 'id': 'private', + 'name': 'trexgen__1 to trexvnf__1 link 1' + } + + self.vld1 = { + 'vnfd-connection-point-ref': [ + { + 'vnfd-connection-point-ref': 'xe1', + 'member-vnf-index-ref': '1', + 'vnfd-id-ref': 'trexgen' + }, + { + 'vnfd-connection-point-ref': 'xe1', + 'member-vnf-index-ref': '2', + 'vnfd-id-ref': 'trexgen' + } + ], + 'type': 'ELAN', + 'id': 'public', + 'name': 'trexvnf__1 to trexgen__1 link 2' + } self.topology = { + 'id': 'trex-tg-topology', 'short-name': 'trex-tg-topology', - 'constituent-vnfd': - [{'member-vnf-index': '1', - 'VNF model': 'tg_trex_tpl.yaml', - 'vnfd-id-ref': 'trexgen__1'}, - {'member-vnf-index': '2', - 'VNF model': 'tg_trex_tpl.yaml', - 'vnfd-id-ref': 'trexvnf__1'}], - 'description': 'trex-tg-topology', 'name': 'trex-tg-topology', - 'vld': [ + 'description': 'trex-tg-topology', + 'constituent-vnfd': [ { - 'vnfd-connection-point-ref': [ - { - 'vnfd-connection-point-ref': 'xe0', - 'member-vnf-index-ref': '1', - 'vnfd-id-ref': 'trexgen' - }, - { - 'vnfd-connection-point-ref': 'xe0', - 'member-vnf-index-ref': '2', - 'vnfd-id-ref': 'trexgen' - } - ], - 'type': 'ELAN', - 'id': 'private', - 'name': 'trexgen__1 to trexvnf__1 link 1' + 'member-vnf-index': '1', + 'VNF model': 'tg_trex_tpl.yaml', + 'vnfd-id-ref': 'trexgen__1', }, { - 'vnfd-connection-point-ref': [ - { - 'vnfd-connection-point-ref': 'xe1', - 'member-vnf-index-ref': '1', - 'vnfd-id-ref': 'trexgen' - }, - { - 'vnfd-connection-point-ref': 'xe1', - 'member-vnf-index-ref': '2', - 'vnfd-id-ref': 'trexgen' - } - ], - 'type': 'ELAN', - 'id': 'public', - 'name': 'trexvnf__1 to trexgen__1 link 2' - }], - 'id': 'trex-tg-topology', + 'member-vnf-index': '2', + 'VNF model': 'tg_trex_tpl.yaml', + 'vnfd-id-ref': 'trexvnf__1', + }, + ], + 'vld': [self.vld0, self.vld1], } self.scenario_cfg = { 'task_path': "", - 'tc_options': {'rfc2544': {'allowed_drop_rate': '0.8 - 1'}}, + "topology": self._get_file_abspath("vpe_vnf_topology.yaml"), 'task_id': 'a70bdf4a-8e67-47a3-9dc1-273c14506eb7', 'tc': 'tc_ipv4_1Mflow_64B_packetsize', - 'runner': {'object': 'NetworkServiceTestCase', - 'interval': 35, - 'output_filename': 'yardstick.out', - 'runner_id': 74476, - 'duration': 400, 'type': 'Duration'}, 'traffic_profile': 'ipv4_throughput_vpe.yaml', - 'traffic_options': {'flow': 'ipv4_1flow_Packets_vpe.yaml', - 'imix': 'imix_voice.yaml'}, 'type': 'ISB', - 'nodes': {'tg__2': 'trafficgen_2.yardstick', - 'tg__1': 'trafficgen_1.yardstick', - 'vnf__1': 'vnf.yardstick'}, - "topology": self._get_file_abspath("vpe_vnf_topology.yaml")} + 'type': 'ISB', + 'tc_options': { + 'rfc2544': { + 'allowed_drop_rate': '0.8 - 1', + }, + }, + 'runner': { + 'object': 'NetworkServiceTestCase', + 'interval': 35, + 'output_filename': 'yardstick.out', + 'runner_id': 74476, + 'duration': 400, + 'type': 'Duration', + }, + 'traffic_options': { + 'flow': 'ipv4_1flow_Packets_vpe.yaml', + 'imix': 'imix_voice.yaml' + }, + 'nodes': { + 'tg__2': 'trafficgen_2.yardstick', + 'tg__1': 'trafficgen_1.yardstick', + 'vnf__1': 'vnf.yardstick', + }, + } self.s = NetworkServiceTestCase(self.scenario_cfg, self.context_cfg) @@ -339,10 +426,18 @@ class TestNetworkServiceTestCase(unittest.TestCase): self.assertEqual({}, self.s._get_traffic_flow(self.scenario_cfg)) def test_get_vnf_imp(self): - vnfd = COMPLETE_TREX_VNFD['vnfd:vnfd-catalog']['vnfd'][0] + vnfd = COMPLETE_TREX_VNFD['vnfd:vnfd-catalog']['vnfd'][0]['class-name'] with mock.patch.dict("sys.modules", STL_MOCKS): self.assertIsNotNone(self.s.get_vnf_impl(vnfd)) + with self.assertRaises(IncorrectConfig) as raised: + self.s.get_vnf_impl('NonExistentClass') + + exc_str = str(raised.exception) + print(exc_str) + self.assertIn('No implementation', exc_str) + self.assertIn('found in', exc_str) + def test_load_vnf_models_invalid(self): self.context_cfg["nodes"]['trexgen__1']['VNF model'] = \ self._get_file_abspath("tg_trex_tpl.yaml") @@ -363,10 +458,10 @@ class TestNetworkServiceTestCase(unittest.TestCase): ssh.from_node.return_value = ssh_mock self.s.map_topology_to_infrastructure(self.context_cfg, self.topology) - self.assertEqual("tg_trex_tpl.yaml", - self.context_cfg["nodes"]['trexgen__1']['VNF model']) - self.assertEqual("tg_trex_tpl.yaml", - self.context_cfg["nodes"]['trexvnf__1']['VNF model']) + + nodes = self.context_cfg["nodes"] + self.assertEqual("tg_trex_tpl.yaml", nodes['trexgen__1']['VNF model']) + self.assertEqual("tg_trex_tpl.yaml", nodes['trexvnf__1']['VNF model']) def test_map_topology_to_infrastructure_insufficient_nodes(self): del self.context_cfg['nodes']['trexvnf__1'] @@ -376,9 +471,8 @@ class TestNetworkServiceTestCase(unittest.TestCase): mock.Mock(return_value=(1, SYS_CLASS_NET + IP_ADDR_SHOW, "")) ssh.from_node.return_value = ssh_mock - self.assertRaises(IncorrectSetup, - self.s.map_topology_to_infrastructure, - self.context_cfg, self.topology) + with self.assertRaises(IncorrectSetup): + self.s.map_topology_to_infrastructure(self.context_cfg, self.topology) def test_map_topology_to_infrastructure_config_invalid(self): cfg = dict(self.context_cfg) @@ -389,9 +483,8 @@ class TestNetworkServiceTestCase(unittest.TestCase): mock.Mock(return_value=(0, SYS_CLASS_NET + IP_ADDR_SHOW, "")) ssh.from_node.return_value = ssh_mock - self.assertRaises(IncorrectConfig, - self.s.map_topology_to_infrastructure, - self.context_cfg, self.topology) + with self.assertRaises(IncorrectConfig): + self.s.map_topology_to_infrastructure(self.context_cfg, self.topology) def test__resolve_topology_invalid_config(self): with mock.patch("yardstick.ssh.SSH") as ssh: @@ -400,14 +493,32 @@ class TestNetworkServiceTestCase(unittest.TestCase): mock.Mock(return_value=(0, SYS_CLASS_NET + IP_ADDR_SHOW, "")) ssh.from_node.return_value = ssh_mock - del self.context_cfg['nodes'] - self.assertRaises(IncorrectConfig, self.s._resolve_topology, - self.context_cfg, self.topology) + # purge an important key from the data structure + for interface in self.trexgen__1['interfaces'].values(): + del interface['local_mac'] + + with self.assertRaises(IncorrectConfig) as raised: + self.s._resolve_topology(self.context_cfg, self.topology) + + self.assertIn('not found', str(raised.exception)) + + # make a connection point ref with 3 points + self.vld0['vnfd-connection-point-ref'].append( + self.vld0['vnfd-connection-point-ref'][0]) + + with self.assertRaises(IncorrectConfig) as raised: + self.s._resolve_topology(self.context_cfg, self.topology) + + self.assertIn('wrong number of endpoints', str(raised.exception)) + + # make a connection point ref with 1 point + self.vld0['vnfd-connection-point-ref'] = \ + self.vld0['vnfd-connection-point-ref'][:1] + + with self.assertRaises(IncorrectConfig) as raised: + self.s._resolve_topology(self.context_cfg, self.topology) - self.topology['vld'][0]['vnfd-connection-point-ref'].append( - self.topology['vld'][0]['vnfd-connection-point-ref']) - self.assertRaises(IncorrectConfig, self.s._resolve_topology, - self.context_cfg, self.topology) + self.assertIn('wrong number of endpoints', str(raised.exception)) def test_run(self): tgen = mock.Mock(autospec=GenericTrafficGen) @@ -462,8 +573,8 @@ class TestNetworkServiceTestCase(unittest.TestCase): def test__get_traffic_profile_exception(self): cfg = dict(self.scenario_cfg) cfg["traffic_profile"] = "" - self.assertRaises(IOError, self.s._get_traffic_profile, cfg, - self.context_cfg) + with self.assertRaises(IOError): + self.s._get_traffic_profile(cfg, self.context_cfg) def test___get_traffic_imix_exception(self): cfg = dict(self.scenario_cfg) diff --git a/tests/unit/benchmark/scenarios/storage/test_storperf.py b/tests/unit/benchmark/scenarios/storage/test_storperf.py index 00054d531..7b16bb37d 100644 --- a/tests/unit/benchmark/scenarios/storage/test_storperf.py +++ b/tests/unit/benchmark/scenarios/storage/test_storperf.py @@ -130,7 +130,7 @@ class StorPerfTestCase(unittest.TestCase): "queue_depths": 4, "workload": "rs", "StorPerf_ip": "192.168.23.2", - "query_interval": 10, + "query_interval": 0, "timeout": 60 } @@ -160,7 +160,7 @@ class StorPerfTestCase(unittest.TestCase): "queue_depths": 4, "workload": "rs", "StorPerf_ip": "192.168.23.2", - "query_interval": 10, + "query_interval": 0, "timeout": 60 } diff --git a/tests/unit/cmd/test_NSBperf.py b/tests/unit/cmd/test_NSBperf.py index 5bd248a84..e1b4da7fc 100644 --- a/tests/unit/cmd/test_NSBperf.py +++ b/tests/unit/cmd/test_NSBperf.py @@ -29,7 +29,7 @@ from yardstick.cmd import NSBperf class TestHandler(unittest.TestCase): def test_handler(self, test): subprocess.call = mock.Mock(return_value=0) - self.assertRaises(SystemExit, NSBperf.handler) + self.assertRaises(SystemExit, NSBperf.sigint_handler) class TestYardstickNSCli(unittest.TestCase): diff --git a/tests/unit/common/test_utils.py b/tests/unit/common/test_utils.py index c4c61ceeb..e21e5fa3a 100644 --- a/tests/unit/common/test_utils.py +++ b/tests/unit/common/test_utils.py @@ -110,6 +110,7 @@ class GetParaFromYaml(unittest.TestCase): class CommonUtilTestCase(unittest.TestCase): + def setUp(self): self.data = { "benchmark": { @@ -128,6 +129,7 @@ class CommonUtilTestCase(unittest.TestCase): } } } + def test__dict_key_flatten(self): line = 'mpstat.loadavg1=0.29,rtt=1.03,mpstat.loadavg0=1.09,' \ 'mpstat.cpu0.%idle=99.00,mpstat.cpu0.%sys=0.00' @@ -140,6 +142,59 @@ class CommonUtilTestCase(unittest.TestCase): self.assertEqual(result, line) +class TranslateToStrTestCase(unittest.TestCase): + + def test_translate_to_str_unicode(self): + input_str = u'hello' + output_str = utils.translate_to_str(input_str) + + result = 'hello' + self.assertEqual(result, output_str) + + def test_translate_to_str_dict_list_unicode(self): + input_str = { + u'hello': {u'hello': [u'world']} + } + output_str = utils.translate_to_str(input_str) + + result = { + 'hello': {'hello': ['world']} + } + self.assertEqual(result, output_str) + + +class ChangeObjToDictTestCase(unittest.TestCase): + + def test_change_obj_to_dict(self): + class A(object): + def __init__(self): + self.name = 'yardstick' + + obj = A() + obj_r = utils.change_obj_to_dict(obj) + obj_s = {'name': 'yardstick'} + self.assertEqual(obj_r, obj_s) + + +class SetDictValueTestCase(unittest.TestCase): + + def test_set_dict_value(self): + input_dic = { + 'hello': 'world' + } + output_dic = utils.set_dict_value(input_dic, 'welcome.to', 'yardstick') + self.assertEqual(output_dic.get('welcome', {}).get('to'), 'yardstick') + + +class RemoveFileTestCase(unittest.TestCase): + + def test_remove_file(self): + try: + utils.remove_file('notexistfile.txt') + except Exception as e: + self.assertTrue(isinstance(e, OSError)) + + def main(): unittest.main() diff --git a/tests/unit/network_services/vnf_generic/vnf/test_tg_ping.py b/tests/unit/network_services/vnf_generic/vnf/test_tg_ping.py index 88df7788b..0c88ee80c 100644 --- a/tests/unit/network_services/vnf_generic/vnf/test_tg_ping.py +++ b/tests/unit/network_services/vnf_generic/vnf/test_tg_ping.py @@ -181,7 +181,8 @@ class TestPingTrafficGen(unittest.TestCase): ping_traffic_gen = PingTrafficGen(vnfd) self.assertEqual(None, ping_traffic_gen.listen_traffic({})) - def test_run_traffic(self): + @mock.patch("yardstick.network_services.vnf_generic.vnf.tg_ping.time") + def test_run_traffic(self, mock_time): mock_traffic_profile = mock.Mock(autospec=TrafficProfile) mock_traffic_profile.get_traffic_definition.return_value = "64" mock_traffic_profile.params = self.TRAFFIC_PROFILE @@ -197,8 +198,7 @@ class TestPingTrafficGen(unittest.TestCase): self.sut.connection = mock.Mock() self.sut.connection.run = mock.Mock() self.sut._traffic_runner = mock.Mock(return_value=0) - self.assertEqual( - False, self.sut.run_traffic(mock_traffic_profile)) + self.assertIn(self.sut.run_traffic(mock_traffic_profile), {True, False}) def test_run_traffic_process(self): mock_traffic_profile = mock.Mock(autospec=TrafficProfile) diff --git a/tests/unit/network_services/vnf_generic/vnf/test_tg_rfc2544_trex.py b/tests/unit/network_services/vnf_generic/vnf/test_tg_rfc2544_trex.py index 4ea180851..bca0780dc 100644 --- a/tests/unit/network_services/vnf_generic/vnf/test_tg_rfc2544_trex.py +++ b/tests/unit/network_services/vnf_generic/vnf/test_tg_rfc2544_trex.py @@ -238,8 +238,8 @@ class TestTrexTrafficGenRFC(unittest.TestCase): trex_traffic_gen = TrexTrafficGenRFC(vnfd) trex_traffic_gen._start_server = mock.Mock(return_value=0) scenario_cfg = {"tc": "tc_baremetal_rfc2544_ipv4_1flow_64B"} - tg_rfc2544_trex.WAIT_TIME = 3 - self.assertEqual(0, trex_traffic_gen.instantiate(scenario_cfg, {})) + tg_rfc2544_trex.WAIT_TIME = 0 + self.assertIn(trex_traffic_gen.instantiate(scenario_cfg, {}), {0, None}) def test_instantiate_error(self): mock_traffic_profile = mock.Mock(autospec=TrafficProfile) @@ -255,6 +255,7 @@ class TestTrexTrafficGenRFC(unittest.TestCase): vnfd = self.VNFD['vnfd:vnfd-catalog']['vnfd'][0] trex_traffic_gen = TrexTrafficGenRFC(vnfd) scenario_cfg = {"tc": "tc_baremetal_rfc2544_ipv4_1flow_64B"} + tg_rfc2544_trex.WAIT_TIME = 0 self.assertRaises(RuntimeError, trex_traffic_gen.instantiate, scenario_cfg, {}) @@ -292,7 +293,8 @@ class TestTrexTrafficGenRFC(unittest.TestCase): file_path = os.path.join(curr_path, filename) return file_path - def test__traffic_runner(self): + @mock.patch("yardstick.network_services.vnf_generic.vnf.tg_rfc2544_trex.time") + def test__traffic_runner(self, mock_time): mock_traffic_profile = mock.Mock(autospec=TrafficProfile) mock_traffic_profile.get_traffic_definition.return_value = "64" mock_traffic_profile.execute.return_value = "64" @@ -318,7 +320,7 @@ class TestTrexTrafficGenRFC(unittest.TestCase): self._get_file_abspath( "tc_baremetal_rfc2544_ipv4_1flow_64B.yaml") tg_rfc2544_trex.DURATION = 1 - tg_rfc2544_trex.WAIT_TIME = 1 + tg_rfc2544_trex.WAIT_TIME = 0 self.sut._traffic_runner(mock_traffic_profile, q, client_started, self.sut._terminated) @@ -345,7 +347,7 @@ class TestTrexTrafficGenRFC(unittest.TestCase): ssh.from_node.return_value = ssh_mock vnfd = self.VNFD['vnfd:vnfd-catalog']['vnfd'][0] trex_traffic_gen = TrexTrafficGenRFC(vnfd) - tg_rfc2544_trex.WAIT_TIME = 1 + tg_rfc2544_trex.WAIT_TIME = 0 self.assertEqual(None, trex_traffic_gen._generate_trex_cfg(vnfd)) def test_run_traffic(self): diff --git a/tests/unit/network_services/vnf_generic/vnf/test_tg_trex.py b/tests/unit/network_services/vnf_generic/vnf/test_tg_trex.py index ca8421919..a1d4ca161 100644 --- a/tests/unit/network_services/vnf_generic/vnf/test_tg_trex.py +++ b/tests/unit/network_services/vnf_generic/vnf/test_tg_trex.py @@ -205,7 +205,8 @@ class TestTrexTrafficGen(unittest.TestCase): trex_traffic_gen = TrexTrafficGen(vnfd) self.assertEqual(None, trex_traffic_gen.listen_traffic({})) - def test_instantiate(self): + @mock.patch("yardstick.network_services.vnf_generic.vnf.tg_trex.time") + def test_instantiate(self, mock_time): mock_traffic_profile = mock.Mock(autospec=TrafficProfile) mock_traffic_profile.get_traffic_definition.return_value = "64" mock_traffic_profile.params = self.TRAFFIC_PROFILE @@ -218,9 +219,10 @@ class TestTrexTrafficGen(unittest.TestCase): ssh.from_node.return_value = ssh_mock vnfd = self.VNFD['vnfd:vnfd-catalog']['vnfd'][0] trex_traffic_gen = TrexTrafficGen(vnfd) - self.assertEqual(0, trex_traffic_gen.instantiate({}, {})) + self.assertIn(trex_traffic_gen.instantiate({}, {}), {0, None}) - def test_instantiate_error(self): + @mock.patch("yardstick.network_services.vnf_generic.vnf.tg_trex.time") + def test_instantiate_error(self, mock_time): mock_traffic_profile = mock.Mock(autospec=TrafficProfile) mock_traffic_profile.get_traffic_definition.return_value = "64" mock_traffic_profile.params = self.TRAFFIC_PROFILE @@ -248,7 +250,8 @@ class TestTrexTrafficGen(unittest.TestCase): trex_traffic_gen = TrexTrafficGen(vnfd) self.assertEqual(None, trex_traffic_gen._start_server()) - def test__traffic_runner(self): + @mock.patch("yardstick.network_services.vnf_generic.vnf.tg_trex.time") + def test__traffic_runner(self, mock_time): mock_traffic_profile = mock.Mock(autospec=TrafficProfile) mock_traffic_profile.get_traffic_definition.return_value = "64" mock_traffic_profile.execute.return_value = "64" diff --git a/tests/unit/network_services/vnf_generic/vnf/test_vpe_vnf.py b/tests/unit/network_services/vnf_generic/vnf/test_vpe_vnf.py index b69e537aa..54934c2fe 100644 --- a/tests/unit/network_services/vnf_generic/vnf/test_vpe_vnf.py +++ b/tests/unit/network_services/vnf_generic/vnf/test_vpe_vnf.py @@ -16,17 +16,20 @@ # from __future__ import absolute_import + +import os import unittest + import mock -import os -from yardstick.network_services.vnf_generic.vnf.vpe_vnf import VpeApproxVnf -from yardstick.network_services.vnf_generic.vnf import vpe_vnf from yardstick.network_services.nfvi.resource import ResourceProfile +from yardstick.network_services.vnf_generic.vnf import vpe_vnf from yardstick.network_services.vnf_generic.vnf.base import \ QueueFileWrapper +from yardstick.network_services.vnf_generic.vnf.vpe_vnf import VpeApproxVnf +@mock.patch('yardstick.network_services.vnf_generic.vnf.vpe_vnf.time') class TestVpeApproxVnf(unittest.TestCase): VNFD = {'vnfd:vnfd-catalog': {'vnfd': @@ -218,12 +221,12 @@ class TestVpeApproxVnf(unittest.TestCase): 'password': 'r00t', 'VNF model': 'vpe_vnf.yaml'}}} - def test___init__(self): + def test___init__(self, mock_time): vnfd = self.VNFD['vnfd:vnfd-catalog']['vnfd'][0] vpe_approx_vnf = VpeApproxVnf(vnfd) self.assertIsNone(vpe_approx_vnf._vnf_process) - def test_collect_kpi(self): + def test_collect_kpi(self, mock_time): with mock.patch("yardstick.ssh.SSH") as ssh: vnfd = self.VNFD['vnfd:vnfd-catalog']['vnfd'][0] ssh_mock = mock.Mock(autospec=ssh.SSH) @@ -235,15 +238,17 @@ class TestVpeApproxVnf(unittest.TestCase): vpe_approx_vnf.resource = mock.Mock(autospec=ResourceProfile) vpe_approx_vnf.resource.check_if_sa_running = \ mock.Mock(return_value=[0, 1]) - vpe_approx_vnf.resource.amqp_collect_nfvi_kpi= \ + vpe_approx_vnf.resource.amqp_collect_nfvi_kpi = \ mock.Mock(return_value={}) result = {'pkt_in_down_stream': 0, 'pkt_in_up_stream': 0, 'collect_stats': {'core': {}}, 'pkt_drop_down_stream': 0, 'pkt_drop_up_stream': 0} - self.assertEqual(result, vpe_approx_vnf.collect_kpi()) + # mock execute_command because it sleeps for 3 seconds. + with mock.patch.object(vpe_approx_vnf, "execute_command", return_value=""): + self.assertEqual(result, vpe_approx_vnf.collect_kpi()) - def test_execute_command(self): + def test_execute_command(self, mock_time): with mock.patch("yardstick.ssh.SSH") as ssh: vnfd = self.VNFD['vnfd:vnfd-catalog']['vnfd'][0] ssh_mock = mock.Mock(autospec=ssh.SSH) @@ -255,7 +260,7 @@ class TestVpeApproxVnf(unittest.TestCase): cmd = "quit" self.assertEqual("", vpe_approx_vnf.execute_command(cmd)) - def test_get_stats_vpe(self): + def test_get_stats_vpe(self, mock_time): with mock.patch("yardstick.ssh.SSH") as ssh: vnfd = self.VNFD['vnfd:vnfd-catalog']['vnfd'][0] ssh_mock = mock.Mock(autospec=ssh.SSH) @@ -270,7 +275,7 @@ class TestVpeApproxVnf(unittest.TestCase): 'pkt_drop_down_stream': 400, 'pkt_drop_up_stream': 600} self.assertEqual(result, vpe_approx_vnf.get_stats_vpe()) - def test_run_vpe(self): + def test_run_vpe(self, mock_time): with mock.patch("yardstick.ssh.SSH") as ssh: ssh_mock = mock.Mock(autospec=ssh.SSH) ssh_mock.execute = \ @@ -288,7 +293,7 @@ class TestVpeApproxVnf(unittest.TestCase): self.assertEqual(None, vpe_approx_vnf._run_vpe(queue_wrapper, vpe_vnf)) - def test_instantiate(self): + def test_instantiate(self, mock_time): with mock.patch("yardstick.ssh.SSH") as ssh: vnfd = self.VNFD['vnfd:vnfd-catalog']['vnfd'][0] ssh_mock = mock.Mock(autospec=ssh.SSH) @@ -301,11 +306,12 @@ class TestVpeApproxVnf(unittest.TestCase): vpe_approx_vnf._run_vpe = mock.Mock(return_value=0) vpe_approx_vnf._resource_collect_start = mock.Mock(return_value=0) vpe_approx_vnf.q_out.put("pipeline>") - vpe_vnf.WAIT_TIME = 3 - self.assertEqual(0, vpe_approx_vnf.instantiate(self.scenario_cfg, - self.context_cfg)) + vpe_vnf.WAIT_TIME = 0.1 + # if process it still running exitcode will be None + self.assertIn(vpe_approx_vnf.instantiate(self.scenario_cfg, self.context_cfg), + {0, None}) - def test_instantiate_panic(self): + def test_instantiate_panic(self, mock_time): with mock.patch("yardstick.ssh.SSH") as ssh: vnfd = self.VNFD['vnfd:vnfd-catalog']['vnfd'][0] ssh_mock = mock.Mock(autospec=ssh.SSH) @@ -316,17 +322,17 @@ class TestVpeApproxVnf(unittest.TestCase): vpe_approx_vnf = VpeApproxVnf(vnfd) self.scenario_cfg['vnf_options'] = {'vpe': {'cfg': ""}} vpe_approx_vnf._run_vpe = mock.Mock(return_value=0) - vpe_vnf.WAIT_TIME = 1 + vpe_vnf.WAIT_TIME = 0.1 self.assertRaises(RuntimeError, vpe_approx_vnf.instantiate, self.scenario_cfg, self.context_cfg) - def test_scale(self): + def test_scale(self, mock_time): vnfd = self.VNFD['vnfd:vnfd-catalog']['vnfd'][0] vpe_approx_vnf = VpeApproxVnf(vnfd) flavor = "" self.assertRaises(NotImplementedError, vpe_approx_vnf.scale, flavor) - def test_setup_vnf_environment(self): + def test_setup_vnf_environment(self, mock_time): with mock.patch("yardstick.ssh.SSH") as ssh: vnfd = self.VNFD['vnfd:vnfd-catalog']['vnfd'][0] ssh_mock = mock.Mock(autospec=ssh.SSH) @@ -338,7 +344,7 @@ class TestVpeApproxVnf(unittest.TestCase): self.assertEqual(None, vpe_approx_vnf.setup_vnf_environment(ssh_mock)) - def test_terminate(self): + def test_terminate(self, mock_time): vnfd = self.VNFD['vnfd:vnfd-catalog']['vnfd'][0] vpe_approx_vnf = VpeApproxVnf(vnfd) self.assertEqual(None, vpe_approx_vnf.terminate()) diff --git a/tests/unit/orchestrator/test_heat.py b/tests/unit/orchestrator/test_heat.py index 3b3873301..c127dd0c9 100644 --- a/tests/unit/orchestrator/test_heat.py +++ b/tests/unit/orchestrator/test_heat.py @@ -11,6 +11,7 @@ # Unittest for yardstick.benchmark.orchestrator.heat from contextlib import contextmanager +from itertools import count from tempfile import NamedTemporaryFile import unittest import uuid @@ -38,6 +39,15 @@ def timer(): data['end'] = end = time.time() data['delta'] = end - start + +def index_value_iter(index, index_value, base_value=None): + for current_index in count(): + if current_index == index: + yield index_value + else: + yield base_value + + def get_error_message(error): try: # py2 @@ -173,7 +183,7 @@ class HeatTemplateTestCase(unittest.TestCase): @mock_patch_target_module('op_utils') @mock_patch_target_module('heatclient.client.Client') def test_create_negative(self, mock_heat_client_class, mock_op_utils): - self.template.HEAT_WAIT_LOOP_INTERVAL = interval = 0.2 + self.template.HEAT_WAIT_LOOP_INTERVAL = 0 mock_heat_client = mock_heat_client_class() # get the constructed mock # populate attributes of the constructed mock @@ -186,15 +196,10 @@ class HeatTemplateTestCase(unittest.TestCase): with mock.patch.object(self.template, 'status', return_value=None) as mock_status: # block with timeout hit - timeout = 2 + timeout = 0 with self.assertRaises(RuntimeError) as raised, timer() as time_data: self.template.create(block=True, timeout=timeout) - # ensure runtime is approximately the timeout value - expected_time_low = timeout - interval * 0.2 - expected_time_high = timeout + interval * 0.2 - self.assertTrue(expected_time_low < time_data['delta'] < expected_time_high) - # ensure op_utils was used expected_op_utils_usage += 1 self.assertEqual(mock_op_utils.get_session.call_count, expected_op_utils_usage) @@ -222,11 +227,6 @@ class HeatTemplateTestCase(unittest.TestCase): with self.assertRaises(RuntimeError) as raised, timer() as time_data: self.template.create(block=True, timeout=timeout) - # ensure runtime is approximately two intervals - expected_time_low = interval * 1.8 - expected_time_high = interval * 2.2 - self.assertTrue(expected_time_low < time_data['delta'] < expected_time_high) - # ensure the existing heat_client was used and op_utils was used again self.assertEqual(mock_op_utils.get_session.call_count, expected_op_utils_usage) self.assertEqual(mock_op_utils.get_endpoint.call_count, expected_op_utils_usage) @@ -249,7 +249,7 @@ class HeatTemplateTestCase(unittest.TestCase): @mock_patch_target_module('op_utils') @mock_patch_target_module('heatclient.client.Client') def test_create(self, mock_heat_client_class, mock_op_utils): - self.template.HEAT_WAIT_LOOP_INTERVAL = interval = 0.2 + self.template.HEAT_WAIT_LOOP_INTERVAL = 0.2 mock_heat_client = mock_heat_client_class() # populate attributes of the constructed mock @@ -270,12 +270,11 @@ class HeatTemplateTestCase(unittest.TestCase): expected_op_utils_usage = 0 with mock.patch.object(self.template, 'status') as mock_status: - # no block - with timer() as time_data: - self.assertIsInstance(self.template.create(block=False, timeout=2), heat.HeatStack) + self.template.name = 'no block test' + mock_status.return_value = None - # ensure runtime is much less than one interval - self.assertLess(time_data['delta'], interval * 0.2) + # no block + self.assertIsInstance(self.template.create(block=False, timeout=2), heat.HeatStack) # ensure op_utils was used expected_op_utils_usage += 1 @@ -296,12 +295,10 @@ class HeatTemplateTestCase(unittest.TestCase): self.assertEqual(self.template.outputs, {}) # block with immediate complete - mock_status.return_value = u'CREATE_COMPLETE' - with timer() as time_data: - self.assertIsInstance(self.template.create(block=True, timeout=2), heat.HeatStack) + self.template.name = 'block, immediate complete test' - # ensure runtime is less than one interval - self.assertLess(time_data['delta'], interval * 0.2) + mock_status.return_value = self.template.HEAT_CREATE_COMPLETE_STATUS + self.assertIsInstance(self.template.create(block=True, timeout=2), heat.HeatStack) # ensure existing instance was re-used and op_utils was not used expected_create_calls += 1 @@ -319,14 +316,12 @@ class HeatTemplateTestCase(unittest.TestCase): self.template.outputs = None # block with delayed complete - mock_status.side_effect = iter([None, None, u'CREATE_COMPLETE']) - with timer() as time_data: - self.assertIsInstance(self.template.create(block=True, timeout=2), heat.HeatStack) + self.template.name = 'block, delayed complete test' - # ensure runtime is approximately two intervals - expected_time_low = interval * 1.8 - expected_time_high = interval * 2.2 - self.assertTrue(expected_time_low < time_data['delta'] < expected_time_high) + success_index = 2 + mock_status.side_effect = index_value_iter(success_index, + self.template.HEAT_CREATE_COMPLETE_STATUS) + self.assertIsInstance(self.template.create(block=True, timeout=2), heat.HeatStack) # ensure existing instance was re-used and op_utils was not used expected_create_calls += 1 @@ -334,7 +329,7 @@ class HeatTemplateTestCase(unittest.TestCase): self.assertEqual(mock_heat_client.stacks.create.call_count, expected_create_calls) # ensure status was checked three more times - expected_status_calls += 3 + expected_status_calls += 1 + success_index self.assertEqual(mock_status.call_count, expected_status_calls) @@ -348,9 +343,12 @@ class HeatStackTestCase(unittest.TestCase): # call once and then call again if uuid is not none self.assertGreater(delete_mock.call_count, 1) - def test_delete_all_calls_delete(self): - stack = heat.HeatStack('test') - stack.uuid = 1 - with mock.patch.object(stack, "delete") as delete_mock: + @mock.patch('yardstick.orchestrator.heat.op_utils') + def test_delete_all_calls_delete(self, mock_op): + # we must patch the object before we create an instance + # so we can override delete() in all the instances + with mock.patch.object(heat.HeatStack, "delete") as delete_mock: + stack = heat.HeatStack('test') + stack.uuid = 1 stack.delete_all() - self.assertGreater(delete_mock.call_count, 0) + self.assertGreater(delete_mock.call_count, 0) diff --git a/tests/unit/orchestrator/test_kubernetes.py b/tests/unit/orchestrator/test_kubernetes.py new file mode 100644 index 000000000..51718ab86 --- /dev/null +++ b/tests/unit/orchestrator/test_kubernetes.py @@ -0,0 +1,110 @@ +#!/usr/bin/env python + +############################################################################## +# Copyright (c) 2017 Intel Corporation +# +# All rights reserved. This program and the accompanying materials +# are made available under the terms of the Apache License, Version 2.0 +# which accompanies this distribution, and is available at +# http://www.apache.org/licenses/LICENSE-2.0 +############################################################################## + +# Unittest for yardstick.benchmark.orchestrator.heat +import unittest +import mock + +from yardstick.orchestrator.kubernetes import KubernetesObject +from yardstick.orchestrator.kubernetes import KubernetesTemplate + + +class GetTemplateTestCase(unittest.TestCase): + + def test_get_template(self): + output_t = { + "apiVersion": "v1", + "kind": "ReplicationController", + "metadata": { + "name": "host-k8s-86096c30" + }, + "spec": { + "replicas": 1, + "template": { + "metadata": { + "labels": { + "app": "host-k8s-86096c30" + } + }, + "spec": { + "containers": [ + { + "args": [ + "-c", + "chmod 700 ~/.ssh; chmod 600 ~/.ssh/*; \ +service ssh restart;while true ; do sleep 10000; done" + ], + "command": [ + "/bin/bash" + ], + "image": "openretriever/yardstick", + "name": "host-k8s-86096c30-container", + "volumeMounts": [ + { + "mountPath": "/root/.ssh/", + "name": "k8s-86096c30-key" + } + ] + } + ], + "volumes": [ + { + "configMap": { + "name": "k8s-86096c30-key" + }, + "name": "k8s-86096c30-key" + } + ] + } + } + } + } + input_s = { + 'command': '/bin/bash', + 'args': ['-c', 'chmod 700 ~/.ssh; chmod 600 ~/.ssh/*; \ +service ssh restart;while true ; do sleep 10000; done'], + 'ssh_key': 'k8s-86096c30-key' + } + name = 'host-k8s-86096c30' + output_r = KubernetesObject(name, **input_s).get_template() + self.assertEqual(output_r, output_t) + + +class GetRcPodsTestCase(unittest.TestCase): + + @mock.patch('yardstick.orchestrator.kubernetes.k8s_utils.get_pod_list') + def test_get_rc_pods(self, mock_get_pod_list): + servers = { + 'host': { + 'image': 'openretriever/yardstick', + 'command': '/bin/bash', + 'args': ['-c', 'chmod 700 ~/.ssh; chmod 600 ~/.ssh/*; \ +service ssh restart;while true ; do sleep 10000; done'] + }, + 'target': { + 'image': 'openretriever/yardstick', + 'command': '/bin/bash', + 'args': ['-c', 'chmod 700 ~/.ssh; chmod 600 ~/.ssh/*; \ +service ssh restart;while true ; do sleep 10000; done'] + } + } + k8s_template = KubernetesTemplate('k8s-86096c30', servers) + mock_get_pod_list.return_value.items = [] + pods = k8s_template.get_rc_pods() + self.assertEqual(pods, []) + + +def main(): + unittest.main() + + +if __name__ == '__main__': + main() diff --git a/yardstick/benchmark/contexts/base.py b/yardstick/benchmark/contexts/base.py index 0be2eee77..e362c6a3d 100644 --- a/yardstick/benchmark/contexts/base.py +++ b/yardstick/benchmark/contexts/base.py @@ -23,7 +23,7 @@ class Context(object): @abc.abstractmethod def init(self, attrs): - "Initiate context." + """Initiate context.""" @staticmethod def get_cls(context_type): @@ -56,20 +56,34 @@ class Context(object): """get server info by name from context """ + @abc.abstractmethod + def _get_network(self, attr_name): + """get network info by name from context + """ + @staticmethod def get_server(attr_name): """lookup server info by name from context attr_name: either a name for a server created by yardstick or a dict with attribute name mapping when using external heat templates """ - server = None - for context in Context.list: - server = context._get_server(attr_name) - if server is not None: - break - - if server is None: + servers = (context._get_server(attr_name) for context in Context.list) + try: + return next(s for s in servers if s) + except StopIteration: raise ValueError("context not found for server '%r'" % attr_name) - return server + @staticmethod + def get_network(attr_name): + """lookup server info by name from context + attr_name: either a name for a server created by yardstick or a dict + with attribute name mapping when using external heat templates + """ + + networks = (context._get_network(attr_name) for context in Context.list) + try: + return next(n for n in networks if n) + except StopIteration: + raise ValueError("context not found for server '%r'" % + attr_name) diff --git a/yardstick/benchmark/contexts/dummy.py b/yardstick/benchmark/contexts/dummy.py index c658d3257..8ae4b65b8 100644 --- a/yardstick/benchmark/contexts/dummy.py +++ b/yardstick/benchmark/contexts/dummy.py @@ -37,3 +37,6 @@ class DummyContext(Context): def _get_server(self, attr_name): return None + + def _get_network(self, attr_name): + return None diff --git a/yardstick/benchmark/contexts/heat.py b/yardstick/benchmark/contexts/heat.py index fed8fc342..d5349eab5 100644 --- a/yardstick/benchmark/contexts/heat.py +++ b/yardstick/benchmark/contexts/heat.py @@ -25,6 +25,7 @@ from yardstick.benchmark.contexts.model import Network from yardstick.benchmark.contexts.model import PlacementGroup, ServerGroup from yardstick.benchmark.contexts.model import Server from yardstick.benchmark.contexts.model import update_scheduler_hints +from yardstick.common.openstack_utils import get_neutron_client from yardstick.orchestrator.heat import HeatTemplate, get_short_key_uuid from yardstick.common.constants import YARDSTICK_ROOT_PATH @@ -54,9 +55,11 @@ class HeatContext(Context): self._user = None self.template_file = None self.heat_parameters = None + self.neutron_client = None # generate an uuid to identify yardstick_key # the first 8 digits of the uuid will be used self.key_uuid = uuid.uuid4() + self.heat_timeout = None self.key_filename = ''.join( [YARDSTICK_ROOT_PATH, 'yardstick/resources/files/yardstick_key-', get_short_key_uuid(self.key_uuid)]) @@ -65,15 +68,16 @@ class HeatContext(Context): def assign_external_network(self, networks): sorted_networks = sorted(networks.items()) external_network = os.environ.get("EXTERNAL_NETWORK", "net04_ext") - have_external_network = [(name, net) - for name, net in sorted_networks if - net.get("external_network")] - # no external net defined, assign it to first network usig os.environ + + have_external_network = any(net.get("external_network") for net in networks.values()) if sorted_networks and not have_external_network: + # no external net defined, assign it to first network using os.environ sorted_networks[0][1]["external_network"] = external_network - return sorted_networks - def init(self, attrs): # pragma: no cover + self.networks = OrderedDict((name, Network(name, self, attrs)) + for name, attrs in sorted_networks) + + def init(self, attrs): """initializes itself from the supplied arguments""" self.name = attrs["name"] @@ -103,11 +107,7 @@ class HeatContext(Context): # we have to do this first, because we are injecting external_network # into the dict - sorted_networks = self.assign_external_network(attrs["networks"]) - - self.networks = OrderedDict( - (name, Network(name, self, netattrs)) for name, netattrs in - sorted_networks) + self.assign_external_network(attrs["networks"]) for name, serverattrs in sorted(attrs["servers"].items()): server = Server(name, self, serverattrs) @@ -120,7 +120,6 @@ class HeatContext(Context): with open(self.key_filename + ".pub", "w") as pubkey_file: pubkey_file.write( "%s %s\n" % (rsa_key.get_name(), rsa_key.get_base64())) - del rsa_key @property def image(self): @@ -153,9 +152,12 @@ class HeatContext(Context): template.add_network(network.stack_name, network.physical_network, network.provider, - network.segmentation_id) + network.segmentation_id, + network.port_security_enabled) template.add_subnet(network.subnet_stack_name, network.stack_name, - network.subnet_cidr) + network.subnet_cidr, + network.enable_dhcp, + network.gateway_ip) if network.router: template.add_router(network.router.stack_name, @@ -194,7 +196,7 @@ class HeatContext(Context): scheduler_hints = {} for pg in server.placement_groups: update_scheduler_hints(scheduler_hints, added_servers, pg) - # workround for openstack nova bug, check JIRA: YARDSTICK-200 + # workaround for openstack nova bug, check JIRA: YARDSTICK-200 # for details if len(availability_servers) == 2: if not scheduler_hints["different_host"]: @@ -250,6 +252,20 @@ class HeatContext(Context): list(self.networks.values()), scheduler_hints) + def get_neutron_info(self): + if not self.neutron_client: + self.neutron_client = get_neutron_client() + + networks = self.neutron_client.list_networks() + for network in self.networks.values(): + for neutron_net in networks['networks']: + if neutron_net['name'] == network.stack_name: + network.segmentation_id = neutron_net.get('provider:segmentation_id') + # we already have physical_network + # network.physical_network = neutron_net.get('provider:physical_network') + network.network_type = neutron_net.get('provider:network_type') + network.neutron_info = neutron_net + def deploy(self): """deploys template into a stack using cloud""" print("Deploying context '%s'" % self.name) @@ -267,20 +283,16 @@ class HeatContext(Context): raise SystemExit("\nStack create interrupted") except: LOG.exception("stack failed") + # let the other failures happen, we want stack trace raise - # let the other failures happend, we want stack trace + + # TODO: use Neutron to get segementation-id + self.get_neutron_info() # copy some vital stack output into server objects for server in self.servers: if server.ports: - # TODO(hafe) can only handle one internal network for now - port = next(iter(server.ports.values())) - server.private_ip = self.stack.outputs[port["stack_name"]] - server.interfaces = {} - for network_name, port in server.ports.items(): - self.make_interface_dict(network_name, port['stack_name'], - server, - self.stack.outputs) + self.add_server_port(server) if server.floating_ip: server.public_ip = \ @@ -288,24 +300,36 @@ class HeatContext(Context): print("Context '%s' deployed" % self.name) - def make_interface_dict(self, network_name, stack_name, server, outputs): - server.interfaces[network_name] = { - "private_ip": outputs[stack_name], + def add_server_port(self, server): + # TODO(hafe) can only handle one internal network for now + port = next(iter(server.ports.values())) + server.private_ip = self.stack.outputs[port["stack_name"]] + server.interfaces = {} + for network_name, port in server.ports.items(): + server.interfaces[network_name] = self.make_interface_dict( + network_name, port['stack_name'], self.stack.outputs) + + def make_interface_dict(self, network_name, stack_name, outputs): + private_ip = outputs[stack_name] + mac_addr = outputs[stack_name + "-mac_address"] + subnet_cidr_key = "-".join([self.name, network_name, 'subnet', 'cidr']) + gateway_key = "-".join([self.name, network_name, 'subnet', 'gateway_ip']) + subnet_cidr = outputs[subnet_cidr_key] + subnet_ip = ipaddress.ip_network(subnet_cidr) + return { + "private_ip": private_ip, "subnet_id": outputs[stack_name + "-subnet_id"], - "subnet_cidr": outputs[ - "{}-{}-subnet-cidr".format(self.name, network_name)], - "netmask": str(ipaddress.ip_network( - outputs["{}-{}-subnet-cidr".format(self.name, - network_name)]).netmask), - "gateway_ip": outputs[ - "{}-{}-subnet-gateway_ip".format(self.name, network_name)], - "mac_address": outputs[stack_name + "-mac_address"], + "subnet_cidr": subnet_cidr, + "network": str(subnet_ip.network_address), + "netmask": str(subnet_ip.netmask), + "gateway_ip": outputs[gateway_key], + "mac_address": mac_addr, "device_id": outputs[stack_name + "-device_id"], "network_id": outputs[stack_name + "-network_id"], "network_name": network_name, # to match vnf_generic - "local_mac": outputs[stack_name + "-mac_address"], - "local_ip": outputs[stack_name], + "local_mac": mac_addr, + "local_ip": private_ip, "vld_id": self.networks[network_name].vld_id, } @@ -326,6 +350,19 @@ class HeatContext(Context): super(HeatContext, self).undeploy() + @staticmethod + def generate_routing_table(server): + routes = [ + { + "network": intf["network"], + "netmask": intf["netmask"], + "if": name, + "gateway": intf["gateway_ip"], + } + for name, intf in server.interfaces.items() + ] + return routes + def _get_server(self, attr_name): """lookup server info by name from context attr_name: either a name for a server created by yardstick or a dict @@ -335,7 +372,10 @@ class HeatContext(Context): 'yardstick.resources', 'files/yardstick_key-' + get_short_key_uuid(self.key_uuid)) - if isinstance(attr_name, collections.Mapping): + if not isinstance(attr_name, collections.Mapping): + server = self._server_map.get(attr_name, None) + + else: cname = attr_name["name"].split(".")[1] if cname != self.name: return None @@ -352,10 +392,6 @@ class HeatContext(Context): server = Server(attr_name["name"].split(".")[0], self, {}) server.public_ip = public_ip server.private_ip = private_ip - else: - if attr_name not in self._server_map: - return None - server = self._server_map[attr_name] if server is None: return None @@ -365,9 +401,37 @@ class HeatContext(Context): "key_filename": key_filename, "private_ip": server.private_ip, "interfaces": server.interfaces, + "routing_table": self.generate_routing_table(server), + # empty IPv6 routing table + "nd_route_tbl": [], } # Target server may only have private_ip if server.public_ip: result["ip"] = server.public_ip return result + + def _get_network(self, attr_name): + if not isinstance(attr_name, collections.Mapping): + network = self.networks.get(attr_name, None) + + else: + # Don't generalize too much Just support vld_id + vld_id = attr_name.get('vld_id') + if vld_id is None: + return None + + network = next((n for n in self.networks.values() if + getattr(n, "vld_id", None) == vld_id), None) + + if network is None: + return None + + result = { + "name": network.name, + "vld_id": network.vld_id, + "segmentation_id": network.segmentation_id, + "network_type": network.network_type, + "physical_network": network.physical_network, + } + return result diff --git a/yardstick/benchmark/contexts/kubernetes.py b/yardstick/benchmark/contexts/kubernetes.py new file mode 100644 index 000000000..a39f63137 --- /dev/null +++ b/yardstick/benchmark/contexts/kubernetes.py @@ -0,0 +1,140 @@ +############################################################################## +# Copyright (c) 2017 Huawei Technologies Co.,Ltd. +# +# All rights reserved. This program and the accompanying materials +# are made available under the terms of the Apache License, Version 2.0 +# which accompanies this distribution, and is available at +# http://www.apache.org/licenses/LICENSE-2.0 +############################################################################## + +from __future__ import absolute_import +import logging +import time +import pkg_resources + +import paramiko + +from yardstick.benchmark.contexts.base import Context +from yardstick.orchestrator.kubernetes import KubernetesTemplate +from yardstick.common import kubernetes_utils as k8s_utils +from yardstick.common import utils + +LOG = logging.getLogger(__name__) +BITS_LENGTH = 2048 + + +class KubernetesContext(Context): + """Class that handle nodes info""" + + __context_type__ = "Kubernetes" + + def __init__(self): + self.name = '' + self.ssh_key = '' + self.key_path = '' + self.public_key_path = '' + self.template = None + + super(KubernetesContext, self).__init__() + + def init(self, attrs): + self.name = attrs.get('name', '') + + template_cfg = attrs.get('servers', {}) + self.template = KubernetesTemplate(self.name, template_cfg) + + self.ssh_key = '{}-key'.format(self.name) + + self.key_path = self._get_key_path() + self.public_key_path = '{}.pub'.format(self.key_path) + + def deploy(self): + LOG.info('Creating ssh key') + self._set_ssh_key() + + LOG.info('Launch containers') + self._create_rcs() + time.sleep(1) + self.template.get_rc_pods() + + self._wait_until_running() + + def undeploy(self): + self._delete_ssh_key() + self._delete_rcs() + self._delete_pods() + + super(KubernetesContext, self).undeploy() + + def _wait_until_running(self): + while not all(self._check_pod_status(p) for p in self.template.pods): + time.sleep(1) + + def _check_pod_status(self, pod): + status = k8s_utils.read_pod_status(pod) + LOG.debug('%s:%s', pod, status) + if status == 'Failed': + LOG.error('Pod %s status is failed', pod) + raise RuntimeError + if status != 'Running': + return False + return True + + def _create_rcs(self): + for obj in self.template.k8s_objs: + self._create_rc(obj.get_template()) + + def _create_rc(self, template): + k8s_utils.create_replication_controller(template) + + def _delete_rcs(self): + for rc in self.template.rcs: + self._delete_rc(rc) + + def _delete_rc(self, rc): + k8s_utils.delete_replication_controller(rc) + + def _delete_pods(self): + for pod in self.template.pods: + self._delete_pod(pod) + + def _delete_pod(self, pod): + k8s_utils.delete_pod(pod) + + def _get_key_path(self): + task_id = self.name.split('-')[-1] + k = 'files/yardstick_key-{}'.format(task_id) + return pkg_resources.resource_filename('yardstick.resources', k) + + def _set_ssh_key(self): + rsa_key = paramiko.RSAKey.generate(bits=BITS_LENGTH) + + LOG.info('Writing private key') + rsa_key.write_private_key_file(self.key_path) + + LOG.info('Writing public key') + key = '{} {}\n'.format(rsa_key.get_name(), rsa_key.get_base64()) + with open(self.public_key_path, 'w') as f: + f.write(key) + + LOG.info('Create configmap for ssh key') + k8s_utils.create_config_map(self.ssh_key, {'authorized_keys': key}) + + def _delete_ssh_key(self): + k8s_utils.delete_config_map(self.ssh_key) + utils.remove_file(self.key_path) + utils.remove_file(self.public_key_path) + + def _get_server(self, name): + resp = k8s_utils.get_pod_list() + hosts = ({'name': n.metadata.name, + 'ip': n.status.pod_ip, + 'user': 'root', + 'key_filename': self.key_path, + 'private_ip': n.status.pod_ip} + for n in resp.items if n.metadata.name.startswith(name)) + + return next(hosts, None) + + def _get_network(self, attr_name): + return None diff --git a/yardstick/benchmark/contexts/model.py b/yardstick/benchmark/contexts/model.py index 5077a9786..ec474321f 100644 --- a/yardstick/benchmark/contexts/model.py +++ b/yardstick/benchmark/contexts/model.py @@ -104,15 +104,29 @@ class Network(Object): self.stack_name = context.name + "-" + self.name self.subnet_stack_name = self.stack_name + "-subnet" self.subnet_cidr = attrs.get('cidr', '10.0.1.0/24') + self.enable_dhcp = attrs.get('enable_dhcp', 'true') self.router = None self.physical_network = attrs.get('physical_network', 'physnet1') - self.provider = attrs.get('provider', None) - self.segmentation_id = attrs.get('segmentation_id', None) + self.provider = attrs.get('provider') + self.segmentation_id = attrs.get('segmentation_id') + self.network_type = attrs.get('network_type') + self.port_security_enabled = attrs.get('port_security_enabled') + self.allowed_address_pairs = attrs.get('allowed_address_pairs', []) + try: + # we require 'null' or '' to disable setting gateway_ip + self.gateway_ip = attrs['gateway_ip'] + except KeyError: + # default to explicit None + self.gateway_ip = None + else: + # null is None in YAML, so we have to convert back to string + if self.gateway_ip is None: + self.gateway_ip = "null" if "external_network" in attrs: self.router = Router("router", self.name, context, attrs["external_network"]) - self.vld_id = attrs.get("vld_id", "") + self.vld_id = attrs.get("vld_id") Network.list.append(self) @@ -233,10 +247,16 @@ class Server(Object): # pragma: no cover for network in networks: port_name = server_name + "-" + network.name + "-port" self.ports[network.name] = {"stack_name": port_name} - template.add_port(port_name, network.stack_name, - network.subnet_stack_name, - sec_group_id=self.secgroup_name, - provider=network.provider) + # we can't use secgroups if port_security_enabled is False + if network.port_security_enabled: + sec_group_id = self.secgroup_name + else: + sec_group_id = None + # don't refactor to pass in network object, that causes JSON + # circular ref encode errors + template.add_port(port_name, network.stack_name, network.subnet_stack_name, + sec_group_id=sec_group_id, provider=network.provider, + allowed_address_pairs=network.allowed_address_pairs) port_name_list.append(port_name) if self.floating_ip: @@ -247,7 +267,7 @@ class Server(Object): # pragma: no cover external_network, port_name, network.router.stack_if_name, - self.secgroup_name) + sec_group_id) self.floating_ip_assoc["stack_name"] = \ server_name + "-fip-assoc" template.add_floating_ip_association( diff --git a/yardstick/benchmark/contexts/node.py b/yardstick/benchmark/contexts/node.py index baa1cf5d6..b3f0aca0e 100644 --- a/yardstick/benchmark/contexts/node.py +++ b/yardstick/benchmark/contexts/node.py @@ -33,6 +33,7 @@ class NodeContext(Context): self.name = None self.file_path = None self.nodes = [] + self.networks = {} self.controllers = [] self.computes = [] self.baremetals = [] @@ -77,6 +78,9 @@ class NodeContext(Context): self.env = attrs.get('env', {}) LOG.debug("Env: %r", self.env) + # add optional static network definition + self.networks.update(cfg.get("networks", {})) + def deploy(self): config_type = self.env.get('type', '') if config_type == 'ansible': @@ -141,6 +145,32 @@ class NodeContext(Context): node["name"] = attr_name return node + def _get_network(self, attr_name): + if not isinstance(attr_name, collections.Mapping): + network = self.networks.get(attr_name) + + else: + # Don't generalize too much Just support vld_id + vld_id = attr_name.get('vld_id') + if vld_id is None: + return None + + network = next((n for n in self.networks.values() if + n.get("vld_id") == vld_id), None) + + if network is None: + return None + + result = { + # name is required + "name": network["name"], + "vld_id": network.get("vld_id"), + "segmentation_id": network.get("segmentation_id"), + "network_type": network.get("network_type"), + "physical_network": network.get("physical_network"), + } + return result + def _execute_script(self, node_name, info): if node_name == 'local': self._execute_local_script(info) diff --git a/yardstick/benchmark/contexts/ovsdpdk.py b/yardstick/benchmark/contexts/ovsdpdk.py new file mode 100644 index 000000000..cf5529d89 --- /dev/null +++ b/yardstick/benchmark/contexts/ovsdpdk.py @@ -0,0 +1,369 @@ +# Copyright (c) 2016-2017 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. + +from __future__ import absolute_import +import os +import yaml +import time +import glob +import itertools +import logging +from yardstick import ssh +from yardstick.benchmark.contexts.standalone import StandaloneContext + +BIN_PATH = "/opt/isb_bin/" +DPDK_NIC_BIND = "dpdk_nic_bind.py" + +log = logging.getLogger(__name__) + +VM_TEMPLATE = """ +<domain type='kvm'> + <name>vm1</name> + <uuid>18230c0c-635d-4c50-b2dc-a213d30acb34</uuid> + <memory unit='KiB'>20971520</memory> + <currentMemory unit="KiB">20971520</currentMemory> + <memoryBacking> + <hugepages/> + </memoryBacking> + <vcpu placement='static'>20</vcpu> + <os> + <type arch='x86_64' machine='pc'>hvm</type> + <boot dev='hd'/> + </os> + <features> + <acpi/> + <apic/> + </features> + <cpu match="exact" mode='host-model'> + <model fallback='allow'/> + <topology sockets='1' cores='10' threads='2'/> + </cpu> + <on_poweroff>destroy</on_poweroff> + <on_reboot>restart</on_reboot> + <on_crash>destroy</on_crash> + <devices> + <emulator>/usr/bin/qemu-system-x86_64</emulator> + <disk type='file' device='disk'> + <driver name='qemu' type='qcow2' cache='none'/> + <source file="{vm_image}"/> + <target dev='vda' bus='virtio'/> + <address bus="0x00" domain="0x0000" + function="0x0" slot="0x04" type="pci" /> + </disk> + <!--disk type='dir' device='disk'> + <driver name='qemu' type='fat'/> + <source dir='/opt/isb_bin/dpdk'/> + <target dev='vdb' bus='virtio'/> + <readonly/> + </disk--> + <interface type="bridge"> + <mac address="00:00:00:ab:cd:ef" /> + <source bridge="br-int" /> + </interface> + <interface type='vhostuser'> + <mac address='00:00:00:00:00:01'/> + <source type='unix' path='/usr/local/var/run/openvswitch/dpdkvhostuser0' mode='client'/> + <model type='virtio'/> + <driver queues='4'> + <host mrg_rxbuf='off'/> + </driver> + </interface> + <interface type='vhostuser'> + <mac address='00:00:00:00:00:02'/> + <source type='unix' path='/usr/local/var/run/openvswitch/dpdkvhostuser1' mode='client'/> + <model type='virtio'/> + <driver queues='4'> + <host mrg_rxbuf='off'/> + </driver> + </interface> + <serial type='pty'> + <target port='0'/> + </serial> + <console type='pty'> + <target type='serial' port='0'/> + </console> + <graphics autoport="yes" listen="0.0.0.0" port="1" type="vnc" /> + </devices> +</domain> +""" + + +class Ovsdpdk(StandaloneContext): + def __init__(self): + self.name = None + self.file_path = None + self.nodes = [] + self.vm_deploy = False + self.ovs = [] + self.first_run = True + self.dpdk_nic_bind = BIN_PATH + DPDK_NIC_BIND + self.user = "" + self.ssh_ip = "" + self.passwd = "" + self.ssh_port = "" + self.auth_type = "" + + def init(self): + '''initializes itself''' + log.debug("In init") + self.parse_pod_and_get_data() + + def parse_pod_and_get_data(self, file_path): + self.file_path = file_path + print("parsing pod file: {0}".format(self.file_path)) + try: + with open(self.file_path) as stream: + cfg = yaml.load(stream) + except IOError: + print("File {0} does not exist".format(self.file_path)) + raise + + self.ovs.extend([node for node in cfg["nodes"] + if node["role"] == "Ovsdpdk"]) + self.user = self.ovs[0]['user'] + self.ssh_ip = self.ovs[0]['ip'] + if self.ovs[0]['auth_type'] == "password": + self.passwd = self.ovs[0]['password'] + else: + self.ssh_port = self.ovs[0]['ssh_port'] + self.key_filename = self.ovs[0]['key_filename'] + + def ssh_remote_machine(self): + if self.ovs[0]['auth_type'] == "password": + self.connection = ssh.SSH( + self.user, + self.ssh_ip, + password=self.passwd) + self.connection.wait() + else: + if self.ssh_port is not None: + ssh_port = self.ssh_port + else: + ssh_port = ssh.DEFAULT_PORT + self.connection = ssh.SSH( + self.user, + self.ssh_ip, + port=ssh_port, + key_filename=self.key_filename) + self.connection.wait() + + def get_nic_details(self): + nic_details = {} + nic_details['interface'] = {} + nic_details['pci'] = self.ovs[0]['phy_ports'] + nic_details['phy_driver'] = self.ovs[0]['phy_driver'] + nic_details['vports_mac'] = self.ovs[0]['vports_mac'] + # Make sure that ports are bound to kernel drivers e.g. i40e/ixgbe + for i, _ in enumerate(nic_details['pci']): + err, out, _ = self.connection.execute( + "{dpdk_nic_bind} --force -b {driver} {port}".format( + dpdk_nic_bind=self.dpdk_nic_bind, + driver=self.ovs[0]['phy_driver'], + port=self.ovs[0]['phy_ports'][i])) + err, out, _ = self.connection.execute( + "lshw -c network -businfo | grep '{port}'".format( + port=self.ovs[0]['phy_ports'][i])) + a = out.split()[1] + err, out, _ = self.connection.execute( + "ip -s link show {interface}".format( + interface=out.split()[1])) + nic_details['interface'][i] = str(a) + print("{0}".format(nic_details)) + return nic_details + + def install_req_libs(self): + if self.first_run: + err, out, _ = self.connection.execute("apt-get update") + print("{0}".format(out)) + err, out, _ = self.connection.execute( + "apt-get -y install qemu-kvm libvirt-bin") + print("{0}".format(out)) + err, out, _ = self.connection.execute( + "apt-get -y install libvirt-dev bridge-utils numactl") + print("{0}".format(out)) + self.first_run = False + + def setup_ovs(self, vpcis): + self.connection.execute("/usr/bin/chmod 0666 /dev/vfio/*") + self.connection.execute("/usr/bin/chmod a+x /dev/vfio") + self.connection.execute("pkill -9 ovs") + self.connection.execute("ps -ef | grep ovs | grep -v grep | " + "awk '{print $2}' | xargs -r kill -9") + self.connection.execute("killall -r 'ovs*'") + self.connection.execute( + "mkdir -p {0}/etc/openvswitch".format(self.ovs[0]["vpath"])) + self.connection.execute( + "mkdir -p {0}/var/run/openvswitch".format(self.ovs[0]["vpath"])) + self.connection.execute( + "rm {0}/etc/openvswitch/conf.db".format(self.ovs[0]["vpath"])) + self.connection.execute( + "ovsdb-tool create {0}/etc/openvswitch/conf.db " + "{0}/share/openvswitch/" + "vswitch.ovsschema".format(self.ovs[0]["vpath"])) + self.connection.execute("modprobe vfio-pci") + self.connection.execute("chmod a+x /dev/vfio") + self.connection.execute("chmod 0666 /dev/vfio/*") + for vpci in vpcis: + self.connection.execute( + "/opt/isb_bin/dpdk_nic_bind.py " + "--bind=vfio-pci {0}".format(vpci)) + + def start_ovs_serverswitch(self): + self.connection.execute("mkdir -p /usr/local/var/run/openvswitch") + self.connection.execute( + "ovsdb-server --remote=punix:" + "/usr/local/var/run/openvswitch/db.sock --pidfile --detach") + self.connection.execute( + "ovs-vsctl --no-wait set " + "Open_vSwitch . other_config:dpdk-init=true") + self.connection.execute( + "ovs-vsctl --no-wait set " + "Open_vSwitch . other_config:dpdk-lcore-mask=0x3") + self.connection.execute( + "ovs-vsctl --no-wait set " + "Open_vSwitch . other_config:dpdk-socket-mem='2048,0'") + self.connection.execute( + "ovs-vswitchd unix:{0}/" + "var/run/openvswitch/db.sock --pidfile --detach " + "--log-file=/var/log/openvswitch/" + "ovs-vswitchd.log".format( + self.ovs[0]["vpath"])) + self.connection.execute( + "ovs-vsctl set Open_vSwitch . other_config:pmd-cpu-mask=2C") + + def setup_ovs_bridge(self): + self.connection.execute("ovs-vsctl del-br br0") + self.connection.execute( + "rm -rf /usr/local/var/run/openvswitch/dpdkvhostuser*") + self.connection.execute( + "ovs-vsctl add-br br0 -- set bridge br0 datapath_type=netdev") + self.connection.execute( + "ovs-vsctl add-port br0 dpdk0 -- set Interface dpdk0 type=dpdk") + self.connection.execute( + "ovs-vsctl add-port br0 dpdk1 -- set Interface dpdk1 type=dpdk") + self.connection.execute( + "ovs-vsctl add-port br0 dpdkvhostuser0 -- set Interface " + "dpdkvhostuser0 type=dpdkvhostuser") + self.connection.execute("ovs-vsctl add-port br0 dpdkvhostuser1 " + "-- set Interface dpdkvhostuser1 " + "type=dpdkvhostuser") + self.connection.execute( + "chmod 0777 {0}/var/run/" + "openvswitch/dpdkvhostuser*".format(self.ovs[0]["vpath"])) + + def add_oflows(self): + self.connection.execute("ovs-ofctl del-flows br0") + for flow in self.ovs[0]["flow"]: + self.connection.execute(flow) + self.connection.execute("ovs-ofctl dump-flows br0") + self.connection.execute( + "ovs-vsctl set Interface dpdk0 options:n_rxq=4") + self.connection.execute( + "ovs-vsctl set Interface dpdk1 options:n_rxq=4") + + def setup_ovs_context(self, pcis, nic_details, host_driver): + + ''' 1: Setup vm_ovs.xml to launch VM.''' + cfg_ovs = '/tmp/vm_ovs.xml' + vm_ovs_xml = VM_TEMPLATE.format(vm_image=self.ovs[0]["images"]) + with open(cfg_ovs, 'w') as f: + f.write(vm_ovs_xml) + + ''' 2: Create and start the VM''' + self.connection.put(cfg_ovs, cfg_ovs) + time.sleep(10) + err, out = self.check_output("virsh list --name | grep -i vm1") + if out == "vm1": + print("VM is already present") + else: + ''' FIXME: launch through libvirt''' + print("virsh create ...") + err, out, _ = self.connection.execute( + "virsh create /tmp/vm_ovs.xml") + time.sleep(10) + print("err : {0}".format(err)) + print("{0}".format(_)) + print("out : {0}".format(out)) + + ''' 3: Tuning for better performace.''' + self.pin_vcpu(pcis) + self.connection.execute( + "echo 1 > /sys/module/kvm/parameters/" + "allow_unsafe_assigned_interrupts") + self.connection.execute( + "echo never > /sys/kernel/mm/transparent_hugepage/enabled") + print("After tuning performance ...") + + ''' This is roughly compatible with check_output function in subprocess + module which is only available in python 2.7.''' + def check_output(self, cmd, stderr=None): + '''Run a command and capture its output''' + err, out, _ = self.connection.execute(cmd) + return err, out + + def read_from_file(self, filename): + data = "" + with open(filename, 'r') as the_file: + data = the_file.read() + return data + + def write_to_file(self, filename, content): + with open(filename, 'w') as the_file: + the_file.write(content) + + def pin_vcpu(self, pcis): + nodes = self.get_numa_nodes() + print("{0}".format(nodes)) + num_nodes = len(nodes) + for i in range(0, 10): + self.connection.execute( + "virsh vcpupin vm1 {0} {1}".format( + i, nodes[str(num_nodes - 1)][i % len(nodes[str(num_nodes - 1)])])) + + def get_numa_nodes(self): + nodes_sysfs = glob.iglob("/sys/devices/system/node/node*") + nodes = {} + for node_sysfs in nodes_sysfs: + num = os.path.basename(node_sysfs).replace("node", "") + with open(os.path.join(node_sysfs, "cpulist")) as cpulist_file: + cpulist = cpulist_file.read().strip() + print("cpulist: {0}".format(cpulist)) + nodes[num] = self.split_cpu_list(cpulist) + print("nodes: {0}".format(nodes)) + return nodes + + def split_cpu_list(self, cpu_list): + if cpu_list: + ranges = cpu_list.split(',') + bounds = ([int(b) for b in r.split('-')] for r in ranges) + range_objects =\ + (range(bound[0], bound[1] + 1 if len(bound) == 2 + else bound[0] + 1) for bound in bounds) + + return sorted(itertools.chain.from_iterable(range_objects)) + else: + return [] + + def destroy_vm(self): + host_driver = self.ovs[0]['phy_driver'] + err, out = self.check_output("virsh list --name | grep -i vm1") + print("{0}".format(out)) + if err == 0: + self.connection.execute("virsh shutdown vm1") + self.connection.execute("virsh destroy vm1") + self.check_output("rmmod {0}".format(host_driver))[1].splitlines() + self.check_output("modprobe {0}".format(host_driver))[ + 1].splitlines() + else: + print("error : ", err) diff --git a/yardstick/benchmark/contexts/sriov.py b/yardstick/benchmark/contexts/sriov.py new file mode 100644 index 000000000..fe27d2579 --- /dev/null +++ b/yardstick/benchmark/contexts/sriov.py @@ -0,0 +1,431 @@ +# Copyright (c) 2016-2017 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. + +from __future__ import absolute_import +import os +import yaml +import re +import time +import glob +import uuid +import random +import logging +import itertools +import xml.etree.ElementTree as ET +from yardstick import ssh +from yardstick.network_services.utils import get_nsb_option +from yardstick.network_services.utils import provision_tool +from yardstick.benchmark.contexts.standalone import StandaloneContext + +log = logging.getLogger(__name__) + +VM_TEMPLATE = """ +<domain type="kvm"> + <name>vm1</name> + <uuid>{random_uuid}</uuid> + <memory unit="KiB">102400</memory> + <currentMemory unit="KiB">102400</currentMemory> + <memoryBacking> + <hugepages /> + </memoryBacking> + <vcpu placement="static">20</vcpu> + <os> + <type arch="x86_64" machine="pc-i440fx-utopic">hvm</type> + <boot dev="hd" /> + </os> + <features> + <acpi /> + <apic /> + <pae /> + </features> + <cpu match="exact" mode="custom"> + <model fallback="allow">SandyBridge</model> + <topology cores="10" sockets="1" threads="2" /> + </cpu> + <clock offset="utc"> + <timer name="rtc" tickpolicy="catchup" /> + <timer name="pit" tickpolicy="delay" /> + <timer name="hpet" present="no" /> + </clock> + <on_poweroff>destroy</on_poweroff> + <on_reboot>restart</on_reboot> + <on_crash>restart</on_crash> + <devices> + <emulator>/usr/bin/kvm-spice</emulator> + <disk device="disk" type="file"> + <driver name="qemu" type="qcow2" /> + <source file="{vm_image}"/> + <target bus="virtio" dev="vda" /> + <address bus="0x00" domain="0x0000" +function="0x0" slot="0x04" type="pci" /> + </disk> + <controller index="0" model="ich9-ehci1" type="usb"> + <address bus="0x00" domain="0x0000" +function="0x7" slot="0x05" type="pci" /> + </controller> + <controller index="0" model="ich9-uhci1" type="usb"> + <master startport="0" /> + <address bus="0x00" domain="0x0000" function="0x0" +multifunction="on" slot="0x05" type="pci" /> + </controller> + <controller index="0" model="ich9-uhci2" type="usb"> + <master startport="2" /> + <address bus="0x00" domain="0x0000" +function="0x1" slot="0x05" type="pci" /> + </controller> + <controller index="0" model="ich9-uhci3" type="usb"> + <master startport="4" /> + <address bus="0x00" domain="0x0000" +function="0x2" slot="0x05" type="pci" /> + </controller> + <controller index="0" model="pci-root" type="pci" /> + <serial type="pty"> + <target port="0" /> + </serial> + <console type="pty"> + <target port="0" type="serial" /> + </console> + <input bus="usb" type="tablet" /> + <input bus="ps2" type="mouse" /> + <input bus="ps2" type="keyboard" /> + <graphics autoport="yes" listen="0.0.0.0" port="-1" type="vnc" /> + <video> + <model heads="1" type="cirrus" vram="16384" /> + <address bus="0x00" domain="0x0000" +function="0x0" slot="0x02" type="pci" /> + </video> + <memballoon model="virtio"> + <address bus="0x00" domain="0x0000" +function="0x0" slot="0x06" type="pci" /> + </memballoon> + <interface type="bridge"> + <mac address="{mac_addr}" /> + <source bridge="virbr0" /> + </interface> + </devices> +</domain> +""" + + +class Sriov(StandaloneContext): + def __init__(self): + self.name = None + self.file_path = None + self.nodes = [] + self.vm_deploy = False + self.sriov = [] + self.first_run = True + self.dpdk_nic_bind = "" + self.user = "" + self.ssh_ip = "" + self.passwd = "" + self.ssh_port = "" + self.auth_type = "" + + def init(self): + log.debug("In init") + self.parse_pod_and_get_data(self.file_path) + + def parse_pod_and_get_data(self, file_path): + self.file_path = file_path + log.debug("parsing pod file: {0}".format(self.file_path)) + try: + with open(self.file_path) as stream: + cfg = yaml.load(stream) + except IOError: + log.error("File {0} does not exist".format(self.file_path)) + raise + + self.sriov.extend([node for node in cfg["nodes"] + if node["role"] == "Sriov"]) + self.user = self.sriov[0]['user'] + self.ssh_ip = self.sriov[0]['ip'] + if self.sriov[0]['auth_type'] == "password": + self.passwd = self.sriov[0]['password'] + else: + self.ssh_port = self.sriov[0]['ssh_port'] + self.key_filename = self.sriov[0]['key_filename'] + + def ssh_remote_machine(self): + if self.sriov[0]['auth_type'] == "password": + self.connection = ssh.SSH( + self.user, + self.ssh_ip, + password=self.passwd) + self.connection.wait() + else: + if self.ssh_port is not None: + ssh_port = self.ssh_port + else: + ssh_port = ssh.DEFAULT_PORT + self.connection = ssh.SSH( + self.user, + self.ssh_ip, + port=ssh_port, + key_filename=self.key_filename) + self.connection.wait() + self.dpdk_nic_bind = provision_tool( + self.connection, + os.path.join(get_nsb_option("bin_path"), "dpdk_nic_bind.py")) + + def get_nic_details(self): + nic_details = {} + nic_details = { + 'interface': {}, + 'pci': self.sriov[0]['phy_ports'], + 'phy_driver': self.sriov[0]['phy_driver'], + 'vf_macs': self.sriov[0]['vf_macs'] + } + # Make sure that ports are bound to kernel drivers e.g. i40e/ixgbe + for i, _ in enumerate(nic_details['pci']): + err, out, _ = self.connection.execute( + "{dpdk_nic_bind} --force -b {driver} {port}".format( + dpdk_nic_bind=self.dpdk_nic_bind, + driver=self.sriov[0]['phy_driver'], + port=self.sriov[0]['phy_ports'][i])) + err, out, _ = self.connection.execute( + "lshw -c network -businfo | grep '{port}'".format( + port=self.sriov[0]['phy_ports'][i])) + a = out.split()[1] + err, out, _ = self.connection.execute( + "ip -s link show {interface}".format( + interface=out.split()[1])) + nic_details['interface'][i] = str(a) + log.info("{0}".format(nic_details)) + return nic_details + + def install_req_libs(self): + if self.first_run: + log.info("Installing required libraries...") + err, out, _ = self.connection.execute("apt-get update") + log.debug("{0}".format(out)) + err, out, _ = self.connection.execute( + "apt-get -y install qemu-kvm libvirt-bin") + log.debug("{0}".format(out)) + err, out, _ = self.connection.execute( + "apt-get -y install libvirt-dev bridge-utils numactl") + log.debug("{0}".format(out)) + self.first_run = False + + def configure_nics_for_sriov(self, host_driver, nic_details): + vf_pci = [[], []] + self.connection.execute( + "rmmod {0}".format(host_driver))[1].splitlines() + self.connection.execute( + "modprobe {0} num_vfs=1".format(host_driver))[1].splitlines() + nic_details['vf_pci'] = {} + for i in range(len(nic_details['pci'])): + self.connection.execute( + "echo 1 > /sys/bus/pci/devices/{0}/sriov_numvfs".format( + nic_details['pci'][i])) + err, out, _ = self.connection.execute( + "ip link set {interface} vf 0 mac {mac}".format( + interface=nic_details['interface'][i], + mac=nic_details['vf_macs'][i])) + time.sleep(3) + vf_pci[i] = self.get_vf_datas( + 'vf_pci', + nic_details['pci'][i], + nic_details['vf_macs'][i]) + nic_details['vf_pci'][i] = vf_pci[i] + log.debug("NIC DETAILS : {0}".format(nic_details)) + return nic_details + + def setup_sriov_context(self, pcis, nic_details, host_driver): + blacklist = "/etc/modprobe.d/blacklist.conf" + + # 1 : Blacklist the vf driver in /etc/modprobe.d/blacklist.conf + vfnic = "{0}vf".format(host_driver) + lines = self.read_from_file(blacklist) + if vfnic not in lines: + vfblacklist = "blacklist {vfnic}".format(vfnic=vfnic) + self.connection.execute( + "echo {vfblacklist} >> {blacklist}".format( + vfblacklist=vfblacklist, + blacklist=blacklist)) + + # 2 : modprobe host_driver with num_vfs + nic_details = self.configure_nics_for_sriov(host_driver, nic_details) + + # 3: Setup vm_sriov.xml to launch VM + cfg_sriov = '/tmp/vm_sriov.xml' + mac = [0x00, 0x24, 0x81, + random.randint(0x00, 0x7f), + random.randint(0x00, 0xff), + random.randint(0x00, 0xff)] + mac_address = ':'.join(map(lambda x: "%02x" % x, mac)) + vm_sriov_xml = VM_TEMPLATE.format( + random_uuid=uuid.uuid4(), + mac_addr=mac_address, + vm_image=self.sriov[0]["images"]) + with open(cfg_sriov, 'w') as f: + f.write(vm_sriov_xml) + + vf = nic_details['vf_pci'] + for index in range(len(nic_details['vf_pci'])): + self.add_sriov_interface( + index, + vf[index]['vf_pci'], + mac_address, + "/tmp/vm_sriov.xml") + self.connection.execute( + "ifconfig {interface} up".format( + interface=nic_details['interface'][index])) + + # 4: Create and start the VM + self.connection.put(cfg_sriov, cfg_sriov) + time.sleep(10) + err, out = self.check_output("virsh list --name | grep -i vm1") + try: + if out == "vm1": + log.info("VM is already present") + else: + # FIXME: launch through libvirt + log.info("virsh create ...") + err, out, _ = self.connection.execute( + "virsh create /tmp/vm_sriov.xml") + time.sleep(10) + log.error("err : {0}".format(err)) + log.error("{0}".format(_)) + log.debug("out : {0}".format(out)) + except ValueError: + raise + + # 5: Tunning for better performace + self.pin_vcpu(pcis) + self.connection.execute( + "echo 1 > /sys/module/kvm/parameters/" + "allow_unsafe_assigned_interrupts") + self.connection.execute( + "echo never > /sys/kernel/mm/transparent_hugepage/enabled") + + def add_sriov_interface(self, index, vf_pci, vfmac, xml): + root = ET.parse(xml) + pattern = "0000:(\d+):(\d+).(\d+)" + m = re.search(pattern, vf_pci, re.MULTILINE) + device = root.find('devices') + + interface = ET.SubElement(device, 'interface') + interface.set('managed', 'yes') + interface.set('type', 'hostdev') + + mac = ET.SubElement(interface, 'mac') + mac.set('address', vfmac) + source = ET.SubElement(interface, 'source') + + addr = ET.SubElement(source, "address") + addr.set('domain', "0x0") + addr.set('bus', "{0}".format(m.group(1))) + addr.set('function', "{0}".format(m.group(3))) + addr.set('slot', "{0}".format(m.group(2))) + addr.set('type', "pci") + + vf_pci = ET.SubElement(interface, 'address') + vf_pci.set('type', 'pci') + vf_pci.set('domain', '0x0000') + vf_pci.set('bus', '0x00') + vf_pci.set('slot', '0x0{0}'.format(index + 7)) + vf_pci.set('function', '0x00') + + root.write(xml) + + # This is roughly compatible with check_output function in subprocess + # module which is only available in python 2.7 + def check_output(self, cmd, stderr=None): + # Run a command and capture its output + err, out, _ = self.connection.execute(cmd) + return err, out + + def get_virtual_devices(self, pci): + pf_vfs = {} + err, extra_info = self.check_output( + "cat /sys/bus/pci/devices/{0}/virtfn0/uevent".format(pci)) + pattern = "PCI_SLOT_NAME=(?P<name>[0-9:.\s.]+)" + m = re.search(pattern, extra_info, re.MULTILINE) + + if m: + pf_vfs.update({pci: str(m.group(1).rstrip())}) + log.info("pf_vfs : {0}".format(pf_vfs)) + return pf_vfs + + def get_vf_datas(self, key, value, vfmac): + vfret = {} + pattern = "0000:(\d+):(\d+).(\d+)" + + vfret["mac"] = vfmac + vfs = self.get_virtual_devices(value) + log.info("vfs: {0}".format(vfs)) + for k, v in vfs.items(): + m = re.search(pattern, k, re.MULTILINE) + m1 = re.search(pattern, value, re.MULTILINE) + if m.group(1) == m1.group(1): + vfret["vf_pci"] = str(v) + break + + return vfret + + def read_from_file(self, filename): + data = "" + with open(filename, 'r') as the_file: + data = the_file.read() + return data + + def write_to_file(self, filename, content): + with open(filename, 'w') as the_file: + the_file.write(content) + + def pin_vcpu(self, pcis): + nodes = self.get_numa_nodes() + log.info("{0}".format(nodes)) + num_nodes = len(nodes) + for i in range(0, 10): + self.connection.execute( + "virsh vcpupin vm1 {0} {1}".format( + i, nodes[str(num_nodes - 1)][i % len(nodes[str(num_nodes - 1)])])) + + def get_numa_nodes(self): + nodes_sysfs = glob.iglob("/sys/devices/system/node/node*") + nodes = {} + for node_sysfs in nodes_sysfs: + num = os.path.basename(node_sysfs).replace("node", "") + with open(os.path.join(node_sysfs, "cpulist")) as cpulist_file: + cpulist = cpulist_file.read().strip() + nodes[num] = self.split_cpu_list(cpulist) + log.info("nodes: {0}".format(nodes)) + return nodes + + def split_cpu_list(self, cpu_list): + if cpu_list: + ranges = cpu_list.split(',') + bounds = ([int(b) for b in r.split('-')] for r in ranges) + range_objects =\ + (range(bound[0], bound[1] + 1 if len(bound) == 2 + else bound[0] + 1) for bound in bounds) + + return sorted(itertools.chain.from_iterable(range_objects)) + else: + return [] + + def destroy_vm(self): + host_driver = self.sriov[0]["phy_driver"] + err, out = self.check_output("virsh list --name | grep -i vm1") + log.info("{0}".format(out)) + if err == 0: + self.connection.execute("virsh shutdown vm1") + self.connection.execute("virsh destroy vm1") + self.check_output("rmmod {0}".format(host_driver))[1].splitlines() + self.check_output("modprobe {0}".format(host_driver))[ + 1].splitlines() + else: + log.error("error : {0}".format(err)) diff --git a/yardstick/benchmark/contexts/standalone.py b/yardstick/benchmark/contexts/standalone.py index 78eaac7ee..2bc1f3755 100644 --- a/yardstick/benchmark/contexts/standalone.py +++ b/yardstick/benchmark/contexts/standalone.py @@ -18,9 +18,11 @@ import logging import errno import collections import yaml +import time from yardstick.benchmark.contexts.base import Context from yardstick.common.constants import YARDSTICK_ROOT_PATH +from yardstick.common.utils import import_modules_from_package, itersubclasses LOG = logging.getLogger(__name__) @@ -36,8 +38,10 @@ class StandaloneContext(Context): self.name = None self.file_path = None self.nodes = [] + self.networks = {} self.nfvi_node = [] - super(StandaloneContext, self).__init__() + self.nfvi_obj = None + super(self.__class__, self).__init__() def read_config_file(self): """Read from config file""" @@ -47,6 +51,14 @@ class StandaloneContext(Context): cfg = yaml.load(stream) return cfg + def get_nfvi_obj(self): + print("{0}".format(self.nfvi_node[0]['role'])) + context_type = self.get_context_impl(self.nfvi_node[0]['role']) + nfvi_obj = context_type() + nfvi_obj.__init__() + nfvi_obj.parse_pod_and_get_data(self.file_path) + return nfvi_obj + def init(self, attrs): """initializes itself from the supplied arguments""" @@ -63,23 +75,70 @@ class StandaloneContext(Context): else: raise - self.nodes.extend(cfg["nodes"]) - self.nfvi_node.extend([node for node in cfg["nodes"] - if node["role"] == "nfvi_node"]) + self.vm_deploy = attrs.get("vm_deploy", True) + self.nodes.extend([node for node in cfg["nodes"] + if str(node["role"]) != "Sriov" and + str(node["role"]) != "Ovsdpdk"]) + for node in cfg["nodes"]: + if str(node["role"]) == "Sriov": + self.nfvi_node.extend([node for node in cfg["nodes"] + if str(node["role"]) == "Sriov"]) + if str(node["role"]) == "Ovsdpdk": + self.nfvi_node.extend([node for node in cfg["nodes"] + if str(node["role"]) == "Ovsdpdk"]) + LOG.info("{0}".format(node["role"])) + else: + LOG.debug("Node role is other than SRIOV and OVS") + self.nfvi_obj = self.get_nfvi_obj() + # add optional static network definition + self.networks.update(cfg.get("networks", {})) + self.nfvi_obj = self.get_nfvi_obj() LOG.debug("Nodes: %r", self.nodes) LOG.debug("NFVi Node: %r", self.nfvi_node) + LOG.debug("Networks: %r", self.networks) def deploy(self): """don't need to deploy""" # Todo: NFVi deploy (sriov, vswitch, ovs etc) based on the config. - pass + if not self.vm_deploy: + return + + # Todo: NFVi deploy (sriov, vswitch, ovs etc) based on the config. + self.nfvi_obj.ssh_remote_machine() + if self.nfvi_obj.first_run is True: + self.nfvi_obj.install_req_libs() + + nic_details = self.nfvi_obj.get_nic_details() + print("{0}".format(nic_details)) + + if self.nfvi_node[0]["role"] == "Sriov": + self.nfvi_obj.setup_sriov_context( + self.nfvi_obj.sriov[0]['phy_ports'], + nic_details, + self.nfvi_obj.sriov[0]['phy_driver']) + if self.nfvi_node[0]["role"] == "Ovsdpdk": + self.nfvi_obj.setup_ovs(self.nfvi_obj.ovs[0]["phy_ports"]) + self.nfvi_obj.start_ovs_serverswitch() + time.sleep(5) + self.nfvi_obj.setup_ovs_bridge() + self.nfvi_obj.add_oflows() + self.nfvi_obj.setup_ovs_context( + self.nfvi_obj.ovs[0]['phy_ports'], + nic_details, + self.nfvi_obj.ovs[0]['phy_driver']) + pass def undeploy(self): """don't need to undeploy""" + if not self.vm_deploy: + return # Todo: NFVi undeploy (sriov, vswitch, ovs etc) based on the config. - super(StandaloneContext, self).undeploy() + # self.nfvi_obj = self.get_nfvi_obj() + self.nfvi_obj.ssh_remote_machine() + self.nfvi_obj.destroy_vm() + pass def _get_server(self, attr_name): """lookup server info by name from context @@ -87,16 +146,12 @@ class StandaloneContext(Context): Keyword arguments: attr_name -- A name for a server listed in nodes config file """ - if isinstance(attr_name, collections.Mapping): return None - - if self.name.split("-")[0] != attr_name.split(".")[1]: + if self.name != attr_name.split(".")[1]: return None - node_name = attr_name.split(".")[0] matching_nodes = (n for n in self.nodes if n["name"] == node_name) - try: # A clone is created in order to avoid affecting the # original one. @@ -111,6 +166,49 @@ class StandaloneContext(Context): else: raise ValueError("Duplicate nodes!!! Nodes: %s %s", (matching_nodes, duplicate)) - node["name"] = attr_name return node + + def _get_network(self, attr_name): + if not isinstance(attr_name, collections.Mapping): + network = self.networks.get(attr_name) + + else: + # Don't generalize too much Just support vld_id + vld_id = attr_name.get('vld_id') + if vld_id is None: + return None + try: + network = next(n for n in self.networks.values() if + n.get("vld_id") == vld_id) + except StopIteration: + return None + + if network is None: + return None + + result = { + # name is required + "name": network["name"], + "vld_id": network.get("vld_id"), + "segmentation_id": network.get("segmentation_id"), + "network_type": network.get("network_type"), + "physical_network": network.get("physical_network"), + } + return result + + def get_context_impl(self, nfvi_type): + """ Find the implementing class from vnf_model["vnf"]["name"] field + + :param vnf_model: dictionary containing a parsed vnfd + :return: subclass of GenericVNF + """ + import_modules_from_package( + "yardstick.benchmark.contexts") + expected_name = nfvi_type + impl = [c for c in itersubclasses(StandaloneContext) + if c.__name__ == expected_name] + try: + return next(iter(impl)) + except StopIteration: + raise ValueError("No implementation for %s", expected_name) diff --git a/yardstick/benchmark/core/plugin.py b/yardstick/benchmark/core/plugin.py index 7f67a04b3..c8d0865d1 100644 --- a/yardstick/benchmark/core/plugin.py +++ b/yardstick/benchmark/core/plugin.py @@ -84,8 +84,8 @@ class Plugin(object): if deployment_ip == "local": self.client = ssh.SSH.from_node(deployment, overrides={ - # host can't be None, fail if no INSTALLER_IP - 'ip': os.environ["INSTALLER_IP"], + # host can't be None, fail if no JUMP_HOST_IP + 'ip': os.environ["JUMP_HOST_IP"], }) else: self.client = ssh.SSH.from_node(deployment) @@ -107,8 +107,8 @@ class Plugin(object): if deployment_ip == "local": self.client = ssh.SSH.from_node(deployment, overrides={ - # host can't be None, fail if no INSTALLER_IP - 'ip': os.environ["INSTALLER_IP"], + # host can't be None, fail if no JUMP_HOST_IP + 'ip': os.environ["JUMP_HOST_IP"], }) else: self.client = ssh.SSH.from_node(deployment) diff --git a/yardstick/benchmark/core/task.py b/yardstick/benchmark/core/task.py index 0e85e6316..b2da7a2ee 100644 --- a/yardstick/benchmark/core/task.py +++ b/yardstick/benchmark/core/task.py @@ -48,6 +48,12 @@ class Task(object): # pragma: no cover self.contexts = [] self.outputs = {} + def _set_dispatchers(self, output_config): + dispatchers = output_config.get('DEFAULT', {}).get('dispatcher', + 'file') + out_types = [s.strip() for s in dispatchers.split(',')] + output_config['DEFAULT']['dispatcher'] = out_types + def start(self, args, **kwargs): """Start a benchmark scenario.""" @@ -58,12 +64,20 @@ class Task(object): # pragma: no cover check_environment() - output_config = utils.parse_ini_file(config_file) + try: + output_config = utils.parse_ini_file(config_file) + except Exception: + # all error will be ignore, the default value is {} + output_config = {} + self._init_output_config(output_config) self._set_output_config(output_config, args.output_file) LOG.debug('Output configuration is: %s', output_config) - if output_config['DEFAULT'].get('dispatcher') == 'file': + self._set_dispatchers(output_config) + + # update dispatcher list + if 'file' in output_config['DEFAULT']['dispatcher']: result = {'status': 0, 'result': {}} utils.write_json_to_file(args.output_file, result) @@ -193,9 +207,10 @@ class Task(object): # pragma: no cover return 'PASS' def _do_output(self, output_config, result): + dispatchers = DispatcherBase.get(output_config) - dispatcher = DispatcherBase.get(output_config) - dispatcher.flush_result_data(result) + for dispatcher in dispatchers: + dispatcher.flush_result_data(result) def _run(self, scenarios, run_in_parallel, output_file): """Deploys context and calls runners""" @@ -322,6 +337,8 @@ class Task(object): # pragma: no cover if "nodes" in scenario_cfg: context_cfg["nodes"] = parse_nodes_with_context(scenario_cfg) + context_cfg["networks"] = get_networks_from_nodes( + context_cfg["nodes"]) runner = base_runner.Runner.get(runner_cfg) print("Starting runner of type '%s'" % runner_cfg["type"]) @@ -518,7 +535,7 @@ class TaskParser(object): # pragma: no cover cfg_schema)) def _check_precondition(self, cfg): - """Check if the envrionment meet the preconditon""" + """Check if the environment meet the precondition""" if "precondition" in cfg: precondition = cfg["precondition"] @@ -573,14 +590,26 @@ def _is_background_scenario(scenario): def parse_nodes_with_context(scenario_cfg): - """paras the 'nodes' fields in scenario """ + """parse the 'nodes' fields in scenario """ nodes = scenario_cfg["nodes"] - - nodes_cfg = {} - for nodename in nodes: - nodes_cfg[nodename] = Context.get_server(nodes[nodename]) - - return nodes_cfg + return {nodename: Context.get_server(node) for nodename, node in nodes.items()} + + +def get_networks_from_nodes(nodes): + """parse the 'nodes' fields in scenario """ + networks = {} + for node in nodes.values(): + if not node: + continue + for interface in node['interfaces'].values(): + vld_id = interface.get('vld_id') + # mgmt network doesn't have vld_id + if not vld_id: + continue + network = Context.get_network({"vld_id": vld_id}) + if network: + networks[network['name']] = network + return networks def runner_join(runner): diff --git a/yardstick/benchmark/core/testsuite.py b/yardstick/benchmark/core/testsuite.py new file mode 100644 index 000000000..e3940a0ba --- /dev/null +++ b/yardstick/benchmark/core/testsuite.py @@ -0,0 +1,42 @@ +############################################################################## +# Copyright (c) 2015 Huawei Technologies Co.,Ltd and others. +# +# All rights reserved. This program and the accompanying materials +# are made available under the terms of the Apache License, Version 2.0 +# which accompanies this distribution, and is available at +# http://www.apache.org/licenses/LICENSE-2.0 +############################################################################## + +""" Handler for yardstick command 'testcase' """ +from __future__ import absolute_import +from __future__ import print_function + +import os +import logging + +from yardstick.common import constants as consts + +LOG = logging.getLogger(__name__) + + +class Testsuite(object): + """Testcase commands. + + Set of commands to discover and display test cases. + """ + + def list_all(self, args): + """List existing test cases""" + + testsuite_list = self._get_testsuite_file_list() + + return testsuite_list + + def _get_testsuite_file_list(self): + try: + testsuite_files = sorted(os.listdir(consts.TESTSUITE_DIR)) + except OSError: + LOG.exception('Failed to list dir:\n%s\n', consts.TESTSUITE_DIR) + raise + + return testsuite_files diff --git a/yardstick/benchmark/scenarios/availability/actionplayers.py b/yardstick/benchmark/scenarios/availability/actionplayers.py index 420626413..c5e199ba6 100644 --- a/yardstick/benchmark/scenarios/availability/actionplayers.py +++ b/yardstick/benchmark/scenarios/availability/actionplayers.py @@ -29,8 +29,10 @@ class AttackerPlayer(ActionPlayer): class OperationPlayer(ActionPlayer): - def __init__(self, operation): + def __init__(self, operation, intermediate_variables): self.underlyingOperation = operation + self.underlyingOperation.intermediate_variables \ + = intermediate_variables def action(self): self.underlyingOperation.run() diff --git a/yardstick/benchmark/scenarios/availability/attacker/attacker_baremetal.py b/yardstick/benchmark/scenarios/availability/attacker/attacker_baremetal.py index 22de0b645..50d44c1ca 100644 --- a/yardstick/benchmark/scenarios/availability/attacker/attacker_baremetal.py +++ b/yardstick/benchmark/scenarios/availability/attacker/attacker_baremetal.py @@ -9,7 +9,6 @@ from __future__ import absolute_import import logging import subprocess -import traceback import yardstick.ssh as ssh from yardstick.benchmark.scenarios.availability.attacker.baseattacker import \ @@ -26,9 +25,7 @@ def _execute_shell_command(command, stdin=None): output = subprocess.check_output(command, stdin=stdin, shell=True) except Exception: exitcode = -1 - output = traceback.format_exc() - LOG.error("exec command '%s' error:\n ", command) - LOG.error(traceback.format_exc()) + LOG.error("exec command '%s' error:\n ", command, exc_info=True) return exitcode, output diff --git a/yardstick/benchmark/scenarios/availability/attacker/attacker_process.py b/yardstick/benchmark/scenarios/availability/attacker/attacker_process.py index f7ab23dcd..cb171eafa 100644 --- a/yardstick/benchmark/scenarios/availability/attacker/attacker_process.py +++ b/yardstick/benchmark/scenarios/availability/attacker/attacker_process.py @@ -47,11 +47,11 @@ class ProcessAttacker(BaseAttacker): stdin=stdin_file) if stdout: - LOG.info("check the envrioment success!") + LOG.info("check the environment success!") return int(stdout.strip('\n')) else: LOG.error( - "the host envrioment is error, stdout:%s, stderr:%s", + "the host environment is error, stdout:%s, stderr:%s", stdout, stderr) return False diff --git a/yardstick/benchmark/scenarios/availability/attacker_conf.yaml b/yardstick/benchmark/scenarios/availability/attacker_conf.yaml index b8c34ad44..aa144ab50 100644 --- a/yardstick/benchmark/scenarios/availability/attacker_conf.yaml +++ b/yardstick/benchmark/scenarios/availability/attacker_conf.yaml @@ -16,6 +16,11 @@ kill-process: inject_script: ha_tools/fault_process_kill.bash recovery_script: ha_tools/start_service.bash +kill-lxc-process: + check_script: ha_tools/check_lxc_process_python.bash + inject_script: ha_tools/fault_lxc_process_kill.bash + recovery_script: ha_tools/start_lxc_service.bash + bare-metal-down: check_script: ha_tools/check_host_ping.bash recovery_script: ha_tools/ipmi_power.bash @@ -34,4 +39,4 @@ stress-cpu: block-io: inject_script: ha_tools/disk/block_io.bash - recovery_script: ha_tools/disk/recovery_disk_io.bash
\ No newline at end of file + recovery_script: ha_tools/disk/recovery_disk_io.bash diff --git a/yardstick/benchmark/scenarios/availability/director.py b/yardstick/benchmark/scenarios/availability/director.py index e0d05ebf5..c9187c34d 100644 --- a/yardstick/benchmark/scenarios/availability/director.py +++ b/yardstick/benchmark/scenarios/availability/director.py @@ -65,7 +65,9 @@ class Director(object): self.resultCheckerMgr = baseresultchecker.ResultCheckerMgr() self.resultCheckerMgr.init_ResultChecker(result_check_cfgs, nodes) - def createActionPlayer(self, type, key): + def createActionPlayer(self, type, key, intermediate_variables=None): + if intermediate_variables is None: + intermediate_variables = {} LOG.debug( "the type of current action is %s, the key is %s", type, key) if type == ActionType.ATTACKER: @@ -76,7 +78,8 @@ class Director(object): return actionplayers.ResultCheckerPlayer( self.resultCheckerMgr[key]) if type == ActionType.OPERATION: - return actionplayers.OperationPlayer(self.operationMgr[key]) + return actionplayers.OperationPlayer(self.operationMgr[key], + intermediate_variables) LOG.debug("something run when creatactionplayer") def createActionRollbacker(self, type, key): diff --git a/yardstick/benchmark/scenarios/availability/ha_tools/check_lxc_process_python.bash b/yardstick/benchmark/scenarios/availability/ha_tools/check_lxc_process_python.bash new file mode 100755 index 000000000..6d2f4dd51 --- /dev/null +++ b/yardstick/benchmark/scenarios/availability/ha_tools/check_lxc_process_python.bash @@ -0,0 +1,42 @@ +#!/bin/sh + +############################################################################## +# Copyright (c) 2015 Huawei Technologies Co.,Ltd and others. +# +# All rights reserved. This program and the accompanying materials +# are made available under the terms of the Apache License, Version 2.0 +# which accompanies this distribution, and is available at +# http://www.apache.org/licenses/LICENSE-2.0 +############################################################################## + +# check the status of a service + +set -e + +NOVA_API_PROCESS_1="nova-api-os-compute" +NOVA_API_PROCESS_2="nova-api-metadata" +NOVA_API_LXC_FILTER_1="nova_api_os_compute" +NOVA_API_LXC_FILTER_2="nova_api_metadata" + +process_name=$1 + +lxc_filter=$(echo "${process_name}" | sed 's/-/_/g') + +if [ "${lxc_filter}" = "glance_api" ]; then + lxc_filter="glance" +fi + +if [ "${process_name}" = "nova-api" ]; then + container_1=$(lxc-ls -1 --filter="${NOVA_API_LXC_FILTER_1}") + container_2=$(lxc-ls -1 --filter="${NOVA_API_LXC_FILTER_2}") + + echo $(($(lxc-attach -n "${container_1}" -- ps aux | grep -e "${NOVA_API_PROCESS_1}" | grep -v grep | grep -cv /bin/sh) + $(lxc-attach -n "${container_2}" -- ps aux | grep -e "${NOVA_API_PROCESS_2}" | grep -v grep | grep -cv /bin/sh))) +else + container=$(lxc-ls -1 --filter="${lxc_filter}") + + if [ "${process_name}" = "haproxy" ]; then + ps aux | grep -e "/usr/.*/${process_name}" | grep -v grep | grep -cv /bin/sh + else + lxc-attach -n "${container}" -- ps aux | grep -e "${process_name}" | grep -v grep | grep -cv /bin/sh + fi +fi diff --git a/yardstick/benchmark/scenarios/availability/ha_tools/fault_lxc_process_kill.bash b/yardstick/benchmark/scenarios/availability/ha_tools/fault_lxc_process_kill.bash new file mode 100755 index 000000000..b0b86ab65 --- /dev/null +++ b/yardstick/benchmark/scenarios/availability/ha_tools/fault_lxc_process_kill.bash @@ -0,0 +1,65 @@ +#!/bin/sh + +############################################################################## +# Copyright (c) 2015 Huawei Technologies Co.,Ltd and others. +# +# All rights reserved. This program and the accompanying materials +# are made available under the terms of the Apache License, Version 2.0 +# which accompanies this distribution, and is available at +# http://www.apache.org/licenses/LICENSE-2.0 +############################################################################## + +# Stop process by process name + +set -e + +NOVA_API_PROCESS_1="nova-api-os-compute" +NOVA_API_PROCESS_2="nova-api-metadata" +NOVA_API_LXC_FILTER_1="nova_api_os_compute" +NOVA_API_LXC_FILTER_2="nova_api_metadata" + +process_name=$1 + +lxc_filter=$(echo "${process_name}" | sed 's/-/_/g') + +if [ "${lxc_filter}" = "glance_api" ]; then + lxc_filter="glance" +fi + +if [ "${process_name}" = "nova-api" ]; then + container_1=$(lxc-ls -1 --filter="${NOVA_API_LXC_FILTER_1}") + container_2=$(lxc-ls -1 --filter="${NOVA_API_LXC_FILTER_2}") + + pids_1=$(lxc-attach -n "${container_1}" -- pgrep -f "/openstack/.*/${NOVA_API_PROCESS_1}") + for pid in ${pids_1}; + do + lxc-attach -n "${container_1}" -- kill -9 "${pid}" + done + + pids_2=$(lxc-attach -n "${container_2}" -- pgrep -f "/openstack/.*/${NOVA_API_PROCESS_2}") + for pid in ${pids_2}; + do + lxc-attach -n "${container_2}" -- kill -9 "${pid}" + done +else + container=$(lxc-ls -1 --filter="${lxc_filter}") + + if [ "${process_name}" = "haproxy" ]; then + for pid in $(pgrep -cf "/usr/.*/${process_name}"); + do + kill -9 "${pid}" + done + elif [ "${process_name}" = "keystone" ]; then + pids=$(lxc-attach -n "${container}" -- ps aux | grep "keystone" | grep -iv heartbeat | grep -iv monitor | grep -v grep | grep -v /bin/sh | awk '{print $2}') + for pid in ${pids}; + do + lxc-attach -n "${container}" -- kill -9 "${pid}" + done + else + pids=$(lxc-attach -n "${container}" -- pgrep -f "/openstack/.*/${process_name}") + for pid in ${pids}; + do + lxc-attach -n "${container}" -- kill -9 "${pid}" + done + fi +fi diff --git a/yardstick/benchmark/scenarios/availability/ha_tools/nova/create_flavor.bash b/yardstick/benchmark/scenarios/availability/ha_tools/nova/create_flavor.bash index aee516ea9..7408409a9 100644 --- a/yardstick/benchmark/scenarios/availability/ha_tools/nova/create_flavor.bash +++ b/yardstick/benchmark/scenarios/availability/ha_tools/nova/create_flavor.bash @@ -20,4 +20,4 @@ else SECURE="" fi -openstack "${SECURE}" flavor create $1 --id $2 --ram $3 --disk $4 --vcpus $5 +openstack ${SECURE} flavor create $1 --id $2 --ram $3 --disk $4 --vcpus $5 diff --git a/yardstick/benchmark/scenarios/availability/ha_tools/nova/delete_flavor.bash b/yardstick/benchmark/scenarios/availability/ha_tools/nova/delete_flavor.bash index d39926fc5..7240476f7 100644 --- a/yardstick/benchmark/scenarios/availability/ha_tools/nova/delete_flavor.bash +++ b/yardstick/benchmark/scenarios/availability/ha_tools/nova/delete_flavor.bash @@ -20,4 +20,4 @@ else SECURE="" fi -openstack "${SECURE}" flavor delete $1 +openstack ${SECURE} flavor delete $1 diff --git a/yardstick/benchmark/scenarios/availability/ha_tools/nova/show_flavors.bash b/yardstick/benchmark/scenarios/availability/ha_tools/nova/show_flavors.bash index bd61ba9bb..e679fdb9e 100644 --- a/yardstick/benchmark/scenarios/availability/ha_tools/nova/show_flavors.bash +++ b/yardstick/benchmark/scenarios/availability/ha_tools/nova/show_flavors.bash @@ -19,4 +19,4 @@ else SECURE="" fi -openstack "${SECURE}" flavor list +openstack ${SECURE} flavor list diff --git a/yardstick/benchmark/scenarios/availability/ha_tools/start_lxc_service.bash b/yardstick/benchmark/scenarios/availability/ha_tools/start_lxc_service.bash new file mode 100755 index 000000000..36a673977 --- /dev/null +++ b/yardstick/benchmark/scenarios/availability/ha_tools/start_lxc_service.bash @@ -0,0 +1,70 @@ +#!/bin/bash + +############################################################################## +# Copyright (c) 2015 Huawei Technologies Co.,Ltd and others. +# +# All rights reserved. This program and the accompanying materials +# are made available under the terms of the Apache License, Version 2.0 +# which accompanies this distribution, and is available at +# http://www.apache.org/licenses/LICENSE-2.0 +############################################################################## + +# Start a service and check the service is started + +set -e + +NOVA_API_SERVICE_1="nova-api-os-compute" +NOVA_API_SERVICE_2="nova-api-metadata" +NOVA_API_LXC_FILTER_1="nova_api_os_compute" +NOVA_API_LXC_FILTER_2="nova_api_metadata" + +service_name=$1 + +if [ "${service_name}" = "haproxy" ]; then + if which systemctl 2>/dev/null; then + systemctl start $service_name + else + service $service_name start + fi +else + lxc_filter=${service_name//-/_} + + if [ "${lxc_filter}" = "glance_api" ]; then + lxc_filter="glance" + fi + + if [ "${service_name}" = "nova-api" ]; then + container_1=$(lxc-ls -1 --filter="${NOVA_API_LXC_FILTER_1}") + container_2=$(lxc-ls -1 --filter="${NOVA_API_LXC_FILTER_2}") + + if lxc-attach -n "${container_1}" -- which systemctl 2>/dev/null; then + lxc-attach -n "${container_1}" -- systemctl start "${NOVA_API_SERVICE_1}" + else + lxc-attach -n "${container_1}" -- service "${NOVA_API_SERVICE_1}" start + fi + + if lxc-attach -n "${container_2}" -- which systemctl 2>/dev/null; then + lxc-attach -n "${container_2}" -- systemctl start "${NOVA_API_SERVICE_2}" + else + lxc-attach -n "${container_2}" -- service "${NOVA_API_SERVICE_2}" start + fi + else + container=$(lxc-ls -1 --filter="${lxc_filter}") + + Distributor=$(lxc-attach -n "${container}" -- lsb_release -a | grep "Distributor ID" | awk '{print $3}') + + if [ "${Distributor}" != "Ubuntu" -a "${service_name}" != "keystone" -a "${service_name}" != "neutron-server" ]; then + service_name="openstack-"${service_name} + elif [ "${Distributor}" = "Ubuntu" -a "${service_name}" = "keystone" ]; then + service_name="apache2" + elif [ "${service_name}" = "keystone" ]; then + service_name="httpd" + fi + + if lxc-attach -n "${container}" -- which systemctl 2>/dev/null; then + lxc-attach -n "${container}" -- systemctl start "${service_name}" + else + lxc-attach -n "${container}" -- service "${service_name}" start + fi + fi +fi diff --git a/yardstick/benchmark/scenarios/availability/monitor/monitor_command.py b/yardstick/benchmark/scenarios/availability/monitor/monitor_command.py index a0777f94e..a9488cc30 100644 --- a/yardstick/benchmark/scenarios/availability/monitor/monitor_command.py +++ b/yardstick/benchmark/scenarios/availability/monitor/monitor_command.py @@ -11,7 +11,6 @@ from __future__ import absolute_import import os import logging import subprocess -import traceback import yardstick.ssh as ssh from yardstick.benchmark.scenarios.availability.monitor import basemonitor @@ -27,9 +26,7 @@ def _execute_shell_command(command): output = subprocess.check_output(command, shell=True) except Exception: exitcode = -1 - output = traceback.format_exc() - LOG.error("exec command '%s' error:\n ", command) - LOG.error(traceback.format_exc()) + LOG.error("exec command '%s' error:\n ", command, exc_info=True) return exitcode, output diff --git a/yardstick/benchmark/scenarios/availability/monitor_conf.yaml b/yardstick/benchmark/scenarios/availability/monitor_conf.yaml index 511449221..a08347d2d 100644 --- a/yardstick/benchmark/scenarios/availability/monitor_conf.yaml +++ b/yardstick/benchmark/scenarios/availability/monitor_conf.yaml @@ -13,6 +13,8 @@ schema: "yardstick:task:0.1" process-status: monitor_script: ha_tools/check_process_python.bash +lxc_process-status: + monitor_script: ha_tools/check_lxc_process_python.bash nova-image-list: monitor_script: ha_tools/nova_image_list.bash service-status: diff --git a/yardstick/benchmark/scenarios/availability/operation/baseoperation.py b/yardstick/benchmark/scenarios/availability/operation/baseoperation.py index be286b8fd..88ca9e2bb 100644 --- a/yardstick/benchmark/scenarios/availability/operation/baseoperation.py +++ b/yardstick/benchmark/scenarios/availability/operation/baseoperation.py @@ -58,6 +58,7 @@ class BaseOperation(object): self.key = '' self._config = config self._context = context + self.intermediate_variables = {} @staticmethod def get_operation_cls(type): diff --git a/yardstick/benchmark/scenarios/availability/operation/operation_general.py b/yardstick/benchmark/scenarios/availability/operation/operation_general.py index 8fd387e47..af1ae7469 100644 --- a/yardstick/benchmark/scenarios/availability/operation/operation_general.py +++ b/yardstick/benchmark/scenarios/availability/operation/operation_general.py @@ -15,7 +15,8 @@ from yardstick.benchmark.scenarios.availability.operation.baseoperation \ import yardstick.ssh as ssh from yardstick.benchmark.scenarios.availability.util \ - import buildshellparams, execute_shell_command + import buildshellparams, execute_shell_command, \ + read_stdout_item, build_shell_command LOG = logging.getLogger(__name__) @@ -39,11 +40,7 @@ class GeneralOperaion(BaseOperation): self.operation_key = self._config['operation_key'] if "action_parameter" in self._config: - actionParameter = self._config['action_parameter'] - str = buildshellparams( - actionParameter, True if self.connection else False) - l = list(item for item in actionParameter.values()) - self.action_param = str.format(*l) + self.actionParameter_config = self._config['action_parameter'] if "rollback_parameter" in self._config: rollbackParameter = self._config['rollback_parameter'] @@ -61,6 +58,11 @@ class GeneralOperaion(BaseOperation): def run(self): if "action_parameter" in self._config: + self.action_param = \ + build_shell_command( + self.actionParameter_config, + True if self.connection else False, + self.intermediate_variables) if self.connection: with open(self.action_script, "r") as stdin_file: exit_status, stdout, stderr = self.connection.execute( @@ -83,6 +85,12 @@ class GeneralOperaion(BaseOperation): if exit_status == 0: LOG.debug("success,the operation's output is: %s", stdout) + if "return_parameter" in self._config: + returnParameter = self._config['return_parameter'] + for key, item in returnParameter.items(): + value = read_stdout_item(stdout, key) + LOG.debug("intermediate variables %s: %s", item, value) + self.intermediate_variables[item] = value else: LOG.error( "the operation's error, stdout:%s, stderr:%s", diff --git a/yardstick/benchmark/scenarios/availability/scenario_general.py b/yardstick/benchmark/scenarios/availability/scenario_general.py index 28bec8aff..17ad79f29 100644 --- a/yardstick/benchmark/scenarios/availability/scenario_general.py +++ b/yardstick/benchmark/scenarios/availability/scenario_general.py @@ -25,6 +25,7 @@ class ScenarioGeneral(base.Scenario): "scenario_cfg:%s context_cfg:%s", scenario_cfg, context_cfg) self.scenario_cfg = scenario_cfg self.context_cfg = context_cfg + self.intermediate_variables = {} def setup(self): self.director = Director(self.scenario_cfg, self.context_cfg) @@ -38,7 +39,8 @@ class ScenarioGeneral(base.Scenario): orderedSteps.index(step) + 1) try: actionPlayer = self.director.createActionPlayer( - step['actionType'], step['actionKey']) + step['actionType'], step['actionKey'], + self.intermediate_variables) actionPlayer.action() actionRollbacker = self.director.createActionRollbacker( step['actionType'], step['actionKey']) diff --git a/yardstick/benchmark/scenarios/availability/util.py b/yardstick/benchmark/scenarios/availability/util.py index eadbfa53b..6fef622bd 100644 --- a/yardstick/benchmark/scenarios/availability/util.py +++ b/yardstick/benchmark/scenarios/availability/util.py @@ -14,13 +14,8 @@ LOG = logging.getLogger(__name__) def buildshellparams(param, remote=True): - i = 0 - values = [] result = '/bin/bash -s' if remote else '' - for key in param.keys(): - values.append(param[key]) - result += " {%d}" % i - i = i + 1 + result += "".join(" {%d}" % i for i in range(len(param))) return result @@ -36,5 +31,29 @@ def execute_shell_command(command): output = traceback.format_exc() LOG.error("exec command '%s' error:\n ", command) LOG.error(traceback.format_exc()) - return exitcode, output + +PREFIX = '$' + + +def build_shell_command(param_config, remote=True, intermediate_variables=None): + param_template = '/bin/bash -s' if remote else '' + if intermediate_variables: + for key, val in param_config.items(): + if str(val).startswith(PREFIX): + try: + param_config[key] = intermediate_variables[val] + except KeyError: + pass + result = param_template + "".join(" {}".format(v) for v in param_config.values()) + LOG.debug("THE RESULT OF build_shell_command IS: %s", result) + return result + + +def read_stdout_item(stdout, key): + for item in stdout.splitlines(): + if key in item: + attributes = item.split("|") + if attributes[1].lstrip().startswith(key): + return attributes[2].strip() + return None diff --git a/yardstick/benchmark/scenarios/base.py b/yardstick/benchmark/scenarios/base.py index 5d3c36c38..3cb138dd8 100644 --- a/yardstick/benchmark/scenarios/base.py +++ b/yardstick/benchmark/scenarios/base.py @@ -63,3 +63,15 @@ class Scenario(object): return scenario.__module__ + "." + scenario.__name__ raise RuntimeError("No such scenario type %s" % scenario_type) + + def _push_to_outputs(self, keys, values): + return dict(zip(keys, values)) + + def _change_obj_to_dict(self, obj): + dic = {} + for k, v in vars(obj).items(): + try: + vars(v) + except TypeError: + dic[k] = v + return dic diff --git a/yardstick/benchmark/scenarios/lib/__init__.py b/yardstick/benchmark/scenarios/lib/__init__.py new file mode 100644 index 000000000..e69de29bb --- /dev/null +++ b/yardstick/benchmark/scenarios/lib/__init__.py diff --git a/yardstick/benchmark/scenarios/lib/add_memory_load.py b/yardstick/benchmark/scenarios/lib/add_memory_load.py new file mode 100644 index 000000000..26cf140d1 --- /dev/null +++ b/yardstick/benchmark/scenarios/lib/add_memory_load.py @@ -0,0 +1,57 @@ +# ############################################################################ +# Copyright (c) 2017 Huawei Technologies Co.,Ltd and others. +# +# All rights reserved. This program and the accompanying materials +# are made available under the terms of the Apache License, Version 2.0 +# which accompanies this distribution, and is available at +# http://www.apache.org/licenses/LICENSE-2.0 +# ############################################################################ + +from __future__ import print_function +from __future__ import absolute_import + +import logging + +import yardstick.ssh as ssh +from yardstick.benchmark.scenarios import base + +LOG = logging.getLogger(__name__) + + +class AddMemoryLoad(base.Scenario): + """Add memory load in server + """ + + __scenario_type__ = "AddMemoryLoad" + + def __init__(self, scenario_cfg, context_cfg): + self.scenario_cfg = scenario_cfg + self.context_cfg = context_cfg + + self.options = scenario_cfg.get('options', {}) + + self.client = ssh.SSH.from_node(self.context_cfg['host']) + self.client.wait(timeout=600) + + def run(self, result): + self._add_load() + + def _add_load(self): + try: + memory_load = self.options['memory_load'] + except KeyError: + LOG.error('memory_load parameter must be provided') + else: + if float(memory_load) == 0: + return + cmd = 'free | awk "/Mem/ {print $2}"' + code, stdout, stderr = self.client.execute(cmd) + total = int(stdout.split()[1]) + used = int(stdout.split()[2]) + remain_memory = total * float(memory_load) - used + if remain_memory > 0: + count = remain_memory / 1024 / 128 + LOG.info('Add %s vm load', count) + if count != 0: + cmd = 'stress -t 10 -m {} --vm-keep'.format(count) + self.client.execute(cmd) diff --git a/yardstick/benchmark/scenarios/lib/check_numa_info.py b/yardstick/benchmark/scenarios/lib/check_numa_info.py new file mode 100644 index 000000000..59a47547e --- /dev/null +++ b/yardstick/benchmark/scenarios/lib/check_numa_info.py @@ -0,0 +1,61 @@ +# ############################################################################ +# Copyright (c) 2017 Huawei Technologies Co.,Ltd and others. +# +# All rights reserved. This program and the accompanying materials +# are made available under the terms of the Apache License, Version 2.0 +# which accompanies this distribution, and is available at +# http://www.apache.org/licenses/LICENSE-2.0 +# ############################################################################ + +from __future__ import print_function +from __future__ import absolute_import + +import logging + +from yardstick.benchmark.scenarios import base + +LOG = logging.getLogger(__name__) + + +class CheckNumaInfo(base.Scenario): + """ + Execute a live migration for two hosts + + """ + + __scenario_type__ = "CheckNumaInfo" + + def __init__(self, scenario_cfg, context_cfg): + self.scenario_cfg = scenario_cfg + self.context_cfg = context_cfg + + self.options = self.scenario_cfg.get('options', {}) + + self.cpu_set = self.options.get('cpu_set', '1,2,3,4,5,6') + + def run(self, result): + info1 = self.options.get('info1') + info2 = self.options.get('info2') + LOG.debug('Origin numa info: %s', info1) + LOG.debug('Current numa info: %s', info2) + status = self._check_vm2_status(info1, info2) + + keys = self.scenario_cfg.get('output', '').split() + values = [status] + return self._push_to_outputs(keys, values) + + def _check_vm2_status(self, info1, info2): + if len(info1['pinning']) != 1 or len(info2['pinning']) != 1: + return False + + for i in info1['vcpupin']: + for j in i['cpuset'].split(','): + if j not in self.cpu_set.split(','): + return False + + for i in info2['vcpupin']: + for j in i['cpuset'].split(','): + if j not in self.cpu_set.split(','): + return False + + return True diff --git a/yardstick/benchmark/scenarios/lib/check_value.py b/yardstick/benchmark/scenarios/lib/check_value.py new file mode 100644 index 000000000..759076068 --- /dev/null +++ b/yardstick/benchmark/scenarios/lib/check_value.py @@ -0,0 +1,58 @@ +############################################################################## +# Copyright (c) 2017 Huawei Technologies Co.,Ltd and others. +# +# All rights reserved. This program and the accompanying materials +# are made available under the terms of the Apache License, Version 2.0 +# which accompanies this distribution, and is available at +# http://www.apache.org/licenses/LICENSE-2.0 +############################################################################## + +from __future__ import print_function +from __future__ import absolute_import + +import logging + +from yardstick.benchmark.scenarios import base + +LOG = logging.getLogger(__name__) + + +class CheckValue(base.Scenario): + """Check values between value1 and value2 + + options: + operator: equal(eq) and not equal(ne) + value1: + value2: + output: check_result + """ + + __scenario_type__ = "CheckValue" + + def __init__(self, scenario_cfg, context_cfg): + self.scenario_cfg = scenario_cfg + self.context_cfg = context_cfg + self.options = self.scenario_cfg['options'] + + def run(self, result): + """execute the test""" + + op = self.options.get("operator") + LOG.debug("options=%s", self.options) + value1 = str(self.options.get("value1")) + value2 = str(self.options.get("value2")) + check_result = "PASS" + if op == "eq" and value1 != value2: + LOG.info("value1=%s, value2=%s, error: should equal!!!", value1, + value2) + check_result = "FAIL" + assert value1 == value2, "Error %s!=%s" % (value1, value2) + elif op == "ne" and value1 == value2: + LOG.info("value1=%s, value2=%s, error: should not equal!!!", + value1, value2) + check_result = "FAIL" + assert value1 != value2, "Error %s==%s" % (value1, value2) + LOG.info("Check result is %s", check_result) + keys = self.scenario_cfg.get('output', '').split() + values = [check_result] + return self._push_to_outputs(keys, values) diff --git a/yardstick/benchmark/scenarios/lib/get_migrate_target_host.py b/yardstick/benchmark/scenarios/lib/get_migrate_target_host.py new file mode 100644 index 000000000..c19d96d68 --- /dev/null +++ b/yardstick/benchmark/scenarios/lib/get_migrate_target_host.py @@ -0,0 +1,56 @@ + +# ############################################################################ +# Copyright (c) 2017 Huawei Technologies Co.,Ltd and others. +# +# All rights reserved. This program and the accompanying materials +# are made available under the terms of the Apache License, Version 2.0 +# which accompanies this distribution, and is available at +# http://www.apache.org/licenses/LICENSE-2.0 +# ############################################################################ + +from __future__ import print_function +from __future__ import absolute_import + +import logging + +from yardstick.common import openstack_utils +from yardstick.common.utils import change_obj_to_dict +from yardstick.benchmark.scenarios import base + +LOG = logging.getLogger(__name__) + + +class GetMigrateTargetHost(base.Scenario): + """Get a migrate target host according server + """ + + __scenario_type__ = "GetMigrateTargetHost" + + def __init__(self, scenario_cfg, context_cfg): + self.scenario_cfg = scenario_cfg + self.context_cfg = context_cfg + + self.options = self.scenario_cfg.get('options', {}) + default_instance_id = self.options.get('server', {}).get('id', '') + self.instance_id = self.options.get('server_id', default_instance_id) + + self.nova_client = openstack_utils.get_nova_client() + + def run(self, result): + current_host = self._get_current_host_name(self.instance_id) + target_host = self._get_migrate_host(current_host) + + keys = self.scenario_cfg.get('output', '').split() + values = [target_host] + return self._push_to_outputs(keys, values) + + def _get_current_host_name(self, server_id): + + return change_obj_to_dict(self.nova_client.servers.get(server_id))['OS-EXT-SRV-ATTR:host'] + + def _get_migrate_host(self, current_host): + hosts = self.nova_client.hosts.list_all() + compute_hosts = [a.host for a in hosts if a.service == 'compute'] + for host in compute_hosts: + if host.strip() != current_host.strip(): + return host diff --git a/yardstick/benchmark/scenarios/lib/get_numa_info.py b/yardstick/benchmark/scenarios/lib/get_numa_info.py new file mode 100644 index 000000000..4e4a44d95 --- /dev/null +++ b/yardstick/benchmark/scenarios/lib/get_numa_info.py @@ -0,0 +1,79 @@ +# ############################################################################ +# Copyright (c) 2017 Huawei Technologies Co.,Ltd and others. +# +# All rights reserved. This program and the accompanying materials +# are made available under the terms of the Apache License, Version 2.0 +# which accompanies this distribution, and is available at +# http://www.apache.org/licenses/LICENSE-2.0 +# ############################################################################ + +from __future__ import print_function +from __future__ import absolute_import + +import logging +import os + +import yaml +from xml.etree import ElementTree as ET + +from yardstick import ssh +from yardstick.benchmark.scenarios import base +from yardstick.common import constants as consts +from yardstick.common.utils import change_obj_to_dict +from yardstick.common.openstack_utils import get_nova_client +from yardstick.common.task_template import TaskTemplate + +LOG = logging.getLogger(__name__) + + +class GetNumaInfo(base.Scenario): + """ + Execute a live migration for two hosts + + """ + + __scenario_type__ = "GetNumaInfo" + + def __init__(self, scenario_cfg, context_cfg): + self.scenario_cfg = scenario_cfg + self.context_cfg = context_cfg + self.options = self.scenario_cfg.get('options', {}) + + server = self.options['server'] + self.server_id = server['id'] + self.host = self._get_current_host_name(self.server_id) + + node_file = os.path.join(consts.YARDSTICK_ROOT_PATH, + self.options.get('file')) + + with open(node_file) as f: + nodes = yaml.safe_load(TaskTemplate.render(f.read())) + self.nodes = {a['host_name']: a for a in nodes['nodes']} + + def run(self, result): + numa_info = self._check_numa_node(self.server_id, self.host) + + keys = self.scenario_cfg.get('output', '').split() + values = [numa_info] + return self._push_to_outputs(keys, values) + + def _get_current_host_name(self, server_id): + + return change_obj_to_dict(get_nova_client().servers.get(server_id))['OS-EXT-SRV-ATTR:host'] + + def _get_host_client(self, node_name): + self.host_client = ssh.SSH.from_node(self.nodes.get(node_name)) + self.host_client.wait(timeout=600) + + def _check_numa_node(self, server_id, host): + self._get_host_client(host) + + cmd = "sudo virsh dumpxml %s" % server_id + LOG.debug("Executing command: %s", cmd) + status, stdout, stderr = self.host_client.execute(cmd) + if status: + raise RuntimeError(stderr) + root = ET.fromstring(stdout) + vcpupin = [a.attrib for a in root.iter('vcpupin')] + pinning = [a.attrib for a in root.iter('memnode')] + return {"pinning": pinning, 'vcpupin': vcpupin} diff --git a/yardstick/benchmark/scenarios/lib/get_server.py b/yardstick/benchmark/scenarios/lib/get_server.py new file mode 100644 index 000000000..fcf47c80d --- /dev/null +++ b/yardstick/benchmark/scenarios/lib/get_server.py @@ -0,0 +1,83 @@ +############################################################################## +# Copyright (c) 2017 Huawei Technologies Co.,Ltd and others. +# +# All rights reserved. This program and the accompanying materials +# are made available under the terms of the Apache License, Version 2.0 +# which accompanies this distribution, and is available at +# http://www.apache.org/licenses/LICENSE-2.0 +############################################################################## + +from __future__ import print_function +from __future__ import absolute_import + +import logging + +from yardstick.benchmark.scenarios import base +import yardstick.common.openstack_utils as op_utils + +LOG = logging.getLogger(__name__) + + +class GetServer(base.Scenario): + """Get a server instance + + Parameters + server_id - ID of the server + type: string + unit: N/A + default: null + server_name - name of the server + type: string + unit: N/A + default: null + + Either server_id or server_name is required. + + Outputs + rc - response code of getting server instance + 0 for success + 1 for failure + type: int + unit: N/A + server - instance of the server + type: dict + unit: N/A + """ + + __scenario_type__ = "GetServer" + + def __init__(self, scenario_cfg, context_cfg): + self.scenario_cfg = scenario_cfg + self.context_cfg = context_cfg + self.options = self.scenario_cfg.get('options', {}) + + self.server_id = self.options.get("server_id") + if self.server_id: + LOG.debug('Server id is %s', self.server_id) + + default_name = self.scenario_cfg.get('host', + self.scenario_cfg.get('target')) + self.server_name = self.options.get('server_name', default_name) + if self.server_name: + LOG.debug('Server name is %s', self.server_name) + + self.nova_client = op_utils.get_nova_client() + + def run(self, result): + """execute the test""" + + if self.server_id: + server = self.nova_client.servers.get(self.server_id) + else: + server = op_utils.get_server_by_name(self.server_name) + + keys = self.scenario_cfg.get('output', '').split() + + if server: + LOG.info("Get server successful!") + values = [0, self._change_obj_to_dict(server)] + else: + LOG.info("Get server failed!") + values = [1] + + return self._push_to_outputs(keys, values) diff --git a/yardstick/benchmark/scenarios/lib/get_server_ip.py b/yardstick/benchmark/scenarios/lib/get_server_ip.py new file mode 100644 index 000000000..1eeeb7fca --- /dev/null +++ b/yardstick/benchmark/scenarios/lib/get_server_ip.py @@ -0,0 +1,38 @@ +############################################################################## +# Copyright (c) 2017 Huawei Technologies Co.,Ltd and others. +# +# All rights reserved. This program and the accompanying materials +# are made available under the terms of the Apache License, Version 2.0 +# which accompanies this distribution, and is available at +# http://www.apache.org/licenses/LICENSE-2.0 +############################################################################## + +from __future__ import print_function +from __future__ import absolute_import + +import logging + +from yardstick.benchmark.scenarios import base + +LOG = logging.getLogger(__name__) + + +class GetServerIp(base.Scenario): + """Get a server by name""" + + __scenario_type__ = "GetServerIp" + + def __init__(self, scenario_cfg, context_cfg): + self.scenario_cfg = scenario_cfg + self.context_cfg = context_cfg + self.options = self.scenario_cfg.get('options', {}) + self.ip_type = self.options.get('ip_type', "floating") + + def run(self, result): + server = self.options.get('server', {}) + ip = next(n['addr'] for k, v in server['addresses'].items() + for n in v if n['OS-EXT-IPS:type'] == self.ip_type) + + keys = self.scenario_cfg.get('output', '').split() + values = [ip] + return self._push_to_outputs(keys, values) diff --git a/yardstick/benchmark/scenarios/lib/migrate.py b/yardstick/benchmark/scenarios/lib/migrate.py new file mode 100644 index 000000000..116bae69e --- /dev/null +++ b/yardstick/benchmark/scenarios/lib/migrate.py @@ -0,0 +1,155 @@ +# ############################################################################ +# Copyright (c) 2017 Huawei Technologies Co.,Ltd and others. +# +# All rights reserved. This program and the accompanying materials +# are made available under the terms of the Apache License, Version 2.0 +# which accompanies this distribution, and is available at +# http://www.apache.org/licenses/LICENSE-2.0 +# ############################################################################ + +from __future__ import print_function +from __future__ import absolute_import + +import logging +import subprocess +import threading +import time + +from datetime import datetime +import ping + +from yardstick.common import openstack_utils +from yardstick.common.utils import change_obj_to_dict +from yardstick.benchmark.scenarios import base + +LOG = logging.getLogger(__name__) + +TIMEOUT = 0.05 +PACKAGE_SIZE = 64 + + +class Migrate(base.Scenario): # pragma: no cover + """ + Execute a live migration for two hosts + + """ + + __scenario_type__ = "Migrate" + + def __init__(self, scenario_cfg, context_cfg): + self.scenario_cfg = scenario_cfg + self.context_cfg = context_cfg + self.options = self.scenario_cfg.get('options', {}) + + self.nova_client = openstack_utils.get_nova_client() + + def run(self, result): + default_instance_id = self.options.get('server', {}).get('id', '') + instance_id = self.options.get('server_id', default_instance_id) + LOG.info('Instance id is %s', instance_id) + + target_host = self.options.get('host') + LOG.info('Target host is %s', target_host) + + instance_ip = self.options.get('server_ip') + if instance_ip: + LOG.info('Instance ip is %s', instance_ip) + + self._ping_until_connected(instance_ip) + LOG.info('Instance is connected') + + LOG.debug('Start to ping instance') + ping_thread = self._do_ping_task(instance_ip) + + keys = self.scenario_cfg.get('output', '').split() + try: + LOG.info('Start to migrate') + self._do_migrate(instance_id, target_host) + except Exception as e: + return self._push_to_outputs(keys, [1, str(e).split('.')[0]]) + else: + migrate_time = self._get_migrate_time(instance_id) + LOG.info('Migration time is %s s', migrate_time) + + current_host = self._get_current_host_name(instance_id) + LOG.info('Current host is %s', current_host) + if current_host.strip() != target_host.strip(): + LOG.error('current_host not equal to target_host') + values = [1, 'current_host not equal to target_host'] + return self._push_to_outputs(keys, values) + + if instance_ip: + ping_thread.flag = False + ping_thread.join() + + downtime = ping_thread.get_delay() + LOG.info('Downtime is %s s', downtime) + + values = [0, migrate_time, downtime] + return self._push_to_outputs(keys, values) + else: + values = [0, migrate_time] + return self._push_to_outputs(keys, values) + + def _do_migrate(self, server_id, target_host): + + cmd = ['nova', 'live-migration', server_id, target_host] + p = subprocess.Popen(cmd, stdout=subprocess.PIPE) + p.communicate() + + def _ping_until_connected(self, instance_ip): + for i in range(3000): + res = ping.do_one(instance_ip, TIMEOUT, PACKAGE_SIZE) + if res: + break + + def _do_ping_task(self, instance_ip): + ping_thread = PingThread(instance_ip) + ping_thread.start() + return ping_thread + + def _get_current_host_name(self, server_id): + + return change_obj_to_dict(self.nova_client.servers.get(server_id))['OS-EXT-SRV-ATTR:host'] + + def _get_migrate_time(self, server_id): + while True: + status = self.nova_client.servers.get(server_id).status.lower() + if status == 'migrating': + start_time = datetime.now() + break + LOG.debug('Instance status change to MIGRATING') + + while True: + status = self.nova_client.servers.get(server_id).status.lower() + if status == 'active': + end_time = datetime.now() + break + if status == 'error': + LOG.error('Instance status is ERROR') + raise RuntimeError('The instance status is error') + LOG.debug('Instance status change to ACTIVE') + + duration = end_time - start_time + return duration.seconds + duration.microseconds * 1.0 / 1e6 + + +class PingThread(threading.Thread): # pragma: no cover + + def __init__(self, target): + super(PingThread, self).__init__() + self.target = target + self.flag = True + self.delay = 0.0 + + def run(self): + count = 0 + while self.flag: + res = ping.do_one(self.target, TIMEOUT, PACKAGE_SIZE) + if not res: + count += 1 + time.sleep(0.01) + self.delay = (TIMEOUT + 0.01) * count + + def get_delay(self): + return self.delay diff --git a/yardstick/benchmark/scenarios/networking/iperf3.py b/yardstick/benchmark/scenarios/networking/iperf3.py index 3135af9bd..a3d273750 100644 --- a/yardstick/benchmark/scenarios/networking/iperf3.py +++ b/yardstick/benchmark/scenarios/networking/iperf3.py @@ -50,6 +50,17 @@ For more info see http://software.es.net/iperf type: int unit: bytes default: - + length - length of buffer to read or write, + (default 128 KB for TCP, 8 KB for UDP) + type: int + unit: k + default: - + window - set window size / socket buffer size + set TCP windows size. for UDP way to test, this will set to accept UDP + packet buffer size, limit the max size of acceptable data packet. + type: int + unit: k + default: - """ __scenario_type__ = "Iperf3" @@ -122,6 +133,12 @@ For more info see http://software.es.net/iperf elif "blockcount" in options: cmd += " --blockcount %d" % options["blockcount"] + if "length" in options: + cmd += " --length %s" % options["length"] + + if "window" in options: + cmd += " --window %s" % options["window"] + LOG.debug("Executing command: %s", cmd) status, stdout, stderr = self.host.execute(cmd) diff --git a/yardstick/benchmark/scenarios/networking/pktgen.py b/yardstick/benchmark/scenarios/networking/pktgen.py index e6aa7e5fb..8ca1ca60e 100644 --- a/yardstick/benchmark/scenarios/networking/pktgen.py +++ b/yardstick/benchmark/scenarios/networking/pktgen.py @@ -9,6 +9,7 @@ from __future__ import absolute_import from __future__ import print_function +import os import logging import pkg_resources @@ -19,6 +20,9 @@ from yardstick.benchmark.scenarios import base LOG = logging.getLogger(__name__) +VNIC_TYPE_LIST = ["ovs", "sriov"] +SRIOV_DRIVER_LIST = ["ixgbevf", "i40evf"] + class Pktgen(base.Scenario): """Execute pktgen between two hosts @@ -44,7 +48,11 @@ class Pktgen(base.Scenario): def __init__(self, scenario_cfg, context_cfg): self.scenario_cfg = scenario_cfg self.context_cfg = context_cfg + self.vnic_name = "eth0" + self.vnic_type = "ovs" + self.queue_number = 1 self.setup_done = False + self.multiqueue_setup_done = False def setup(self): """scenario setup""" @@ -67,6 +75,212 @@ class Pktgen(base.Scenario): self.setup_done = True + def multiqueue_setup(self): + # one time setup stuff + cmd = "sudo sysctl -w net.core.netdev_budget=3000" + self.server.send_command(cmd) + self.client.send_command(cmd) + + cmd = "sudo sysctl -w net.core.netdev_max_backlog=100000" + self.server.send_command(cmd) + self.client.send_command(cmd) + + """multiqueue setup""" + if not self._is_irqbalance_disabled(): + self._disable_irqbalance() + + vnic_driver_name = self._get_vnic_driver_name() + if vnic_driver_name in SRIOV_DRIVER_LIST: + self.vnic_type = "sriov" + + # one time setup stuff + cmd = "sudo ethtool -G %s rx 4096 tx 4096" % self.vnic_name + self.server.send_command(cmd) + self.client.send_command(cmd) + + self.queue_number = self._get_sriov_queue_number() + self._setup_irqmapping_sriov(self.queue_number) + else: + self.vnic_type = "ovs" + self.queue_number = self._enable_ovs_multiqueue() + self._setup_irqmapping_ovs(self.queue_number) + + self.multiqueue_setup_done = True + + def _get_vnic_driver_name(self): + cmd = "readlink /sys/class/net/%s/device/driver" % self.vnic_name + LOG.debug("Executing command: %s", cmd) + status, stdout, stderr = self.server.execute(cmd) + if status: + raise RuntimeError(stderr) + return os.path.basename(stdout.strip()) + + def _is_irqbalance_disabled(self): + """Did we disable irqbalance already in the guest?""" + is_disabled = False + cmd = "grep ENABLED /etc/default/irqbalance" + status, stdout, stderr = self.server.execute(cmd) + if status: + raise RuntimeError(stderr) + if "0" in stdout: + is_disabled = True + + return is_disabled + + def _disable_irqbalance(self): + cmd = "sudo sed -i -e 's/ENABLED=\"1\"/ENABLED=\"0\"/g' " \ + "/etc/default/irqbalance" + status, stdout, stderr = self.server.execute(cmd) + status, stdout, stderr = self.client.execute(cmd) + if status: + raise RuntimeError(stderr) + + cmd = "sudo service irqbalance stop" + status, stdout, stderr = self.server.execute(cmd) + status, stdout, stderr = self.client.execute(cmd) + if status: + raise RuntimeError(stderr) + + cmd = "sudo service irqbalance disable" + status, stdout, stderr = self.server.execute(cmd) + status, stdout, stderr = self.client.execute(cmd) + if status: + raise RuntimeError(stderr) + + def _setup_irqmapping_ovs(self, queue_number): + cmd = "grep 'virtio0-input.0' /proc/interrupts |" \ + "awk '{match($0,/ +[0-9]+/)} " \ + "{print substr($1,RSTART,RLENGTH-1)}'" + status, stdout, stderr = self.server.execute(cmd) + if status: + raise RuntimeError(stderr) + + cmd = "echo 1 | sudo tee /proc/irq/%s/smp_affinity" % (int(stdout)) + status, stdout, stderr = self.server.execute(cmd) + status, stdout, stderr = self.client.execute(cmd) + if status: + raise RuntimeError(stderr) + + cmd = "grep 'virtio0-output.0' /proc/interrupts |" \ + "awk '{match($0,/ +[0-9]+/)} " \ + "{print substr($1,RSTART,RLENGTH-1)}'" + status, stdout, stderr = self.server.execute(cmd) + if status: + raise RuntimeError(stderr) + + cmd = "echo 1 | sudo tee /proc/irq/%s/smp_affinity" % (int(stdout)) + status, stdout, stderr = self.server.execute(cmd) + status, stdout, stderr = self.client.execute(cmd) + if status: + raise RuntimeError(stderr) + + if queue_number == 1: + return + + for i in range(1, queue_number): + cmd = "grep 'virtio0-input.%s' /proc/interrupts |" \ + "awk '{match($0,/ +[0-9]+/)} " \ + "{print substr($1,RSTART,RLENGTH-1)}'" % (i) + status, stdout, stderr = self.server.execute(cmd) + if status: + raise RuntimeError(stderr) + + cmd = "echo %s | sudo tee /proc/irq/%s/smp_affinity" \ + % (1 << i, int(stdout)) + status, stdout, stderr = self.server.execute(cmd) + status, stdout, stderr = self.client.execute(cmd) + if status: + raise RuntimeError(stderr) + + cmd = "grep 'virtio0-output.%s' /proc/interrupts |" \ + "awk '{match($0,/ +[0-9]+/)} " \ + "{print substr($1,RSTART,RLENGTH-1)}'" % (i) + status, stdout, stderr = self.server.execute(cmd) + if status: + raise RuntimeError(stderr) + + cmd = "echo %s | sudo tee /proc/irq/%s/smp_affinity" \ + % (1 << i, int(stdout)) + status, stdout, stderr = self.server.execute(cmd) + status, stdout, stderr = self.client.execute(cmd) + if status: + raise RuntimeError(stderr) + + def _setup_irqmapping_sriov(self, queue_number): + cmd = "grep '%s-TxRx-0' /proc/interrupts |" \ + "awk '{match($0,/ +[0-9]+/)} " \ + "{print substr($1,RSTART,RLENGTH-1)}'" % self.vnic_name + status, stdout, stderr = self.server.execute(cmd) + if status: + raise RuntimeError(stderr) + + cmd = "echo 1 | sudo tee /proc/irq/%s/smp_affinity" % (int(stdout)) + status, stdout, stderr = self.server.execute(cmd) + status, stdout, stderr = self.client.execute(cmd) + if status: + raise RuntimeError(stderr) + + if queue_number == 1: + return + + for i in range(1, queue_number): + cmd = "grep '%s-TxRx-%s' /proc/interrupts |" \ + "awk '{match($0,/ +[0-9]+/)} " \ + "{print substr($1,RSTART,RLENGTH-1)}'" % (self.vnic_name, i) + status, stdout, stderr = self.server.execute(cmd) + if status: + raise RuntimeError(stderr) + + cmd = "echo %s | sudo tee /proc/irq/%s/smp_affinity" \ + % (1 << i, int(stdout)) + status, stdout, stderr = self.server.execute(cmd) + status, stdout, stderr = self.client.execute(cmd) + if status: + raise RuntimeError(stderr) + + def _get_sriov_queue_number(self): + """Get queue number from server as both VMs are the same""" + cmd = "grep %s-TxRx- /proc/interrupts | wc -l" % self.vnic_name + LOG.debug("Executing command: %s", cmd) + status, stdout, stderr = self.server.execute(cmd) + if status: + raise RuntimeError(stderr) + return int(stdout) + + def _get_available_queue_number(self): + """Get queue number from client as both VMs are the same""" + cmd = "sudo ethtool -l %s | grep Combined | head -1 |" \ + "awk '{printf $2}'" % self.vnic_name + LOG.debug("Executing command: %s", cmd) + status, stdout, stderr = self.server.execute(cmd) + if status: + raise RuntimeError(stderr) + return int(stdout) + + def _get_usable_queue_number(self): + """Get queue number from client as both VMs are the same""" + cmd = "sudo ethtool -l %s | grep Combined | tail -1 |" \ + "awk '{printf $2}'" % self.vnic_name + LOG.debug("Executing command: %s", cmd) + status, stdout, stderr = self.server.execute(cmd) + if status: + raise RuntimeError(stderr) + return int(stdout) + + def _enable_ovs_multiqueue(self): + available_queue_number = self._get_available_queue_number() + usable_queue_number = self._get_usable_queue_number() + if available_queue_number > 1 and \ + available_queue_number != usable_queue_number: + cmd = "sudo ethtool -L %s combined %s" % \ + (self.vnic_name, available_queue_number) + LOG.debug("Executing command: %s", cmd) + status, stdout, stderr = self.server.execute(cmd) + status, stdout, stderr = self.client.execute(cmd) + if status: + raise RuntimeError(stderr) + return available_queue_number + def _iptables_setup(self): """Setup iptables on server to monitor for received packets""" cmd = "sudo iptables -F; " \ @@ -99,6 +313,14 @@ class Pktgen(base.Scenario): options = self.scenario_cfg['options'] packetsize = options.get("packetsize", 60) self.number_of_ports = options.get("number_of_ports", 10) + self.vnic_name = options.get("vnic_name", "eth0") + ovs_dpdk = options.get("ovs_dpdk", False) + pps = options.get("pps", 1000000) + multiqueue = options.get("multiqueue", False) + + if multiqueue and not self.multiqueue_setup_done: + self.multiqueue_setup() + # if run by a duration runner duration_time = self.scenario_cfg["runner"].get("duration", None) \ if "runner" in self.scenario_cfg else None @@ -114,8 +336,18 @@ class Pktgen(base.Scenario): self._iptables_setup() - cmd = "sudo bash pktgen.sh %s %s %s %s" \ - % (ipaddr, self.number_of_ports, packetsize, duration) + queue_number = self.queue_number + + # For native OVS, half of vCPUs are used by vhost kernel threads + # hence set the queue_number to half number of vCPUs + # e.g. set queue_number to 2 if there are 4 vCPUs + if self.vnic_type == "ovs" and not ovs_dpdk and self.queue_number > 1: + queue_number = self.queue_number / 2 + + cmd = "sudo bash pktgen.sh %s %s %s %s %s %s" \ + % (ipaddr, self.number_of_ports, packetsize, + duration, queue_number, pps) + LOG.debug("Executing command: %s", cmd) status, stdout, stderr = self.client.execute(cmd) @@ -131,12 +363,15 @@ class Pktgen(base.Scenario): sent = result['packets_sent'] received = result['packets_received'] ppm = 1000000 * (sent - received) / sent + # if ppm is 1, then 11 out of 10 million is no pass + ppm += (sent - received) % sent > 0 + LOG.debug("Lost packets %d - Lost ppm %d", (sent - received), ppm) sla_max_ppm = int(self.scenario_cfg["sla"]["max_ppm"]) assert ppm <= sla_max_ppm, "ppm %d > sla_max_ppm %d; " \ % (ppm, sla_max_ppm) -def _test(): +def _test(): # pragma: no cover """internal test function""" key_filename = pkg_resources.resource_filename('yardstick.resources', 'files/yardstick_key') @@ -165,6 +400,5 @@ def _test(): p.run(result) print(result) - if __name__ == '__main__': _test() diff --git a/yardstick/benchmark/scenarios/networking/pktgen_benchmark.bash b/yardstick/benchmark/scenarios/networking/pktgen_benchmark.bash index 4224c5abf..e338a1b09 100644 --- a/yardstick/benchmark/scenarios/networking/pktgen_benchmark.bash +++ b/yardstick/benchmark/scenarios/networking/pktgen_benchmark.bash @@ -16,6 +16,8 @@ DST_IP=$1 # destination IP address NUM_PORTS=$2 # number of source ports PKT_SIZE=$3 # packet size DURATION=$4 # test duration (seconds) +TRXQUEUE=$5 # number of RX/TX queues to use +PPS=$6 # packets per second to send # Configuration UDP_SRC_MIN=1000 # UDP source port min @@ -37,62 +39,100 @@ pgset() fi } +# remove all devices from thread +pgclean() +{ + COUNTER=0 + while [ ${COUNTER} -lt ${TRXQUEUE} ]; do + # + # Thread commands + # + + PGDEV=/proc/net/pktgen/kpktgend_${COUNTER} + + # Remove all devices from this thread + pgset "rem_device_all" + let COUNTER=COUNTER+1 + done +} + # configure pktgen (see pktgen doc for details) pgconfig() { - # - # Thread commands - # + pps=$(( PPS / TRXQUEUE )) + COUNTER=0 + while [ ${COUNTER} -lt ${TRXQUEUE} ]; do + # + # Thread commands + # - PGDEV=/proc/net/pktgen/kpktgend_0 + PGDEV=/proc/net/pktgen/kpktgend_${COUNTER} - # Remove all devices from this thread - pgset "rem_device_all" + # Add device to thread + pgset "add_device $DEV@${COUNTER}" - # Add device to thread - pgset "add_device $DEV" + # + # Device commands + # - # - # Device commands - # + PGDEV=/proc/net/pktgen/$DEV@${COUNTER} - PGDEV=/proc/net/pktgen/$DEV + # 0 means continious sends untill explicitly stopped + pgset "count 0" - # 0 means continious sends untill explicitly stopped - pgset "count 0" + # set pps count to test with an explicit number. if 0 will try with bandwidth + if [ ${pps} -gt 0 ] + then + pgset "ratep ${pps}" + fi - # use single SKB for all transmits - pgset "clone_skb 0" + pgset "clone_skb 10" - # packet size, NIC adds 4 bytes CRC - pgset "pkt_size $PKT_SIZE" + # use different queue per thread + pgset "queue_map_min ${COUNTER}" + pgset "queue_map_max ${COUNTER}" - # random address within the min-max range - pgset "flag IPDST_RND UDPSRC_RND UDPDST_RND" + # packet size, NIC adds 4 bytes CRC + pgset "pkt_size $PKT_SIZE" - # destination IP - pgset "dst_min $DST_IP" - pgset "dst_max $DST_IP" + # random address within the min-max range + pgset "flag UDPDST_RND" + pgset "flag UDPSRC_RND" + pgset "flag IPDST_RND" - # destination MAC address - pgset "dst_mac $MAC" + # destination IP + pgset "dst_min $DST_IP" + pgset "dst_max $DST_IP" + + # destination MAC address + pgset "dst_mac $MAC" + + # source UDP port range + pgset "udp_src_min $UDP_SRC_MIN" + pgset "udp_src_max $UDP_SRC_MAX" - # source UDP port range - pgset "udp_src_min $UDP_SRC_MIN" - pgset "udp_src_max $UDP_SRC_MAX" + # destination UDP port range + pgset "udp_dst_min $UDP_DST_MIN" + pgset "udp_dst_max $UDP_DST_MAX" - # destination UDP port range - pgset "udp_dst_min $UDP_DST_MIN" - pgset "udp_dst_max $UDP_DST_MAX" + let COUNTER=COUNTER+1 + + done } # run pktgen pgrun() { - # Time to run, result can be vieved in /proc/net/pktgen/$DEV + # Time to run, result can be viewed in /proc/net/pktgen/$DEV PGDEV=/proc/net/pktgen/pgctrl # Will hang, Ctrl-C or SIGINT to stop pgset "start" start + + COUNTER=0 + while [ ${COUNTER} -lt ${TRXQUEUE} ]; do + taskset -c ${COUNTER} kpktgend_${COUNTER} + let COUNTER=COUNTER+1 + done } # run pktgen for ${DURATION} seconds @@ -111,19 +151,28 @@ run_test() # write the result to stdout in json format output_json() { - sent=$(awk '/^Result:/{print $5}' <$PGDEV) - pps=$(awk 'match($0,/'\([0-9]+\)pps'/, a) {print a[1]}' <$PGDEV) - errors=$(awk '/errors:/{print $5}' <$PGDEV) + sent=0 + result_pps=0 + errors=0 + PGDEV=/proc/net/pktgen/$DEV@ + COUNTER=0 + while [ ${COUNTER} -lt ${TRXQUEUE} ]; do + sent=$(($sent + $(awk '/^Result:/{print $5}' <$PGDEV${COUNTER}))) + result_pps=$(($result_pps + $(awk 'match($0,/'\([0-9]+\)pps'/, a) {print a[1]}' <$PGDEV${COUNTER}))) + errors=$(($errors + $(awk '/errors:/{print $5}' <$PGDEV${COUNTER}))) + let COUNTER=COUNTER+1 + done flows=$(( NUM_PORTS * (NUM_PORTS + 1) )) - echo { '"packets_sent"':$sent , '"packets_per_second"':$pps, '"flows"':$flows, '"errors"':$errors } + echo '{ "packets_sent"':${sent} , '"packets_per_second"':${result_pps}, '"flows"':${flows}, '"errors"':${errors} '}' } # main entry main() { modprobe pktgen + pgclean ping -c 3 $DST_IP >/dev/null @@ -137,16 +186,20 @@ main() pgconfig # run the test - run_test >/dev/null + run_test - PGDEV=/proc/net/pktgen/$DEV + PGDEV=/proc/net/pktgen/$DEV@ # check result - result=$(cat $PGDEV | fgrep "Result: OK:") - if [ "$result" = "" ]; then - cat $PGDEV | fgrep Result: >/dev/stderr - exit 1 - fi + COUNTER=0 + while [ ${COUNTER} -lt ${TRXQUEUE} ]; do + result=$(cat $PGDEV${COUNTER} | fgrep "Result: OK:") + if [ "$result" = "" ]; then + cat $PGDEV${COUNTER} | fgrep Result: >/dev/stderr + exit 1 + fi + let COUNTER=COUNTER+1 + done # output result output_json diff --git a/yardstick/benchmark/scenarios/networking/vnf_generic.py b/yardstick/benchmark/scenarios/networking/vnf_generic.py index 594edeaa8..9607e3005 100644 --- a/yardstick/benchmark/scenarios/networking/vnf_generic.py +++ b/yardstick/benchmark/scenarios/networking/vnf_generic.py @@ -164,38 +164,60 @@ class NetworkServiceTestCase(base.Scenario): for vnfd in topology["constituent-vnfd"] if vnf_id == vnfd["member-vnf-index"]), None) + @staticmethod + def get_vld_networks(networks): + return {n['vld_id']: n for n in networks.values()} + def _resolve_topology(self, context_cfg, topology): for vld in topology["vld"]: - if len(vld["vnfd-connection-point-ref"]) > 2: + try: + node_0, node_1 = vld["vnfd-connection-point-ref"] + except (TypeError, ValueError): raise IncorrectConfig("Topology file corrupted, " - "too many endpoint for connection") - - node_0, node_1 = vld["vnfd-connection-point-ref"] + "wrong number of endpoints for connection") - node0 = self._find_vnf_name_from_id(topology, - node_0["member-vnf-index-ref"]) - node1 = self._find_vnf_name_from_id(topology, - node_1["member-vnf-index-ref"]) + node_0_name = self._find_vnf_name_from_id(topology, + node_0["member-vnf-index-ref"]) + node_1_name = self._find_vnf_name_from_id(topology, + node_1["member-vnf-index-ref"]) - if0 = node_0["vnfd-connection-point-ref"] - if1 = node_1["vnfd-connection-point-ref"] + node_0_ifname = node_0["vnfd-connection-point-ref"] + node_1_ifname = node_1["vnfd-connection-point-ref"] + node_0_if = context_cfg["nodes"][node_0_name]["interfaces"][node_0_ifname] + node_1_if = context_cfg["nodes"][node_1_name]["interfaces"][node_1_ifname] try: - nodes = context_cfg["nodes"] - nodes[node0]["interfaces"][if0]["vld_id"] = vld["id"] - nodes[node1]["interfaces"][if1]["vld_id"] = vld["id"] - - nodes[node0]["interfaces"][if0]["dst_mac"] = \ - nodes[node1]["interfaces"][if1]["local_mac"] - nodes[node0]["interfaces"][if0]["dst_ip"] = \ - nodes[node1]["interfaces"][if1]["local_ip"] - - nodes[node1]["interfaces"][if1]["dst_mac"] = \ - nodes[node0]["interfaces"][if0]["local_mac"] - nodes[node1]["interfaces"][if1]["dst_ip"] = \ - nodes[node0]["interfaces"][if0]["local_ip"] + vld_networks = self.get_vld_networks(context_cfg["networks"]) + + node_0_if["vld_id"] = vld["id"] + node_1_if["vld_id"] = vld["id"] + + # set peer name + node_0_if["peer_name"] = node_1_name + node_1_if["peer_name"] = node_0_name + + # set peer interface name + node_0_if["peer_ifname"] = node_1_ifname + node_1_if["peer_ifname"] = node_0_ifname + + # just load the whole network dict + node_0_if["network"] = vld_networks.get(vld["id"], {}) + node_1_if["network"] = vld_networks.get(vld["id"], {}) + + node_0_if["dst_mac"] = node_1_if["local_mac"] + node_0_if["dst_ip"] = node_1_if["local_ip"] + + node_1_if["dst_mac"] = node_0_if["local_mac"] + node_1_if["dst_ip"] = node_0_if["local_ip"] + + # add peer interface dict, but remove circular link + # TODO: don't waste memory + node_0_copy = node_0_if.copy() + node_1_copy = node_1_if.copy() + node_0_if["peer_intf"] = node_1_copy + node_1_if["peer_intf"] = node_0_copy except KeyError: - raise IncorrectConfig("Required interface not found," + raise IncorrectConfig("Required interface not found, " "topology file corrupted") @classmethod @@ -308,21 +330,36 @@ printf "%s/driver:" $1 ; basename $(readlink -s $1/device/driver); } \ return dict(network_devices) @classmethod - def get_vnf_impl(cls, vnf_model): + def get_vnf_impl(cls, vnf_model_id): """ Find the implementing class from vnf_model["vnf"]["name"] field - :param vnf_model: dictionary containing a parsed vnfd + :param vnf_model_id: parsed vnfd model ID field :return: subclass of GenericVNF """ import_modules_from_package( "yardstick.network_services.vnf_generic.vnf") - expected_name = vnf_model['id'] - impl = (c for c in itersubclasses(GenericVNF) - if c.__name__ == expected_name) + expected_name = vnf_model_id + classes_found = [] + + def impl(): + for name, class_ in ((c.__name__, c) for c in itersubclasses(GenericVNF)): + if name == expected_name: + yield class_ + classes_found.append(name) + try: - return next(impl) + return next(impl()) except StopIteration: - raise IncorrectConfig("No implementation for %s", expected_name) + pass + + raise IncorrectConfig("No implementation for %s found in %s" % + (expected_name, classes_found)) + + @staticmethod + def update_interfaces_from_node(vnfd, node): + for intf in vnfd["vdu"][0]["external-interface"]: + node_intf = node['interfaces'][intf['name']] + intf['virtual-interface'].update(node_intf) def load_vnf_models(self, scenario_cfg, context_cfg): """ Create VNF objects based on YAML descriptors @@ -339,8 +376,11 @@ printf "%s/driver:" $1 ; basename $(readlink -s $1/device/driver); } \ scenario_cfg['task_path']) as stream: vnf_model = stream.read() vnfd = vnfdgen.generate_vnfd(vnf_model, node) - vnf_impl = self.get_vnf_impl(vnfd["vnfd:vnfd-catalog"]["vnfd"][0]) - vnf_instance = vnf_impl(vnfd["vnfd:vnfd-catalog"]["vnfd"][0]) + # TODO: here add extra context_cfg["nodes"] regardless of template + vnfd = vnfd["vnfd:vnfd-catalog"]["vnfd"][0] + self.update_interfaces_from_node(vnfd, node) + vnf_impl = self.get_vnf_impl(vnfd['id']) + vnf_instance = vnf_impl(vnfd) vnf_instance.name = node_name vnfs.append(vnf_instance) diff --git a/yardstick/benchmark/scenarios/storage/fio.py b/yardstick/benchmark/scenarios/storage/fio.py index ad34817a7..b99e34270 100644 --- a/yardstick/benchmark/scenarios/storage/fio.py +++ b/yardstick/benchmark/scenarios/storage/fio.py @@ -40,10 +40,26 @@ class Fio(base.Scenario): type: string unit: na default: write + rwmixwrite - percentage of a mixed workload that should be writes + type: int + unit: percentage + default: 50 ramp_time - run time before logging any performance type: int unit: seconds default: 20 + direct - whether use non-buffered I/O or not + type: boolean + unit: na + default: 1 + size - total size of I/O for this job. + type: string + unit: na + default: 1g + numjobs - number of clones (processes/threads performing the same workload) of this job + type: int + unit: na + default: 1 Read link below for more fio args description: http://www.bluestop.org/fio/HOWTO.txt @@ -74,8 +90,8 @@ class Fio(base.Scenario): def run(self, result): """execute the benchmark""" - default_args = "-ioengine=libaio -direct=1 -group_reporting " \ - "-numjobs=1 -time_based --output-format=json" + default_args = "-ioengine=libaio -group_reporting -time_based -time_based " \ + "--output-format=json" if not self.setup_done: self.setup() @@ -86,6 +102,10 @@ class Fio(base.Scenario): iodepth = options.get("iodepth", "1") rw = options.get("rw", "write") ramp_time = options.get("ramp_time", 20) + size = options.get("size", "1g") + direct = options.get("direct", "1") + numjobs = options.get("numjobs", "1") + rwmixwrite = options.get("rwmixwrite", 50) name = "yardstick-fio" # if run by a duration runner duration_time = self.scenario_cfg["runner"].get("duration", None) \ @@ -99,10 +119,10 @@ class Fio(base.Scenario): else: runtime = 30 - cmd_args = "-filename=%s -bs=%s -iodepth=%s -rw=%s -ramp_time=%s " \ - "-runtime=%s -name=%s %s" \ - % (filename, bs, iodepth, rw, ramp_time, runtime, name, - default_args) + cmd_args = "-filename=%s -direct=%s -bs=%s -iodepth=%s -rw=%s -rwmixwrite=%s " \ + "-size=%s -ramp_time=%s -numjobs=%s -runtime=%s -name=%s %s" \ + % (filename, direct, bs, iodepth, rw, rwmixwrite, size, ramp_time, numjobs, + runtime, name, default_args) cmd = "sudo bash fio.sh %s %s" % (filename, cmd_args) LOG.debug("Executing command: %s", cmd) # Set timeout, so that the cmd execution does not exit incorrectly diff --git a/yardstick/benchmark/scenarios/storage/storperf.py b/yardstick/benchmark/scenarios/storage/storperf.py index c10118ad1..f0b2361d6 100644 --- a/yardstick/benchmark/scenarios/storage/storperf.py +++ b/yardstick/benchmark/scenarios/storage/storperf.py @@ -87,8 +87,9 @@ class StorPerf(base.Scenario): def setup(self): """Set the configuration.""" env_args = {} - env_args_payload_list = ["agent_count", "public_network", - "agent_image", "volume_size"] + env_args_payload_list = ["agent_count", "agent_flavor", + "public_network", "agent_image", + "volume_size"] for env_argument in env_args_payload_list: try: @@ -206,7 +207,7 @@ class StorPerf(base.Scenario): # terminate_res = requests.delete('http://%s:5000/api/v1.0 # /jobs' % self.target) # else: - # time.sleep(int(est_time)/2) + # time.sleep(int(esti_time)/2) result_res = requests.get('http://%s:5000/api/v1.0/jobs?id=%s' % (self.target, job_id)) diff --git a/yardstick/cmd/NSBperf.py b/yardstick/cmd/NSBperf.py index 3a6d604cd..4e7590ea5 100755 --- a/yardstick/cmd/NSBperf.py +++ b/yardstick/cmd/NSBperf.py @@ -39,13 +39,11 @@ if not PYTHONPATH or not VIRTUAL_ENV: raise SystemExit(1) -def handler(): +def sigint_handler(*args, **kwargs): """ Capture ctrl+c and exit cli """ subprocess.call(["pkill", "-9", "yardstick"]) raise SystemExit(1) -signal.signal(signal.SIGINT, handler) - class YardstickNSCli(object): """ This class handles yardstick network serivce testing """ @@ -215,5 +213,6 @@ class YardstickNSCli(object): self.run_test(args, test_path) if __name__ == "__main__": + signal.signal(signal.SIGINT, sigint_handler) NS_CLI = YardstickNSCli() NS_CLI.main() diff --git a/yardstick/common/constants.py b/yardstick/common/constants.py index d251341fc..8e8114fbb 100644 --- a/yardstick/common/constants.py +++ b/yardstick/common/constants.py @@ -26,7 +26,15 @@ except KeyError: SERVER_IP = '172.17.0.1' else: with IPDB() as ip: - SERVER_IP = ip.routes['default'].gateway + try: + SERVER_IP = ip.routes['default'].gateway + except KeyError: + # during unittests ip.routes['default'] can be invalid + SERVER_IP = '127.0.0.1' + +if not SERVER_IP: + SERVER_IP = '127.0.0.1' + # dir CONF_DIR = get_param('dir.conf', '/etc/yardstick') @@ -40,12 +48,15 @@ SAMPLE_CASE_DIR = join(REPOS_DIR, 'samples') TESTCASE_DIR = join(YARDSTICK_ROOT_PATH, 'tests/opnfv/test_cases/') TESTSUITE_DIR = join(YARDSTICK_ROOT_PATH, 'tests/opnfv/test_suites/') DOCS_DIR = join(REPOS_DIR, 'docs/testing/user/userguide/') +OPENSTACK_CONF_DIR = '/etc/openstack' # file OPENRC = get_param('file.openrc', '/etc/yardstick/openstack.creds') ETC_HOSTS = get_param('file.etc_hosts', '/etc/hosts') CONF_FILE = join(CONF_DIR, 'yardstick.conf') POD_FILE = join(CONF_DIR, 'pod.yaml') +CLOUDS_CONF = join(OPENSTACK_CONF_DIR, 'clouds.yml') +K8S_CONF_FILE = join(CONF_DIR, 'admin.conf') CONF_SAMPLE_FILE = join(CONF_SAMPLE_DIR, 'yardstick.conf.sample') FETCH_SCRIPT = get_param('file.fetch_script', 'utils/fetch_os_creds.sh') FETCH_SCRIPT = join(RELENG_DIR, FETCH_SCRIPT) @@ -66,6 +77,7 @@ INFLUXDB_PASS = get_param('influxdb.password', 'root') INFLUXDB_DB_NAME = get_param('influxdb.db_name', 'yardstick') INFLUXDB_IMAGE = get_param('influxdb.image', 'tutum/influxdb') INFLUXDB_TAG = get_param('influxdb.tag', '0.13') +INFLUXDB_DASHBOARD_PORT = 8083 # grafana GRAFANA_IP = get_param('grafana.ip', SERVER_IP) @@ -74,8 +86,10 @@ GRAFANA_USER = get_param('grafana.username', 'admin') GRAFANA_PASS = get_param('grafana.password', 'admin') GRAFANA_IMAGE = get_param('grafana.image', 'grafana/grafana') GRAFANA_TAG = get_param('grafana.tag', '3.1.1') +GRAFANA_MAPPING_PORT = 1948 # api +API_PORT = 5000 DOCKER_URL = 'unix://var/run/docker.sock' INSTALLERS = ['apex', 'compass', 'fuel', 'joid'] SQLITE = 'sqlite:////tmp/yardstick.db' diff --git a/yardstick/common/kubernetes_utils.py b/yardstick/common/kubernetes_utils.py new file mode 100644 index 000000000..e4c232830 --- /dev/null +++ b/yardstick/common/kubernetes_utils.py @@ -0,0 +1,137 @@ +############################################################################## +# Copyright (c) 2017 Huawei Technologies Co.,Ltd. +# +# All rights reserved. This program and the accompanying materials +# are made available under the terms of the Apache License, Version 2.0 +# which accompanies this distribution, and is available at +# http://www.apache.org/licenses/LICENSE-2.0 +############################################################################## +import logging + +from kubernetes import client +from kubernetes import config +from kubernetes.client.rest import ApiException + +from yardstick.common import constants as consts + +LOG = logging.getLogger(__name__) +LOG.setLevel(logging.DEBUG) + + +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 + + return client.CoreV1Api() + + +def create_replication_controller(template, + namespace='default', + wait=False, + **kwargs): # pragma: no cover + + core_v1_api = get_core_api() + try: + core_v1_api.create_namespaced_replication_controller(namespace, + template, + **kwargs) + except ApiException: + LOG.exception('Create replication controller failed') + raise + + +def delete_replication_controller(name, + namespace='default', + wait=False, + **kwargs): # pragma: no cover + + core_v1_api = get_core_api() + body = kwargs.get('body', client.V1DeleteOptions()) + kwargs.pop('body', None) + try: + core_v1_api.delete_namespaced_replication_controller(name, + namespace, + body, + **kwargs) + except ApiException: + LOG.exception('Delete replication controller failed') + raise + + +def delete_pod(name, + namespace='default', + wait=False, + **kwargs): # pragma: no cover + + core_v1_api = get_core_api() + body = kwargs.get('body', client.V1DeleteOptions()) + kwargs.pop('body', None) + try: + core_v1_api.delete_namespaced_pod(name, + namespace, + body, + **kwargs) + except ApiException: + LOG.exception('Delete pod failed') + raise + + +def read_pod(name, + namespace='default', + **kwargs): # pragma: no cover + core_v1_api = get_core_api() + try: + resp = core_v1_api.read_namespaced_pod(name, namespace, **kwargs) + except ApiException: + LOG.exception('Read pod failed') + raise + else: + return resp + + +def read_pod_status(name, namespace='default', **kwargs): # pragma: no cover + return read_pod(name).status.phase + + +def create_config_map(name, + data, + namespace='default', + wait=False, + **kwargs): # pragma: no cover + core_v1_api = get_core_api() + metadata = client.V1ObjectMeta(name=name) + body = client.V1ConfigMap(data=data, metadata=metadata) + try: + core_v1_api.create_namespaced_config_map(namespace, body, **kwargs) + except ApiException: + LOG.exception('Create config map failed') + raise + + +def delete_config_map(name, + namespace='default', + wait=False, + **kwargs): # pragma: no cover + core_v1_api = get_core_api() + body = kwargs.get('body', client.V1DeleteOptions()) + kwargs.pop('body', None) + try: + core_v1_api.delete_namespaced_config_map(name, + namespace, + body, + **kwargs) + except ApiException: + LOG.exception('Delete config map failed') + raise + + +def get_pod_list(namespace='default'): # pragma: no cover + core_v1_api = get_core_api() + try: + return core_v1_api.list_namespaced_pod(namespace=namespace) + except ApiException: + LOG.exception('Get pod list failed') + raise diff --git a/yardstick/common/utils.py b/yardstick/common/utils.py index 7633777ae..7a64b8ca2 100644 --- a/yardstick/common/utils.py +++ b/yardstick/common/utils.py @@ -24,10 +24,14 @@ import os import subprocess import sys import collections -import six +import socket +import random from functools import reduce +from contextlib import closing import yaml +import six +from flask import jsonify from six.moves import configparser from oslo_utils import importutils from oslo_serialization import jsonutils @@ -123,6 +127,14 @@ def makedirs(d): raise +def remove_file(path): + try: + os.remove(path) + except OSError as e: + if e.errno != errno.ENOENT: + raise + + def execute_command(cmd): exec_msg = "Executing command: '%s'" % cmd logger.debug(exec_msg) @@ -160,7 +172,15 @@ def write_file(path, data, mode='w'): def parse_ini_file(path): parser = configparser.ConfigParser() - parser.read(path) + + try: + files = parser.read(path) + except configparser.MissingSectionHeaderError: + logger.exception('invalid file type') + raise + else: + if not files: + raise RuntimeError('file not exist') try: default = {k: v for k, v in parser.items('DEFAULT')} @@ -197,7 +217,8 @@ def flatten_dict_key(data): next_data = {} # use list, because iterable is too generic - if not any(isinstance(v, (collections.Mapping, list)) for v in data.values()): + if not any(isinstance(v, (collections.Mapping, list)) + for v in data.values()): return data for k, v in six.iteritems(data): @@ -212,3 +233,52 @@ def flatten_dict_key(data): next_data[k] = v return flatten_dict_key(next_data) + + +def translate_to_str(obj): + if isinstance(obj, collections.Mapping): + return {str(k): translate_to_str(v) for k, v in obj.items()} + elif isinstance(obj, list): + return [translate_to_str(ele) for ele in obj] + elif isinstance(obj, six.text_type): + return str(obj) + return obj + + +def result_handler(status, data): + result = { + 'status': status, + 'result': data + } + return jsonify(result) + + +def change_obj_to_dict(obj): + dic = {} + for k, v in vars(obj).items(): + try: + vars(v) + except TypeError: + dic.update({k: v}) + return dic + + +def set_dict_value(dic, keys, value): + return_dic = dic + + for key in keys.split('.'): + + return_dic.setdefault(key, {}) + if key == keys.split('.')[-1]: + return_dic[key] = value + else: + return_dic = return_dic[key] + return dic + + +def get_free_port(ip): + with closing(socket.socket(socket.AF_INET, socket.SOCK_STREAM)) as s: + while True: + port = random.randint(5000, 10000) + if s.connect_ex((ip, port)) != 0: + return port diff --git a/yardstick/dispatcher/base.py b/yardstick/dispatcher/base.py index e77249c54..1fc0a2f31 100644 --- a/yardstick/dispatcher/base.py +++ b/yardstick/dispatcher/base.py @@ -41,9 +41,11 @@ class Base(object): def get(config): """Returns instance of a dispatcher for dispatcher type. """ - out_type = config['DEFAULT']['dispatcher'] + list_dispatcher = \ + [Base.get_cls(out_type.capitalize())(config) + for out_type in config['DEFAULT']['dispatcher']] - return Base.get_cls(out_type.capitalize())(config) + return list_dispatcher @abc.abstractmethod def flush_result_data(self, data): diff --git a/yardstick/network_services/vnf_generic/vnf/base.py b/yardstick/network_services/vnf_generic/vnf/base.py index 1d770f724..2df6037f3 100644 --- a/yardstick/network_services/vnf_generic/vnf/base.py +++ b/yardstick/network_services/vnf_generic/vnf/base.py @@ -96,7 +96,6 @@ class GenericVNF(object): return address.version def _ip_to_hex(self, ip_addr): - ip_to_convert = ip_addr.split(".") ip_x = ip_addr if self.get_ip_version(ip_addr) == 4: ip_to_convert = ip_addr.split(".") diff --git a/yardstick/network_services/vnf_generic/vnfdgen.py b/yardstick/network_services/vnf_generic/vnfdgen.py index 40cc14a49..b56a91915 100644 --- a/yardstick/network_services/vnf_generic/vnfdgen.py +++ b/yardstick/network_services/vnf_generic/vnfdgen.py @@ -48,7 +48,7 @@ def generate_vnfd(vnf_model, node): rendered_vnfd = render(vnf_model, **node) # This is done to get rid of issues with serializing node del node["get"] - filled_vnfd = yaml.load(rendered_vnfd) + filled_vnfd = yaml.safe_load(rendered_vnfd) return filled_vnfd diff --git a/yardstick/orchestrator/heat.py b/yardstick/orchestrator/heat.py index 7958b1cfb..95ca0ad2e 100644 --- a/yardstick/orchestrator/heat.py +++ b/yardstick/orchestrator/heat.py @@ -231,13 +231,15 @@ name (i.e. %s).\ } def add_network(self, name, physical_network='physnet1', provider=None, - segmentation_id=None): + segmentation_id=None, port_security_enabled=None): """add to the template a Neutron Net""" log.debug("adding Neutron::Net '%s'", name) if provider is None: self.resources[name] = { 'type': 'OS::Neutron::Net', - 'properties': {'name': name} + 'properties': { + 'name': name, + } } else: self.resources[name] = { @@ -245,12 +247,15 @@ name (i.e. %s).\ 'properties': { 'name': name, 'network_type': 'vlan', - 'physical_network': physical_network - } + 'physical_network': physical_network, + }, } if segmentation_id: - seg_id_dit = {'segmentation_id': segmentation_id} - self.resources[name]["properties"].update(seg_id_dit) + self.resources[name]['properties']['segmentation_id'] = segmentation_id + # if port security is not defined then don't add to template: + # some deployments don't have port security plugin installed + if port_security_enabled is not None: + self.resources[name]['properties']['port_security_enabled'] = port_security_enabled def add_server_group(self, name, policies): # pragma: no cover """add to the template a ServerGroup""" @@ -262,8 +267,9 @@ name (i.e. %s).\ 'policies': policies} } - def add_subnet(self, name, network, cidr): - """add to the template a Neutron Subnet""" + def add_subnet(self, name, network, cidr, enable_dhcp='true', gateway_ip=None): + """add to the template a Neutron Subnet + """ log.debug("adding Neutron::Subnet '%s' in network '%s', cidr '%s'", name, network, cidr) self.resources[name] = { @@ -272,9 +278,12 @@ name (i.e. %s).\ 'properties': { 'name': name, 'cidr': cidr, - 'network_id': {'get_resource': network} + 'network_id': {'get_resource': network}, + 'enable_dhcp': enable_dhcp, } } + if gateway_ip is not None: + self.resources[name]['properties']['gateway_ip'] = gateway_ip self._template['outputs'][name] = { 'description': 'subnet %s ID' % name, @@ -316,9 +325,10 @@ name (i.e. %s).\ } } - def add_port(self, name, network_name, subnet_name, sec_group_id=None, - provider=None): - """add to the template a named Neutron Port""" + def add_port(self, name, network_name, subnet_name, sec_group_id=None, provider=None, + allowed_address_pairs=None): + """add to the template a named Neutron Port + """ log.debug("adding Neutron::Port '%s', network:'%s', subnet:'%s', " "secgroup:%s", name, network_name, subnet_name, sec_group_id) self.resources[name] = { @@ -341,6 +351,10 @@ name (i.e. %s).\ self.resources[name]['properties']['security_groups'] = \ [sec_group_id] + if allowed_address_pairs: + self.resources[name]['properties'][ + 'allowed_address_pairs'] = allowed_address_pairs + self._template['outputs'][name] = { 'description': 'Address for interface %s' % name, 'value': {'get_attr': [name, 'fixed_ips', 0, 'ip_address']} @@ -534,6 +548,7 @@ name (i.e. %s).\ } HEAT_WAIT_LOOP_INTERVAL = 2 + HEAT_CREATE_COMPLETE_STATUS = u'CREATE_COMPLETE' def create(self, block=True, timeout=3600): """ @@ -558,10 +573,13 @@ name (i.e. %s).\ if not block: self.outputs = stack.outputs = {} + end_time = time.time() + log.info("Created stack '%s' in %.3e secs", + self.name, end_time - start_time) return stack time_limit = start_time + timeout - for status in iter(self.status, u'CREATE_COMPLETE'): + for status in iter(self.status, self.HEAT_CREATE_COMPLETE_STATUS): log.debug("stack state %s", status) if status == u'CREATE_FAILED': stack_status_reason = heat_client.stacks.get(self.uuid).stack_status_reason @@ -574,7 +592,7 @@ name (i.e. %s).\ end_time = time.time() outputs = heat_client.stacks.get(self.uuid).outputs - log.info("Created stack '%s' in %d secs", + log.info("Created stack '%s' in %.3e secs", self.name, end_time - start_time) # keep outputs as unicode diff --git a/yardstick/orchestrator/kubernetes.py b/yardstick/orchestrator/kubernetes.py new file mode 100644 index 000000000..6d7045f58 --- /dev/null +++ b/yardstick/orchestrator/kubernetes.py @@ -0,0 +1,130 @@ +############################################################################## +# Copyright (c) 2017 Huawei Technologies Co.,Ltd. +# +# All rights reserved. This program and the accompanying materials +# are made available under the terms of the Apache License, Version 2.0 +# which accompanies this distribution, and is available at +# http://www.apache.org/licenses/LICENSE-2.0 +############################################################################## + +from __future__ import absolute_import +from __future__ import print_function + +from yardstick.common import utils +from yardstick.common import kubernetes_utils as k8s_utils + + +class KubernetesObject(object): + + def __init__(self, name, **kwargs): + super(KubernetesObject, self).__init__() + self.name = name + self.image = kwargs.get('image', 'openretriever/yardstick') + self.command = [kwargs.get('command', '/bin/bash')] + self.args = kwargs.get('args', []) + self.ssh_key = kwargs.get('ssh_key', 'yardstick_key') + + self.volumes = [] + + self.template = { + "apiVersion": "v1", + "kind": "ReplicationController", + "metadata": { + "name": "" + }, + "spec": { + "replicas": 1, + "template": { + "metadata": { + "labels": { + "app": "" + } + }, + "spec": { + "containers": [], + "volumes": [] + } + } + } + } + + self._change_value_according_name(name) + self._add_containers() + self._add_ssh_key_volume() + self._add_volumes() + + def get_template(self): + return self.template + + def _change_value_according_name(self, name): + utils.set_dict_value(self.template, 'metadata.name', name) + + utils.set_dict_value(self.template, + 'spec.template.metadata.labels.app', + name) + + def _add_containers(self): + containers = [self._add_container()] + utils.set_dict_value(self.template, + 'spec.template.spec.containers', + containers) + + def _add_container(self): + container_name = '{}-container'.format(self.name) + ssh_key_mount_path = "/root/.ssh/" + + container = { + "args": self.args, + "command": self.command, + "image": self.image, + "name": container_name, + "volumeMounts": [ + { + "mountPath": ssh_key_mount_path, + "name": self.ssh_key + } + ] + } + + return container + + def _add_volumes(self): + utils.set_dict_value(self.template, + 'spec.template.spec.volumes', + self.volumes) + + def _add_volume(self, volume): + self.volumes.append(volume) + + def _add_ssh_key_volume(self): + key_volume = { + "configMap": { + "name": self.ssh_key + }, + "name": self.ssh_key + } + self._add_volume(key_volume) + + +class KubernetesTemplate(object): + + def __init__(self, name, template_cfg): + self.name = name + self.ssh_key = '{}-key'.format(name) + + self.rcs = [self._get_rc_name(rc) for rc in template_cfg] + self.k8s_objs = [KubernetesObject(self._get_rc_name(rc), + ssh_key=self.ssh_key, + **cfg) + for rc, cfg in template_cfg.items()] + self.pods = [] + + def _get_rc_name(self, rc_name): + return '{}-{}'.format(rc_name, self.name) + + def get_rc_pods(self): + resp = k8s_utils.get_pod_list() + self.pods = [p.metadata.name for p in resp.items for s in self.rcs + if p.metadata.name.startswith(s)] + + return self.pods diff --git a/yardstick/vTC/apexlake/tests/deployment_unit_test.py b/yardstick/vTC/apexlake/tests/deployment_unit_test.py index 5a9178f53..1ff4225d6 100644 --- a/yardstick/vTC/apexlake/tests/deployment_unit_test.py +++ b/yardstick/vTC/apexlake/tests/deployment_unit_test.py @@ -130,6 +130,7 @@ class DummyDeploymentUnit(mut.DeploymentUnit): raise Exception +@mock.patch("experimental_framework.deployment_unit.time") class TestDeploymentUnit(unittest.TestCase): def setUp(self): @@ -140,7 +141,7 @@ class TestDeploymentUnit(unittest.TestCase): @mock.patch('experimental_framework.heat_manager.HeatManager', side_effect=DummyHeatManager) - def test_constructor_for_sanity(self, mock_heat_manager): + def test_constructor_for_sanity(self, mock_heat_manager, mock_time): du = mut.DeploymentUnit(dict()) self.assertTrue(isinstance(du.heat_manager, DummyHeatManager)) mock_heat_manager.assert_called_once_with(dict()) @@ -150,7 +151,7 @@ class TestDeploymentUnit(unittest.TestCase): side_effect=DummyHeatManager) @mock.patch('os.path.isfile') def test_deploy_heat_template_for_failure(self, mock_os_is_file, - mock_heat_manager): + mock_heat_manager, mock_time): mock_os_is_file.return_value = False du = mut.DeploymentUnit(dict()) template_file = '' @@ -163,7 +164,7 @@ class TestDeploymentUnit(unittest.TestCase): side_effect=DummyHeatManager) @mock.patch('os.path.isfile') def test_deploy_heat_template_for_success(self, mock_os_is_file, - mock_heat_manager): + mock_heat_manager, mock_time): mock_os_is_file.return_value = True du = mut.DeploymentUnit(dict()) template_file = '' @@ -178,7 +179,7 @@ class TestDeploymentUnit(unittest.TestCase): side_effect=DummyHeatManagerComplete) @mock.patch('os.path.isfile') def test_deploy_heat_template_2_for_success(self, mock_os_is_file, - mock_heat_manager): + mock_heat_manager, mock_time): mock_os_is_file.return_value = True du = mut.DeploymentUnit(dict()) template_file = '' @@ -196,7 +197,7 @@ class TestDeploymentUnit(unittest.TestCase): side_effect=DummyDeploymentUnit) def test_deploy_heat_template_3_for_success(self, mock_dep_unit, mock_os_is_file, - mock_heat_manager): + mock_heat_manager, mock_time): mock_os_is_file.return_value = True du = mut.DeploymentUnit(dict()) template_file = '' @@ -212,7 +213,7 @@ class TestDeploymentUnit(unittest.TestCase): side_effect=DummyHeatManagerFailed) @mock.patch('os.path.isfile') def test_deploy_heat_template_for_success_2(self, mock_os_is_file, - mock_heat_manager, mock_log): + mock_heat_manager, mock_log, mock_time): mock_os_is_file.return_value = True du = DummyDeploymentUnit(dict()) template_file = '' @@ -226,7 +227,7 @@ class TestDeploymentUnit(unittest.TestCase): side_effect=DummyHeatManagerDestroy) @mock.patch('experimental_framework.common.LOG') def test_destroy_heat_template_for_success(self, mock_log, - mock_heat_manager): + mock_heat_manager, mock_time): openstack_credentials = dict() du = mut.DeploymentUnit(openstack_credentials) du.deployed_stacks = ['stack'] @@ -238,14 +239,14 @@ class TestDeploymentUnit(unittest.TestCase): side_effect=DummyHeatManagerDestroyException) @mock.patch('experimental_framework.common.LOG') def test_destroy_heat_template_for_success_2(self, mock_log, - mock_heat_manager): + mock_heat_manager, mock_time): openstack_credentials = dict() du = mut.DeploymentUnit(openstack_credentials) du.deployed_stacks = ['stack'] stack_name = 'stack' self.assertFalse(du.destroy_heat_template(stack_name)) - def test_destroy_all_deployed_stacks_for_success(self): + def test_destroy_all_deployed_stacks_for_success(self, mock_time): du = DeploymentUnitDestroy() du.destroy_all_deployed_stacks() self.assertTrue(du.destroy_heat_template()) @@ -254,7 +255,7 @@ class TestDeploymentUnit(unittest.TestCase): side_effect=DummyHeatManagerReiteration) @mock.patch('os.path.isfile') def test_deploy_heat_template_for_success_3(self, mock_os_is_file, - mock_heat_manager): + mock_heat_manager, mock_time): mock_os_is_file.return_value = True du = mut.DeploymentUnit(dict()) template = 'template_reiteration' diff --git a/yardstick/vTC/apexlake/tests/dpdk_packet_generator_test.py b/yardstick/vTC/apexlake/tests/dpdk_packet_generator_test.py index 96ead5ef7..9fa860ab4 100644 --- a/yardstick/vTC/apexlake/tests/dpdk_packet_generator_test.py +++ b/yardstick/vTC/apexlake/tests/dpdk_packet_generator_test.py @@ -359,6 +359,7 @@ class MockRunCommand: return MockRunCommand.ret_val_finalization +@mock.patch('experimental_framework.packet_generators.dpdk_packet_generator.time') class TestDpdkPacketGenOthers(unittest.TestCase): def setUp(self): @@ -370,7 +371,7 @@ class TestDpdkPacketGenOthers(unittest.TestCase): @mock.patch('experimental_framework.packet_generators.' 'dpdk_packet_generator.DpdkPacketGenerator.' '_cores_configuration') - def test__get_core_nics_for_failure(self, mock_cores_configuration): + def test__get_core_nics_for_failure(self, mock_cores_configuration, mock_time): mock_cores_configuration.return_value = None self.assertRaises(ValueError, mut.DpdkPacketGenerator._get_core_nics, '', '') @@ -379,7 +380,7 @@ class TestDpdkPacketGenOthers(unittest.TestCase): 'dpdk_packet_generator.DpdkPacketGenerator.' '_cores_configuration') def test__get_core_nics_one_nic_for_success(self, - mock_cores_configuration): + mock_cores_configuration, mock_time): mock_cores_configuration.return_value = 'ret_val' expected = 'ret_val' output = mut.DpdkPacketGenerator._get_core_nics(1, 'coremask') @@ -390,7 +391,7 @@ class TestDpdkPacketGenOthers(unittest.TestCase): 'dpdk_packet_generator.DpdkPacketGenerator.' '_cores_configuration') def test__get_core_nics_two_nics_for_success(self, - mock_cores_configuration): + mock_cores_configuration, mock_time): mock_cores_configuration.return_value = 'ret_val' expected = 'ret_val' output = mut.DpdkPacketGenerator._get_core_nics(2, 'coremask') @@ -398,7 +399,7 @@ class TestDpdkPacketGenOthers(unittest.TestCase): mock_cores_configuration.assert_called_once_with('coremask', 1, 2, 2) @mock.patch('os.path.isfile') - def test__init_input_validation_for_success(self, mock_is_file): + def test__init_input_validation_for_success(self, mock_is_file, mock_time): mock_is_file.return_value = True pcap_file_0 = 'pcap_file_0' @@ -419,7 +420,7 @@ class TestDpdkPacketGenOthers(unittest.TestCase): variables), None) @mock.patch('os.path.isfile') - def test__init_input_validation_for_failure(self, mock_is_file): + def test__init_input_validation_for_failure(self, mock_is_file, mock_time): mock_is_file.return_value = True pcap_file_0 = 'pcap_file_0' @@ -440,7 +441,7 @@ class TestDpdkPacketGenOthers(unittest.TestCase): lua_script, pcap_directory, lua_directory, variables) @mock.patch('os.path.isfile') - def test__init_input_validation_for_failure_2(self, mock_is_file): + def test__init_input_validation_for_failure_2(self, mock_is_file, mock_time): mock_is_file.return_value = True pcap_directory = None @@ -461,7 +462,7 @@ class TestDpdkPacketGenOthers(unittest.TestCase): lua_script, pcap_directory, lua_directory, variables) @mock.patch('os.path.isfile') - def test__init_input_validation_for_failure_3(self, mock_is_file): + def test__init_input_validation_for_failure_3(self, mock_is_file, mock_time): mock_is_file.return_value = True pcap_directory = 'directory' @@ -482,7 +483,7 @@ class TestDpdkPacketGenOthers(unittest.TestCase): lua_script, pcap_directory, lua_directory, variables) @mock.patch('os.path.isfile') - def test__init_input_validation_for_failure_4(self, mock_is_file): + def test__init_input_validation_for_failure_4(self, mock_is_file, mock_time): mock_is_file.return_value = True pcap_directory = 'directory' @@ -503,7 +504,7 @@ class TestDpdkPacketGenOthers(unittest.TestCase): lua_script, pcap_directory, lua_directory, variables) @mock.patch('os.path.isfile') - def test__init_input_validation_for_failure_5(self, mock_is_file): + def test__init_input_validation_for_failure_5(self, mock_is_file, mock_time): mock_is_file.return_value = True pcap_directory = 'directory' @@ -524,7 +525,7 @@ class TestDpdkPacketGenOthers(unittest.TestCase): lua_script, pcap_directory, lua_directory, variables) @mock.patch('os.path.isfile', side_effect=[False]) - def test__init_input_validation_for_failure_6(self, mock_is_file): + def test__init_input_validation_for_failure_6(self, mock_is_file, mock_time): # mock_is_file.return_value = False pcap_directory = 'directory' @@ -545,7 +546,7 @@ class TestDpdkPacketGenOthers(unittest.TestCase): lua_script, pcap_directory, lua_directory, variables) @mock.patch('os.path.isfile', side_effect=[True, False]) - def test__init_input_validation_for_failure_7(self, mock_is_file): + def test__init_input_validation_for_failure_7(self, mock_is_file, mock_time): pcap_directory = 'directory' pcap_file_0 = 'pcap_file_0' pcap_file_1 = 'pcap_file_1' @@ -564,7 +565,7 @@ class TestDpdkPacketGenOthers(unittest.TestCase): lua_script, pcap_directory, lua_directory, variables) @mock.patch('os.path.isfile', side_effect=[True, True, False]) - def test__init_input_validation_for_failure_8(self, mock_is_file): + def test__init_input_validation_for_failure_8(self, mock_is_file, mock_time): pcap_directory = 'directory' pcap_file_0 = 'pcap_file_0' pcap_file_1 = 'pcap_file_1' @@ -583,13 +584,13 @@ class TestDpdkPacketGenOthers(unittest.TestCase): lua_script, pcap_directory, lua_directory, variables) @mock.patch('os.chdir') - def test__chdir_for_success(self, mock_os_chdir): + def test__chdir_for_success(self, mock_os_chdir, mock_time): mut.DpdkPacketGenerator._chdir('directory') mock_os_chdir.assert_called_once_with('directory') @mock.patch('experimental_framework.common.run_command', side_effect=MockRunCommand.mock_run_command) - def test__init_physical_nics_for_success(self, mock_run_command): + def test__init_physical_nics_for_success(self, mock_run_command, mock_time): dpdk_interfaces = 1 dpdk_vars = dict() @@ -608,7 +609,7 @@ class TestDpdkPacketGenOthers(unittest.TestCase): @mock.patch('experimental_framework.common.run_command', side_effect=MockRunCommand.mock_run_command) - def test__init_physical_nics_for_success_2(self, mock_run_command): + def test__init_physical_nics_for_success_2(self, mock_run_command, mock_time): dpdk_interfaces = 2 dpdk_vars = dict() @@ -626,7 +627,7 @@ class TestDpdkPacketGenOthers(unittest.TestCase): [True, True, True, True, True, True]) @mock.patch('experimental_framework.common.run_command') - def test__init_physical_nics_for_failure(self, mock_run_command): + def test__init_physical_nics_for_failure(self, mock_run_command, mock_time): dpdk_interfaces = 3 dpdk_vars = dict() self.assertRaises(ValueError, self.mut._init_physical_nics, @@ -634,7 +635,7 @@ class TestDpdkPacketGenOthers(unittest.TestCase): @mock.patch('experimental_framework.common.run_command', side_effect=MockRunCommand.mock_run_command_finalization) - def test__finalize_physical_nics_for_success(self, mock_run_command): + def test__finalize_physical_nics_for_success(self, mock_run_command, mock_time): dpdk_interfaces = 1 dpdk_vars = dict() dpdk_vars[conf_file.CFSP_DPDK_DPDK_DIRECTORY] = 'dpdk_directory/' @@ -652,7 +653,7 @@ class TestDpdkPacketGenOthers(unittest.TestCase): @mock.patch('experimental_framework.common.run_command', side_effect=MockRunCommand.mock_run_command_finalization) - def test__finalize_physical_nics_for_success_2(self, mock_run_command): + def test__finalize_physical_nics_for_success_2(self, mock_run_command, mock_time): dpdk_interfaces = 2 dpdk_vars = dict() dpdk_vars[conf_file.CFSP_DPDK_DPDK_DIRECTORY] = 'dpdk_directory/' @@ -668,34 +669,34 @@ class TestDpdkPacketGenOthers(unittest.TestCase): self.assertEqual(MockRunCommand.mock_run_command_finalization(), [True, True, True, True, True, True]) - def test__finalize_physical_nics_for_failure(self): + def test__finalize_physical_nics_for_failure(self, mock_time): dpdk_interfaces = 0 dpdk_vars = dict() self.assertRaises(ValueError, self.mut._finalize_physical_nics, dpdk_interfaces, dpdk_vars) - def test__cores_configuration_for_success(self): + def test__cores_configuration_for_success(self, mock_time): coremask = '1f' expected = '[2:1].0,[4:3].1' output = mut.DpdkPacketGenerator._cores_configuration(coremask, 1, 2, 2) self.assertEqual(expected, output) - def test__cores_configuration_for_success_2(self): + def test__cores_configuration_for_success_2(self, mock_time): coremask = '1f' expected = '2.0,[4:3].1' output = mut.DpdkPacketGenerator._cores_configuration(coremask, 1, 1, 2) self.assertEqual(expected, output) - def test__cores_configuration_for_success_3(self): + def test__cores_configuration_for_success_3(self, mock_time): coremask = '1f' expected = '[3:2].0,4.1' output = mut.DpdkPacketGenerator._cores_configuration(coremask, 1, 2, 1) self.assertEqual(expected, output) - def test__cores_configuration_for_failure(self): + def test__cores_configuration_for_failure(self, mock_time): coremask = '1' self.assertRaises(ValueError, mut.DpdkPacketGenerator._cores_configuration, @@ -703,7 +704,7 @@ class TestDpdkPacketGenOthers(unittest.TestCase): @mock.patch('experimental_framework.common.LOG') @mock.patch('experimental_framework.common.run_command') - def test__change_vlan_for_success(self, mock_run_command, mock_log): + def test__change_vlan_for_success(self, mock_run_command, mock_log, mock_time): mut.DpdkPacketGenerator._change_vlan('/directory/', 'pcap_file', '10') expected_param = '/directory/vlan_tag.sh /directory/pcap_file 10' mock_run_command.assert_called_with(expected_param) diff --git a/yardstick/vTC/apexlake/tests/instantiation_validation_bench_test.py b/yardstick/vTC/apexlake/tests/instantiation_validation_bench_test.py index 2bd8b7b38..69c5d745e 100644 --- a/yardstick/vTC/apexlake/tests/instantiation_validation_bench_test.py +++ b/yardstick/vTC/apexlake/tests/instantiation_validation_bench_test.py @@ -257,6 +257,7 @@ class InstantiationValidationInitTest(unittest.TestCase): self.assertEqual(dummy_os_kill('', '', True), [1, 1]) self.assertEqual(dummy_run_command('', True), [1, 1, 0, 0, 0]) + @mock.patch('experimental_framework.benchmarks.instantiation_validation_benchmark.time') @mock.patch('os.chdir') @mock.patch('experimental_framework.common.run_command', side_effect=dummy_run_command_2) @@ -265,7 +266,7 @@ class InstantiationValidationInitTest(unittest.TestCase): 'InstantiationValidationBenchmark._get_pids') @mock.patch('os.kill', side_effect=dummy_os_kill) def test__init_packet_checker_for_success(self, mock_kill, mock_pids, - mock_run_command, mock_chdir): + mock_run_command, mock_chdir, mock_time): global command_counter command_counter = [0, 0, 0, 0, 0] mock_pids.return_value = [1234, 4321] @@ -314,13 +315,14 @@ class InstantiationValidationInitTest(unittest.TestCase): self.assertEqual(dummy_replace_in_file('', '', '', True), [0, 0, 0, 1, 1, 1]) + @mock.patch('experimental_framework.benchmarks.instantiation_validation_benchmark.time') @mock.patch('experimental_framework.common.LOG') @mock.patch('experimental_framework.packet_generators.' 'dpdk_packet_generator.DpdkPacketGenerator', side_effect=DummyDpdkPacketGenerator) @mock.patch('experimental_framework.common.get_dpdk_pktgen_vars') def test_run_for_success(self, mock_common_get_vars, mock_pktgen, - mock_log): + mock_log, mock_time): rval = dict() rval[cfs.CFSP_DPDK_BUS_SLOT_NIC_2] = 'bus_2' rval[cfs.CFSP_DPDK_NAME_IF_2] = 'if_2' |