3 # Copyright (c) 2016 Orange and others.
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
10 """Orchestra Clearwater IMS testcase implementation."""
20 import functest.core.vnf as vnf
21 import functest.utils.openstack_utils as os_utils
22 from functest.opnfv_tests.openstack.snaps import snaps_utils
23 from functest.utils.constants import CONST
25 from org.openbaton.cli.errors.errors import NfvoException
26 from org.openbaton.cli.agents.agents import MainAgent
27 from snaps.config.flavor import FlavorConfig
28 from snaps.config.image import ImageConfig
29 from snaps.config.network import NetworkConfig, PortConfig, SubnetConfig
30 from snaps.config.router import RouterConfig
31 from snaps.config.security_group import (
32 Direction, Protocol, SecurityGroupConfig, SecurityGroupRuleConfig)
33 from snaps.config.vm_inst import VmInstanceConfig
34 from snaps.openstack.utils import keystone_utils
35 from snaps.openstack.create_flavor import OpenStackFlavor
36 from snaps.openstack.create_image import OpenStackImage
37 from snaps.openstack.create_instance import OpenStackVmInstance
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
43 __author__ = "Pauls, Michael <michael.pauls@fokus.fraunhofer.de>"
44 # ----------------------------------------------------------
48 # -----------------------------------------------------------
51 def get_config(parameter, file_path):
55 Returns the value of a given parameter in file.yaml
56 parameter must be given in string format with dots
57 Example: general.openstack.image_name
59 with open(file_path) as config_file:
60 file_yaml = yaml.safe_load(config_file)
63 for element in parameter.split("."):
64 value = value.get(element)
66 raise ValueError("The parameter %s is not defined in"
67 " reporting.yaml", parameter)
71 def servertest(host, port):
72 """Method to test that a server is reachable at IP:port"""
73 args = socket.getaddrinfo(host, port, socket.AF_INET, socket.SOCK_STREAM)
74 for family, socktype, proto, canonname, sockaddr in args:
75 sock = socket.socket(family, socktype, proto)
77 sock.connect(sockaddr)
85 def get_userdata(orchestrator=dict):
86 """Build userdata for Open Baton machine"""
87 userdata = "#!/bin/bash\n"
88 userdata += "echo \"Executing userdata...\"\n"
89 userdata += "set -x\n"
90 userdata += "set -e\n"
91 userdata += "echo \"Set nameserver to '8.8.8.8'...\"\n"
92 userdata += "echo \"nameserver 8.8.8.8\" >> /etc/resolv.conf\n"
93 userdata += "echo \"Install curl...\"\n"
94 userdata += "apt-get install curl\n"
95 userdata += "echo \"Inject public key...\"\n"
96 userdata += ("echo \"ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABAQCuPXrV3"
97 "geeHc6QUdyUr/1Z+yQiqLcOskiEGBiXr4z76MK4abiFmDZ18OMQlc"
98 "fl0p3kS0WynVgyaOHwZkgy/DIoIplONVr2CKBKHtPK+Qcme2PVnCtv"
99 "EqItl/FcD+1h5XSQGoa+A1TSGgCod/DPo+pes0piLVXP8Ph6QS1k7S"
100 "ic7JDeRQ4oT1bXYpJ2eWBDMfxIWKZqcZRiGPgMIbJ1iEkxbpeaAd9O"
101 "4MiM9nGCPESmed+p54uYFjwEDlAJZShcAZziiZYAvMZhvAhe6USljc"
102 "7YAdalAnyD/jwCHuwIrUw/lxo7UdNCmaUxeobEYyyFA1YVXzpNFZya"
103 "XPGAAYIJwEq/ openbaton@opnfv\" >> /home/ubuntu/.ssh/aut"
105 userdata += "echo \"Download bootstrap...\"\n"
106 userdata += ("curl -s %s "
107 "> ./bootstrap\n" % orchestrator['bootstrap']['url'])
108 userdata += ("curl -s %s" "> ./config_file\n" %
109 orchestrator['bootstrap']['config']['url'])
110 userdata += ("echo \"Disable usage of mysql...\"\n")
111 userdata += "sed -i s/mysql=.*/mysql=no/g /config_file\n"
112 userdata += ("echo \"Setting 'rabbitmq_broker_ip' to '%s'\"\n"
113 % orchestrator['details']['fip'].ip)
114 userdata += ("sed -i s/rabbitmq_broker_ip=localhost/rabbitmq_broker_ip"
115 "=%s/g /config_file\n" % orchestrator['details']['fip'].ip)
116 userdata += "echo \"Set autostart of components to 'false'\"\n"
117 userdata += "export OPENBATON_COMPONENT_AUTOSTART=false\n"
118 userdata += "echo \"Execute bootstrap...\"\n"
119 bootstrap = "sh ./bootstrap release -configFile=./config_file"
120 userdata += bootstrap + "\n"
121 userdata += "echo \"Setting 'nfvo.plugin.timeout' to '300000'\"\n"
122 userdata += ("echo \"nfvo.plugin.timeout=600000\" >> "
123 "/etc/openbaton/openbaton-nfvo.properties\n")
125 "wget %s -O /etc/openbaton/openbaton-vnfm-generic-user-data.sh\n" %
126 orchestrator['gvnfm']['userdata']['url'])
127 userdata += "sed -i '113i"'\ \ \ \ '"sleep 60' " \
128 "/etc/openbaton/openbaton-vnfm-generic-user-data.sh\n"
129 userdata += ("sed -i s/nfvo.marketplace.port=8082/nfvo.marketplace."
130 "port=8080/g /etc/openbaton/openbaton-nfvo.properties\n")
131 userdata += "echo \"Starting NFVO\"\n"
132 userdata += "service openbaton-nfvo restart\n"
133 userdata += "echo \"Starting Generic VNFM\"\n"
134 userdata += "service openbaton-vnfm-generic restart\n"
135 userdata += "echo \"...end of userdata...\"\n"
139 class ClearwaterImsVnf(vnf.VnfOnBoarding):
140 """Clearwater IMS VNF deployed with openBaton orchestrator"""
142 # logger = logging.getLogger(__name__)
144 def __init__(self, **kwargs):
145 if "case_name" not in kwargs:
146 kwargs["case_name"] = "orchestra_clearwaterims"
147 super(ClearwaterImsVnf, self).__init__(**kwargs)
148 self.logger = logging.getLogger("functest.ci.run_tests.orchestra")
149 self.logger.info("kwargs %s", (kwargs))
151 self.case_dir = pkg_resources.resource_filename(
152 'functest', 'opnfv_tests/vnf/ims/')
153 self.data_dir = CONST.__getattribute__('dir_ims_data')
154 self.test_dir = CONST.__getattribute__('dir_repo_vims_test')
155 self.created_resources = []
156 self.logger.info("%s VNF onboarding test starting", self.case_name)
159 self.config = CONST.__getattribute__(
160 'vnf_{}_config'.format(self.case_name))
161 except BaseException:
162 raise Exception("Orchestra VNF config file not found")
164 config_file = self.case_dir + self.config
167 get_config("mano", config_file),
170 self.logger.debug("Orchestrator configuration %s", self.mano)
172 self.details['orchestrator'] = dict(
173 name=self.mano['name'],
174 version=self.mano['version'],
180 get_config(self.case_name, config_file),
182 self.logger.debug("VNF configuration: %s", self.vnf)
184 self.details['vnf'] = dict(
185 name=self.vnf['name'],
188 self.details['test_vnf'] = dict(
192 # Orchestra base Data directory creation
193 if not os.path.exists(self.data_dir):
194 os.makedirs(self.data_dir)
196 self.images = get_config("tenant_images.orchestrator", config_file)
202 self.snaps_creds = None
205 """Prepare testscase (Additional pre-configuration steps)."""
206 super(ClearwaterImsVnf, self).prepare()
208 self.logger.info("Additional pre-configuration steps")
210 public_auth_url = keystone_utils.get_endpoint(
211 self.snaps_creds, 'identity')
214 "tenant": self.snaps_creds.project_name,
215 "username": self.snaps_creds.username,
216 "password": self.snaps_creds.password,
217 "auth_url": public_auth_url
219 self.prepare_images()
220 self.prepare_flavor()
221 self.prepare_security_groups()
222 self.prepare_network()
223 self.prepare_floating_ip()
225 def prepare_images(self):
226 """Upload images if they doen't exist yet"""
227 self.logger.info("Upload images if they doen't exist yet")
228 for image_name, image_file in self.images.iteritems():
229 self.logger.info("image: %s, file: %s", image_name, image_file)
230 if image_file and image_name:
231 image = OpenStackImage(
233 ImageConfig(name=image_name,
236 image_file=image_file,
239 # self.created_resources.append(image);
241 def prepare_security_groups(self):
242 """Create Open Baton security group if it doesn't exist yet"""
244 "Creating security group for Open Baton if not yet existing...")
247 SecurityGroupRuleConfig(
248 sec_grp_name="orchestra-sec-group-allowall",
249 direction=Direction.ingress,
250 protocol=Protocol.tcp,
252 port_range_max=65535))
254 SecurityGroupRuleConfig(
255 sec_grp_name="orchestra-sec-group-allowall",
256 direction=Direction.egress,
257 protocol=Protocol.tcp,
259 port_range_max=65535))
261 SecurityGroupRuleConfig(
262 sec_grp_name="orchestra-sec-group-allowall",
263 direction=Direction.ingress,
264 protocol=Protocol.udp,
266 port_range_max=65535))
268 SecurityGroupRuleConfig(
269 sec_grp_name="orchestra-sec-group-allowall",
270 direction=Direction.egress,
271 protocol=Protocol.udp,
273 port_range_max=65535))
275 SecurityGroupRuleConfig(
276 sec_grp_name="orchestra-sec-group-allowall",
277 direction=Direction.ingress,
278 protocol=Protocol.icmp))
280 SecurityGroupRuleConfig(
281 sec_grp_name="orchestra-sec-group-allowall",
282 direction=Direction.egress,
283 protocol=Protocol.icmp))
284 security_group = OpenStackSecurityGroup(
287 name="orchestra-sec-group-allowall",
288 rule_settings=sg_rules))
290 security_group_info = security_group.create()
291 self.created_resources.append(security_group)
292 self.mano['details']['sec_group'] = security_group_info.name
294 "Security group orchestra-sec-group-allowall prepared")
296 def prepare_flavor(self):
297 """Create Open Baton flavor if it doesn't exist yet"""
299 "Create Flavor for Open Baton NFVO if not yet existing")
301 flavor_settings = FlavorConfig(
302 name=self.mano['requirements']['flavor']['name'],
303 ram=self.mano['requirements']['flavor']['ram_min'],
304 disk=self.mano['requirements']['flavor']['disk'],
305 vcpus=self.mano['requirements']['flavor']['vcpus'])
306 flavor = OpenStackFlavor(self.snaps_creds, flavor_settings)
307 flavor_info = flavor.create()
308 self.created_resources.append(flavor)
309 self.mano['details']['flavor'] = {}
310 self.mano['details']['flavor']['name'] = flavor_settings.name
311 self.mano['details']['flavor']['id'] = flavor_info.id
313 def prepare_network(self):
314 """Create network/subnet/router if they doen't exist yet"""
316 "Creating network/subnet/router if they doen't exist yet...")
317 subnet_settings = SubnetConfig(
320 cidr="192.168.100.0/24")
321 network_settings = NetworkConfig(
324 subnet_settings=[subnet_settings])
325 orchestra_network = OpenStackNetwork(
326 self.snaps_creds, network_settings)
327 orchestra_network_info = orchestra_network.create()
328 self.mano['details']['network'] = {}
329 self.mano['details']['network']['id'] = orchestra_network_info.id
330 self.mano['details']['network']['name'] = orchestra_network_info.name
331 self.mano['details']['external_net_name'] = snaps_utils.\
332 get_ext_net_name(self.snaps_creds)
333 self.created_resources.append(orchestra_network)
334 orchestra_router = OpenStackRouter(
339 external_gateway=self.mano['details']['external_net_name'],
341 subnet_settings.name]))
342 orchestra_router.create()
343 self.created_resources.append(orchestra_router)
344 self.logger.info("Created network and router for Open Baton NFVO...")
346 def prepare_floating_ip(self):
347 """Select/Create Floating IP if it doesn't exist yet"""
348 self.logger.info("Retrieving floating IP for Open Baton NFVO")
349 neutron_client = snaps_utils.neutron_utils.neutron_client(
351 # Finding Tenant ID to check to which tenant the Floating IP belongs
352 tenant_id = os_utils.get_tenant_id(
353 os_utils.get_keystone_client(self.creds),
355 # Use os_utils to retrieve complete information of Floating IPs
356 floating_ips = os_utils.get_floating_ips(neutron_client)
358 # Filter Floating IPs with tenant id
359 for floating_ip in floating_ips:
360 if floating_ip.get('tenant_id') == tenant_id:
361 my_floating_ips.append(floating_ip.get('floating_ip_address'))
362 # Select if Floating IP exist else create new one
363 if len(my_floating_ips) >= 1:
364 # Get Floating IP object from snaps for clean up
365 snaps_floating_ips = snaps_utils.neutron_utils.get_floating_ips(
367 for my_floating_ip in my_floating_ips:
368 for snaps_floating_ip in snaps_floating_ips:
369 if snaps_floating_ip.ip == my_floating_ip:
370 self.mano['details']['fip'] = snaps_floating_ip
372 "Selected floating IP for Open Baton NFVO %s",
373 (self.mano['details']['fip'].ip))
375 if self.mano['details']['fip'] is not None:
378 self.logger.info("Creating floating IP for Open Baton NFVO")
379 self.mano['details']['fip'] = snaps_utils.neutron_utils.\
382 self.mano['details']['external_net_name'])
384 "Created floating IP for Open Baton NFVO %s",
385 (self.mano['details']['fip'].ip))
387 def get_vim_descriptor(self):
388 """"Create VIM descriptor to be used for onboarding"""
390 "Building VIM descriptor with PoP creds: %s",
392 # Depending on API version either tenant ID or project name must be
394 if os_utils.is_keystone_v3():
396 "Using v3 API of OpenStack... -> Using OS_PROJECT_ID")
397 project_id = os_utils.get_tenant_id(
398 os_utils.get_keystone_client(),
399 self.creds.get("project_name"))
402 "Using v2 API of OpenStack... -> Using OS_TENANT_NAME")
403 project_id = self.creds.get("tenant_name")
404 self.logger.debug("VIM project/tenant id: %s", project_id)
406 "name": "vim-instance",
407 "authUrl": self.creds.get("auth_url"),
408 "tenant": project_id,
409 "username": self.creds.get("username"),
410 "password": self.creds.get("password"),
412 self.mano['details']['sec_group']
417 "latitude": "52.525876",
418 "longitude": "13.314400"
421 self.logger.info("Built VIM descriptor: %s", vim_json)
424 def deploy_orchestrator(self):
425 self.logger.info("Deploying Open Baton...")
426 self.logger.info("Details: %s", self.mano['details'])
427 start_time = time.time()
429 self.logger.info("Creating orchestra instance...")
430 userdata = get_userdata(self.mano)
431 self.logger.info("flavor: %s\n"
434 self.mano['details']['flavor']['name'],
435 self.mano['requirements']['image'],
436 self.mano['details']['network']['id'])
437 self.logger.debug("userdata: %s\n", userdata)
439 image_settings = ImageConfig(
440 name=self.mano['requirements']['image'],
444 port_settings = PortConfig(
445 name='%s_port' % self.case_name,
446 network_name=self.mano['details']['network']['name'])
447 # build configuration of vm
448 orchestra_settings = VmInstanceConfig(
450 flavor=self.mano['details']['flavor']['name'],
451 port_settings=[port_settings],
452 security_group_names=[self.mano['details']['sec_group']],
453 userdata=str(userdata))
454 orchestra_vm = OpenStackVmInstance(self.snaps_creds,
458 orchestra_vm.create()
459 self.created_resources.append(orchestra_vm)
460 self.mano['details']['id'] = orchestra_vm.get_vm_info()['id']
462 "Created orchestra instance: %s",
463 self.mano['details']['id'])
465 self.logger.info("Associating floating ip: '%s' to VM '%s' ",
466 self.mano['details']['fip'].ip,
468 nova_client = os_utils.get_nova_client()
469 if not os_utils.add_floating_ip(
471 self.mano['details']['id'],
472 self.mano['details']['fip'].ip):
473 duration = time.time() - start_time
474 self.details["orchestrator"].update(
475 status='FAIL', duration=duration)
476 self.logger.error("Cannot associate floating IP to VM.")
479 self.logger.info("Waiting for Open Baton NFVO to be up and running...")
483 self.mano['details']['fip'].ip,
488 "Open Baton NFVO is not started yet (%ss)",
494 duration = time.time() - start_time
495 self.details["orchestrator"].update(
496 status='FAIL', duration=duration)
497 self.logger.error("Open Baton is not started correctly")
500 self.logger.info("Waiting for all components to be up and running...")
502 duration = time.time() - start_time
503 self.details["orchestrator"].update(status='PASS', duration=duration)
504 self.logger.info("Deploy Open Baton NFVO: OK")
507 def deploy_vnf(self):
508 start_time = time.time()
509 self.logger.info("Deploying %s...", self.vnf['name'])
511 main_agent = MainAgent(
512 nfvo_ip=self.mano['details']['fip'].ip,
516 username=self.mano['credentials']['username'],
517 password=self.mano['credentials']['password'])
520 "Create %s Flavor if not existing", self.vnf['name'])
521 flavor_settings = FlavorConfig(
522 name=self.vnf['requirements']['flavor']['name'],
523 ram=self.vnf['requirements']['flavor']['ram_min'],
524 disk=self.vnf['requirements']['flavor']['disk'],
525 vcpus=self.vnf['requirements']['flavor']['vcpus'])
526 flavor = OpenStackFlavor(self.snaps_creds, flavor_settings)
527 flavor_info = flavor.create()
528 self.logger.debug("Flavor id: %s", flavor_info.id)
530 self.logger.info("Getting project 'default'...")
531 project_agent = main_agent.get_agent("project", "")
532 for project in json.loads(project_agent.find()):
533 if project.get("name") == "default":
534 self.mano['details']['project_id'] = project.get("id")
535 self.logger.info("Found project 'default': %s", project)
538 vim_json = self.get_vim_descriptor()
539 self.logger.info("Registering VIM: %s", vim_json)
541 main_agent.get_agent(
542 "vim", project_id=self.mano['details']['project_id']).create(
543 entity=json.dumps(vim_json))
545 market_agent = main_agent.get_agent(
546 "market", project_id=self.mano['details']['project_id'])
549 self.logger.info("sending: %s", self.vnf['descriptor']['url'])
550 nsd = market_agent.create(entity=self.vnf['descriptor']['url'])
551 if nsd.get('id') is None:
552 self.logger.error("NSD not onboarded correctly")
553 duration = time.time() - start_time
554 self.details["vnf"].update(status='FAIL', duration=duration)
556 self.mano['details']['nsd_id'] = nsd.get('id')
557 self.logger.info("Onboarded NSD: " + nsd.get("name"))
559 nsr_agent = main_agent.get_agent(
560 "nsr", project_id=self.mano['details']['project_id'])
562 self.mano['details']['nsr'] = nsr_agent.create(
563 self.mano['details']['nsd_id'])
564 except NfvoException as exc:
565 self.logger.error(exc.message)
566 duration = time.time() - start_time
567 self.details["vnf"].update(status='FAIL', duration=duration)
570 if self.mano['details']['nsr'].get('code') is not None:
572 "%s cannot be deployed: %s -> %s",
574 self.mano['details']['nsr'].get('code'),
575 self.mano['details']['nsr'].get('message'))
576 self.logger.error("%s cannot be deployed", self.vnf['name'])
577 duration = time.time() - start_time
578 self.details["vnf"].update(status='FAIL', duration=duration)
582 self.logger.info("Waiting for NSR to go to ACTIVE...")
583 while self.mano['details']['nsr'].get("status") != 'ACTIVE' \
584 and self.mano['details']['nsr'].get("status") != 'ERROR':
586 self.logger.info("NSR is not yet ACTIVE... (%ss)", 60 * timeout)
588 self.logger.error("INACTIVE NSR after %s sec..", 60 * timeout)
589 duration = time.time() - start_time
590 self.details["vnf"].update(status='FAIL', duration=duration)
593 self.mano['details']['nsr'] = json.loads(
594 nsr_agent.find(self.mano['details']['nsr'].get('id')))
596 duration = time.time() - start_time
597 if self.mano['details']['nsr'].get("status") == 'ACTIVE':
598 self.details["vnf"].update(status='PASS', duration=duration)
599 self.logger.info("Sleep for 60s to ensure that all "
600 "services are up and running...")
604 self.details["vnf"].update(status='FAIL', duration=duration)
605 self.logger.error("NSR: %s", self.mano['details'].get('nsr'))
611 "Testing VNF Clearwater IMS is not yet implemented...")
612 start_time = time.time()
614 duration = time.time() - start_time
615 self.details["test_vnf"].update(status='PASS', duration=duration)
616 self.logger.info("Test VNF: OK")
620 self.logger.info("Cleaning %s...", self.case_name)
622 main_agent = MainAgent(
623 nfvo_ip=self.mano['details']['fip'].ip,
627 username=self.mano['credentials']['username'],
628 password=self.mano['credentials']['password'])
629 self.logger.info("Terminating %s...", self.vnf['name'])
630 if (self.mano['details'].get('nsr')):
631 main_agent.get_agent(
633 project_id=self.mano['details']['project_id']).delete(
634 self.mano['details']['nsr'].get('id'))
635 self.logger.info("Sleeping 60 seconds...")
638 self.logger.info("No need to terminate the VNF...")
639 # os_utils.delete_instance(nova_client=os_utils.get_nova_client(),
640 # instance_id=self.mano_instance_id)
641 except (NfvoException, KeyError) as exc:
642 self.logger.error('Unexpected error cleaning - %s', exc)
645 neutron_client = os_utils.get_neutron_client(self.creds)
646 self.logger.info("Deleting Open Baton Port...")
647 port = snaps_utils.neutron_utils.get_port(
649 port_name='%s_port' % self.case_name)
650 snaps_utils.neutron_utils.delete_port(neutron_client, port)
652 except Exception as exc: # pylint: disable=broad-except
653 self.logger.error('Unexpected error cleaning - %s', exc)
655 self.logger.info("Deleting Open Baton Floating IP...")
656 snaps_utils.neutron_utils.delete_floating_ip(
657 neutron_client, self.mano['details']['fip'])
658 except Exception as exc: # pylint: disable=broad-except
659 self.logger.error('Unexpected error cleaning - %s', exc)
661 for resource in reversed(self.created_resources):
663 self.logger.info("Cleaning %s", str(resource))
665 except Exception as exc: # pylint: disable=broad-except
666 self.logger.error('Unexpected error cleaning - %s', exc)
667 super(ClearwaterImsVnf, self).clean()