2 # Copyright (c) 2016 Cable Television Laboratories, Inc. ("CableLabs")
3 # and others. All rights reserved.
5 # Licensed under the Apache License, Version 2.0 (the "License");
6 # you may not use this file except in compliance with the License.
7 # You may obtain a copy of the License at:
9 # http://www.apache.org/licenses/LICENSE-2.0
11 # Unless required by applicable law or agreed to in writing, software
12 # distributed under the License is distributed on an "AS IS" BASIS,
13 # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14 # See the License for the specific language governing permissions and
15 # limitations under the License.
17 # This utility makes it easy to create OpenStack objects
25 from keystoneauth1.exceptions import Unauthorized
27 from snaps.config.flavor import FlavorConfig
28 from snaps.config.image import ImageConfig
29 from snaps.config.keypair import KeypairConfig
30 from snaps.config.network import PortConfig, NetworkConfig
31 from snaps.config.project import ProjectConfig
32 from snaps.config.qos import QoSConfig
33 from snaps.config.router import RouterConfig
34 from snaps.config.security_group import SecurityGroupConfig
35 from snaps.config.user import UserConfig
36 from snaps.config.vm_inst import VmInstanceConfig
37 from snaps.config.volume import VolumeConfig
38 from snaps.config.volume_type import VolumeTypeConfig
39 from snaps.openstack.create_flavor import OpenStackFlavor
40 from snaps.openstack.create_image import OpenStackImage
41 from snaps.openstack.create_keypairs import OpenStackKeypair
42 from snaps.openstack.create_network import OpenStackNetwork
43 from snaps.openstack.create_project import OpenStackProject
44 from snaps.openstack.create_qos import OpenStackQoS
45 from snaps.openstack.create_router import OpenStackRouter
46 from snaps.openstack.create_security_group import OpenStackSecurityGroup
47 from snaps.openstack.create_user import OpenStackUser
48 from snaps.openstack.create_volume import OpenStackVolume
49 from snaps.openstack.create_volume_type import OpenStackVolumeType
50 from snaps.openstack.os_credentials import OSCreds, ProxySettings
51 from snaps.openstack.utils import deploy_utils, neutron_utils, keystone_utils
52 from snaps.provisioning import ansible_utils
54 logger = logging.getLogger('lanuch_utils')
55 DEFAULT_CREDS_KEY = 'admin'
58 def launch_config(config, tmplt_file, deploy, clean, clean_image):
60 Launches all objects and applies any configured ansible playbooks
61 :param config: the environment configuration dict object
62 :param tmplt_file: the path to the SNAPS-OO template file
63 :param deploy: when True deploy
64 :param clean: when True clean
65 :param clean_image: when True clean the image when clean is True
67 os_config = config.get('openstack')
73 networks_dict = dict()
75 os_creds_dict = dict()
78 os_creds_dict = __get_creds_dict(os_config)
81 projects_dict = __create_instances(
82 os_creds_dict, OpenStackProject, ProjectConfig,
83 os_config.get('projects'), 'project', clean)
84 creators.append(projects_dict)
87 users_dict = __create_instances(
88 os_creds_dict, OpenStackUser, UserConfig,
89 os_config.get('users'), 'user', clean)
90 creators.append(users_dict)
92 # Associate new users to projects
94 for project_creator in projects_dict.values():
95 users = project_creator.project_settings.users
96 for user_name in users:
97 user_creator = users_dict.get(user_name)
99 project_creator.assoc_user(
100 user_creator.get_user())
103 flavors_dict = __create_instances(
104 os_creds_dict, OpenStackFlavor, FlavorConfig,
105 os_config.get('flavors'), 'flavor', clean, users_dict)
106 creators.append(flavors_dict)
109 qos_dict = __create_instances(
110 os_creds_dict, OpenStackQoS, QoSConfig,
111 os_config.get('qos_specs'), 'qos_spec', clean, users_dict)
112 creators.append(qos_dict)
114 # Create volume types
115 vol_type_dict = __create_instances(
116 os_creds_dict, OpenStackVolumeType, VolumeTypeConfig,
117 os_config.get('volume_types'), 'volume_type', clean,
119 creators.append(vol_type_dict)
122 vol_dict = __create_instances(
123 os_creds_dict, OpenStackVolume, VolumeConfig,
124 os_config.get('volumes'), 'volume', clean, users_dict)
125 creators.append(vol_dict)
128 images_dict = __create_instances(
129 os_creds_dict, OpenStackImage, ImageConfig,
130 os_config.get('images'), 'image', clean, users_dict)
131 creators.append(images_dict)
134 networks_dict = __create_instances(
135 os_creds_dict, OpenStackNetwork, NetworkConfig,
136 os_config.get('networks'), 'network', clean, users_dict)
137 creators.append(networks_dict)
140 routers_dict = __create_instances(
141 os_creds_dict, OpenStackRouter, RouterConfig,
142 os_config.get('routers'), 'router', clean, users_dict)
143 creators.append(routers_dict)
146 keypairs_dict = __create_instances(
147 os_creds_dict, OpenStackKeypair, KeypairConfig,
148 os_config.get('keypairs'), 'keypair', clean, users_dict)
149 creators.append(keypairs_dict)
151 # Create security groups
152 creators.append(__create_instances(
153 os_creds_dict, OpenStackSecurityGroup,
155 os_config.get('security_groups'), 'security_group', clean,
159 vm_dict = __create_vm_instances(
160 os_creds_dict, users_dict, os_config.get('instances'),
161 images_dict, keypairs_dict, clean)
162 creators.append(vm_dict)
164 'Completed creating/retrieving all configured instances')
166 # Must enter either block
168 # Cleanup Environment
169 __cleanup(creators, clean_image)
172 ansible_config = config.get('ansible')
173 if ansible_config and vm_dict:
174 if not __apply_ansible_playbooks(
175 ansible_config, os_creds_dict, vm_dict, images_dict,
176 flavors_dict, networks_dict, routers_dict, tmplt_file):
177 logger.error("Problem applying ansible playbooks")
180 def __get_creds_dict(os_conn_config):
182 Returns a dict of OSCreds where the key is the creds name.
183 For backwards compatibility, credentials not contained in a list (only
184 one) will be returned with the key of None
185 :param os_conn_config: the credential configuration
186 :return: a dict of OSCreds objects
188 if 'connection' in os_conn_config:
189 return {DEFAULT_CREDS_KEY: __get_os_credentials(os_conn_config)}
190 elif 'connections' in os_conn_config:
192 for os_conn_dict in os_conn_config['connections']:
193 config = os_conn_dict.get('connection')
195 raise Exception('Invalid connection format')
197 name = config.get('name')
199 raise Exception('Connection config requires a name field')
201 out[name] = __get_os_credentials(os_conn_dict)
205 def __get_creds(os_creds_dict, os_user_dict, inst_config):
207 Returns the appropriate credentials
208 :param os_creds_dict: a dictionary of OSCreds objects where the name is the
210 :param os_user_dict: a dictionary of OpenStackUser objects where the name
213 :return: an OSCreds instance or None
215 os_creds = os_creds_dict.get(DEFAULT_CREDS_KEY)
216 if 'os_user' in inst_config:
217 os_user_conf = inst_config['os_user']
218 if 'name' in os_user_conf:
219 user_creator = os_user_dict.get(os_user_conf['name'])
221 return user_creator.get_os_creds(
222 project_name=os_user_conf.get('project_name'))
223 elif 'os_creds_name' in inst_config:
224 if 'os_creds_name' in inst_config:
225 os_creds = os_creds_dict[inst_config['os_creds_name']]
229 def __get_os_credentials(os_conn_config):
231 Returns an object containing all of the information required to access
233 :param os_conn_config: The configuration holding the credentials
234 :return: an OSCreds instance
236 config = os_conn_config.get('connection')
238 raise Exception('Invalid connection configuration')
240 proxy_settings = None
241 http_proxy = config.get('http_proxy')
243 tokens = re.split(':', http_proxy)
244 ssh_proxy_cmd = config.get('ssh_proxy_cmd')
245 proxy_settings = ProxySettings(host=tokens[0], port=tokens[1],
246 ssh_proxy_cmd=ssh_proxy_cmd)
248 if 'proxy_settings' in config:
249 host = config['proxy_settings'].get('host')
250 port = config['proxy_settings'].get('port')
251 if host and host != 'None' and port and port != 'None':
252 proxy_settings = ProxySettings(**config['proxy_settings'])
255 config['proxy_settings'] = proxy_settings
257 if config.get('proxy_settings'):
258 del config['proxy_settings']
260 return OSCreds(**config)
263 def __parse_ports_config(config):
265 Parses the "ports" configuration
266 :param config: The dictionary to parse
267 :return: a list of PortConfig objects
270 for port_config in config:
271 out.append(PortConfig(**port_config.get('port')))
275 def __create_instances(os_creds_dict, creator_class, config_class, config,
276 config_key, cleanup=False, os_users_dict=None):
278 Returns a dictionary of SNAPS creator objects where the key is the name
279 :param os_creds_dict: Dictionary of OSCreds objects where the key is the
281 :param config: The list of configurations for the same type
282 :param config_key: The list of configurations for the same type
283 :param cleanup: Denotes whether or not this is being called for cleanup
289 for config_dict in config:
290 inst_config = config_dict.get(config_key)
292 creds = __get_creds(os_creds_dict, os_users_dict, inst_config)
294 creator = creator_class(
296 config_class(**inst_config))
302 except Unauthorized as e:
304 'Unable to initialize creator [%s] - %s',
309 out[inst_config['name']] = creator
311 raise Exception('Unable to instantiate creator')
313 logger.info('Initialized configured %ss', config_key)
318 def __create_vm_instances(os_creds_dict, os_users_dict, instances_config,
319 image_dict, keypairs_dict, cleanup=False):
321 Returns a dictionary of OpenStackVmInstance objects where the key is the
323 :param os_creds_dict: Dictionary of OSCreds objects where the key is the
325 :param os_users_dict: Dictionary of OpenStackUser objects where the key is
327 :param instances_config: The list of VM instance configurations
328 :param image_dict: A dictionary of images that will probably be used to
329 instantiate the VM instance
330 :param keypairs_dict: A dictionary of keypairs that will probably be used
331 to instantiate the VM instance
332 :param cleanup: Denotes whether or not this is being called for cleanup
338 for instance_config in instances_config:
339 conf = instance_config.get('instance')
342 image_creator = image_dict.get(conf.get('imageName'))
344 instance_settings = VmInstanceConfig(
345 **instance_config['instance'])
346 kp_creator = keypairs_dict.get(
347 conf.get('keypair_name'))
351 'name']] = deploy_utils.create_vm_instance(
353 os_creds_dict, os_users_dict, conf),
355 image_creator.image_settings,
356 keypair_creator=kp_creator,
358 except Unauthorized as e:
360 logger.warn('Unable to initialize VM - %s', e)
363 raise Exception('Image creator instance not found.'
364 ' Cannot instantiate')
367 raise Exception('Image dictionary is None. Cannot '
370 raise Exception('Instance configuration is None. Cannot '
372 logger.info('Created configured instances')
377 def __apply_ansible_playbooks(ansible_configs, os_creds_dict, vm_dict,
378 image_dict, flavor_dict, networks_dict,
379 routers_dict, tmplt_file):
381 Applies ansible playbooks to running VMs with floating IPs
382 :param ansible_configs: a list of Ansible configurations
383 :param os_creds_dict: Dictionary of OSCreds objects where the key is the
385 :param vm_dict: the dictionary of newly instantiated VMs where the name is
387 :param image_dict: the dictionary of newly instantiated images where the
389 :param flavor_dict: the dictionary of newly instantiated flavors where the
391 :param networks_dict: the dictionary of newly instantiated networks where
393 :param routers_dict: the dictionary of newly instantiated routers where
395 :param tmplt_file: the path of the SNAPS-OO template file for setting the
396 CWD so playbook location is relative to the deployment
398 :return: t/f - true if successful
400 logger.info("Applying Ansible Playbooks")
402 # Set CWD so the deployment file's playbook location can leverage
404 orig_cwd = os.getcwd()
405 env_dir = os.path.dirname(tmplt_file)
409 for ansible_config in ansible_configs:
410 # Ensure all hosts are accepting SSH session requests
411 for vm_name in ansible_config['hosts']:
412 vm_inst = vm_dict.get(vm_name)
414 if not vm_inst.vm_ssh_active(block=True):
416 'Timeout waiting for instance to respond to '
420 os_creds = os_creds_dict.get('admin-creds')
421 __apply_ansible_playbook(
422 ansible_config, os_creds, vm_dict, image_dict, flavor_dict,
423 networks_dict, routers_dict)
425 # Return to original directory
431 def __apply_ansible_playbook(ansible_config, os_creds, vm_dict, image_dict,
432 flavor_dict, networks_dict, routers_dict):
434 Applies an Ansible configuration setting
435 :param ansible_config: the configuration settings
436 :param os_creds: the OpenStack admin credentials object
437 :param vm_dict: the dictionary of newly instantiated VMs where the name is
439 :param image_dict: the dictionary of newly instantiated images where the
441 :param flavor_dict: the dictionary of newly instantiated flavors where the
443 :param networks_dict: the dictionary of newly instantiated networks where
445 :param routers_dict: the dictionary of newly instantiated routers where
449 (remote_user, floating_ips, private_key_filepath,
450 proxy_settings) = __get_connection_info(
451 ansible_config, vm_dict)
453 for key, vm_creator in vm_dict.items():
454 fip = vm_creator.get_floating_ip()
455 if fip and fip.ip in floating_ips:
456 if not vm_creator.cloud_init_complete(block=True):
458 'Cannot apply playbooks as cloud-init has not '
461 variables = __get_variables(
462 ansible_config.get('variables'), os_creds, vm_dict, image_dict,
463 flavor_dict, networks_dict, routers_dict)
465 retval = ansible_utils.apply_playbook(
466 ansible_config['playbook_location'], floating_ips, remote_user,
467 ssh_priv_key_file_path=private_key_filepath,
469 proxy_setting=proxy_settings)
471 # Not a fatal type of event
473 'Error applying playbook found at location - %s',
474 ansible_config.get('playbook_location'))
475 elif ansible_config.get('post_processing'):
476 post_proc_config = ansible_config['post_processing']
477 if 'sleep' in post_proc_config:
478 time.sleep(post_proc_config['sleep'])
479 if 'reboot' in post_proc_config:
480 for vm_name in post_proc_config['reboot']:
481 if vm_name in vm_dict:
482 logger.info('Rebooting VM - %s', vm_name)
483 vm_dict[vm_name].reboot()
488 def __get_connection_info(ansible_config, vm_dict):
490 Returns a tuple of data required for connecting to the running VMs
491 (remote_user, [floating_ips], private_key_filepath, proxy_settings)
492 :param ansible_config: the configuration settings
493 :param vm_dict: the dictionary of VMs where the VM name is the key
494 :return: tuple where the first element is the user and the second is a list
495 of floating IPs and the third is the
496 private key file location and the fourth is an instance of the
497 snaps.ProxySettings class
498 (note: in order to work, each of the hosts need to have the same sudo_user
499 and private key file location values)
501 if ansible_config.get('hosts'):
502 hosts = ansible_config['hosts']
504 floating_ips = list()
507 proxy_settings = None
509 vm = vm_dict.get(host)
511 fip = vm.get_floating_ip()
513 remote_user = vm.get_image_user()
516 floating_ips.append(fip.ip)
519 'Could not find floating IP for VM - ' +
522 pk_file = vm.keypair_settings.private_filepath
523 proxy_settings = vm.get_os_creds().proxy_settings
525 logger.error('Could not locate VM with name - ' + host)
527 return remote_user, floating_ips, pk_file, proxy_settings
531 def __get_variables(var_config, os_creds, vm_dict, image_dict, flavor_dict,
532 networks_dict, routers_dict):
534 Returns a dictionary of substitution variables to be used for Ansible
536 :param var_config: the variable configuration settings
537 :param os_creds: the OpenStack admin credentials object
538 :param vm_dict: the dictionary of newly instantiated VMs where the name is
540 :param image_dict: the dictionary of newly instantiated images where the
542 :param flavor_dict: the dictionary of newly instantiated flavors where the
544 :param networks_dict: the dictionary of newly instantiated networks where
546 :param routers_dict: the dictionary of newly instantiated routers where
548 :return: dictionary or None
550 if var_config and vm_dict and len(vm_dict) > 0:
552 for key, value in var_config.items():
553 value = __get_variable_value(
554 value, os_creds, vm_dict, image_dict, flavor_dict,
555 networks_dict, routers_dict)
557 variables[key] = value
559 "Set Jinga2 variable with key [%s] the value [%s]",
563 'Key - [' + str(key) + '] or Value [' + str(value)
564 + '] must not be None')
569 def __get_variable_value(var_config_values, os_creds, vm_dict, image_dict,
570 flavor_dict, networks_dict, routers_dict):
572 Returns the associated variable value for use by Ansible for substitution
574 :param var_config_values: the configuration dictionary
575 :param os_creds: the OpenStack admin credentials object
576 :param vm_dict: the dictionary of newly instantiated VMs where the name is
578 :param image_dict: the dictionary of newly instantiated images where the
580 :param flavor_dict: the dictionary of newly instantiated flavors where the
582 :param networks_dict: the dictionary of newly instantiated networks where
584 :param routers_dict: the dictionary of newly instantiated routers where
588 if var_config_values['type'] == 'string':
589 return __get_string_variable_value(var_config_values)
590 if var_config_values['type'] == 'vm-attr':
591 return __get_vm_attr_variable_value(var_config_values, vm_dict)
592 if var_config_values['type'] == 'os_creds':
593 return __get_os_creds_variable_value(var_config_values, os_creds)
594 if var_config_values['type'] == 'network':
595 return __get_network_variable_value(var_config_values, networks_dict)
596 if var_config_values['type'] == 'router':
597 return __get_router_variable_value(var_config_values, routers_dict,
599 if var_config_values['type'] == 'port':
600 return __get_vm_port_variable_value(var_config_values, vm_dict)
601 if var_config_values['type'] == 'floating_ip':
602 return __get_vm_fip_variable_value(var_config_values, vm_dict)
603 if var_config_values['type'] == 'image':
604 return __get_image_variable_value(var_config_values, image_dict)
605 if var_config_values['type'] == 'flavor':
606 return __get_flavor_variable_value(var_config_values, flavor_dict)
610 def __get_string_variable_value(var_config_values):
612 Returns the associated string value
613 :param var_config_values: the configuration dictionary
614 :return: the value contained in the dictionary with the key 'value'
616 return var_config_values['value']
619 def __get_vm_attr_variable_value(var_config_values, vm_dict):
621 Returns the associated value contained on a VM instance
622 :param var_config_values: the configuration dictionary
623 :param vm_dict: the dictionary containing all VMs where the key is the VM's
627 vm = vm_dict.get(var_config_values['vm_name'])
629 if var_config_values['value'] == 'floating_ip':
630 return vm.get_floating_ip().ip
631 if var_config_values['value'] == 'image_user':
632 return vm.get_image_user()
635 def __get_os_creds_variable_value(var_config_values, os_creds):
637 Returns the associated OS credentials value
638 :param var_config_values: the configuration dictionary
639 :param os_creds: the admin OpenStack OSCreds object
643 if var_config_values['value'] == 'username':
644 logger.info("Returning OS username")
645 return os_creds.username
646 elif var_config_values['value'] == 'password':
647 logger.info("Returning OS password")
648 return os_creds.password
649 elif var_config_values['value'] == 'auth_url':
650 logger.info("Returning OS auth_url")
651 return os_creds.auth_url
652 elif var_config_values['value'] == 'project_name':
653 logger.info("Returning OS project_name")
654 return os_creds.project_name
657 def __get_network_variable_value(var_config_values, networks_dict):
659 Returns the associated network value
660 :param var_config_values: the configuration dictionary
661 :param networks_dict: the dictionary containing all networks where the key
665 net_name = var_config_values.get('network_name')
667 if net_name and networks_dict.get(net_name):
668 network_creator = networks_dict[net_name]
670 if 'subnet_name' in var_config_values:
671 subnet_name = var_config_values.get('subnet_name')
673 for subnet in network_creator.get_network().subnets:
674 if subnet_name == subnet.name:
675 if 'value' in var_config_values:
676 if 'gateway_ip' == var_config_values['value']:
677 return subnet.gateway_ip
678 if 'ip_range' == var_config_values['value']:
679 return subnet.start + ' ' + subnet.end
680 if 'ip_range_start' == var_config_values['value']:
682 if 'ip_range_end' == var_config_values['value']:
684 if 'cidr' == var_config_values['value']:
686 if 'cidr_ip' == var_config_values['value']:
687 cidr_split = subnet.cidr.split('/')
689 if 'netmask' == var_config_values['value']:
690 cidr_split = subnet.cidr.split('/')
691 cidr_bits = 32 - int(cidr_split[1])
692 netmask = socket.inet_ntoa(
694 '!I', (1 << 32) - (1 << cidr_bits)))
696 if 'broadcast_ip' == var_config_values['value']:
697 end_split = subnet.end.split('.')
699 end_split[0] + '.' + end_split[1] + '.'
700 + end_split[2] + '.255')
704 def __get_router_variable_value(var_config_values, routers_dict, os_creds):
706 Returns the associated network value
707 :param var_config_values: the configuration dictionary
708 :param routers_dict: the dictionary containing all networks where the key
710 :param os_creds: the admin OpenStack credentials
713 router_name = var_config_values.get('router_name')
714 router_creator = routers_dict[router_name]
717 if 'external_fixed_ip' == var_config_values.get('attr'):
718 session = keystone_utils.keystone_session(os_creds)
719 neutron = neutron_utils.neutron_client(os_creds, session)
721 ext_nets = neutron_utils.get_external_networks(neutron)
723 subnet_name = var_config_values.get('subnet_name')
725 for ext_net in ext_nets:
726 for subnet in ext_net.subnets:
727 if subnet_name == subnet.name:
728 router = router_creator.get_router()
729 for fixed_ips in router.external_fixed_ips:
730 if subnet.id == fixed_ips['subnet_id']:
731 return fixed_ips['ip_address']
733 keystone_utils.close_session(session)
736 def __get_vm_port_variable_value(var_config_values, vm_dict):
738 Returns the associated OS credentials value
739 :param var_config_values: the configuration dictionary
740 :param vm_dict: the dictionary containing all VMs where the key is the VM's
744 port_name = var_config_values.get('port_name')
745 vm_name = var_config_values.get('vm_name')
747 if port_name and vm_name:
748 vm = vm_dict.get(vm_name)
750 for vm_port in vm.get_vm_inst().ports:
751 if vm_port.name == port_name:
752 port_value_id = var_config_values.get('port_value')
754 if port_value_id == 'mac_address':
755 return vm_port.mac_address
756 if port_value_id == 'ip_address':
757 return vm_port.ips[0]['ip_address']
760 def __get_vm_fip_variable_value(var_config_values, vm_dict):
762 Returns the floating IP value if found
763 :param var_config_values: the configuration dictionary
764 :param vm_dict: the dictionary containing all VMs where the key is the VM's
766 :return: the floating IP string value or None
768 fip_name = var_config_values.get('fip_name')
769 vm_name = var_config_values.get('vm_name')
772 vm = vm_dict.get(vm_name)
774 fip = vm.get_floating_ip(fip_name)
779 def __get_image_variable_value(var_config_values, image_dict):
781 Returns the associated image value
782 :param var_config_values: the configuration dictionary
783 :param image_dict: the dictionary containing all images where the key is
788 if var_config_values.get('image_name'):
789 image_creator = image_dict.get(var_config_values['image_name'])
791 if (var_config_values.get('value')
792 and var_config_values['value'] == 'id'):
793 return image_creator.get_image().id
794 if (var_config_values.get('value')
795 and var_config_values['value'] == 'user'):
796 return image_creator.image_settings.image_user
799 def __get_flavor_variable_value(var_config_values, flavor_dict):
801 Returns the associated flavor value
802 :param var_config_values: the configuration dictionary
803 :param flavor_dict: the dictionary containing all flavor creators where the
805 :return: the value or None
808 if var_config_values.get('flavor_name'):
809 flavor_creator = flavor_dict.get(var_config_values['flavor_name'])
811 if (var_config_values.get('value')
812 and var_config_values['value'] == 'id'):
813 return flavor_creator.get_flavor().id
816 def __cleanup(creators, clean_image=False):
818 Cleans up environment
819 :param creators: the list of creators by type
820 :param clean_image: when true
823 for creator_dict in reversed(creators):
824 for key, creator in creator_dict.items():
825 if ((isinstance(creator, OpenStackImage) and clean_image)
826 or not isinstance(creator, OpenStackImage)):