Merge "Implement trunk ports testcase"
[functest.git] / functest / opnfv_tests / vnf / epc / juju_epc.py
1 #!/usr/bin/env python
2
3 # Copyright (c) 2016 Rebaca and others.
4 #
5 # All rights reserved. This program and the accompanying materials
6 # are made available under the terms of the Apache License, Version 2.0
7 # which accompanies this distribution, and is available at
8 # http://www.apache.org/licenses/LICENSE-2.0
9 """Juju testcase implementation."""
10
11 import errno
12 import logging
13 import os
14 import time
15 import json
16 import sys
17 import uuid
18 from copy import deepcopy
19 from urlparse import urljoin
20 import pkg_resources
21 import yaml
22
23 from functest.core import vnf
24 from functest.opnfv_tests.openstack.snaps import snaps_utils
25 from functest.utils import config
26 from functest.utils import env
27
28 from snaps.config.flavor import FlavorConfig
29 from snaps.config.image import ImageConfig
30 from snaps.config.network import NetworkConfig, SubnetConfig
31 from snaps.config.router import RouterConfig
32 from snaps.config.security_group import (
33     Direction, Protocol, SecurityGroupConfig, SecurityGroupRuleConfig)
34 from snaps.config.user import UserConfig
35 from snaps.openstack.create_flavor import OpenStackFlavor
36 from snaps.openstack.create_image import OpenStackImage
37 from snaps.openstack.create_network import OpenStackNetwork
38 from snaps.openstack.create_router import OpenStackRouter
39 from snaps.openstack.create_security_group import OpenStackSecurityGroup
40 from snaps.openstack.create_user import OpenStackUser
41 from snaps.openstack.utils import keystone_utils
42 from snaps.openstack.utils import nova_utils
43
44 __author__ = "Amarendra Meher <amarendra@rebaca.com>"
45 __author__ = "Soumaya K Nayek <soumaya.nayek@rebaca.com>"
46
47 CLOUD_TEMPLATE = """clouds:
48   abot-epc:
49     type: openstack
50     auth-types: [userpass]
51     endpoint: {url}
52     regions:
53       {region}:
54         endpoint: {url}"""
55
56 CREDS_TEMPLATE2 = """credentials:
57   abot-epc:
58     default-credential: abot-epc
59     abot-epc:
60       auth-type: userpass
61       password: {pass}
62       project-domain-name: {project_domain_n}
63       tenant-name: {tenant_n}"""
64
65 CREDS_TEMPLATE3 = """credentials:
66   abot-epc:
67     default-credential: abot-epc
68     abot-epc:
69       auth-type: userpass
70       password: {pass}
71       project-domain-name: {project_domain_n}
72       tenant-name: {tenant_n}
73       user-domain-name: {user_domain_n}
74       username: {user_n}"""
75
76
77 class JujuEpc(vnf.VnfOnBoarding):
78     # pylint:disable=too-many-instance-attributes
79     """Abot EPC deployed with JUJU Orchestrator Case"""
80
81     __logger = logging.getLogger(__name__)
82
83     def __init__(self, **kwargs):
84         if "case_name" not in kwargs:
85             kwargs["case_name"] = "juju_epc"
86         super(JujuEpc, self).__init__(**kwargs)
87
88         # Retrieve the configuration
89         self.case_dir = pkg_resources.resource_filename(
90             'functest', 'opnfv_tests/vnf/epc')
91         try:
92             self.config = getattr(
93                 config.CONF, 'vnf_{}_config'.format(self.case_name))
94         except Exception:
95             raise Exception("VNF config file not found")
96         self.config_file = os.path.join(self.case_dir, self.config)
97         self.orchestrator = dict(requirements=get_config(
98             "orchestrator.requirements", self.config_file))
99
100         self.created_object = []
101         self.details['orchestrator'] = dict(
102             name=get_config("orchestrator.name", self.config_file),
103             version=get_config("orchestrator.version", self.config_file),
104             status='ERROR',
105             result=''
106         )
107
108         self.vnf = dict(
109             descriptor=get_config("vnf.descriptor", self.config_file),
110             requirements=get_config("vnf.requirements", self.config_file)
111         )
112         self.details['vnf'] = dict(
113             descriptor_version=self.vnf['descriptor']['version'],
114             name=get_config("vnf.name", self.config_file),
115             version=get_config("vnf.version", self.config_file),
116         )
117         self.__logger.debug("VNF configuration: %s", self.vnf)
118
119         self.details['test_vnf'] = dict(
120             name=get_config("vnf_test_suite.name", self.config_file),
121             version=get_config("vnf_test_suite.version", self.config_file),
122             tag_name=get_config("vnf_test_suite.tag_name", self.config_file)
123         )
124         self.public_auth_url = None
125
126         self.res_dir = os.path.join(
127             getattr(config.CONF, 'dir_results'), self.case_name)
128
129     def _bypass_juju_network_discovery_bug(self, name):
130         user_creator = OpenStackUser(
131             self.snaps_creds,
132             UserConfig(
133                 name=name, password=str(uuid.uuid4()),
134                 domain=self.snaps_creds.user_domain_name,
135                 roles={'_member_': self.tenant_name}))
136         user_creator.create()
137         self.created_object.append(user_creator)
138         return user_creator
139
140     def _register_cloud(self):
141         self.__logger.info("Creating Cloud for Abot-epc .....")
142         clouds_yaml = os.path.join(self.res_dir, "clouds.yaml")
143         # It allows gating APEX and ensures this testcase is working till
144         # https://jira.opnfv.org/browse/APEX-570 is fixed in APEX.
145         # It must be removed as soon as possible to disable per installer
146         # processing in Functest.
147         region = self.snaps_creds.region_name
148         if not region and env.get('INSTALLER_TYPE') == 'apex':
149             region = "regionOne"
150         cloud_data = {
151             'url': self.public_auth_url,
152             'region': region}
153         with open(clouds_yaml, 'w') as yfile:
154             yfile.write(CLOUD_TEMPLATE.format(**cloud_data))
155         if os.system(
156                 'juju add-cloud abot-epc -f {} --replace'.format(clouds_yaml)):
157             raise vnf.VnfPreparationException
158
159     def _register_credentials_v2(self):
160         self.__logger.info("Creating Credentials for Abot-epc .....")
161         user_creator = self._bypass_juju_network_discovery_bug(
162             'juju_network_discovery_bug')
163         snaps_creds = user_creator.get_os_creds(self.snaps_creds.project_name)
164         credentials_yaml = os.path.join(self.res_dir, "credentials.yaml")
165         creds_data = {
166             'pass': snaps_creds.password,
167             'tenant_n': snaps_creds.project_name,
168             'user_n': snaps_creds.username}
169         with open(credentials_yaml, 'w') as yfile:
170             yfile.write(CREDS_TEMPLATE2.format(**creds_data))
171         if os.system(
172                 'juju add-credential abot-epc -f {} --replace'.format(
173                     credentials_yaml)):
174             raise vnf.VnfPreparationException
175
176     def _register_credentials_v3(self):
177         self.__logger.info("Creating Credentials for Abot-epc .....")
178         user_creator = self._bypass_juju_network_discovery_bug(
179             'juju_network_discovery_bug')
180         snaps_creds = user_creator.get_os_creds(self.snaps_creds.project_name)
181         credentials_yaml = os.path.join(self.res_dir, "credentials.yaml")
182         creds_data = {
183             'pass': snaps_creds.password,
184             'tenant_n': snaps_creds.project_name,
185             'user_n': snaps_creds.username,
186             'project_domain_n': snaps_creds.project_domain_name,
187             'user_domain_n': snaps_creds.user_domain_name}
188         with open(credentials_yaml, 'w') as yfile:
189             yfile.write(CREDS_TEMPLATE3.format(**creds_data))
190         if os.system(
191                 'juju add-credential abot-epc -f {} --replace'.format(
192                     credentials_yaml)):
193             raise vnf.VnfPreparationException
194
195     def _add_custom_rule(self, sec_grp_name):
196         """ To add custom rule for SCTP Traffic """
197         sec_grp_rules = list()
198         sec_grp_rules.append(
199             SecurityGroupRuleConfig(
200                 sec_grp_name=sec_grp_name, direction=Direction.ingress,
201                 protocol=Protocol.sctp))
202         security_group = OpenStackSecurityGroup(
203             self.snaps_creds,
204             SecurityGroupConfig(
205                 name=sec_grp_name,
206                 rule_settings=sec_grp_rules))
207         security_group.create()
208         self.created_object.append(security_group)
209
210     def prepare(self):
211         """Prepare testcase (Additional pre-configuration steps)."""
212         self.__logger.info("Additional pre-configuration steps")
213         super(JujuEpc, self).prepare()
214         try:
215             os.makedirs(self.res_dir)
216         except OSError as ex:
217             if ex.errno != errno.EEXIST:
218                 self.__logger.exception("Cannot create %s", self.res_dir)
219                 raise vnf.VnfPreparationException
220         self.public_auth_url = keystone_utils.get_endpoint(
221             self.snaps_creds, 'identity')
222         # it enforces a versioned public identity endpoint as juju simply
223         # adds /auth/tokens wich fails vs an unversioned endpoint.
224         if not self.public_auth_url.endswith(('v3', 'v3/', 'v2.0', 'v2.0/')):
225             self.public_auth_url = urljoin(self.public_auth_url, 'v3')
226         self._register_cloud()
227         if self.snaps_creds.identity_api_version == 3:
228             self._register_credentials_v3()
229         else:
230             self._register_credentials_v2()
231
232     def deploy_orchestrator(self):  # pylint: disable=too-many-locals
233         """
234         Create network, subnet, router
235
236         Bootstrap juju
237         """
238         self.__logger.info("Deployed Orchestrator")
239         private_net_name = getattr(
240             config.CONF, 'vnf_{}_private_net_name'.format(self.case_name))
241         private_subnet_name = '{}-{}'.format(
242             getattr(config.CONF,
243                     'vnf_{}_private_subnet_name'.format(self.case_name)),
244             self.uuid)
245         private_subnet_cidr = getattr(
246             config.CONF, 'vnf_{}_private_subnet_cidr'.format(self.case_name))
247         abot_router = '{}-{}'.format(
248             getattr(config.CONF,
249                     'vnf_{}_external_router'.format(self.case_name)),
250             self.uuid)
251         self.__logger.info("Creating full network ...")
252         subnet_settings = SubnetConfig(
253             name=private_subnet_name, cidr=private_subnet_cidr)
254         network_settings = NetworkConfig(
255             name=private_net_name, subnet_settings=[subnet_settings])
256         network_creator = OpenStackNetwork(self.snaps_creds, network_settings)
257         net_id = network_creator.create().id
258         self.created_object.append(network_creator)
259
260         ext_net_name = snaps_utils.get_ext_net_name(self.snaps_creds)
261         self.__logger.info("Creating network Router ....")
262         router_creator = OpenStackRouter(
263             self.snaps_creds, RouterConfig(
264                 name=abot_router,
265                 external_gateway=ext_net_name,
266                 internal_subnets=[subnet_settings.name]))
267         router_creator.create()
268         self.created_object.append(router_creator)
269         self.__logger.info("Creating Flavor ....")
270         flavor_settings = FlavorConfig(
271             name=self.orchestrator['requirements']['flavor']['name'],
272             ram=self.orchestrator['requirements']['flavor']['ram_min'],
273             disk=10, vcpus=1)
274         flavor_creator = OpenStackFlavor(self.snaps_creds, flavor_settings)
275         flavor_creator.create()
276         self.created_object.append(flavor_creator)
277         self.__logger.info("Upload some OS images if it doesn't exist")
278         images = get_config("tenant_images", self.config_file)
279         self.__logger.info("Images needed for vEPC: %s", images)
280         for image_name, image_file in images.iteritems():
281             self.__logger.info("image: %s, file: %s", image_name, image_file)
282             if image_file and image_name:
283                 image_creator = OpenStackImage(self.snaps_creds, ImageConfig(
284                     name=image_name, image_user='cloud', img_format='qcow2',
285                     image_file=image_file))
286                 image_id = image_creator.create().id
287                 # It allows gating APEX and ensures this testcase is working
288                 # till https://jira.opnfv.org/browse/APEX-570 is fixed in APEX.
289                 # It must be removed as soon as possible to disable per
290                 # installer processing in Functest.
291                 region = self.snaps_creds.region_name
292                 if not region and env.get('INSTALLER_TYPE') == 'apex':
293                     region = "regionOne"
294                 os.system(
295                     'juju metadata generate-image -d ~ -i {} -s {} -r '
296                     '{} -u {}'.format(
297                         image_id, image_name, region,
298                         self.public_auth_url))
299                 self.created_object.append(image_creator)
300         self.__logger.info("Network ID  : %s", net_id)
301         juju_bootstrap_command = (
302             'juju bootstrap abot-epc abot-controller --config network={} '
303             '--metadata-source ~  --config ssl-hostname-verification=false '
304             '--constraints mem=2G --bootstrap-series xenial '
305             '--config use-floating-ip=true --debug '
306             '--config use-default-secgroup=true'.format(net_id))
307         os.system(juju_bootstrap_command)
308         return True
309
310     def deploy_vnf(self):
311         """Deploy ABOT-OAI-EPC."""
312         self.__logger.info("Upload VNFD")
313         descriptor = self.vnf['descriptor']
314         self.__logger.info("Get or create flavor for all Abot-EPC")
315         flavor_settings = FlavorConfig(
316             name=self.vnf['requirements']['flavor']['name'],
317             ram=self.vnf['requirements']['flavor']['ram_min'],
318             disk=10,
319             vcpus=1)
320         flavor_creator = OpenStackFlavor(self.snaps_creds, flavor_settings)
321         flavor_creator.create()
322         self.created_object.append(flavor_creator)
323         self.__logger.info("Deploying Abot-epc bundle file ...")
324         os.system('juju deploy {}'.format('/' + descriptor.get('file_name')))
325         self.__logger.info("Waiting for instances .....")
326         status = os.system('juju-wait')
327         self.__logger.info("juju wait completed: %s", status)
328         self.__logger.info("Deployed Abot-epc on Openstack")
329         nova_client = nova_utils.nova_client(self.snaps_creds)
330         if status == 0:
331             instances = get_instances(nova_client)
332             self.__logger.info("List of Instance: %s", instances)
333             for items in instances:
334                 metadata = get_instance_metadata(nova_client, items)
335                 if 'juju-units-deployed' in metadata:
336                     sec_group = ('juju-' +
337                                  metadata['juju-controller-uuid'] +
338                                  '-' + metadata['juju-model-uuid'])
339                     self.__logger.info("Instance: %s", sec_group)
340                     break
341             self.__logger.info("Adding Security group rule....")
342             # This will add sctp rule to a common Security Group Created
343             # by juju and shared to all deployed units.
344             self._add_custom_rule(sec_group)
345             self.__logger.info("Copying the feature files to Abot_node ")
346             os.system('juju scp -- -r {}/featureFiles abot-'
347                       'epc-basic/0:~/'.format(self.case_dir))
348             self.__logger.info("Copying the feature files in Abot_node ")
349             os.system("juju ssh abot-epc-basic/0 'sudo rsync -azvv "
350                       "~/featureFiles /etc/rebaca-test-suite"
351                       "/featureFiles'")
352             count = 0
353             while count < 10:
354                 epcstatus = os.system('juju status oai-epc | '
355                                       'grep {} | grep {} | grep {}'
356                                       .format('EPC', 'is', 'running'))
357                 if epcstatus == 0:
358                     break
359                 else:
360                     time.sleep(60)
361                     count = count + 1
362             os.system('juju-wait')
363             return True
364         return False
365
366     def test_vnf(self):
367         """Run test on ABoT."""
368         start_time = time.time()
369         self.__logger.info("Running VNF Test cases....")
370         os.system('juju run-action abot-epc-basic/0 run '
371                   'tagnames={}'.format(self.details['test_vnf']['tag_name']))
372         os.system('juju-wait')
373         duration = time.time() - start_time
374         self.__logger.info("Getting results from Abot node....")
375         os.system('juju scp abot-epc-basic/0:/var/lib/abot-'
376                   'epc-basic/artifacts/TestResults.json {}/.'
377                   .format(self.case_dir))
378         self.__logger.info("Parsing the Test results...")
379         res = (process_abot_test_result('{}/TestResults.'
380                                         'json'.format(self.case_dir)))
381         short_result = sig_test_format(res)
382         self.__logger.info(short_result)
383         self.details['test_vnf'].update(status='PASS',
384                                         result=short_result,
385                                         full_result=res,
386                                         duration=duration)
387
388         self.__logger.info("Test VNF result: Passed: %d, Failed:"
389                            "%d, Skipped: %d", short_result['passed'],
390                            short_result['failures'], short_result['skipped'])
391         return True
392
393     def clean(self):
394         """Clean created objects/functions."""
395         try:
396             if not self.orchestrator['requirements']['preserve_setup']:
397                 self.__logger.info("Removing deployment files...")
398                 testresult = os.path.join(self.case_dir, 'TestResults.json')
399                 if os.path.exists(testresult):
400                     os.remove(testresult)
401                 self.__logger.info("Destroying Orchestrator...")
402                 os.system('juju destroy-controller -y abot-controller '
403                           '--destroy-all-models')
404         except Exception:  # pylint: disable=broad-except
405             self.__logger.warn("Some issue during the undeployment ..")
406             self.__logger.warn("Tenant clean continue ..")
407
408         if not self.orchestrator['requirements']['preserve_setup']:
409             self.__logger.info('Remove the Abot_epc OS object ..')
410             super(JujuEpc, self).clean()
411
412         return True
413
414
415 # ----------------------------------------------------------
416 #
417 #               YAML UTILS
418 #
419 # -----------------------------------------------------------
420 def get_config(parameter, file_path):
421     """
422     Returns the value of a given parameter in file.yaml
423     parameter must be given in string format with dots
424     Example: general.openstack.image_name
425     """
426     with open(file_path) as config_file:
427         file_yaml = yaml.safe_load(config_file)
428     config_file.close()
429     value = file_yaml
430     for element in parameter.split("."):
431         value = value.get(element)
432         if value is None:
433             raise ValueError("The parameter %s is not defined in"
434                              " reporting.yaml" % parameter)
435     return value
436
437
438 def sig_test_format(sig_test):
439     """
440     Process the signaling result to have a short result
441     """
442     nb_passed = 0
443     nb_failures = 0
444     nb_skipped = 0
445     for data_test in sig_test:
446         if data_test['result'] == "passed":
447             nb_passed += 1
448         elif data_test['result'] == "failed":
449             nb_failures += 1
450         elif data_test['result'] == "skipped":
451             nb_skipped += 1
452     total_sig_test_result = {}
453     total_sig_test_result['passed'] = nb_passed
454     total_sig_test_result['failures'] = nb_failures
455     total_sig_test_result['skipped'] = nb_skipped
456     return total_sig_test_result
457
458
459 def process_abot_test_result(file_path):
460     """ Process ABoT Result """
461     with open(file_path) as test_result:
462         data = json.load(test_result)
463         res = []
464         for tests in data:
465             tests = update_data(tests)
466             try:
467                 flatten_steps = tests['elements'][0].pop('flatten_steps')
468                 for steps in flatten_steps:
469                     steps['result'] = steps['step_status']
470                     res.append(steps)
471             except:
472                 logging.error("Could not post data to ElasticSearch host")
473                 raise
474         return res
475
476
477 def update_data(obj):
478     """ Update Result data"""
479     try:
480         obj['feature_file'] = os.path.splitext(os.path.basename(obj['uri']))[0]
481
482         for element in obj['elements']:
483             element['final_result'] = "passed"
484             element['flatten_steps'] = []
485
486             for step in element['steps']:
487                 temp_dict = {}
488                 step['result'][step['result']['status']] = 1
489                 if step['result']['status'].lower() in ['fail', 'failed']:
490                     element['final_result'] = "failed"
491
492                 temp_dict['feature_file'] = obj['feature_file']
493                 temp_dict['step_name'] = step['name']
494                 temp_dict['step_status'] = step['result']['status']
495                 temp_dict['step_duration'] = step['result'].get('duration', 0)
496                 temp_dict['step_' + step['result']['status']] = 1
497                 element['flatten_steps'].append(deepcopy(temp_dict))
498
499             # Need to put the tag in OBJ and not ELEMENT
500             if 'tags' in obj:
501                 element['tags'] = deepcopy(obj['tags'])
502                 for tag in obj['tags']:
503                     element[tag['name']] = 1
504             else:
505                 for tag in element['tags']:
506                     element[tag['name']] = 1
507
508     except Exception:  # pylint: disable=broad-except
509         logging.error("Error in updating data, %s", sys.exc_info()[0])
510         raise
511
512     return obj
513
514
515 def get_instances(nova_client):
516     """ To get all vm info of a project """
517     try:
518         instances = nova_client.servers.list()
519         return instances
520     except Exception as exc:  # pylint: disable=broad-except
521         logging.error("Error [get_instances(nova_client)]: %s", exc)
522         return None
523
524
525 def get_instance_metadata(nova_client, instance):
526     """ Get instance Metadata - Instance ID """
527     try:
528         instance = nova_client.servers.get(instance.id)
529         return instance.metadata
530     except Exception as exc:  # pylint: disable=broad-except
531         logging.error("Error [get_instance_status(nova_client)]: %s", exc)
532         return None