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 ##############################################################################
14 from keystoneauth1 import loading
15 from keystoneauth1 import session
19 from cinderclient import client as cinderclient
20 from novaclient import client as novaclient
21 from glanceclient import client as glanceclient
22 from neutronclient.neutron import client as neutronclient
25 log = logging.getLogger(__name__)
27 DEFAULT_HEAT_API_VERSION = '1'
28 DEFAULT_API_VERSION = '2'
31 # *********************************************
33 # *********************************************
34 def get_credentials():
35 """Returns a creds dictionary filled with parsed from env
37 Keystone API version used is 3; v2 was deprecated in 2014 (Icehouse). Along
38 with this deprecation, environment variable 'OS_TENANT_NAME' is replaced by
41 creds = {'username': os.environ.get('OS_USERNAME'),
42 'password': os.environ.get('OS_PASSWORD'),
43 'auth_url': os.environ.get('OS_AUTH_URL'),
44 'project_name': os.environ.get('OS_PROJECT_NAME')
47 if os.getenv('OS_USER_DOMAIN_NAME'):
48 creds['user_domain_name'] = os.getenv('OS_USER_DOMAIN_NAME')
49 if os.getenv('OS_PROJECT_DOMAIN_NAME'):
50 creds['project_domain_name'] = os.getenv('OS_PROJECT_DOMAIN_NAME')
55 def get_session_auth():
56 loader = loading.get_plugin_loader('password')
57 creds = get_credentials()
58 auth = loader.load_from_options(**creds)
63 auth = get_session_auth()
65 cacert = os.environ['OS_CACERT']
67 return session.Session(auth=auth)
69 insecure = os.getenv('OS_INSECURE', '').lower() == 'true'
70 cacert = False if insecure else cacert
71 return session.Session(auth=auth, verify=cacert)
74 def get_endpoint(service_type, endpoint_type='publicURL'):
75 auth = get_session_auth()
76 # for multi-region, we need to specify region
77 # when finding the endpoint
78 return get_session().get_endpoint(auth=auth,
79 service_type=service_type,
80 endpoint_type=endpoint_type,
81 region_name=os.environ.get(
85 # *********************************************
87 # *********************************************
88 def get_heat_api_version(): # pragma: no cover
90 api_version = os.environ['HEAT_API_VERSION']
92 return DEFAULT_HEAT_API_VERSION
94 log.info("HEAT_API_VERSION is set in env as '%s'", api_version)
98 def get_cinder_client_version(): # pragma: no cover
100 api_version = os.environ['OS_VOLUME_API_VERSION']
102 return DEFAULT_API_VERSION
104 log.info("OS_VOLUME_API_VERSION is set in env as '%s'", api_version)
108 def get_cinder_client(): # pragma: no cover
110 return cinderclient.Client(get_cinder_client_version(), session=sess)
113 def get_nova_client_version(): # pragma: no cover
115 api_version = os.environ['OS_COMPUTE_API_VERSION']
117 return DEFAULT_API_VERSION
119 log.info("OS_COMPUTE_API_VERSION is set in env as '%s'", api_version)
123 def get_nova_client(): # pragma: no cover
125 return novaclient.Client(get_nova_client_version(), session=sess)
128 def get_neutron_client_version(): # pragma: no cover
130 api_version = os.environ['OS_NETWORK_API_VERSION']
132 return DEFAULT_API_VERSION
134 log.info("OS_NETWORK_API_VERSION is set in env as '%s'", api_version)
138 def get_neutron_client(): # pragma: no cover
140 return neutronclient.Client(get_neutron_client_version(), session=sess)
143 def get_glance_client_version(): # pragma: no cover
145 api_version = os.environ['OS_IMAGE_API_VERSION']
147 return DEFAULT_API_VERSION
149 log.info("OS_IMAGE_API_VERSION is set in env as '%s'", api_version)
153 def get_glance_client(): # pragma: no cover
155 return glanceclient.Client(get_glance_client_version(), session=sess)
158 def get_shade_client():
159 return shade.openstack_cloud()
162 # *********************************************
164 # *********************************************
165 def create_keypair(shade_client, name, public_key=None):
166 """Create a new keypair.
168 :param name: Name of the keypair being created.
169 :param public_key: Public key for the new keypair.
171 :return: Created keypair.
174 return shade_client.create_keypair(name, public_key=public_key)
175 except exc.OpenStackCloudException as o_exc:
176 log.error("Error [create_keypair(shade_client)]. "
177 "Exception message, '%s'", o_exc.orig_message)
180 def create_instance_and_wait_for_active(shade_client, name, image,
181 flavor, auto_ip=True, ips=None,
182 ip_pool=None, root_volume=None,
183 terminate_volume=False, wait=True,
184 timeout=180, reuse_ips=True,
185 network=None, boot_from_volume=False,
186 volume_size='20', boot_volume=None,
187 volumes=None, nat_destination=None,
189 """Create a virtual server instance.
191 :param name:(string) Name of the server.
192 :param image:(dict) Image dict, name or ID to boot with. Image is required
193 unless boot_volume is given.
194 :param flavor:(dict) Flavor dict, name or ID to boot onto.
195 :param auto_ip: Whether to take actions to find a routable IP for
197 :param ips: List of IPs to attach to the server.
198 :param ip_pool:(string) Name of the network or floating IP pool to get an
200 :param root_volume:(string) Name or ID of a volume to boot from.
201 (defaults to None - deprecated, use boot_volume)
202 :param boot_volume:(string) Name or ID of a volume to boot from.
203 :param terminate_volume:(bool) If booting from a volume, whether it should
204 be deleted when the server is destroyed.
205 :param volumes:(optional) A list of volumes to attach to the server.
206 :param wait:(optional) Wait for the address to appear as assigned to the server.
207 :param timeout: Seconds to wait, defaults to 60.
208 :param reuse_ips:(bool)Whether to attempt to reuse pre-existing
209 floating ips should a floating IP be needed.
210 :param network:(dict) Network dict or name or ID to attach the server to.
211 Mutually exclusive with the nics parameter. Can also be be
212 a list of network names or IDs or network dicts.
213 :param boot_from_volume:(bool) Whether to boot from volume. 'boot_volume'
214 implies True, but boot_from_volume=True with
215 no boot_volume is valid and will create a
216 volume from the image and use that.
217 :param volume_size: When booting an image from volume, how big should
218 the created volume be?
219 :param nat_destination: Which network should a created floating IP
220 be attached to, if it's not possible to infer from
221 the cloud's configuration.
222 :param meta:(optional) A dict of arbitrary key/value metadata to store for
223 this server. Both keys and values must be <=255 characters.
224 :param reservation_id: A UUID for the set of servers being requested.
225 :param min_count:(optional extension) The minimum number of servers to
227 :param max_count:(optional extension) The maximum number of servers to
229 :param security_groups: A list of security group names.
230 :param userdata: User data to pass to be exposed by the metadata server
231 this can be a file type object as well or a string.
232 :param key_name:(optional extension) Name of previously created keypair to
233 inject into the instance.
234 :param availability_zone: Name of the availability zone for instance
236 :param block_device_mapping:(optional) A dict of block device mappings for
238 :param block_device_mapping_v2:(optional) A dict of block device mappings
240 :param nics:(optional extension) An ordered list of nics to be added to
241 this server, with information about connected networks, fixed
243 :param scheduler_hints:(optional extension) Arbitrary key-value pairs
244 specified by the client to help boot an instance.
245 :param config_drive:(optional extension) Value for config drive either
246 boolean, or volume-id.
247 :param disk_config:(optional extension) Control how the disk is partitioned
248 when the server is created. Possible values are 'AUTO'
250 :param admin_pass:(optional extension) Add a user supplied admin password.
252 :returns: The created server.
255 return shade_client.create_server(
256 name, image, flavor, auto_ip=auto_ip, ips=ips, ip_pool=ip_pool,
257 root_volume=root_volume, terminate_volume=terminate_volume,
258 wait=wait, timeout=timeout, reuse_ips=reuse_ips, network=network,
259 boot_from_volume=boot_from_volume, volume_size=volume_size,
260 boot_volume=boot_volume, volumes=volumes,
261 nat_destination=nat_destination, **kwargs)
262 except exc.OpenStackCloudException as o_exc:
263 log.error("Error [create_instance(shade_client)]. "
264 "Exception message, '%s'", o_exc.orig_message)
267 def attach_volume_to_server(shade_client, server_name_or_id, volume_name_or_id,
268 device=None, wait=True, timeout=None):
269 """Attach a volume to a server.
271 This will attach a volume, described by the passed in volume
272 dict, to the server described by the passed in server dict on the named
273 device on the server.
275 If the volume is already attached to the server, or generally not
276 available, then an exception is raised. To re-attach to a server,
277 but under a different device, the user must detach it first.
279 :param server_name_or_id:(string) The server name or id to attach to.
280 :param volume_name_or_id:(string) The volume name or id to attach.
281 :param device:(string) The device name where the volume will attach.
282 :param wait:(bool) If true, waits for volume to be attached.
283 :param timeout: Seconds to wait for volume attachment. None is forever.
285 :returns: True if attached successful, False otherwise.
288 server = shade_client.get_server(name_or_id=server_name_or_id)
289 volume = shade_client.get_volume(volume_name_or_id)
290 shade_client.attach_volume(
291 server, volume, device=device, wait=wait, timeout=timeout)
293 except exc.OpenStackCloudException as o_exc:
294 log.error("Error [attach_volume_to_server(shade_client)]. "
295 "Exception message: %s", o_exc.orig_message)
299 def delete_instance(shade_client, name_or_id, wait=False, timeout=180,
300 delete_ips=False, delete_ip_retry=1):
301 """Delete a server instance.
303 :param name_or_id: name or ID of the server to delete
304 :param wait:(bool) If true, waits for server to be deleted.
305 :param timeout:(int) Seconds to wait for server deletion.
306 :param delete_ips:(bool) If true, deletes any floating IPs associated with
308 :param delete_ip_retry:(int) Number of times to retry deleting
309 any floating ips, should the first try be
311 :returns: True if delete succeeded, False otherwise.
314 return shade_client.delete_server(
315 name_or_id, wait=wait, timeout=timeout, delete_ips=delete_ips,
316 delete_ip_retry=delete_ip_retry)
317 except exc.OpenStackCloudException as o_exc:
318 log.error("Error [delete_instance(shade_client, '%s')]. "
319 "Exception message: %s", name_or_id,
324 def get_server(shade_client, name_or_id=None, filters=None, detailed=False,
326 """Get a server by name or ID.
328 :param name_or_id: Name or ID of the server.
329 :param filters:(dict) A dictionary of meta data to use for further
331 :param detailed:(bool) Whether or not to add detailed additional
333 :param bare:(bool) Whether to skip adding any additional information to the
336 :returns: A server ``munch.Munch`` or None if no matching server is found.
339 return shade_client.get_server(name_or_id=name_or_id, filters=filters,
340 detailed=detailed, bare=bare)
341 except exc.OpenStackCloudException as o_exc:
342 log.error("Error [get_server(shade_client, '%s')]. "
343 "Exception message: %s", name_or_id, o_exc.orig_message)
346 def create_flavor(name, ram, vcpus, disk, **kwargs): # pragma: no cover
348 return get_nova_client().flavors.create(name, ram, vcpus,
350 except Exception: # pylint: disable=broad-except
351 log.exception("Error [create_flavor(nova_client, %s, %s, %s, %s, %s)]",
352 name, ram, disk, vcpus, kwargs['is_public'])
356 def get_flavor_id(nova_client, flavor_name): # pragma: no cover
357 flavors = nova_client.flavors.list(detailed=True)
360 if f.name == flavor_name:
366 def get_flavor(shade_client, name_or_id, filters=None, get_extra=True):
367 """Get a flavor by name or ID.
369 :param name_or_id: Name or ID of the flavor.
370 :param filters: A dictionary of meta data to use for further filtering.
371 :param get_extra: Whether or not the list_flavors call should get the extra
374 :returns: A flavor ``munch.Munch`` or None if no matching flavor is found.
377 return shade_client.get_flavor(name_or_id, filters=filters,
379 except exc.OpenStackCloudException as o_exc:
380 log.error("Error [get_flavor(shade_client, '%s')]. "
381 "Exception message: %s", name_or_id, o_exc.orig_message)
384 def delete_flavor(flavor_id): # pragma: no cover
386 get_nova_client().flavors.delete(flavor_id)
387 except Exception: # pylint: disable=broad-except
388 log.exception("Error [delete_flavor(nova_client, %s)]", flavor_id)
394 def delete_keypair(shade_client, name):
397 :param name: Name of the keypair to delete.
399 :returns: True if delete succeeded, False otherwise.
402 return shade_client.delete_keypair(name)
403 except exc.OpenStackCloudException as o_exc:
404 log.error("Error [delete_neutron_router(shade_client, '%s')]. "
405 "Exception message: %s", name, o_exc.orig_message)
409 # *********************************************
411 # *********************************************
412 def create_neutron_net(shade_client, network_name, shared=False,
413 admin_state_up=True, external=False, provider=None,
415 """Create a neutron network.
417 :param network_name:(string) name of the network being created.
418 :param shared:(bool) whether the network is shared.
419 :param admin_state_up:(bool) set the network administrative state.
420 :param external:(bool) whether this network is externally accessible.
421 :param provider:(dict) a dict of network provider options.
422 :param project_id:(string) specify the project ID this network
423 will be created on (admin-only).
424 :returns:(string) the network id.
427 networks = shade_client.create_network(
428 name=network_name, shared=shared, admin_state_up=admin_state_up,
429 external=external, provider=provider, project_id=project_id)
430 return networks['id']
431 except exc.OpenStackCloudException as o_exc:
432 log.error("Error [create_neutron_net(shade_client)]."
433 "Exception message, '%s'", o_exc.orig_message)
437 def delete_neutron_net(shade_client, network_id):
439 return shade_client.delete_network(network_id)
440 except exc.OpenStackCloudException:
441 log.error("Error [delete_neutron_net(shade_client, '%s')]", network_id)
445 def create_neutron_subnet(shade_client, network_name_or_id, cidr=None,
446 ip_version=4, enable_dhcp=False, subnet_name=None,
447 tenant_id=None, allocation_pools=None,
448 gateway_ip=None, disable_gateway_ip=False,
449 dns_nameservers=None, host_routes=None,
450 ipv6_ra_mode=None, ipv6_address_mode=None,
451 use_default_subnetpool=False):
452 """Create a subnet on a specified network.
454 :param network_name_or_id:(string) the unique name or ID of the
455 attached network. If a non-unique name is
456 supplied, an exception is raised.
457 :param cidr:(string) the CIDR.
458 :param ip_version:(int) the IP version.
459 :param enable_dhcp:(bool) whether DHCP is enable.
460 :param subnet_name:(string) the name of the subnet.
461 :param tenant_id:(string) the ID of the tenant who owns the network.
462 :param allocation_pools: A list of dictionaries of the start and end
463 addresses for the allocation pools.
464 :param gateway_ip:(string) the gateway IP address.
465 :param disable_gateway_ip:(bool) whether gateway IP address is enabled.
466 :param dns_nameservers: A list of DNS name servers for the subnet.
467 :param host_routes: A list of host route dictionaries for the subnet.
468 :param ipv6_ra_mode:(string) IPv6 Router Advertisement mode.
469 Valid values are: 'dhcpv6-stateful',
470 'dhcpv6-stateless', or 'slaac'.
471 :param ipv6_address_mode:(string) IPv6 address mode.
472 Valid values are: 'dhcpv6-stateful',
473 'dhcpv6-stateless', or 'slaac'.
474 :param use_default_subnetpool:(bool) use the default subnetpool for
475 ``ip_version`` to obtain a CIDR. It is
476 required to pass ``None`` to the ``cidr``
477 argument when enabling this option.
478 :returns:(string) the subnet id.
481 subnet = shade_client.create_subnet(
482 network_name_or_id, cidr=cidr, ip_version=ip_version,
483 enable_dhcp=enable_dhcp, subnet_name=subnet_name,
484 tenant_id=tenant_id, allocation_pools=allocation_pools,
485 gateway_ip=gateway_ip, disable_gateway_ip=disable_gateway_ip,
486 dns_nameservers=dns_nameservers, host_routes=host_routes,
487 ipv6_ra_mode=ipv6_ra_mode, ipv6_address_mode=ipv6_address_mode,
488 use_default_subnetpool=use_default_subnetpool)
490 except exc.OpenStackCloudException as o_exc:
491 log.error("Error [create_neutron_subnet(shade_client)]. "
492 "Exception message: %s", o_exc.orig_message)
496 def create_neutron_router(shade_client, name=None, admin_state_up=True,
497 ext_gateway_net_id=None, enable_snat=None,
498 ext_fixed_ips=None, project_id=None):
499 """Create a logical router.
501 :param name:(string) the router name.
502 :param admin_state_up:(bool) the administrative state of the router.
503 :param ext_gateway_net_id:(string) network ID for the external gateway.
504 :param enable_snat:(bool) enable Source NAT (SNAT) attribute.
505 :param ext_fixed_ips: List of dictionaries of desired IP and/or subnet
506 on the external network.
507 :param project_id:(string) project ID for the router.
509 :returns:(string) the router id.
512 router = shade_client.create_router(
513 name, admin_state_up, ext_gateway_net_id, enable_snat,
514 ext_fixed_ips, project_id)
516 except exc.OpenStackCloudException as o_exc:
517 log.error("Error [create_neutron_router(shade_client)]. "
518 "Exception message: %s", o_exc.orig_message)
521 def delete_neutron_router(shade_client, router_id):
523 return shade_client.delete_router(router_id)
524 except exc.OpenStackCloudException as o_exc:
525 log.error("Error [delete_neutron_router(shade_client, '%s')]. "
526 "Exception message: %s", router_id, o_exc.orig_message)
530 def remove_gateway_router(neutron_client, router_id): # pragma: no cover
532 neutron_client.remove_gateway_router(router_id)
534 except Exception: # pylint: disable=broad-except
535 log.error("Error [remove_gateway_router(neutron_client, '%s')]",
540 def remove_router_interface(shade_client, router, subnet_id=None,
542 """Detach a subnet from an internal router interface.
544 At least one of subnet_id or port_id must be supplied. If you specify both
545 subnet and port ID, the subnet ID must correspond to the subnet ID of the
546 first IP address on the port specified by the port ID.
547 Otherwise an error occurs.
549 :param router: The dict object of the router being changed
550 :param subnet_id:(string) The ID of the subnet to use for the interface
551 :param port_id:(string) The ID of the port to use for the interface
552 :returns: True on success
555 shade_client.remove_router_interface(
556 router, subnet_id=subnet_id, port_id=port_id)
558 except exc.OpenStackCloudException as o_exc:
559 log.error("Error [remove_interface_router(shade_client)]. "
560 "Exception message: %s", o_exc.orig_message)
564 def create_floating_ip(shade_client, network_name_or_id=None, server=None,
565 fixed_address=None, nat_destination=None,
566 port=None, wait=False, timeout=60):
567 """Allocate a new floating IP from a network or a pool.
569 :param network_name_or_id: Name or ID of the network
570 that the floating IP should come from.
571 :param server: Server dict for the server to create
572 the IP for and to which it should be attached.
573 :param fixed_address: Fixed IP to attach the floating ip to.
574 :param nat_destination: Name or ID of the network
575 that the fixed IP to attach the floating
577 :param port: The port ID that the floating IP should be
578 attached to. Specifying a port conflicts with specifying a
579 server,fixed_address or nat_destination.
580 :param wait: Whether to wait for the IP to be active.Only applies
581 if a server is provided.
582 :param timeout: How long to wait for the IP to be active.Only
583 applies if a server is provided.
585 :returns:Floating IP id and address
588 fip = shade_client.create_floating_ip(
589 network=network_name_or_id, server=server,
590 fixed_address=fixed_address, nat_destination=nat_destination,
591 port=port, wait=wait, timeout=timeout)
592 return {'fip_addr': fip['floating_ip_address'], 'fip_id': fip['id']}
593 except exc.OpenStackCloudException as o_exc:
594 log.error("Error [create_floating_ip(shade_client)]. "
595 "Exception message: %s", o_exc.orig_message)
598 def delete_floating_ip(shade_client, floating_ip_id, retry=1):
600 return shade_client.delete_floating_ip(floating_ip_id=floating_ip_id,
602 except exc.OpenStackCloudException as o_exc:
603 log.error("Error [delete_floating_ip(shade_client,'%s')]. "
604 "Exception message: %s", floating_ip_id, o_exc.orig_message)
608 def create_security_group_rule(shade_client, secgroup_name_or_id,
609 port_range_min=None, port_range_max=None,
610 protocol=None, remote_ip_prefix=None,
611 remote_group_id=None, direction='ingress',
612 ethertype='IPv4', project_id=None):
613 """Create a new security group rule
615 :param secgroup_name_or_id:(string) The security group name or ID to
616 associate with this security group rule. If a
617 non-unique group name is given, an exception is
619 :param port_range_min:(int) The minimum port number in the range that is
620 matched by the security group rule. If the protocol
621 is TCP or UDP, this value must be less than or equal
622 to the port_range_max attribute value. If nova is
623 used by the cloud provider for security groups, then
624 a value of None will be transformed to -1.
625 :param port_range_max:(int) The maximum port number in the range that is
626 matched by the security group rule. The
627 port_range_min attribute constrains the
628 port_range_max attribute. If nova is used by the
629 cloud provider for security groups, then a value of
630 None will be transformed to -1.
631 :param protocol:(string) The protocol that is matched by the security group
632 rule. Valid values are None, tcp, udp, and icmp.
633 :param remote_ip_prefix:(string) The remote IP prefix to be associated with
634 this security group rule. This attribute matches
635 the specified IP prefix as the source IP address of
637 :param remote_group_id:(string) The remote group ID to be associated with
638 this security group rule.
639 :param direction:(string) Ingress or egress: The direction in which the
640 security group rule is applied.
641 :param ethertype:(string) Must be IPv4 or IPv6, and addresses represented
642 in CIDR must match the ingress or egress rules.
643 :param project_id:(string) Specify the project ID this security group will
644 be created on (admin-only).
646 :returns: True on success.
650 shade_client.create_security_group_rule(
651 secgroup_name_or_id, port_range_min=port_range_min,
652 port_range_max=port_range_max, protocol=protocol,
653 remote_ip_prefix=remote_ip_prefix, remote_group_id=remote_group_id,
654 direction=direction, ethertype=ethertype, project_id=project_id)
656 except exc.OpenStackCloudException as op_exc:
657 log.error("Failed to create_security_group_rule(shade_client). "
658 "Exception message: %s", op_exc.orig_message)
662 def create_security_group_full(shade_client, sg_name,
663 sg_description, project_id=None):
664 security_group = shade_client.get_security_group(sg_name)
667 log.info("Using existing security group '%s'...", sg_name)
668 return security_group['id']
670 log.info("Creating security group '%s'...", sg_name)
672 security_group = shade_client.create_security_group(
673 sg_name, sg_description, project_id=project_id)
674 except (exc.OpenStackCloudException,
675 exc.OpenStackCloudUnavailableFeature) as op_exc:
676 log.error("Error [create_security_group(shade_client, %s, %s)]. "
677 "Exception message: %s", sg_name, sg_description,
681 log.debug("Security group '%s' with ID=%s created successfully.",
682 security_group['name'], security_group['id'])
684 log.debug("Adding ICMP rules in security group '%s'...", sg_name)
685 if not create_security_group_rule(shade_client, security_group['id'],
686 direction='ingress', protocol='icmp'):
687 log.error("Failed to create the security group rule...")
688 shade_client.delete_security_group(sg_name)
691 log.debug("Adding SSH rules in security group '%s'...", sg_name)
692 if not create_security_group_rule(shade_client, security_group['id'],
693 direction='ingress', protocol='tcp',
695 port_range_max='22'):
696 log.error("Failed to create the security group rule...")
697 shade_client.delete_security_group(sg_name)
700 if not create_security_group_rule(shade_client, security_group['id'],
701 direction='egress', protocol='tcp',
703 port_range_max='22'):
704 log.error("Failed to create the security group rule...")
705 shade_client.delete_security_group(sg_name)
707 return security_group['id']
710 # *********************************************
712 # *********************************************
713 def get_image_id(glance_client, image_name): # pragma: no cover
714 images = glance_client.images.list()
715 return next((i.id for i in images if i.name == image_name), None)
718 def create_image(glance_client, image_name, file_path, disk_format,
719 container_format, min_disk, min_ram, protected, tag,
720 public, **kwargs): # pragma: no cover
721 if not os.path.isfile(file_path):
722 log.error("Error: file %s does not exist.", file_path)
725 image_id = get_image_id(glance_client, image_name)
726 if image_id is not None:
727 log.info("Image %s already exists.", image_name)
729 log.info("Creating image '%s' from '%s'...", image_name, file_path)
731 image = glance_client.images.create(
732 name=image_name, visibility=public, disk_format=disk_format,
733 container_format=container_format, min_disk=min_disk,
734 min_ram=min_ram, tags=tag, protected=protected, **kwargs)
736 with open(file_path) as image_data:
737 glance_client.images.upload(image_id, image_data)
739 except Exception: # pylint: disable=broad-except
741 "Error [create_glance_image(glance_client, '%s', '%s', '%s')]",
742 image_name, file_path, public)
746 def delete_image(glance_client, image_id): # pragma: no cover
748 glance_client.images.delete(image_id)
750 except Exception: # pylint: disable=broad-except
751 log.exception("Error [delete_flavor(glance_client, %s)]", image_id)
757 def list_images(shade_client=None):
758 if shade_client is None:
759 shade_client = get_shade_client()
762 return shade_client.list_images()
763 except exc.OpenStackCloudException as o_exc:
764 log.error("Error [list_images(shade_client)]."
765 "Exception message, '%s'", o_exc.orig_message)
769 # *********************************************
771 # *********************************************
772 def get_volume_id(volume_name): # pragma: no cover
773 volumes = get_cinder_client().volumes.list()
774 return next((v.id for v in volumes if v.name == volume_name), None)
777 def create_volume(cinder_client, volume_name, volume_size,
778 volume_image=False): # pragma: no cover
781 volume = cinder_client.volumes.create(name=volume_name,
783 imageRef=volume_image)
785 volume = cinder_client.volumes.create(name=volume_name,
788 except Exception: # pylint: disable=broad-except
789 log.exception("Error [create_volume(cinder_client, %s)]",
790 (volume_name, volume_size))
794 def delete_volume(cinder_client, volume_id,
795 forced=False): # pragma: no cover
799 cinder_client.volumes.detach(volume_id)
800 except Exception: # pylint: disable=broad-except
801 log.error(sys.exc_info()[0])
802 cinder_client.volumes.force_delete(volume_id)
805 volume = get_cinder_client().volumes.get(volume_id)
806 if volume.status.lower() == 'available':
808 cinder_client.volumes.delete(volume_id)
810 except Exception: # pylint: disable=broad-except
811 log.exception("Error [delete_volume(cinder_client, '%s')]", volume_id)
815 def detach_volume(server_id, volume_id): # pragma: no cover
817 get_nova_client().volumes.delete_server_volume(server_id, volume_id)
819 except Exception: # pylint: disable=broad-except
820 log.exception("Error [detach_server_volume(nova_client, '%s', '%s')]",
821 server_id, volume_id)