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_aggregates(nova_client): # pragma: no cover
168 return nova_client.aggregates.list()
169 except Exception: # pylint: disable=broad-except
170 log.exception("Error [get_aggregates(nova_client)]")
173 def get_availability_zones(nova_client): # pragma: no cover
175 return nova_client.availability_zones.list()
176 except Exception: # pylint: disable=broad-except
177 log.exception("Error [get_availability_zones(nova_client)]")
180 def get_availability_zone_names(nova_client): # pragma: no cover
182 return [az.zoneName for az in get_availability_zones(nova_client)]
183 except Exception: # pylint: disable=broad-except
184 log.exception("Error [get_availability_zone_names(nova_client)]")
187 def create_aggregate(nova_client, aggregate_name, av_zone): # pragma: no cover
189 nova_client.aggregates.create(aggregate_name, av_zone)
190 except Exception: # pylint: disable=broad-except
191 log.exception("Error [create_aggregate(nova_client, %s, %s)]",
192 aggregate_name, av_zone)
198 def get_aggregate_id(nova_client, aggregate_name): # pragma: no cover
200 aggregates = get_aggregates(nova_client)
201 _id = next((ag.id for ag in aggregates if ag.name == aggregate_name))
202 except Exception: # pylint: disable=broad-except
203 log.exception("Error [get_aggregate_id(nova_client, %s)]",
209 def add_host_to_aggregate(nova_client, aggregate_name,
210 compute_host): # pragma: no cover
212 aggregate_id = get_aggregate_id(nova_client, aggregate_name)
213 nova_client.aggregates.add_host(aggregate_id, compute_host)
214 except Exception: # pylint: disable=broad-except
215 log.exception("Error [add_host_to_aggregate(nova_client, %s, %s)]",
216 aggregate_name, compute_host)
222 def create_aggregate_with_host(nova_client, aggregate_name, av_zone,
223 compute_host): # pragma: no cover
225 create_aggregate(nova_client, aggregate_name, av_zone)
226 add_host_to_aggregate(nova_client, aggregate_name, compute_host)
227 except Exception: # pylint: disable=broad-except
228 log.exception("Error [create_aggregate_with_host("
229 "nova_client, %s, %s, %s)]",
230 aggregate_name, av_zone, compute_host)
236 def create_keypair(name, key_path=None): # pragma: no cover
238 with open(key_path) as fpubkey:
239 keypair = get_nova_client().keypairs.create(
240 name=name, public_key=fpubkey.read())
242 except Exception: # pylint: disable=broad-except
243 log.exception("Error [create_keypair(nova_client)]")
246 def create_instance(json_body): # pragma: no cover
248 return get_nova_client().servers.create(**json_body)
249 except Exception: # pylint: disable=broad-except
250 log.exception("Error create instance failed")
254 def create_instance_and_wait_for_active(shade_client, name, image,
255 flavor, auto_ip=True, ips=None,
256 ip_pool=None, root_volume=None,
257 terminate_volume=False, wait=True,
258 timeout=180, reuse_ips=True,
259 network=None, boot_from_volume=False,
260 volume_size='20', boot_volume=None,
261 volumes=None, nat_destination=None,
263 """Create a virtual server instance.
265 :param name:(string) Name of the server.
266 :param image:(dict) Image dict, name or ID to boot with. Image is required
267 unless boot_volume is given.
268 :param flavor:(dict) Flavor dict, name or ID to boot onto.
269 :param auto_ip: Whether to take actions to find a routable IP for
271 :param ips: List of IPs to attach to the server.
272 :param ip_pool:(string) Name of the network or floating IP pool to get an
274 :param root_volume:(string) Name or ID of a volume to boot from.
275 (defaults to None - deprecated, use boot_volume)
276 :param boot_volume:(string) Name or ID of a volume to boot from.
277 :param terminate_volume:(bool) If booting from a volume, whether it should
278 be deleted when the server is destroyed.
279 :param volumes:(optional) A list of volumes to attach to the server.
280 :param wait:(optional) Wait for the address to appear as assigned to the server.
281 :param timeout: Seconds to wait, defaults to 60.
282 :param reuse_ips:(bool)Whether to attempt to reuse pre-existing
283 floating ips should a floating IP be needed.
284 :param network:(dict) Network dict or name or ID to attach the server to.
285 Mutually exclusive with the nics parameter. Can also be be
286 a list of network names or IDs or network dicts.
287 :param boot_from_volume:(bool) Whether to boot from volume. 'boot_volume'
288 implies True, but boot_from_volume=True with
289 no boot_volume is valid and will create a
290 volume from the image and use that.
291 :param volume_size: When booting an image from volume, how big should
292 the created volume be?
293 :param nat_destination: Which network should a created floating IP
294 be attached to, if it's not possible to infer from
295 the cloud's configuration.
296 :param meta:(optional) A dict of arbitrary key/value metadata to store for
297 this server. Both keys and values must be <=255 characters.
298 :param reservation_id: A UUID for the set of servers being requested.
299 :param min_count:(optional extension) The minimum number of servers to
301 :param max_count:(optional extension) The maximum number of servers to
303 :param security_groups: A list of security group names.
304 :param userdata: User data to pass to be exposed by the metadata server
305 this can be a file type object as well or a string.
306 :param key_name:(optional extension) Name of previously created keypair to
307 inject into the instance.
308 :param availability_zone: Name of the availability zone for instance
310 :param block_device_mapping:(optional) A dict of block device mappings for
312 :param block_device_mapping_v2:(optional) A dict of block device mappings
314 :param nics:(optional extension) An ordered list of nics to be added to
315 this server, with information about connected networks, fixed
317 :param scheduler_hints:(optional extension) Arbitrary key-value pairs
318 specified by the client to help boot an instance.
319 :param config_drive:(optional extension) Value for config drive either
320 boolean, or volume-id.
321 :param disk_config:(optional extension) Control how the disk is partitioned
322 when the server is created. Possible values are 'AUTO'
324 :param admin_pass:(optional extension) Add a user supplied admin password.
326 :returns: The created server.
329 return shade_client.create_server(
330 name, image, flavor, auto_ip=auto_ip, ips=ips, ip_pool=ip_pool,
331 root_volume=root_volume, terminate_volume=terminate_volume,
332 wait=wait, timeout=timeout, reuse_ips=reuse_ips, network=network,
333 boot_from_volume=boot_from_volume, volume_size=volume_size,
334 boot_volume=boot_volume, volumes=volumes,
335 nat_destination=nat_destination, **kwargs)
336 except exc.OpenStackCloudException as o_exc:
337 log.error("Error [create_instance(shade_client)]. "
338 "Exception message, '%s'", o_exc.orig_message)
341 def attach_server_volume(server_id, volume_id,
342 device=None): # pragma: no cover
344 get_nova_client().volumes.create_server_volume(server_id,
346 except Exception: # pylint: disable=broad-except
347 log.exception("Error [attach_server_volume(nova_client, '%s', '%s')]",
348 server_id, volume_id)
354 def delete_instance(shade_client, name_or_id, wait=False, timeout=180,
355 delete_ips=False, delete_ip_retry=1):
356 """Delete a server instance.
358 :param name_or_id: name or ID of the server to delete
359 :param wait:(bool) If true, waits for server to be deleted.
360 :param timeout:(int) Seconds to wait for server deletion.
361 :param delete_ips:(bool) If true, deletes any floating IPs associated with
363 :param delete_ip_retry:(int) Number of times to retry deleting
364 any floating ips, should the first try be
366 :returns: True if delete succeeded, False otherwise.
369 return shade_client.delete_server(
370 name_or_id, wait=wait, timeout=timeout, delete_ips=delete_ips,
371 delete_ip_retry=delete_ip_retry)
372 except exc.OpenStackCloudException as o_exc:
373 log.error("Error [delete_instance(shade_client, '%s')]. "
374 "Exception message: %s", name_or_id,
379 def remove_host_from_aggregate(nova_client, aggregate_name,
380 compute_host): # pragma: no cover
382 aggregate_id = get_aggregate_id(nova_client, aggregate_name)
383 nova_client.aggregates.remove_host(aggregate_id, compute_host)
384 except Exception: # pylint: disable=broad-except
385 log.exception("Error remove_host_from_aggregate(nova_client, %s, %s)",
386 aggregate_name, compute_host)
392 def remove_hosts_from_aggregate(nova_client,
393 aggregate_name): # pragma: no cover
394 aggregate_id = get_aggregate_id(nova_client, aggregate_name)
395 hosts = nova_client.aggregates.get(aggregate_id).hosts
397 all(remove_host_from_aggregate(nova_client, aggregate_name, host)
401 def delete_aggregate(nova_client, aggregate_name): # pragma: no cover
403 remove_hosts_from_aggregate(nova_client, aggregate_name)
404 nova_client.aggregates.delete(aggregate_name)
405 except Exception: # pylint: disable=broad-except
406 log.exception("Error [delete_aggregate(nova_client, %s)]",
413 def get_server_by_name(name): # pragma: no cover
415 return get_nova_client().servers.list(search_opts={'name': name})[0]
417 log.exception('Failed to get nova client')
421 def create_flavor(name, ram, vcpus, disk, **kwargs): # pragma: no cover
423 return get_nova_client().flavors.create(name, ram, vcpus,
425 except Exception: # pylint: disable=broad-except
426 log.exception("Error [create_flavor(nova_client, %s, %s, %s, %s, %s)]",
427 name, ram, disk, vcpus, kwargs['is_public'])
431 def get_image_by_name(name): # pragma: no cover
432 images = get_nova_client().images.list()
434 return next((a for a in images if a.name == name))
435 except StopIteration:
436 log.exception('No image matched')
439 def get_flavor_id(nova_client, flavor_name): # pragma: no cover
440 flavors = nova_client.flavors.list(detailed=True)
443 if f.name == flavor_name:
449 def get_flavor_by_name(name): # pragma: no cover
450 flavors = get_nova_client().flavors.list()
452 return next((a for a in flavors if a.name == name))
453 except StopIteration:
454 log.exception('No flavor matched')
457 def check_status(status, name, iterations, interval): # pragma: no cover
458 for _ in range(iterations):
460 server = get_server_by_name(name)
462 log.error('Cannot found %s server', name)
465 if server.status == status:
472 def delete_flavor(flavor_id): # pragma: no cover
474 get_nova_client().flavors.delete(flavor_id)
475 except Exception: # pylint: disable=broad-except
476 log.exception("Error [delete_flavor(nova_client, %s)]", flavor_id)
482 def delete_keypair(nova_client, key): # pragma: no cover
484 nova_client.keypairs.delete(key=key)
486 except Exception: # pylint: disable=broad-except
487 log.exception("Error [delete_keypair(nova_client)]")
491 # *********************************************
493 # *********************************************
494 def create_neutron_net(shade_client, network_name, shared=False,
495 admin_state_up=True, external=False, provider=None,
497 """Create a neutron network.
499 :param network_name:(string) name of the network being created.
500 :param shared:(bool) whether the network is shared.
501 :param admin_state_up:(bool) set the network administrative state.
502 :param external:(bool) whether this network is externally accessible.
503 :param provider:(dict) a dict of network provider options.
504 :param project_id:(string) specify the project ID this network
505 will be created on (admin-only).
506 :returns:(string) the network id.
509 networks = shade_client.create_network(
510 name=network_name, shared=shared, admin_state_up=admin_state_up,
511 external=external, provider=provider, project_id=project_id)
512 return networks['id']
513 except exc.OpenStackCloudException as o_exc:
514 log.error("Error [create_neutron_net(shade_client)]."
515 "Exception message, '%s'", o_exc.orig_message)
519 def delete_neutron_net(shade_client, network_id):
521 return shade_client.delete_network(network_id)
522 except exc.OpenStackCloudException:
523 log.error("Error [delete_neutron_net(shade_client, '%s')]", network_id)
527 def create_neutron_subnet(shade_client, network_name_or_id, cidr=None,
528 ip_version=4, enable_dhcp=False, subnet_name=None,
529 tenant_id=None, allocation_pools=None,
530 gateway_ip=None, disable_gateway_ip=False,
531 dns_nameservers=None, host_routes=None,
532 ipv6_ra_mode=None, ipv6_address_mode=None,
533 use_default_subnetpool=False):
534 """Create a subnet on a specified network.
536 :param network_name_or_id:(string) the unique name or ID of the
537 attached network. If a non-unique name is
538 supplied, an exception is raised.
539 :param cidr:(string) the CIDR.
540 :param ip_version:(int) the IP version.
541 :param enable_dhcp:(bool) whether DHCP is enable.
542 :param subnet_name:(string) the name of the subnet.
543 :param tenant_id:(string) the ID of the tenant who owns the network.
544 :param allocation_pools: A list of dictionaries of the start and end
545 addresses for the allocation pools.
546 :param gateway_ip:(string) the gateway IP address.
547 :param disable_gateway_ip:(bool) whether gateway IP address is enabled.
548 :param dns_nameservers: A list of DNS name servers for the subnet.
549 :param host_routes: A list of host route dictionaries for the subnet.
550 :param ipv6_ra_mode:(string) IPv6 Router Advertisement mode.
551 Valid values are: 'dhcpv6-stateful',
552 'dhcpv6-stateless', or 'slaac'.
553 :param ipv6_address_mode:(string) IPv6 address mode.
554 Valid values are: 'dhcpv6-stateful',
555 'dhcpv6-stateless', or 'slaac'.
556 :param use_default_subnetpool:(bool) use the default subnetpool for
557 ``ip_version`` to obtain a CIDR. It is
558 required to pass ``None`` to the ``cidr``
559 argument when enabling this option.
560 :returns:(string) the subnet id.
563 subnet = shade_client.create_subnet(
564 network_name_or_id, cidr=cidr, ip_version=ip_version,
565 enable_dhcp=enable_dhcp, subnet_name=subnet_name,
566 tenant_id=tenant_id, allocation_pools=allocation_pools,
567 gateway_ip=gateway_ip, disable_gateway_ip=disable_gateway_ip,
568 dns_nameservers=dns_nameservers, host_routes=host_routes,
569 ipv6_ra_mode=ipv6_ra_mode, ipv6_address_mode=ipv6_address_mode,
570 use_default_subnetpool=use_default_subnetpool)
572 except exc.OpenStackCloudException as o_exc:
573 log.error("Error [create_neutron_subnet(shade_client)]. "
574 "Exception message: %s", o_exc.orig_message)
578 def create_neutron_router(shade_client, name=None, admin_state_up=True,
579 ext_gateway_net_id=None, enable_snat=None,
580 ext_fixed_ips=None, project_id=None):
581 """Create a logical router.
583 :param name:(string) the router name.
584 :param admin_state_up:(bool) the administrative state of the router.
585 :param ext_gateway_net_id:(string) network ID for the external gateway.
586 :param enable_snat:(bool) enable Source NAT (SNAT) attribute.
587 :param ext_fixed_ips: List of dictionaries of desired IP and/or subnet
588 on the external network.
589 :param project_id:(string) project ID for the router.
591 :returns:(string) the router id.
594 router = shade_client.create_router(
595 name, admin_state_up, ext_gateway_net_id, enable_snat,
596 ext_fixed_ips, project_id)
598 except exc.OpenStackCloudException as o_exc:
599 log.error("Error [create_neutron_router(shade_client)]. "
600 "Exception message: %s", o_exc.orig_message)
603 def delete_neutron_router(shade_client, router_id):
605 return shade_client.delete_router(router_id)
606 except exc.OpenStackCloudException as o_exc:
607 log.error("Error [delete_neutron_router(shade_client, '%s')]. "
608 "Exception message: %s", router_id, o_exc.orig_message)
612 def remove_gateway_router(neutron_client, router_id): # pragma: no cover
614 neutron_client.remove_gateway_router(router_id)
616 except Exception: # pylint: disable=broad-except
617 log.error("Error [remove_gateway_router(neutron_client, '%s')]",
622 def remove_router_interface(shade_client, router, subnet_id=None,
624 """Detach a subnet from an internal router interface.
626 At least one of subnet_id or port_id must be supplied. If you specify both
627 subnet and port ID, the subnet ID must correspond to the subnet ID of the
628 first IP address on the port specified by the port ID.
629 Otherwise an error occurs.
631 :param router: The dict object of the router being changed
632 :param subnet_id:(string) The ID of the subnet to use for the interface
633 :param port_id:(string) The ID of the port to use for the interface
634 :returns: True on success
637 shade_client.remove_router_interface(
638 router, subnet_id=subnet_id, port_id=port_id)
640 except exc.OpenStackCloudException as o_exc:
641 log.error("Error [remove_interface_router(shade_client)]. "
642 "Exception message: %s", o_exc.orig_message)
646 def create_floating_ip(shade_client, network_name_or_id=None, server=None,
647 fixed_address=None, nat_destination=None,
648 port=None, wait=False, timeout=60):
649 """Allocate a new floating IP from a network or a pool.
651 :param network_name_or_id: Name or ID of the network
652 that the floating IP should come from.
653 :param server: Server dict for the server to create
654 the IP for and to which it should be attached.
655 :param fixed_address: Fixed IP to attach the floating ip to.
656 :param nat_destination: Name or ID of the network
657 that the fixed IP to attach the floating
659 :param port: The port ID that the floating IP should be
660 attached to. Specifying a port conflicts with specifying a
661 server,fixed_address or nat_destination.
662 :param wait: Whether to wait for the IP to be active.Only applies
663 if a server is provided.
664 :param timeout: How long to wait for the IP to be active.Only
665 applies if a server is provided.
667 :returns:Floating IP id and address
670 fip = shade_client.create_floating_ip(
671 network=network_name_or_id, server=server,
672 fixed_address=fixed_address, nat_destination=nat_destination,
673 port=port, wait=wait, timeout=timeout)
674 return {'fip_addr': fip['floating_ip_address'], 'fip_id': fip['id']}
675 except exc.OpenStackCloudException as o_exc:
676 log.error("Error [create_floating_ip(shade_client)]. "
677 "Exception message: %s", o_exc.orig_message)
680 def delete_floating_ip(shade_client, floating_ip_id, retry=1):
682 return shade_client.delete_floating_ip(floating_ip_id=floating_ip_id,
684 except exc.OpenStackCloudException as o_exc:
685 log.error("Error [delete_floating_ip(shade_client,'%s')]. "
686 "Exception message: %s", floating_ip_id, o_exc.orig_message)
690 def create_security_group_rule(shade_client, secgroup_name_or_id,
691 port_range_min=None, port_range_max=None,
692 protocol=None, remote_ip_prefix=None,
693 remote_group_id=None, direction='ingress',
694 ethertype='IPv4', project_id=None):
695 """Create a new security group rule
697 :param secgroup_name_or_id:(string) The security group name or ID to
698 associate with this security group rule. If a
699 non-unique group name is given, an exception is
701 :param port_range_min:(int) The minimum port number in the range that is
702 matched by the security group rule. If the protocol
703 is TCP or UDP, this value must be less than or equal
704 to the port_range_max attribute value. If nova is
705 used by the cloud provider for security groups, then
706 a value of None will be transformed to -1.
707 :param port_range_max:(int) The maximum port number in the range that is
708 matched by the security group rule. The
709 port_range_min attribute constrains the
710 port_range_max attribute. If nova is used by the
711 cloud provider for security groups, then a value of
712 None will be transformed to -1.
713 :param protocol:(string) The protocol that is matched by the security group
714 rule. Valid values are None, tcp, udp, and icmp.
715 :param remote_ip_prefix:(string) The remote IP prefix to be associated with
716 this security group rule. This attribute matches
717 the specified IP prefix as the source IP address of
719 :param remote_group_id:(string) The remote group ID to be associated with
720 this security group rule.
721 :param direction:(string) Ingress or egress: The direction in which the
722 security group rule is applied.
723 :param ethertype:(string) Must be IPv4 or IPv6, and addresses represented
724 in CIDR must match the ingress or egress rules.
725 :param project_id:(string) Specify the project ID this security group will
726 be created on (admin-only).
728 :returns: True on success.
732 shade_client.create_security_group_rule(
733 secgroup_name_or_id, port_range_min=port_range_min,
734 port_range_max=port_range_max, protocol=protocol,
735 remote_ip_prefix=remote_ip_prefix, remote_group_id=remote_group_id,
736 direction=direction, ethertype=ethertype, project_id=project_id)
738 except exc.OpenStackCloudException as op_exc:
739 log.error("Failed to create_security_group_rule(shade_client). "
740 "Exception message: %s", op_exc.orig_message)
744 def create_security_group_full(shade_client, sg_name,
745 sg_description, project_id=None):
746 security_group = shade_client.get_security_group(sg_name)
749 log.info("Using existing security group '%s'...", sg_name)
750 return security_group['id']
752 log.info("Creating security group '%s'...", sg_name)
754 security_group = shade_client.create_security_group(
755 sg_name, sg_description, project_id=project_id)
756 except (exc.OpenStackCloudException,
757 exc.OpenStackCloudUnavailableFeature) as op_exc:
758 log.error("Error [create_security_group(shade_client, %s, %s)]. "
759 "Exception message: %s", sg_name, sg_description,
763 log.debug("Security group '%s' with ID=%s created successfully.",
764 security_group['name'], security_group['id'])
766 log.debug("Adding ICMP rules in security group '%s'...", sg_name)
767 if not create_security_group_rule(shade_client, security_group['id'],
768 direction='ingress', protocol='icmp'):
769 log.error("Failed to create the security group rule...")
770 shade_client.delete_security_group(sg_name)
773 log.debug("Adding SSH rules in security group '%s'...", sg_name)
774 if not create_security_group_rule(shade_client, security_group['id'],
775 direction='ingress', protocol='tcp',
777 port_range_max='22'):
778 log.error("Failed to create the security group rule...")
779 shade_client.delete_security_group(sg_name)
782 if not create_security_group_rule(shade_client, security_group['id'],
783 direction='egress', protocol='tcp',
785 port_range_max='22'):
786 log.error("Failed to create the security group rule...")
787 shade_client.delete_security_group(sg_name)
789 return security_group['id']
792 # *********************************************
794 # *********************************************
795 def get_image_id(glance_client, image_name): # pragma: no cover
796 images = glance_client.images.list()
797 return next((i.id for i in images if i.name == image_name), None)
800 def create_image(glance_client, image_name, file_path, disk_format,
801 container_format, min_disk, min_ram, protected, tag,
802 public, **kwargs): # pragma: no cover
803 if not os.path.isfile(file_path):
804 log.error("Error: file %s does not exist.", file_path)
807 image_id = get_image_id(glance_client, image_name)
808 if image_id is not None:
809 log.info("Image %s already exists.", image_name)
811 log.info("Creating image '%s' from '%s'...", image_name, file_path)
813 image = glance_client.images.create(
814 name=image_name, visibility=public, disk_format=disk_format,
815 container_format=container_format, min_disk=min_disk,
816 min_ram=min_ram, tags=tag, protected=protected, **kwargs)
818 with open(file_path) as image_data:
819 glance_client.images.upload(image_id, image_data)
821 except Exception: # pylint: disable=broad-except
823 "Error [create_glance_image(glance_client, '%s', '%s', '%s')]",
824 image_name, file_path, public)
828 def delete_image(glance_client, image_id): # pragma: no cover
830 glance_client.images.delete(image_id)
832 except Exception: # pylint: disable=broad-except
833 log.exception("Error [delete_flavor(glance_client, %s)]", image_id)
839 def list_images(shade_client=None):
840 if shade_client is None:
841 shade_client = get_shade_client()
844 return shade_client.list_images()
845 except exc.OpenStackCloudException as o_exc:
846 log.error("Error [list_images(shade_client)]."
847 "Exception message, '%s'", o_exc.orig_message)
851 # *********************************************
853 # *********************************************
854 def get_volume_id(volume_name): # pragma: no cover
855 volumes = get_cinder_client().volumes.list()
856 return next((v.id for v in volumes if v.name == volume_name), None)
859 def create_volume(cinder_client, volume_name, volume_size,
860 volume_image=False): # pragma: no cover
863 volume = cinder_client.volumes.create(name=volume_name,
865 imageRef=volume_image)
867 volume = cinder_client.volumes.create(name=volume_name,
870 except Exception: # pylint: disable=broad-except
871 log.exception("Error [create_volume(cinder_client, %s)]",
872 (volume_name, volume_size))
876 def delete_volume(cinder_client, volume_id,
877 forced=False): # pragma: no cover
881 cinder_client.volumes.detach(volume_id)
882 except Exception: # pylint: disable=broad-except
883 log.error(sys.exc_info()[0])
884 cinder_client.volumes.force_delete(volume_id)
887 volume = get_cinder_client().volumes.get(volume_id)
888 if volume.status.lower() == 'available':
890 cinder_client.volumes.delete(volume_id)
892 except Exception: # pylint: disable=broad-except
893 log.exception("Error [delete_volume(cinder_client, '%s')]", volume_id)
897 def detach_volume(server_id, volume_id): # pragma: no cover
899 get_nova_client().volumes.delete_server_volume(server_id, volume_id)
901 except Exception: # pylint: disable=broad-except
902 log.exception("Error [detach_server_volume(nova_client, '%s', '%s')]",
903 server_id, volume_id)