#!/usr/bin/env python # Copyright (c) 2017 Okinawa Open Laboratory 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 # pylint: disable=missing-docstring """vrouter testcase implementation.""" import logging import os import time from cloudify_rest_client import CloudifyClient from cloudify_rest_client.executions import Execution from scp import SCPClient import yaml from functest.opnfv_tests.openstack.snaps import snaps_utils import functest.opnfv_tests.vnf.router.vrouter_base as vrouter_base from functest.opnfv_tests.vnf.router.utilvnf import Utilvnf from functest.utils.constants import CONST from git import Repo from snaps.config.flavor import FlavorConfig from snaps.config.image import ImageConfig from snaps.config.keypair import KeypairConfig from snaps.config.network import NetworkConfig, PortConfig, SubnetConfig from snaps.config.router import RouterConfig from snaps.config.security_group import ( Direction, Protocol, SecurityGroupConfig, SecurityGroupRuleConfig) from snaps.config.vm_inst import FloatingIpConfig, VmInstanceConfig from snaps.openstack.create_flavor import OpenStackFlavor from snaps.openstack.create_image import OpenStackImage from snaps.openstack.create_instance import OpenStackVmInstance from snaps.openstack.create_keypairs import OpenStackKeypair from snaps.openstack.create_network import OpenStackNetwork from snaps.openstack.create_security_group import OpenStackSecurityGroup from snaps.openstack.create_router import OpenStackRouter import snaps.openstack.utils.glance_utils as glance_utils from snaps.openstack.utils import keystone_utils __author__ = "Shuya Nakama " class CloudifyVrouter(vrouter_base.VrouterOnBoardingBase): # pylint: disable=too-many-instance-attributes """vrouter testcase deployed with Cloudify Orchestrator.""" __logger = logging.getLogger(__name__) name = __name__ def __init__(self, **kwargs): if "case_name" not in kwargs: kwargs["case_name"] = "vyos_vrouter" super(CloudifyVrouter, self).__init__(**kwargs) # Retrieve the configuration try: self.config = CONST.__getattribute__( 'vnf_{}_config'.format(self.case_name)) except Exception: raise Exception("VNF config file not found") self.snaps_creds = '' self.created_object = [] self.cfy_manager_ip = '' self.util_info = {} self.deployment_name = '' config_file = os.path.join(self.case_dir, self.config) self.orchestrator = dict( requirements=get_config("orchestrator.requirements", config_file), ) self.details['orchestrator'] = dict( name=get_config("orchestrator.name", config_file), version=get_config("orchestrator.version", config_file), status='ERROR', result='' ) self.__logger.debug("Orchestrator configuration %s", self.orchestrator) self.__logger.debug("name = %s", self.name) self.vnf = dict( descriptor=get_config("vnf.descriptor", config_file), inputs=get_config("vnf.inputs", config_file), requirements=get_config("vnf.requirements", config_file) ) self.details['vnf'] = dict( descriptor_version=self.vnf['descriptor']['version'], name=get_config("vnf.name", config_file), version=get_config("vnf.version", config_file), ) self.__logger.debug("VNF configuration: %s", self.vnf) self.util = Utilvnf() self.details['test_vnf'] = dict( name=get_config("vnf_test_suite.name", config_file), version=get_config("vnf_test_suite.version", config_file) ) self.images = get_config("tenant_images", config_file) self.__logger.info("Images needed for vrouter: %s", self.images) def prepare(self): super(CloudifyVrouter, self).prepare() self.__logger.info("Additional pre-configuration steps") self.util.set_credentials(self.snaps_creds) # needs some images self.__logger.info("Upload some OS images if it doesn't exist") for image_name, image_file in self.images.iteritems(): self.__logger.info("image: %s, file: %s", image_name, image_file) if image_file and image_name: image_creator = OpenStackImage( self.snaps_creds, ImageConfig(name=image_name, image_user='cloud', img_format='qcow2', image_file=image_file)) image_creator.create() self.created_object.append(image_creator) def deploy_orchestrator(self): # pylint: disable=too-many-locals,too-many-statements """ Deploy Cloudify Manager. network, security group, fip, VM creation """ # network creation start_time = time.time() self.__logger.info("Creating keypair ...") kp_file = os.path.join(self.data_dir, "cloudify_vrouter.pem") keypair_settings = KeypairConfig(name='cloudify_vrouter_kp', private_filepath=kp_file) keypair_creator = OpenStackKeypair(self.snaps_creds, keypair_settings) keypair_creator.create() self.created_object.append(keypair_creator) self.__logger.info("Creating full network ...") subnet_settings = SubnetConfig(name='cloudify_vrouter_subnet', cidr='10.67.79.0/24') network_settings = NetworkConfig(name='cloudify_vrouter_network', subnet_settings=[subnet_settings]) network_creator = OpenStackNetwork(self.snaps_creds, network_settings) network_creator.create() self.created_object.append(network_creator) ext_net_name = snaps_utils.get_ext_net_name(self.snaps_creds) router_creator = OpenStackRouter( self.snaps_creds, RouterConfig( name='cloudify_vrouter_router', external_gateway=ext_net_name, internal_subnets=[subnet_settings.name])) router_creator.create() self.created_object.append(router_creator) # security group creation self.__logger.info("Creating security group for cloudify manager vm") sg_rules = list() sg_rules.append( SecurityGroupRuleConfig(sec_grp_name="sg-cloudify-manager", direction=Direction.ingress, protocol=Protocol.tcp, port_range_min=1, port_range_max=65535)) sg_rules.append( SecurityGroupRuleConfig(sec_grp_name="sg-cloudify-manager", direction=Direction.ingress, protocol=Protocol.udp, port_range_min=1, port_range_max=65535)) security_group_creator = OpenStackSecurityGroup( self.snaps_creds, SecurityGroupConfig( name="sg-cloudify-manager", rule_settings=sg_rules)) security_group_creator.create() self.created_object.append(security_group_creator) # orchestrator VM flavor self.__logger.info("Get or create flavor for cloudify manager vm ...") flavor_settings = FlavorConfig( name=self.orchestrator['requirements']['flavor']['name'], ram=self.orchestrator['requirements']['flavor']['ram_min'], disk=50, vcpus=2) flavor_creator = OpenStackFlavor(self.snaps_creds, flavor_settings) flavor_creator.create() self.created_object.append(flavor_creator) image_settings = ImageConfig( name=self.orchestrator['requirements']['os_image'], image_user='centos', exists=True) port_settings = PortConfig(name='cloudify_manager_port', network_name=network_settings.name) manager_settings = VmInstanceConfig( name='cloudify_manager', flavor=flavor_settings.name, port_settings=[port_settings], security_group_names=[ security_group_creator.sec_grp_settings.name], floating_ip_settings=[FloatingIpConfig( name='cloudify_manager_fip', port_name=port_settings.name, router_name=router_creator.router_settings.name)]) manager_creator = OpenStackVmInstance(self.snaps_creds, manager_settings, image_settings, keypair_settings) self.__logger.info("Creating cloudify manager VM") manager_creator.create() self.created_object.append(manager_creator) public_auth_url = keystone_utils.get_endpoint( self.snaps_creds, 'identity') self.__logger.info("Set creds for cloudify manager") cfy_creds = dict(keystone_username=self.tenant_name, keystone_password=self.tenant_name, keystone_tenant_name=self.tenant_name, keystone_url=public_auth_url) cfy_client = CloudifyClient(host=manager_creator.get_floating_ip().ip, username='admin', password='admin', tenant='default_tenant') self.orchestrator['object'] = cfy_client self.cfy_manager_ip = manager_creator.get_floating_ip().ip self.__logger.info("Attemps running status of the Manager") cfy_status = None retry = 10 while str(cfy_status) != 'running' and retry: try: cfy_status = cfy_client.manager.get_status()['status'] self.__logger.debug("The current manager status is %s", cfy_status) except Exception: # pylint: disable=broad-except self.__logger.warning("Cloudify Manager isn't " + "up and running. Retrying ...") retry = retry - 1 time.sleep(30) if str(cfy_status) == 'running': self.__logger.info("Cloudify Manager is up and running") else: raise Exception("Cloudify Manager isn't up and running") self.__logger.info("Put OpenStack creds in manager") secrets_list = cfy_client.secrets.list() for k, val in cfy_creds.iteritems(): if not any(d.get('key', None) == k for d in secrets_list): cfy_client.secrets.create(k, val) else: cfy_client.secrets.update(k, val) duration = time.time() - start_time self.__logger.info("Put private keypair in manager") if manager_creator.vm_ssh_active(block=True): ssh = manager_creator.ssh_client() scp = SCPClient(ssh.get_transport(), socket_timeout=15.0) scp.put(kp_file, '~/') cmd = "sudo cp ~/cloudify_vrouter.pem /etc/cloudify/" run_blocking_ssh_command(ssh, cmd) cmd = "sudo chmod 444 /etc/cloudify/cloudify_vrouter.pem" run_blocking_ssh_command(ssh, cmd) cmd = "sudo yum install -y gcc python-devel" run_blocking_ssh_command( ssh, cmd, "Unable to install packages on manager") self.details['orchestrator'].update(status='PASS', duration=duration) self.vnf['inputs'].update(dict(external_network_name=ext_net_name)) return True def deploy_vnf(self): start_time = time.time() self.__logger.info("Upload VNFD") cfy_client = self.orchestrator['object'] descriptor = self.vnf['descriptor'] self.deployment_name = descriptor.get('name') vrouter_blueprint_dir = os.path.join(self.data_dir, self.util.blueprint_dir) if not os.path.exists(vrouter_blueprint_dir): Repo.clone_from(descriptor.get('url'), vrouter_blueprint_dir, branch=descriptor.get('version')) cfy_client.blueprints.upload(vrouter_blueprint_dir + self.util.blueprint_file_name, descriptor.get('name')) self.__logger.info("Get or create flavor for vrouter") flavor_settings = FlavorConfig( name=self.vnf['requirements']['flavor']['name'], ram=self.vnf['requirements']['flavor']['ram_min'], disk=25, vcpus=1) flavor_creator = OpenStackFlavor(self.snaps_creds, flavor_settings) flavor = flavor_creator.create() self.created_object.append(flavor_creator) # set image name glance = glance_utils.glance_client(self.snaps_creds) image = glance_utils.get_image(glance, "vyos1.1.7") self.vnf['inputs'].update(dict(target_vnf_image_id=image.id)) self.vnf['inputs'].update(dict(reference_vnf_image_id=image.id)) # set flavor id self.vnf['inputs'].update(dict(target_vnf_flavor_id=flavor.id)) self.vnf['inputs'].update(dict(reference_vnf_flavor_id=flavor.id)) self.vnf['inputs'].update(dict(keystone_username=self.tenant_name)) self.vnf['inputs'].update(dict(keystone_password=self.tenant_name)) self.vnf['inputs'].update(dict(keystone_tenant_name=self.tenant_name)) self.vnf['inputs'].update( dict(keystone_url=keystone_utils.get_endpoint( self.snaps_creds, 'identity'))) self.__logger.info("Create VNF Instance") cfy_client.deployments.create(descriptor.get('name'), descriptor.get('name'), self.vnf.get('inputs')) wait_for_execution(cfy_client, get_execution_id( cfy_client, descriptor.get('name')), self.__logger, timeout=7200) self.__logger.info("Start the VNF Instance deployment") execution = cfy_client.executions.start(descriptor.get('name'), 'install') # Show execution log execution = wait_for_execution(cfy_client, execution, self.__logger) duration = time.time() - start_time self.__logger.info(execution) if execution.status == 'terminated': self.details['vnf'].update(status='PASS', duration=duration) result = True else: self.details['vnf'].update(status='FAIL', duration=duration) result = False return result def test_vnf(self): cfy_client = self.orchestrator['object'] credentials = {"snaps_creds": self.snaps_creds, "username": self.snaps_creds.username, "password": self.snaps_creds.password, "auth_url": self.snaps_creds.auth_url, "tenant_name": self.snaps_creds.project_name} self.util_info = {"credentials": credentials, "cfy": cfy_client, "vnf_data_dir": self.util.vnf_data_dir} start_time = time.time() result, test_result_data = super(CloudifyVrouter, self).test_vnf() duration = time.time() - start_time if result: self.details['test_vnf'].update(status='PASS', result='OK', full_result=test_result_data, duration=duration) else: self.details['test_vnf'].update(status='FAIL', result='NG', full_result=test_result_data, duration=duration) return True def clean(self): try: cfy_client = self.orchestrator['object'] dep_name = self.vnf['descriptor'].get('name') # kill existing execution self.__logger.info('Deleting the current deployment') exec_list = cfy_client.executions.list(dep_name) for execution in exec_list: if execution['status'] == "started": try: cfy_client.executions.cancel(execution['id'], force=True) except Exception: # pylint: disable=broad-except self.__logger.warn("Can't cancel the current exec") execution = cfy_client.executions.start( dep_name, 'uninstall', parameters=dict(ignore_failure=True)) wait_for_execution(cfy_client, execution, self.__logger) cfy_client.deployments.delete(self.vnf['descriptor'].get('name')) cfy_client.blueprints.delete(self.vnf['descriptor'].get('name')) except Exception: # pylint: disable=broad-except self.__logger.warn("Some issue during the undeployment ..") self.__logger.warn("Tenant clean continue ..") super(CloudifyVrouter, self).clean() def get_vnf_info_list(self, target_vnf_name): return self.util.get_vnf_info_list( self.cfy_manager_ip, self.deployment_name, target_vnf_name) # ---------------------------------------------------------- # # YAML UTILS # # ----------------------------------------------------------- def get_config(parameter, file_path): """ Get config parameter. Returns the value of a given parameter in file.yaml parameter must be given in string format with dots Example: general.openstack.image_name """ with open(file_path) as config_file: file_yaml = yaml.safe_load(config_file) config_file.close() value = file_yaml for element in parameter.split("."): value = value.get(element) if value is None: raise ValueError("The parameter %s is not defined in" " reporting.yaml" % parameter) return value def wait_for_execution(client, execution, logger, timeout=7200, ): """Wait for a workflow execution on Cloudify Manager.""" # if execution already ended - return without waiting if execution.status in Execution.END_STATES: return execution if timeout is not None: deadline = time.time() + timeout # Poll for execution status and execution logs, until execution ends # and we receive an event of type in WORKFLOW_END_TYPES offset = 0 batch_size = 50 event_list = [] execution_ended = False while True: event_list = client.events.list( execution_id=execution.id, _offset=offset, _size=batch_size, include_logs=False, sort='@timestamp').items offset = offset + len(event_list) for event in event_list: logger.debug(event.get('message')) if timeout is not None: if time.time() > deadline: raise RuntimeError( 'execution of operation {0} for deployment {1} ' 'timed out'.format(execution.workflow_id, execution.deployment_id)) else: # update the remaining timeout timeout = deadline - time.time() if not execution_ended: execution = client.executions.get(execution.id) execution_ended = execution.status in Execution.END_STATES if execution_ended: break time.sleep(5) return execution def get_execution_id(client, deployment_id): """ Get the execution id of a env preparation. network, security group, fip, VM creation """ executions = client.executions.list(deployment_id=deployment_id) for execution in executions: if execution.workflow_id == 'create_deployment_environment': return execution raise RuntimeError('Failed to get create_deployment_environment ' 'workflow execution.' 'Available executions: {0}'.format(executions)) def run_blocking_ssh_command(ssh, cmd, error_msg="Unable to run this command"): """Command to run ssh command with the exit status.""" (_, stdout, _) = ssh.exec_command(cmd) if stdout.channel.recv_exit_status() != 0: raise Exception(error_msg)