1 ##############################################################################
2 # Copyright (c) 2016 Huawei Technologies Co.,Ltd and others.
4 # All rights reserved. This program and the accompanying materials
5 # are made available under the terms of the Apache License, Version 2.0
6 # which accompanies this distribution, and is available at
7 # http://www.apache.org/licenses/LICENSE-2.0
8 ##############################################################################
15 from keystoneauth1 import loading
16 from keystoneauth1 import session
20 from cinderclient import client as cinderclient
21 from novaclient import client as novaclient
22 from glanceclient import client as glanceclient
23 from neutronclient.neutron import client as neutronclient
26 log = logging.getLogger(__name__)
28 DEFAULT_HEAT_API_VERSION = '1'
29 DEFAULT_API_VERSION = '2'
32 # *********************************************
34 # *********************************************
35 def get_credentials():
36 """Returns a creds dictionary filled with parsed from env
38 Keystone API version used is 3; v2 was deprecated in 2014 (Icehouse). Along
39 with this deprecation, environment variable 'OS_TENANT_NAME' is replaced by
42 creds = {'username': os.environ.get('OS_USERNAME'),
43 'password': os.environ.get('OS_PASSWORD'),
44 'auth_url': os.environ.get('OS_AUTH_URL'),
45 'project_name': os.environ.get('OS_PROJECT_NAME')
48 if os.getenv('OS_USER_DOMAIN_NAME'):
49 creds['user_domain_name'] = os.getenv('OS_USER_DOMAIN_NAME')
50 if os.getenv('OS_PROJECT_DOMAIN_NAME'):
51 creds['project_domain_name'] = os.getenv('OS_PROJECT_DOMAIN_NAME')
56 def get_session_auth():
57 loader = loading.get_plugin_loader('password')
58 creds = get_credentials()
59 auth = loader.load_from_options(**creds)
64 auth = get_session_auth()
66 cacert = os.environ['OS_CACERT']
68 return session.Session(auth=auth)
70 insecure = os.getenv('OS_INSECURE', '').lower() == 'true'
71 cacert = False if insecure else cacert
72 return session.Session(auth=auth, verify=cacert)
75 def get_endpoint(service_type, endpoint_type='publicURL'):
76 auth = get_session_auth()
77 # for multi-region, we need to specify region
78 # when finding the endpoint
79 return get_session().get_endpoint(auth=auth,
80 service_type=service_type,
81 endpoint_type=endpoint_type,
82 region_name=os.environ.get(
86 # *********************************************
88 # *********************************************
89 def get_heat_api_version(): # pragma: no cover
91 api_version = os.environ['HEAT_API_VERSION']
93 return DEFAULT_HEAT_API_VERSION
95 log.info("HEAT_API_VERSION is set in env as '%s'", api_version)
99 def get_cinder_client_version(): # pragma: no cover
101 api_version = os.environ['OS_VOLUME_API_VERSION']
103 return DEFAULT_API_VERSION
105 log.info("OS_VOLUME_API_VERSION is set in env as '%s'", api_version)
109 def get_cinder_client(): # pragma: no cover
111 return cinderclient.Client(get_cinder_client_version(), session=sess)
114 def get_nova_client_version(): # pragma: no cover
116 api_version = os.environ['OS_COMPUTE_API_VERSION']
118 return DEFAULT_API_VERSION
120 log.info("OS_COMPUTE_API_VERSION is set in env as '%s'", api_version)
124 def get_nova_client(): # pragma: no cover
126 return novaclient.Client(get_nova_client_version(), session=sess)
129 def get_neutron_client_version(): # pragma: no cover
131 api_version = os.environ['OS_NETWORK_API_VERSION']
133 return DEFAULT_API_VERSION
135 log.info("OS_NETWORK_API_VERSION is set in env as '%s'", api_version)
139 def get_neutron_client(): # pragma: no cover
141 return neutronclient.Client(get_neutron_client_version(), session=sess)
144 def get_glance_client_version(): # pragma: no cover
146 api_version = os.environ['OS_IMAGE_API_VERSION']
148 return DEFAULT_API_VERSION
150 log.info("OS_IMAGE_API_VERSION is set in env as '%s'", api_version)
154 def get_glance_client(): # pragma: no cover
156 return glanceclient.Client(get_glance_client_version(), session=sess)
159 def get_shade_client():
160 return shade.openstack_cloud()
163 # *********************************************
165 # *********************************************
166 def get_instances(nova_client):
168 return nova_client.servers.list(search_opts={'all_tenants': 1})
169 except Exception: # pylint: disable=broad-except
170 log.exception("Error [get_instances(nova_client)]")
173 def get_instance_status(nova_client, instance): # pragma: no cover
175 return nova_client.servers.get(instance.id).status
176 except Exception: # pylint: disable=broad-except
177 log.exception("Error [get_instance_status(nova_client)]")
180 def get_instance_by_name(nova_client, instance_name): # pragma: no cover
182 return nova_client.servers.find(name=instance_name)
183 except Exception: # pylint: disable=broad-except
184 log.exception("Error [get_instance_by_name(nova_client, '%s')]",
188 def get_aggregates(nova_client): # pragma: no cover
190 return nova_client.aggregates.list()
191 except Exception: # pylint: disable=broad-except
192 log.exception("Error [get_aggregates(nova_client)]")
195 def get_availability_zones(nova_client): # pragma: no cover
197 return nova_client.availability_zones.list()
198 except Exception: # pylint: disable=broad-except
199 log.exception("Error [get_availability_zones(nova_client)]")
202 def get_availability_zone_names(nova_client): # pragma: no cover
204 return [az.zoneName for az in get_availability_zones(nova_client)]
205 except Exception: # pylint: disable=broad-except
206 log.exception("Error [get_availability_zone_names(nova_client)]")
209 def create_aggregate(nova_client, aggregate_name, av_zone): # pragma: no cover
211 nova_client.aggregates.create(aggregate_name, av_zone)
212 except Exception: # pylint: disable=broad-except
213 log.exception("Error [create_aggregate(nova_client, %s, %s)]",
214 aggregate_name, av_zone)
220 def get_aggregate_id(nova_client, aggregate_name): # pragma: no cover
222 aggregates = get_aggregates(nova_client)
223 _id = next((ag.id for ag in aggregates if ag.name == aggregate_name))
224 except Exception: # pylint: disable=broad-except
225 log.exception("Error [get_aggregate_id(nova_client, %s)]",
231 def add_host_to_aggregate(nova_client, aggregate_name,
232 compute_host): # pragma: no cover
234 aggregate_id = get_aggregate_id(nova_client, aggregate_name)
235 nova_client.aggregates.add_host(aggregate_id, compute_host)
236 except Exception: # pylint: disable=broad-except
237 log.exception("Error [add_host_to_aggregate(nova_client, %s, %s)]",
238 aggregate_name, compute_host)
244 def create_aggregate_with_host(nova_client, aggregate_name, av_zone,
245 compute_host): # pragma: no cover
247 create_aggregate(nova_client, aggregate_name, av_zone)
248 add_host_to_aggregate(nova_client, aggregate_name, compute_host)
249 except Exception: # pylint: disable=broad-except
250 log.exception("Error [create_aggregate_with_host("
251 "nova_client, %s, %s, %s)]",
252 aggregate_name, av_zone, compute_host)
258 def create_keypair(name, key_path=None): # pragma: no cover
260 with open(key_path) as fpubkey:
261 keypair = get_nova_client().keypairs.create(
262 name=name, public_key=fpubkey.read())
264 except Exception: # pylint: disable=broad-except
265 log.exception("Error [create_keypair(nova_client)]")
268 def create_instance(json_body): # pragma: no cover
270 return get_nova_client().servers.create(**json_body)
271 except Exception: # pylint: disable=broad-except
272 log.exception("Error create instance failed")
276 def create_instance_and_wait_for_active(json_body): # pragma: no cover
278 VM_BOOT_TIMEOUT = 180
279 nova_client = get_nova_client()
280 instance = create_instance(json_body)
281 for _ in range(int(VM_BOOT_TIMEOUT / SLEEP)):
282 status = get_instance_status(nova_client, instance)
283 if status.lower() == "active":
285 elif status.lower() == "error":
286 log.error("The instance went to ERROR status.")
289 log.error("Timeout booting the instance.")
293 def attach_server_volume(server_id, volume_id,
294 device=None): # pragma: no cover
296 get_nova_client().volumes.create_server_volume(server_id,
298 except Exception: # pylint: disable=broad-except
299 log.exception("Error [attach_server_volume(nova_client, '%s', '%s')]",
300 server_id, volume_id)
306 def delete_instance(nova_client, instance_id): # pragma: no cover
308 nova_client.servers.force_delete(instance_id)
309 except Exception: # pylint: disable=broad-except
310 log.exception("Error [delete_instance(nova_client, '%s')]",
317 def remove_host_from_aggregate(nova_client, aggregate_name,
318 compute_host): # pragma: no cover
320 aggregate_id = get_aggregate_id(nova_client, aggregate_name)
321 nova_client.aggregates.remove_host(aggregate_id, compute_host)
322 except Exception: # pylint: disable=broad-except
323 log.exception("Error remove_host_from_aggregate(nova_client, %s, %s)",
324 aggregate_name, compute_host)
330 def remove_hosts_from_aggregate(nova_client,
331 aggregate_name): # pragma: no cover
332 aggregate_id = get_aggregate_id(nova_client, aggregate_name)
333 hosts = nova_client.aggregates.get(aggregate_id).hosts
335 all(remove_host_from_aggregate(nova_client, aggregate_name, host)
339 def delete_aggregate(nova_client, aggregate_name): # pragma: no cover
341 remove_hosts_from_aggregate(nova_client, aggregate_name)
342 nova_client.aggregates.delete(aggregate_name)
343 except Exception: # pylint: disable=broad-except
344 log.exception("Error [delete_aggregate(nova_client, %s)]",
351 def get_server_by_name(name): # pragma: no cover
353 return get_nova_client().servers.list(search_opts={'name': name})[0]
355 log.exception('Failed to get nova client')
359 def create_flavor(name, ram, vcpus, disk, **kwargs): # pragma: no cover
361 return get_nova_client().flavors.create(name, ram, vcpus,
363 except Exception: # pylint: disable=broad-except
364 log.exception("Error [create_flavor(nova_client, %s, %s, %s, %s, %s)]",
365 name, ram, disk, vcpus, kwargs['is_public'])
369 def get_image_by_name(name): # pragma: no cover
370 images = get_nova_client().images.list()
372 return next((a for a in images if a.name == name))
373 except StopIteration:
374 log.exception('No image matched')
377 def get_flavor_id(nova_client, flavor_name): # pragma: no cover
378 flavors = nova_client.flavors.list(detailed=True)
381 if f.name == flavor_name:
387 def get_flavor_by_name(name): # pragma: no cover
388 flavors = get_nova_client().flavors.list()
390 return next((a for a in flavors if a.name == name))
391 except StopIteration:
392 log.exception('No flavor matched')
395 def check_status(status, name, iterations, interval): # pragma: no cover
396 for _ in range(iterations):
398 server = get_server_by_name(name)
400 log.error('Cannot found %s server', name)
403 if server.status == status:
410 def delete_flavor(flavor_id): # pragma: no cover
412 get_nova_client().flavors.delete(flavor_id)
413 except Exception: # pylint: disable=broad-except
414 log.exception("Error [delete_flavor(nova_client, %s)]", flavor_id)
420 def delete_keypair(nova_client, key): # pragma: no cover
422 nova_client.keypairs.delete(key=key)
424 except Exception: # pylint: disable=broad-except
425 log.exception("Error [delete_keypair(nova_client)]")
429 # *********************************************
431 # *********************************************
432 def get_network_id(shade_client, network_name):
433 networks = shade_client.list_networks({'name': network_name})
435 return networks[0]['id']
438 def create_neutron_net(neutron_client, json_body): # pragma: no cover
440 network = neutron_client.create_network(body=json_body)
441 return network['network']['id']
442 except Exception: # pylint: disable=broad-except
443 log.error("Error [create_neutron_net(neutron_client)]")
444 raise Exception("operation error")
447 def delete_neutron_net(shade_client, network_id):
449 return shade_client.delete_network(network_id)
450 except exc.OpenStackCloudException:
451 log.error("Error [delete_neutron_net(shade_client, '%s')]", network_id)
455 def create_neutron_subnet(neutron_client, json_body): # pragma: no cover
457 subnet = neutron_client.create_subnet(body=json_body)
458 return subnet['subnets'][0]['id']
459 except Exception: # pylint: disable=broad-except
460 log.error("Error [create_neutron_subnet")
461 raise Exception("operation error")
464 def create_neutron_router(neutron_client, json_body): # pragma: no cover
466 router = neutron_client.create_router(json_body)
467 return router['router']['id']
468 except Exception: # pylint: disable=broad-except
469 log.error("Error [create_neutron_router(neutron_client)]")
470 raise Exception("operation error")
473 def delete_neutron_router(neutron_client, router_id): # pragma: no cover
475 neutron_client.delete_router(router=router_id)
477 except Exception: # pylint: disable=broad-except
478 log.error("Error [delete_neutron_router(neutron_client, '%s')]",
483 def remove_gateway_router(neutron_client, router_id): # pragma: no cover
485 neutron_client.remove_gateway_router(router_id)
487 except Exception: # pylint: disable=broad-except
488 log.error("Error [remove_gateway_router(neutron_client, '%s')]",
493 def remove_interface_router(neutron_client, router_id, subnet_id,
494 **json_body): # pragma: no cover
495 json_body.update({"subnet_id": subnet_id})
497 neutron_client.remove_interface_router(router=router_id,
500 except Exception: # pylint: disable=broad-except
501 log.error("Error [remove_interface_router(neutron_client, '%s', "
502 "'%s')]", router_id, subnet_id)
506 def create_floating_ip(neutron_client, extnet_id): # pragma: no cover
507 props = {'floating_network_id': extnet_id}
509 ip_json = neutron_client.create_floatingip({'floatingip': props})
510 fip_addr = ip_json['floatingip']['floating_ip_address']
511 fip_id = ip_json['floatingip']['id']
512 except Exception: # pylint: disable=broad-except
513 log.error("Error [create_floating_ip(neutron_client)]")
515 return {'fip_addr': fip_addr, 'fip_id': fip_id}
518 def delete_floating_ip(nova_client, floatingip_id): # pragma: no cover
520 nova_client.floating_ips.delete(floatingip_id)
522 except Exception: # pylint: disable=broad-except
523 log.error("Error [delete_floating_ip(nova_client, '%s')]",
528 def get_security_groups(neutron_client): # pragma: no cover
530 security_groups = neutron_client.list_security_groups()[
532 return security_groups
533 except Exception: # pylint: disable=broad-except
534 log.error("Error [get_security_groups(neutron_client)]")
538 def get_security_group_id(neutron_client, sg_name): # pragma: no cover
539 security_groups = get_security_groups(neutron_client)
541 for sg in security_groups:
542 if sg['name'] == sg_name:
548 def create_security_group(neutron_client, sg_name,
549 sg_description): # pragma: no cover
550 json_body = {'security_group': {'name': sg_name,
551 'description': sg_description}}
553 secgroup = neutron_client.create_security_group(json_body)
554 return secgroup['security_group']
555 except Exception: # pylint: disable=broad-except
556 log.error("Error [create_security_group(neutron_client, '%s', "
557 "'%s')]", sg_name, sg_description)
561 def create_secgroup_rule(neutron_client, sg_id, direction, protocol,
562 port_range_min=None, port_range_max=None,
563 **json_body): # pragma: no cover
564 # We create a security group in 2 steps
565 # 1 - we check the format and set the json body accordingly
566 # 2 - we call neturon client to create the security group
569 json_body.update({'security_group_rule': {'direction': direction,
570 'security_group_id': sg_id, 'protocol': protocol}})
572 # - both None => we do nothing
573 # - both Not None => we add them to the json description
574 # but one cannot be None is the other is not None
575 if (port_range_min is not None and port_range_max is not None):
576 # add port_range in json description
577 json_body['security_group_rule']['port_range_min'] = port_range_min
578 json_body['security_group_rule']['port_range_max'] = port_range_max
579 log.debug("Security_group format set (port range included)")
581 # either both port range are set to None => do nothing
582 # or one is set but not the other => log it and return False
583 if port_range_min is None and port_range_max is None:
584 log.debug("Security_group format set (no port range mentioned)")
586 log.error("Bad security group format."
587 "One of the port range is not properly set:"
588 "range min: %s, range max: %s", port_range_min,
592 # Create security group using neutron client
594 neutron_client.create_security_group_rule(json_body)
596 except Exception: # pylint: disable=broad-except
597 log.exception("Impossible to create_security_group_rule,"
598 "security group rule probably already exists")
602 def create_security_group_full(neutron_client, sg_name,
603 sg_description): # pragma: no cover
604 sg_id = get_security_group_id(neutron_client, sg_name)
606 log.info("Using existing security group '%s'...", sg_name)
608 log.info("Creating security group '%s'...", sg_name)
609 SECGROUP = create_security_group(neutron_client,
613 log.error("Failed to create the security group...")
616 sg_id = SECGROUP['id']
618 log.debug("Security group '%s' with ID=%s created successfully.",
619 SECGROUP['name'], sg_id)
621 log.debug("Adding ICMP rules in security group '%s'...", sg_name)
622 if not create_secgroup_rule(neutron_client, sg_id,
624 log.error("Failed to create the security group rule...")
627 log.debug("Adding SSH rules in security group '%s'...", sg_name)
628 if not create_secgroup_rule(
629 neutron_client, sg_id, 'ingress', 'tcp', '22', '22'):
630 log.error("Failed to create the security group rule...")
633 if not create_secgroup_rule(
634 neutron_client, sg_id, 'egress', 'tcp', '22', '22'):
635 log.error("Failed to create the security group rule...")
640 # *********************************************
642 # *********************************************
643 def get_image_id(glance_client, image_name): # pragma: no cover
644 images = glance_client.images.list()
645 return next((i.id for i in images if i.name == image_name), None)
648 def create_image(glance_client, image_name, file_path, disk_format,
649 container_format, min_disk, min_ram, protected, tag,
650 public, **kwargs): # pragma: no cover
651 if not os.path.isfile(file_path):
652 log.error("Error: file %s does not exist.", file_path)
655 image_id = get_image_id(glance_client, image_name)
656 if image_id is not None:
657 log.info("Image %s already exists.", image_name)
659 log.info("Creating image '%s' from '%s'...", image_name, file_path)
661 image = glance_client.images.create(
662 name=image_name, visibility=public, disk_format=disk_format,
663 container_format=container_format, min_disk=min_disk,
664 min_ram=min_ram, tags=tag, protected=protected, **kwargs)
666 with open(file_path) as image_data:
667 glance_client.images.upload(image_id, image_data)
669 except Exception: # pylint: disable=broad-except
671 "Error [create_glance_image(glance_client, '%s', '%s', '%s')]",
672 image_name, file_path, public)
676 def delete_image(glance_client, image_id): # pragma: no cover
678 glance_client.images.delete(image_id)
680 except Exception: # pylint: disable=broad-except
681 log.exception("Error [delete_flavor(glance_client, %s)]", image_id)
687 # *********************************************
689 # *********************************************
690 def get_volume_id(volume_name): # pragma: no cover
691 volumes = get_cinder_client().volumes.list()
692 return next((v.id for v in volumes if v.name == volume_name), None)
695 def create_volume(cinder_client, volume_name, volume_size,
696 volume_image=False): # pragma: no cover
699 volume = cinder_client.volumes.create(name=volume_name,
701 imageRef=volume_image)
703 volume = cinder_client.volumes.create(name=volume_name,
706 except Exception: # pylint: disable=broad-except
707 log.exception("Error [create_volume(cinder_client, %s)]",
708 (volume_name, volume_size))
712 def delete_volume(cinder_client, volume_id,
713 forced=False): # pragma: no cover
717 cinder_client.volumes.detach(volume_id)
718 except Exception: # pylint: disable=broad-except
719 log.error(sys.exc_info()[0])
720 cinder_client.volumes.force_delete(volume_id)
723 volume = get_cinder_client().volumes.get(volume_id)
724 if volume.status.lower() == 'available':
726 cinder_client.volumes.delete(volume_id)
728 except Exception: # pylint: disable=broad-except
729 log.exception("Error [delete_volume(cinder_client, '%s')]", volume_id)
733 def detach_volume(server_id, volume_id): # pragma: no cover
735 get_nova_client().volumes.delete_server_volume(server_id, volume_id)
737 except Exception: # pylint: disable=broad-except
738 log.exception("Error [detach_server_volume(nova_client, '%s', '%s')]",
739 server_id, volume_id)