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