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