Leverage cloudify_ims on Cloudify
[functest.git] / functest / opnfv_tests / vnf / ims / cloudify_ims.py
1 #!/usr/bin/env python
2
3 # Copyright (c) 2017 Orange 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
10 """CloudifyIms testcase implementation."""
11
12 from __future__ import division
13
14 import logging
15 import os
16 import time
17 import yaml
18
19 from cloudify_rest_client.executions import Execution
20 import pkg_resources
21 import scp
22 import six
23
24 from functest.core import cloudify
25 from functest.opnfv_tests.vnf.ims import clearwater_ims_base
26 from functest.utils import config
27
28 __author__ = "Valentin Boucher <valentin.boucher@orange.com>"
29
30
31 class CloudifyIms(cloudify.Cloudify):
32     """Clearwater vIMS deployed with Cloudify Orchestrator Case."""
33
34     __logger = logging.getLogger(__name__)
35
36     filename_alt = ('/home/opnfv/functest/images/'
37                     'ubuntu-14.04-server-cloudimg-amd64-disk1.img')
38
39     flavor_alt_ram = 2048
40     flavor_alt_vcpus = 2
41     flavor_alt_disk = 25
42
43     quota_security_group = 20
44     quota_security_group_rule = 100
45     quota_port = 50
46
47     def __init__(self, **kwargs):
48         """Initialize CloudifyIms testcase object."""
49         if "case_name" not in kwargs:
50             kwargs["case_name"] = "cloudify_ims"
51         super(CloudifyIms, self).__init__(**kwargs)
52
53         # Retrieve the configuration
54         try:
55             self.config = getattr(
56                 config.CONF, 'vnf_{}_config'.format(self.case_name))
57         except Exception:
58             raise Exception("VNF config file not found")
59
60         self.case_dir = pkg_resources.resource_filename(
61             'functest', 'opnfv_tests/vnf/ims')
62         config_file = os.path.join(self.case_dir, self.config)
63         self.orchestrator = dict(
64             requirements=get_config("orchestrator.requirements", config_file),
65         )
66         self.details['orchestrator'] = dict(
67             name=get_config("orchestrator.name", config_file),
68             version=get_config("orchestrator.version", config_file),
69             status='ERROR',
70             result=''
71         )
72         self.__logger.debug("Orchestrator configuration %s", self.orchestrator)
73         self.vnf = dict(
74             descriptor=get_config("vnf.descriptor", config_file),
75             inputs=get_config("vnf.inputs", config_file),
76             requirements=get_config("vnf.requirements", config_file)
77         )
78         self.details['vnf'] = dict(
79             descriptor_version=self.vnf['descriptor']['version'],
80             name=get_config("vnf.name", config_file),
81             version=get_config("vnf.version", config_file),
82         )
83         self.__logger.debug("VNF configuration: %s", self.vnf)
84
85         self.details['test_vnf'] = dict(
86             name=get_config("vnf_test_suite.name", config_file),
87             version=get_config("vnf_test_suite.version", config_file)
88         )
89
90         self.image_alt = None
91         self.flavor_alt = None
92
93     def execute(self):
94         assert super(CloudifyIms, self).execute() == 0
95         # pylint: disable=too-many-locals,too-many-statements
96         """
97         Deploy Cloudify Manager.
98
99         network, security group, fip, VM creation
100         """
101         start_time = time.time()
102         self.orig_cloud.set_network_quotas(
103             self.project.project.name,
104             security_group=self.quota_security_group,
105             security_group_rule=self.quota_security_group_rule,
106             port=self.quota_port)
107         self.__logger.info("Put OpenStack creds in manager")
108         cfy_creds = dict(
109             keystone_username=self.project.user.name,
110             keystone_password=self.project.password,
111             keystone_tenant_name=self.project.project.name,
112             keystone_url=self.get_public_auth_url(self.orig_cloud),
113             region=os.environ.get('OS_REGION_NAME', 'RegionOne'),
114             user_domain_name=os.environ.get(
115                 'OS_USER_DOMAIN_NAME', 'Default'),
116             project_domain_name=os.environ.get(
117                 'OS_PROJECT_DOMAIN_NAME', 'Default'))
118         self.__logger.info("Set creds for cloudify manager %s", cfy_creds)
119         secrets_list = self.cfy_client.secrets.list()
120         for k, val in six.iteritems(cfy_creds):
121             if not any(d.get('key', None) == k for d in secrets_list):
122                 self.cfy_client.secrets.create(k, val)
123             else:
124                 self.cfy_client.secrets.update(k, val)
125
126         duration = time.time() - start_time
127
128         self.__logger.info("Put private keypair in manager")
129         scpc = scp.SCPClient(self.ssh.get_transport())
130         scpc.put(self.key_filename, remote_path='~/cloudify_ims.pem')
131         (_, stdout, stderr) = self.ssh.exec_command(
132             "sudo cp ~/cloudify_ims.pem /etc/cloudify/ && "
133             "sudo chmod 444 /etc/cloudify/cloudify_ims.pem && "
134             "sudo yum install -y gcc python-devel python-cmd2 && "
135             "cfy status")
136         self.__logger.info("output:\n%s", stdout.read())
137         self.__logger.info("error:\n%s", stderr.read())
138
139         self.details['orchestrator'].update(status='PASS', duration=duration)
140
141         self.vnf['inputs'].update(dict(
142             external_network_name=self.ext_net.name,
143             network_name=self.network.name,
144             key_pair_name=self.keypair.name
145         ))
146         if (self.deploy_vnf() or self.test_vnf()):
147             self.result = 100
148             return 0
149         self.result = 1/3 * 100
150         return 1
151
152     def deploy_vnf(self):
153         """Deploy Clearwater IMS."""
154         start_time = time.time()
155
156         self.cloud.create_security_group_rule(
157             'default', port_range_min=22, port_range_max=22,
158             protocol='tcp', direction='ingress')
159
160         self.__logger.info("Upload VNFD")
161         descriptor = self.vnf['descriptor']
162         self.cfy_client.blueprints.upload(
163             descriptor.get('file_name'), descriptor.get('name'))
164
165         self.image_alt = self.publish_image_alt()
166         self.flavor_alt = self.create_flavor_alt()
167         self.vnf['inputs'].update(dict(
168             image_id=self.image_alt.id,
169             flavor_id=self.flavor_alt.id,
170         ))
171
172         self.__logger.info("Create VNF Instance")
173         self.cfy_client.deployments.create(
174             descriptor.get('name'), descriptor.get('name'),
175             self.vnf.get('inputs'))
176
177         wait_for_execution(
178             self.cfy_client,
179             get_execution_id(self.cfy_client, descriptor.get('name')),
180             self.__logger, timeout=300)
181
182         self.__logger.info("Start the VNF Instance deployment")
183         execution = self.cfy_client.executions.start(
184             descriptor.get('name'), 'install')
185         # Show execution log
186         execution = wait_for_execution(
187             self.cfy_client, execution, self.__logger, timeout=3600)
188
189         duration = time.time() - start_time
190
191         self.__logger.info(execution)
192         if execution.status == 'terminated':
193             self.details['vnf'].update(status='PASS', duration=duration)
194             self.result += 1/3 * 100
195             result = True
196         else:
197             self.details['vnf'].update(status='FAIL', duration=duration)
198             result = False
199         return result
200
201     def test_vnf(self):
202         """Run test on clearwater ims instance."""
203         start_time = time.time()
204
205         testing = clearwater_ims_base.ClearwaterOnBoardingBase(self.case_name)
206         outputs = self.cfy_client.deployments.outputs.get(
207             self.vnf['descriptor'].get('name'))['outputs']
208         dns_ip = outputs['dns_ip']
209         ellis_ip = outputs['ellis_ip']
210         testing.config_ellis(ellis_ip)
211
212         if not dns_ip:
213             return False
214
215         short_result = testing.run_clearwater_live_test(
216             dns_ip=dns_ip,
217             public_domain=self.vnf['inputs']["public_domain"])
218         duration = time.time() - start_time
219         self.__logger.info(short_result)
220         self.details['test_vnf'].update(result=short_result,
221                                         duration=duration)
222         try:
223             vnf_test_rate = short_result['passed'] / (
224                 short_result['total'] - short_result['skipped'])
225             # orchestrator + vnf + test_vnf
226             self.result += vnf_test_rate / 3 * 100
227         except ZeroDivisionError:
228             self.__logger.error("No test has been executed")
229             self.details['test_vnf'].update(status='FAIL')
230             return False
231         except Exception:  # pylint: disable=broad-except
232             self.__logger.exception("Cannot calculate results")
233             self.details['test_vnf'].update(status='FAIL')
234             return False
235         return True if vnf_test_rate > 0 else False
236
237     def clean(self):
238         """Clean created objects/functions."""
239         try:
240             dep_name = self.vnf['descriptor'].get('name')
241             # kill existing execution
242             self.__logger.info('Deleting the current deployment')
243             exec_list = self.cfy_client.executions.list(dep_name)
244             for execution in exec_list:
245                 if execution['status'] == "started":
246                     try:
247                         self.cfy_client.executions.cancel(
248                             execution['id'], force=True)
249                     except Exception:  # pylint: disable=broad-except
250                         self.__logger.warn("Can't cancel the current exec")
251
252             execution = self.cfy_client.executions.start(
253                 dep_name,
254                 'uninstall',
255                 parameters=dict(ignore_failure=True),
256                 force=True)
257
258             wait_for_execution(self.cfy_client, execution, self.__logger)
259             self.cfy_client.deployments.delete(
260                 self.vnf['descriptor'].get('name'))
261             self.cfy_client.blueprints.delete(
262                 self.vnf['descriptor'].get('name'))
263         except Exception:  # pylint: disable=broad-except
264             self.__logger.exception("Some issue during the undeployment ..")
265
266         super(CloudifyIms, self).clean()
267
268
269 # ----------------------------------------------------------
270 #
271 #               YAML UTILS
272 #
273 # -----------------------------------------------------------
274 def get_config(parameter, file_path):
275     """
276     Get config parameter.
277
278     Returns the value of a given parameter in file.yaml
279     parameter must be given in string format with dots
280     Example: general.openstack.image_name
281     """
282     with open(file_path) as config_file:
283         file_yaml = yaml.safe_load(config_file)
284     config_file.close()
285     value = file_yaml
286     for element in parameter.split("."):
287         value = value.get(element)
288         if value is None:
289             raise ValueError("The parameter %s is not defined in"
290                              " reporting.yaml" % parameter)
291     return value
292
293
294 def wait_for_execution(client, execution, logger, timeout=3600, ):
295     """Wait for a workflow execution on Cloudify Manager."""
296     # if execution already ended - return without waiting
297     if execution.status in Execution.END_STATES:
298         return execution
299
300     if timeout is not None:
301         deadline = time.time() + timeout
302
303     # Poll for execution status and execution logs, until execution ends
304     # and we receive an event of type in WORKFLOW_END_TYPES
305     offset = 0
306     batch_size = 50
307     event_list = []
308     execution_ended = False
309     while True:
310         event_list = client.events.list(
311             execution_id=execution.id,
312             _offset=offset,
313             _size=batch_size,
314             include_logs=True,
315             sort='@timestamp').items
316
317         offset = offset + len(event_list)
318         for event in event_list:
319             logger.debug(event.get('message'))
320
321         if timeout is not None:
322             if time.time() > deadline:
323                 raise RuntimeError(
324                     'execution of operation {0} for deployment {1} '
325                     'timed out'.format(execution.workflow_id,
326                                        execution.deployment_id))
327             else:
328                 # update the remaining timeout
329                 timeout = deadline - time.time()
330
331         if not execution_ended:
332             execution = client.executions.get(execution.id)
333             execution_ended = execution.status in Execution.END_STATES
334
335         if execution_ended:
336             break
337
338         time.sleep(5)
339
340     return execution
341
342
343 def get_execution_id(client, deployment_id):
344     """
345     Get the execution id of a env preparation.
346
347     network, security group, fip, VM creation
348     """
349     executions = client.executions.list(deployment_id=deployment_id)
350     for execution in executions:
351         if execution.workflow_id == 'create_deployment_environment':
352             return execution
353     raise RuntimeError('Failed to get create_deployment_environment '
354                        'workflow execution.'
355                        'Available executions: {0}'.format(executions))