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"""
39 keystone_api_version = os.getenv('OS_IDENTITY_API_VERSION')
41 if keystone_api_version is None or keystone_api_version == '2':
43 tenant_env = 'OS_TENANT_NAME'
44 tenant = 'tenant_name'
47 tenant_env = 'OS_PROJECT_NAME'
48 tenant = 'project_name'
50 # The most common way to pass these info to the script is to do it
51 # through environment variables.
53 "username": os.environ.get("OS_USERNAME"),
54 "password": os.environ.get("OS_PASSWORD"),
55 "auth_url": os.environ.get("OS_AUTH_URL"),
56 tenant: os.environ.get(tenant_env)
60 if os.getenv('OS_USER_DOMAIN_NAME') is not None:
62 "user_domain_name": os.getenv('OS_USER_DOMAIN_NAME')
64 if os.getenv('OS_PROJECT_DOMAIN_NAME') is not None:
66 "project_domain_name": os.getenv('OS_PROJECT_DOMAIN_NAME')
72 def get_session_auth():
73 loader = loading.get_plugin_loader('password')
74 creds = get_credentials()
75 auth = loader.load_from_options(**creds)
80 auth = get_session_auth()
82 cacert = os.environ['OS_CACERT']
84 return session.Session(auth=auth)
86 insecure = os.getenv('OS_INSECURE', '').lower() == 'true'
87 cacert = False if insecure else cacert
88 return session.Session(auth=auth, verify=cacert)
91 def get_endpoint(service_type, endpoint_type='publicURL'):
92 auth = get_session_auth()
93 # for multi-region, we need to specify region
94 # when finding the endpoint
95 return get_session().get_endpoint(auth=auth,
96 service_type=service_type,
97 endpoint_type=endpoint_type,
98 region_name=os.environ.get(
102 # *********************************************
104 # *********************************************
105 def get_heat_api_version(): # pragma: no cover
107 api_version = os.environ['HEAT_API_VERSION']
109 return DEFAULT_HEAT_API_VERSION
111 log.info("HEAT_API_VERSION is set in env as '%s'", api_version)
115 def get_cinder_client_version(): # pragma: no cover
117 api_version = os.environ['OS_VOLUME_API_VERSION']
119 return DEFAULT_API_VERSION
121 log.info("OS_VOLUME_API_VERSION is set in env as '%s'", api_version)
125 def get_cinder_client(): # pragma: no cover
127 return cinderclient.Client(get_cinder_client_version(), session=sess)
130 def get_nova_client_version(): # pragma: no cover
132 api_version = os.environ['OS_COMPUTE_API_VERSION']
134 return DEFAULT_API_VERSION
136 log.info("OS_COMPUTE_API_VERSION is set in env as '%s'", api_version)
140 def get_nova_client(): # pragma: no cover
142 return novaclient.Client(get_nova_client_version(), session=sess)
145 def get_neutron_client_version(): # pragma: no cover
147 api_version = os.environ['OS_NETWORK_API_VERSION']
149 return DEFAULT_API_VERSION
151 log.info("OS_NETWORK_API_VERSION is set in env as '%s'", api_version)
155 def get_neutron_client(): # pragma: no cover
157 return neutronclient.Client(get_neutron_client_version(), session=sess)
160 def get_glance_client_version(): # pragma: no cover
162 api_version = os.environ['OS_IMAGE_API_VERSION']
164 return DEFAULT_API_VERSION
166 log.info("OS_IMAGE_API_VERSION is set in env as '%s'", api_version)
170 def get_glance_client(): # pragma: no cover
172 return glanceclient.Client(get_glance_client_version(), session=sess)
175 def get_shade_client():
176 return shade.openstack_cloud()
179 # *********************************************
181 # *********************************************
182 def get_instances(nova_client):
184 return nova_client.servers.list(search_opts={'all_tenants': 1})
185 except Exception: # pylint: disable=broad-except
186 log.exception("Error [get_instances(nova_client)]")
189 def get_instance_status(nova_client, instance): # pragma: no cover
191 return nova_client.servers.get(instance.id).status
192 except Exception: # pylint: disable=broad-except
193 log.exception("Error [get_instance_status(nova_client)]")
196 def get_instance_by_name(nova_client, instance_name): # pragma: no cover
198 return nova_client.servers.find(name=instance_name)
199 except Exception: # pylint: disable=broad-except
200 log.exception("Error [get_instance_by_name(nova_client, '%s')]",
204 def get_aggregates(nova_client): # pragma: no cover
206 return nova_client.aggregates.list()
207 except Exception: # pylint: disable=broad-except
208 log.exception("Error [get_aggregates(nova_client)]")
211 def get_availability_zones(nova_client): # pragma: no cover
213 return nova_client.availability_zones.list()
214 except Exception: # pylint: disable=broad-except
215 log.exception("Error [get_availability_zones(nova_client)]")
218 def get_availability_zone_names(nova_client): # pragma: no cover
220 return [az.zoneName for az in get_availability_zones(nova_client)]
221 except Exception: # pylint: disable=broad-except
222 log.exception("Error [get_availability_zone_names(nova_client)]")
225 def create_aggregate(nova_client, aggregate_name, av_zone): # pragma: no cover
227 nova_client.aggregates.create(aggregate_name, av_zone)
228 except Exception: # pylint: disable=broad-except
229 log.exception("Error [create_aggregate(nova_client, %s, %s)]",
230 aggregate_name, av_zone)
236 def get_aggregate_id(nova_client, aggregate_name): # pragma: no cover
238 aggregates = get_aggregates(nova_client)
239 _id = next((ag.id for ag in aggregates if ag.name == aggregate_name))
240 except Exception: # pylint: disable=broad-except
241 log.exception("Error [get_aggregate_id(nova_client, %s)]",
247 def add_host_to_aggregate(nova_client, aggregate_name,
248 compute_host): # pragma: no cover
250 aggregate_id = get_aggregate_id(nova_client, aggregate_name)
251 nova_client.aggregates.add_host(aggregate_id, compute_host)
252 except Exception: # pylint: disable=broad-except
253 log.exception("Error [add_host_to_aggregate(nova_client, %s, %s)]",
254 aggregate_name, compute_host)
260 def create_aggregate_with_host(nova_client, aggregate_name, av_zone,
261 compute_host): # pragma: no cover
263 create_aggregate(nova_client, aggregate_name, av_zone)
264 add_host_to_aggregate(nova_client, aggregate_name, compute_host)
265 except Exception: # pylint: disable=broad-except
266 log.exception("Error [create_aggregate_with_host("
267 "nova_client, %s, %s, %s)]",
268 aggregate_name, av_zone, compute_host)
274 def create_keypair(name, key_path=None): # pragma: no cover
276 with open(key_path) as fpubkey:
277 keypair = get_nova_client().keypairs.create(
278 name=name, public_key=fpubkey.read())
280 except Exception: # pylint: disable=broad-except
281 log.exception("Error [create_keypair(nova_client)]")
284 def create_instance(json_body): # pragma: no cover
286 return get_nova_client().servers.create(**json_body)
287 except Exception: # pylint: disable=broad-except
288 log.exception("Error create instance failed")
292 def create_instance_and_wait_for_active(json_body): # pragma: no cover
294 VM_BOOT_TIMEOUT = 180
295 nova_client = get_nova_client()
296 instance = create_instance(json_body)
297 count = VM_BOOT_TIMEOUT / SLEEP
298 for _ in range(count, -1, -1):
299 status = get_instance_status(nova_client, instance)
300 if status.lower() == "active":
302 elif status.lower() == "error":
303 log.error("The instance went to ERROR status.")
306 log.error("Timeout booting the instance.")
310 def attach_server_volume(server_id, volume_id,
311 device=None): # pragma: no cover
313 get_nova_client().volumes.create_server_volume(server_id,
315 except Exception: # pylint: disable=broad-except
316 log.exception("Error [attach_server_volume(nova_client, '%s', '%s')]",
317 server_id, volume_id)
323 def delete_instance(nova_client, instance_id): # pragma: no cover
325 nova_client.servers.force_delete(instance_id)
326 except Exception: # pylint: disable=broad-except
327 log.exception("Error [delete_instance(nova_client, '%s')]",
334 def remove_host_from_aggregate(nova_client, aggregate_name,
335 compute_host): # pragma: no cover
337 aggregate_id = get_aggregate_id(nova_client, aggregate_name)
338 nova_client.aggregates.remove_host(aggregate_id, compute_host)
339 except Exception: # pylint: disable=broad-except
340 log.exception("Error remove_host_from_aggregate(nova_client, %s, %s)",
341 aggregate_name, compute_host)
347 def remove_hosts_from_aggregate(nova_client,
348 aggregate_name): # pragma: no cover
349 aggregate_id = get_aggregate_id(nova_client, aggregate_name)
350 hosts = nova_client.aggregates.get(aggregate_id).hosts
352 all(remove_host_from_aggregate(nova_client, aggregate_name, host)
356 def delete_aggregate(nova_client, aggregate_name): # pragma: no cover
358 remove_hosts_from_aggregate(nova_client, aggregate_name)
359 nova_client.aggregates.delete(aggregate_name)
360 except Exception: # pylint: disable=broad-except
361 log.exception("Error [delete_aggregate(nova_client, %s)]",
368 def get_server_by_name(name): # pragma: no cover
370 return get_nova_client().servers.list(search_opts={'name': name})[0]
372 log.exception('Failed to get nova client')
376 def create_flavor(name, ram, vcpus, disk, **kwargs): # pragma: no cover
378 return get_nova_client().flavors.create(name, ram, vcpus,
380 except Exception: # pylint: disable=broad-except
381 log.exception("Error [create_flavor(nova_client, %s, %s, %s, %s, %s)]",
382 name, ram, disk, vcpus, kwargs['is_public'])
386 def get_image_by_name(name): # pragma: no cover
387 images = get_nova_client().images.list()
389 return next((a for a in images if a.name == name))
390 except StopIteration:
391 log.exception('No image matched')
394 def get_flavor_id(nova_client, flavor_name): # pragma: no cover
395 flavors = nova_client.flavors.list(detailed=True)
398 if f.name == flavor_name:
404 def get_flavor_by_name(name): # pragma: no cover
405 flavors = get_nova_client().flavors.list()
407 return next((a for a in flavors if a.name == name))
408 except StopIteration:
409 log.exception('No flavor matched')
412 def check_status(status, name, iterations, interval): # pragma: no cover
413 for _ in range(iterations):
415 server = get_server_by_name(name)
417 log.error('Cannot found %s server', name)
420 if server.status == status:
427 def delete_flavor(flavor_id): # pragma: no cover
429 get_nova_client().flavors.delete(flavor_id)
430 except Exception: # pylint: disable=broad-except
431 log.exception("Error [delete_flavor(nova_client, %s)]", flavor_id)
437 def delete_keypair(nova_client, key): # pragma: no cover
439 nova_client.keypairs.delete(key=key)
441 except Exception: # pylint: disable=broad-except
442 log.exception("Error [delete_keypair(nova_client)]")
446 # *********************************************
448 # *********************************************
449 def get_network_id(shade_client, network_name):
450 networks = shade_client.list_networks({'name': network_name})
452 return networks[0]['id']
455 def create_neutron_net(neutron_client, json_body): # pragma: no cover
457 network = neutron_client.create_network(body=json_body)
458 return network['network']['id']
459 except Exception: # pylint: disable=broad-except
460 log.error("Error [create_neutron_net(neutron_client)]")
461 raise Exception("operation error")
464 def delete_neutron_net(shade_client, network_id):
466 return shade_client.delete_network(network_id)
467 except exc.OpenStackCloudException:
468 log.error("Error [delete_neutron_net(shade_client, '%s')]", network_id)
472 def create_neutron_subnet(neutron_client, json_body): # pragma: no cover
474 subnet = neutron_client.create_subnet(body=json_body)
475 return subnet['subnets'][0]['id']
476 except Exception: # pylint: disable=broad-except
477 log.error("Error [create_neutron_subnet")
478 raise Exception("operation error")
481 def create_neutron_router(neutron_client, json_body): # pragma: no cover
483 router = neutron_client.create_router(json_body)
484 return router['router']['id']
485 except Exception: # pylint: disable=broad-except
486 log.error("Error [create_neutron_router(neutron_client)]")
487 raise Exception("operation error")
490 def delete_neutron_router(neutron_client, router_id): # pragma: no cover
492 neutron_client.delete_router(router=router_id)
494 except Exception: # pylint: disable=broad-except
495 log.error("Error [delete_neutron_router(neutron_client, '%s')]",
500 def remove_gateway_router(neutron_client, router_id): # pragma: no cover
502 neutron_client.remove_gateway_router(router_id)
504 except Exception: # pylint: disable=broad-except
505 log.error("Error [remove_gateway_router(neutron_client, '%s')]",
510 def remove_interface_router(neutron_client, router_id, subnet_id,
511 **json_body): # pragma: no cover
512 json_body.update({"subnet_id": subnet_id})
514 neutron_client.remove_interface_router(router=router_id,
517 except Exception: # pylint: disable=broad-except
518 log.error("Error [remove_interface_router(neutron_client, '%s', "
519 "'%s')]", router_id, subnet_id)
523 def create_floating_ip(neutron_client, extnet_id): # pragma: no cover
524 props = {'floating_network_id': extnet_id}
526 ip_json = neutron_client.create_floatingip({'floatingip': props})
527 fip_addr = ip_json['floatingip']['floating_ip_address']
528 fip_id = ip_json['floatingip']['id']
529 except Exception: # pylint: disable=broad-except
530 log.error("Error [create_floating_ip(neutron_client)]")
532 return {'fip_addr': fip_addr, 'fip_id': fip_id}
535 def delete_floating_ip(nova_client, floatingip_id): # pragma: no cover
537 nova_client.floating_ips.delete(floatingip_id)
539 except Exception: # pylint: disable=broad-except
540 log.error("Error [delete_floating_ip(nova_client, '%s')]",
545 def get_security_groups(neutron_client): # pragma: no cover
547 security_groups = neutron_client.list_security_groups()[
549 return security_groups
550 except Exception: # pylint: disable=broad-except
551 log.error("Error [get_security_groups(neutron_client)]")
555 def get_security_group_id(neutron_client, sg_name): # pragma: no cover
556 security_groups = get_security_groups(neutron_client)
558 for sg in security_groups:
559 if sg['name'] == sg_name:
565 def create_security_group(neutron_client, sg_name,
566 sg_description): # pragma: no cover
567 json_body = {'security_group': {'name': sg_name,
568 'description': sg_description}}
570 secgroup = neutron_client.create_security_group(json_body)
571 return secgroup['security_group']
572 except Exception: # pylint: disable=broad-except
573 log.error("Error [create_security_group(neutron_client, '%s', "
574 "'%s')]", sg_name, sg_description)
578 def create_secgroup_rule(neutron_client, sg_id, direction, protocol,
579 port_range_min=None, port_range_max=None,
580 **json_body): # pragma: no cover
581 # We create a security group in 2 steps
582 # 1 - we check the format and set the json body accordingly
583 # 2 - we call neturon client to create the security group
586 json_body.update({'security_group_rule': {'direction': direction,
587 'security_group_id': sg_id, 'protocol': protocol}})
589 # - both None => we do nothing
590 # - both Not None => we add them to the json description
591 # but one cannot be None is the other is not None
592 if (port_range_min is not None and port_range_max is not None):
593 # add port_range in json description
594 json_body['security_group_rule']['port_range_min'] = port_range_min
595 json_body['security_group_rule']['port_range_max'] = port_range_max
596 log.debug("Security_group format set (port range included)")
598 # either both port range are set to None => do nothing
599 # or one is set but not the other => log it and return False
600 if port_range_min is None and port_range_max is None:
601 log.debug("Security_group format set (no port range mentioned)")
603 log.error("Bad security group format."
604 "One of the port range is not properly set:"
605 "range min: %s, range max: %s", port_range_min,
609 # Create security group using neutron client
611 neutron_client.create_security_group_rule(json_body)
613 except Exception: # pylint: disable=broad-except
614 log.exception("Impossible to create_security_group_rule,"
615 "security group rule probably already exists")
619 def create_security_group_full(neutron_client, sg_name,
620 sg_description): # pragma: no cover
621 sg_id = get_security_group_id(neutron_client, sg_name)
623 log.info("Using existing security group '%s'...", sg_name)
625 log.info("Creating security group '%s'...", sg_name)
626 SECGROUP = create_security_group(neutron_client,
630 log.error("Failed to create the security group...")
633 sg_id = SECGROUP['id']
635 log.debug("Security group '%s' with ID=%s created successfully.",
636 SECGROUP['name'], sg_id)
638 log.debug("Adding ICMP rules in security group '%s'...", sg_name)
639 if not create_secgroup_rule(neutron_client, sg_id,
641 log.error("Failed to create the security group rule...")
644 log.debug("Adding SSH rules in security group '%s'...", sg_name)
645 if not create_secgroup_rule(
646 neutron_client, sg_id, 'ingress', 'tcp', '22', '22'):
647 log.error("Failed to create the security group rule...")
650 if not create_secgroup_rule(
651 neutron_client, sg_id, 'egress', 'tcp', '22', '22'):
652 log.error("Failed to create the security group rule...")
657 # *********************************************
659 # *********************************************
660 def get_image_id(glance_client, image_name): # pragma: no cover
661 images = glance_client.images.list()
662 return next((i.id for i in images if i.name == image_name), None)
665 def create_image(glance_client, image_name, file_path, disk_format,
666 container_format, min_disk, min_ram, protected, tag,
667 public, **kwargs): # pragma: no cover
668 if not os.path.isfile(file_path):
669 log.error("Error: file %s does not exist.", file_path)
672 image_id = get_image_id(glance_client, image_name)
673 if image_id is not None:
674 log.info("Image %s already exists.", image_name)
676 log.info("Creating image '%s' from '%s'...", image_name, file_path)
678 image = glance_client.images.create(
679 name=image_name, visibility=public, disk_format=disk_format,
680 container_format=container_format, min_disk=min_disk,
681 min_ram=min_ram, tags=tag, protected=protected, **kwargs)
683 with open(file_path) as image_data:
684 glance_client.images.upload(image_id, image_data)
686 except Exception: # pylint: disable=broad-except
688 "Error [create_glance_image(glance_client, '%s', '%s', '%s')]",
689 image_name, file_path, public)
693 def delete_image(glance_client, image_id): # pragma: no cover
695 glance_client.images.delete(image_id)
697 except Exception: # pylint: disable=broad-except
698 log.exception("Error [delete_flavor(glance_client, %s)]", image_id)
704 # *********************************************
706 # *********************************************
707 def get_volume_id(volume_name): # pragma: no cover
708 volumes = get_cinder_client().volumes.list()
709 return next((v.id for v in volumes if v.name == volume_name), None)
712 def create_volume(cinder_client, volume_name, volume_size,
713 volume_image=False): # pragma: no cover
716 volume = cinder_client.volumes.create(name=volume_name,
718 imageRef=volume_image)
720 volume = cinder_client.volumes.create(name=volume_name,
723 except Exception: # pylint: disable=broad-except
724 log.exception("Error [create_volume(cinder_client, %s)]",
725 (volume_name, volume_size))
729 def delete_volume(cinder_client, volume_id,
730 forced=False): # pragma: no cover
734 cinder_client.volumes.detach(volume_id)
735 except Exception: # pylint: disable=broad-except
736 log.error(sys.exc_info()[0])
737 cinder_client.volumes.force_delete(volume_id)
740 volume = get_cinder_client().volumes.get(volume_id)
741 if volume.status.lower() == 'available':
743 cinder_client.volumes.delete(volume_id)
745 except Exception: # pylint: disable=broad-except
746 log.exception("Error [delete_volume(cinder_client, '%s')]", volume_id)
750 def detach_volume(server_id, volume_id): # pragma: no cover
752 get_nova_client().volumes.delete_server_volume(server_id, volume_id)
754 except Exception: # pylint: disable=broad-except
755 log.exception("Error [detach_server_volume(nova_client, '%s', '%s')]",
756 server_id, volume_id)