openstacksdk/openstack/cloud/_compute.py

1779 lines
68 KiB
Python

# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
# import types so that we can reference ListType in sphinx param declarations.
# We can't just use list, because sphinx gets confused by
# openstack.resource.Resource.list and openstack.resource2.Resource.list
import base64
import datetime
import functools
import operator
import threading
import time
import types # noqa
import iso8601
from openstack.cloud import _normalize
from openstack.cloud import _utils
from openstack.cloud import exc
from openstack.cloud import meta
from openstack.compute.v2 import quota_set as _qs
from openstack.compute.v2 import server as _server
from openstack import exceptions
from openstack import proxy
from openstack import utils
class ComputeCloudMixin(_normalize.Normalizer):
def __init__(self):
self._servers = None
self._servers_time = 0
self._servers_lock = threading.Lock()
@property
def _compute_region(self):
# This is only used in exception messages. Can we get rid of it?
return self.config.get_region_name('compute')
def get_flavor_name(self, flavor_id):
"""Get the name of a flavor.
:param flavor_id: ID of the flavor.
:returns: The name of the flavor if a match if found, else None.
"""
flavor = self.get_flavor(flavor_id, get_extra=False)
if flavor:
return flavor['name']
return None
def get_flavor_by_ram(self, ram, include=None, get_extra=True):
"""Get a flavor based on amount of RAM available.
Finds the flavor with the least amount of RAM that is at least
as much as the specified amount. If `include` is given, further
filter based on matching flavor name.
:param int ram: Minimum amount of RAM.
:param string include: If given, will return a flavor whose name
contains this string as a substring.
:param get_extra:
:returns: A compute ``Flavor`` object.
:raises: :class:`~openstack.exceptions.OpenStackCloudException` if no
matching flavour could be found.
"""
flavors = self.list_flavors(get_extra=get_extra)
for flavor in sorted(flavors, key=operator.itemgetter('ram')):
if (flavor['ram'] >= ram
and (not include or include in flavor['name'])):
return flavor
raise exc.OpenStackCloudException(
"Could not find a flavor with {ram} and '{include}'".format(
ram=ram, include=include))
@_utils.cache_on_arguments()
def _nova_extensions(self):
extensions = set([e.alias for e in self.compute.extensions()])
return extensions
def _has_nova_extension(self, extension_name):
return extension_name in self._nova_extensions()
def search_keypairs(self, name_or_id=None, filters=None):
"""Search keypairs.
:param name_or_id:
:param filters:
:returns: A list of compute ``Keypair`` objects matching the search
criteria.
"""
keypairs = self.list_keypairs(
filters=filters if isinstance(filters, dict) else None
)
return _utils._filter_list(keypairs, name_or_id, filters)
def search_flavors(self, name_or_id=None, filters=None, get_extra=True):
"""Search flavors.
:param name_or_id:
:param flavors:
:param get_extra:
:returns: A list of compute ``Flavor`` objects matching the search
criteria.
"""
flavors = self.list_flavors(get_extra=get_extra)
return _utils._filter_list(flavors, name_or_id, filters)
def search_servers(
self, name_or_id=None, filters=None, detailed=False,
all_projects=False, bare=False,
):
"""Search servers.
:param name_or_id:
:param filters:
:param detailed:
:param all_projects:
:param bare:
:returns: A list of compute ``Server`` objects matching the search
criteria.
"""
servers = self.list_servers(
detailed=detailed, all_projects=all_projects, bare=bare)
return _utils._filter_list(servers, name_or_id, filters)
def search_server_groups(self, name_or_id=None, filters=None):
"""Search server groups.
:param name_or_id: Name or unique ID of the server group(s).
:param filters: A dict containing additional filters to use.
:returns: A list of compute ``ServerGroup`` objects matching the search
criteria.
:raises: ``OpenStackCloudException``: if something goes wrong during
the OpenStack API call.
"""
server_groups = self.list_server_groups()
return _utils._filter_list(server_groups, name_or_id, filters)
def list_keypairs(self, filters=None):
"""List all available keypairs.
:param filters:
:returns: A list of compute ``Keypair`` objects.
"""
if not filters:
filters = {}
return list(self.compute.keypairs(**filters))
@_utils.cache_on_arguments()
def list_availability_zone_names(self, unavailable=False):
"""List names of availability zones.
:param bool unavailable: Whether or not to include unavailable zones
in the output. Defaults to False.
:returns: A list of availability zone names, or an empty list if the
list could not be fetched.
"""
try:
zones = self.compute.availability_zones()
ret = []
for zone in zones:
if zone.state['available'] or unavailable:
ret.append(zone.name)
return ret
except exceptions.SDKException:
self.log.debug(
"Availability zone list could not be fetched",
exc_info=True)
return []
@_utils.cache_on_arguments()
def list_flavors(self, get_extra=False):
"""List all available flavors.
:param get_extra: Whether or not to fetch extra specs for each flavor.
Defaults to True. Default behavior value can be overridden in
clouds.yaml by setting openstack.cloud.get_extra_specs to False.
:returns: A list of compute ``Flavor`` objects.
"""
return list(self.compute.flavors(
details=True, get_extra_specs=get_extra))
def list_server_security_groups(self, server):
"""List all security groups associated with the given server.
:returns: A list of security group dictionary objects.
"""
# Don't even try if we're a cloud that doesn't have them
if not self._has_secgroups():
return []
server = self.compute.get_server(server)
server.fetch_security_groups(self.compute)
return server.security_groups
def _get_server_security_groups(self, server, security_groups):
if not self._has_secgroups():
raise exc.OpenStackCloudUnavailableFeature(
"Unavailable feature: security groups"
)
if not isinstance(server, dict):
server = self.get_server(server, bare=True)
if server is None:
self.log.debug('Server %s not found', server)
return None, None
if not isinstance(security_groups, (list, tuple)):
security_groups = [security_groups]
sec_group_objs = []
for sg in security_groups:
if not isinstance(sg, dict):
sg = self.get_security_group(sg)
if sg is None:
self.log.debug('Security group %s not found for adding',
sg)
return None, None
sec_group_objs.append(sg)
return server, sec_group_objs
def add_server_security_groups(self, server, security_groups):
"""Add security groups to a server.
Add existing security groups to an existing server. If the security
groups are already present on the server this will continue unaffected.
:returns: False if server or security groups are undefined, True
otherwise.
:raises: ``OpenStackCloudException``, on operation error.
"""
server, security_groups = self._get_server_security_groups(
server, security_groups)
if not (server and security_groups):
return False
for sg in security_groups:
self.compute.add_security_group_to_server(server, sg)
return True
def remove_server_security_groups(self, server, security_groups):
"""Remove security groups from a server
Remove existing security groups from an existing server. If the
security groups are not present on the server this will continue
unaffected.
:returns: False if server or security groups are undefined, True
otherwise.
:raises: ``OpenStackCloudException``, on operation error.
"""
server, security_groups = self._get_server_security_groups(
server, security_groups)
if not (server and security_groups):
return False
ret = True
for sg in security_groups:
try:
self.compute.remove_security_group_from_server(server, sg)
except exceptions.ResourceNotFound:
# NOTE(jamielennox): Is this ok? If we remove something that
# isn't present should we just conclude job done or is that an
# error? Nova returns ok if you try to add a group twice.
self.log.debug(
"The security group %s was not present on server %s so "
"no action was performed", sg.name, server.name)
ret = False
return ret
def list_servers(
self,
detailed=False,
all_projects=False,
bare=False,
filters=None,
):
"""List all available servers.
:param detailed: Whether or not to add detailed additional information.
Defaults to False.
:param all_projects: Whether to list servers from all projects or just
the current auth scoped project.
:param bare: Whether to skip adding any additional information to the
server record. Defaults to False, meaning the addresses dict will
be populated as needed from neutron. Setting to True implies
detailed = False.
:param filters: Additional query parameters passed to the API server.
:returns: A list of compute ``Server`` objects.
"""
# If pushdown filters are specified and we do not have batched caching
# enabled, bypass local caching and push down the filters.
if filters and self._SERVER_AGE == 0:
return self._list_servers(
detailed=detailed,
all_projects=all_projects,
bare=bare,
filters=filters,
)
if (time.time() - self._servers_time) >= self._SERVER_AGE:
# Since we're using cached data anyway, we don't need to
# have more than one thread actually submit the list
# servers task. Let the first one submit it while holding
# a lock, and the non-blocking acquire method will cause
# subsequent threads to just skip this and use the old
# data until it succeeds.
# Initially when we never got data, block to retrieve some data.
first_run = self._servers is None
if self._servers_lock.acquire(first_run):
try:
if not (first_run and self._servers is not None):
self._servers = self._list_servers(
detailed=detailed,
all_projects=all_projects,
bare=bare)
self._servers_time = time.time()
finally:
self._servers_lock.release()
# Wrap the return with filter_list so that if filters were passed
# but we were batching/caching and thus always fetching the whole
# list from the cloud, we still return a filtered list.
return _utils._filter_list(self._servers, None, filters)
def _list_servers(self, detailed=False, all_projects=False, bare=False,
filters=None):
filters = filters or {}
return [
self._expand_server(server, detailed, bare)
for server in self.compute.servers(
all_projects=all_projects,
**filters)
]
def list_server_groups(self):
"""List all available server groups.
:returns: A list of compute ``ServerGroup`` objects.
"""
return list(self.compute.server_groups())
def get_compute_limits(self, name_or_id=None):
"""Get absolute compute limits for a project
:param name_or_id: (optional) project name or ID to get limits for
if different from the current project
:raises: OpenStackCloudException if it's not a valid project
:returns: A compute
:class:`~openstack.compute.v2.limits.Limits.AbsoluteLimits` object.
"""
params = {}
project_id = None
if name_or_id:
proj = self.get_project(name_or_id)
if not proj:
raise exc.OpenStackCloudException("project does not exist")
project_id = proj.id
params['tenant_id'] = project_id
return self.compute.get_limits(**params).absolute
def get_keypair(self, name_or_id, filters=None):
"""Get a keypair by name or ID.
:param name_or_id: Name or ID of the keypair.
:param filters: A dictionary of meta data to use for further filtering.
Elements of this dictionary may, themselves, be dictionaries.
Example::
{
'last_name': 'Smith',
'other': {
'gender': 'Female'
}
}
OR
A string containing a jmespath expression for further filtering.
Example:: "[?last_name==`Smith`] | [?other.gender]==`Female`]"
:returns: A compute ``Keypair`` object if found, else None.
"""
return _utils._get_entity(self, 'keypair', name_or_id, filters)
def get_flavor(self, name_or_id, filters=None, get_extra=True):
"""Get a flavor by name or ID.
:param name_or_id: Name or ID of the flavor.
:param filters: A dictionary of meta data to use for further filtering.
Elements of this dictionary may, themselves, be dictionaries.
Example::
{
'last_name': 'Smith',
'other': {
'gender': 'Female'
}
}
OR
A string containing a jmespath expression for further filtering.
Example:: "[?last_name==`Smith`] | [?other.gender]==`Female`]"
:param get_extra: Whether or not the list_flavors call should get the
extra flavor specs.
:returns: A compute ``Flavor`` object if found, else None.
"""
if not filters:
filters = {}
flavor = self.compute.find_flavor(
name_or_id, get_extra_specs=get_extra,
ignore_missing=True, **filters)
return flavor
def get_flavor_by_id(self, id, get_extra=False):
""" Get a flavor by ID
:param id: ID of the flavor.
:param get_extra: Whether or not the list_flavors call should get the
extra flavor specs.
:returns: A compute ``Flavor`` object if found, else None.
"""
return self.compute.get_flavor(id, get_extra_specs=get_extra)
def get_server_console(self, server, length=None):
"""Get the console log for a server.
:param server: The server to fetch the console log for. Can be either
a server dict or the Name or ID of the server.
:param int length: The number of lines you would like to retrieve from
the end of the log. (optional, defaults to all)
:returns: A string containing the text of the console log or an
empty string if the cloud does not support console logs.
:raises: OpenStackCloudException if an invalid server argument is given
or if something else unforseen happens
"""
if not isinstance(server, dict):
server = self.get_server(server, bare=True)
if not server:
raise exc.OpenStackCloudException(
"Console log requested for invalid server")
try:
return self._get_server_console_output(server['id'], length)
except exc.OpenStackCloudBadRequest:
return ""
def _get_server_console_output(self, server_id, length=None):
output = self.compute.get_server_console_output(
server=server_id,
length=length
)
if 'output' in output:
return output['output']
def get_server(
self,
name_or_id=None,
filters=None,
detailed=False,
bare=False,
all_projects=False,
):
"""Get a server by name or ID.
:param name_or_id: Name or ID of the server.
:param filters:
A dictionary of meta data to use for further filtering. Elements
of this dictionary may, themselves, be dictionaries. Example::
{
'last_name': 'Smith',
'other': {
'gender': 'Female'
}
}
OR
A string containing a jmespath expression for further filtering.
Example:: "[?last_name==`Smith`] | [?other.gender]==`Female`]"
:param detailed: Whether or not to add detailed additional information.
Defaults to False.
:param bare: Whether to skip adding any additional information to the
server record. Defaults to False, meaning the addresses dict will
be populated as needed from neutron. Setting to True implies
detailed = False.
:param all_projects: Whether to get server from all projects or just
the current auth scoped project.
:returns: A compute ``Server`` object if found, else None.
"""
searchfunc = functools.partial(self.search_servers,
detailed=detailed, bare=True,
all_projects=all_projects)
server = _utils._get_entity(self, searchfunc, name_or_id, filters)
return self._expand_server(server, detailed, bare)
def _expand_server(self, server, detailed, bare):
if bare or not server:
return server
elif detailed:
return meta.get_hostvars_from_server(self, server)
else:
return meta.add_server_interfaces(self, server)
def get_server_by_id(self, id):
"""Get a server by ID.
:param id: ID of the server.
:returns: A compute ``Server`` object if found, else None.
"""
try:
server = self.compute.get_server(id)
return meta.add_server_interfaces(self, server)
except exceptions.ResourceNotFound:
return None
def get_server_group(self, name_or_id=None, filters=None):
"""Get a server group by name or ID.
:param name_or_id: Name or ID of the server group.
:param filters: A dictionary of meta data to use for further filtering.
Elements of this dictionary may, themselves, be dictionaries.
Example::
{
'policy': 'affinity',
}
OR
A string containing a jmespath expression for further filtering.
Example:: "[?last_name==`Smith`] | [?other.gender]==`Female`]"
:returns: A compute ``ServerGroup`` object if found, else None.
"""
return _utils._get_entity(self, 'server_group', name_or_id,
filters)
def create_keypair(self, name, public_key=None):
"""Create a new keypair.
:param name: Name of the keypair being created.
:param public_key: Public key for the new keypair.
:returns: The created compute ``Keypair`` object.
:raises: OpenStackCloudException on operation error.
"""
keypair = {
'name': name,
}
if public_key:
keypair['public_key'] = public_key
return self.compute.create_keypair(**keypair)
def delete_keypair(self, name):
"""Delete a keypair.
:param name: Name of the keypair to delete.
:returns: True if delete succeeded, False otherwise.
:raises: OpenStackCloudException on operation error.
"""
try:
self.compute.delete_keypair(name, ignore_missing=False)
except exceptions.ResourceNotFound:
self.log.debug("Keypair %s not found for deleting", name)
return False
return True
def create_image_snapshot(
self,
name,
server,
wait=False,
timeout=3600,
**metadata,
):
"""Create an image by snapshotting an existing server.
..note::
On most clouds this is a cold snapshot - meaning that the server in
question will be shutdown before taking the snapshot. It is
possible that it's a live snapshot - but there is no way to know as
a user, so caveat emptor.
:param name: Name of the image to be created
:param server: Server name or ID or dict representing the server
to be snapshotted
:param wait: If true, waits for image to be created.
:param timeout: Seconds to wait for image creation. None is forever.
:param metadata: Metadata to give newly-created image entity
:returns: The created image ``Image`` object.
:raises: OpenStackCloudException if there are problems uploading
"""
if not isinstance(server, dict):
server_obj = self.get_server(server, bare=True)
if not server_obj:
raise exc.OpenStackCloudException(
"Server {server} could not be found and therefore"
" could not be snapshotted.".format(server=server))
server = server_obj
image = self.compute.create_server_image(
server, name=name, metadata=metadata, wait=wait, timeout=timeout)
return image
def get_server_id(self, name_or_id):
"""Get the ID of a server.
:param name_or_id:
:returns: The name of the server if found, else None.
"""
server = self.get_server(name_or_id, bare=True)
if server:
return server['id']
return None
def get_server_private_ip(self, server):
"""Get the private IP of a server.
:param server:
:returns: The private IP of the server if set, else None.
"""
return meta.get_server_private_ip(server, self)
def get_server_public_ip(self, server):
"""Get the public IP of a server.
:param server:
:returns: The public IP of the server if set, else None.
"""
return meta.get_server_external_ipv4(self, server)
def get_server_meta(self, server):
"""Get the metadata for a server.
:param server:
:returns: The metadata for the server if found, else None.
"""
# TODO(mordred) remove once ansible has moved to Inventory interface
server_vars = meta.get_hostvars_from_server(self, server)
groups = meta.get_groups_from_server(self, server, server_vars)
return dict(server_vars=server_vars, groups=groups)
@_utils.valid_kwargs(
'meta', 'files', 'userdata', 'description',
'reservation_id', 'return_raw', 'min_count',
'max_count', 'security_groups', 'key_name',
'availability_zone', 'block_device_mapping',
'block_device_mapping_v2', 'nics', 'scheduler_hints',
'config_drive', 'admin_pass', 'disk_config')
def create_server(
self,
name,
image=None,
flavor=None,
auto_ip=True,
ips=None,
ip_pool=None,
root_volume=None,
terminate_volume=False,
wait=False,
timeout=180,
reuse_ips=True,
network=None,
boot_from_volume=False,
volume_size='50',
boot_volume=None,
volumes=None,
nat_destination=None,
group=None,
**kwargs,
):
"""Create a virtual server instance.
:param name: Something to name the server.
:param image: Image dict, name or ID to boot with. image is required
unless boot_volume is given.
:param flavor: Flavor dict, name or ID to boot onto.
:param auto_ip: Whether to take actions to find a routable IP for
the server. (defaults to True)
:param ips: List of IPs to attach to the server (defaults to None)
:param ip_pool: Name of the network or floating IP pool to get an
address from. (defaults to None)
:param root_volume: Name or ID of a volume to boot from
(defaults to None - deprecated, use boot_volume)
:param boot_volume: Name or ID of a volume to boot from
(defaults to None)
:param terminate_volume: If booting from a volume, whether it should
be deleted when the server is destroyed. (defaults to False)
:param volumes: (optional) A list of volumes to attach to the server
:param meta: (optional) A dict of arbitrary key/value metadata to
store for this server. Both keys and values must be <=255
characters.
:param files: (optional, deprecated) A dict of files to overwrite
on the server upon boot. Keys are file names (i.e.
``/etc/passwd``) and values are the file contents (either as a
string or as a file-like object). A maximum of five entries is
allowed, and each file must be 10k or less.
:param reservation_id: a UUID for the set of servers being requested.
:param min_count: (optional extension) The minimum number of servers to
launch.
:param max_count: (optional extension) The maximum number of servers to
launch.
:param security_groups: A list of security group names
:param userdata: user data to pass to be exposed by the metadata
server this can be a file type object as well or a string.
:param key_name: (optional extension) name of previously created
keypair to inject into the instance.
:param availability_zone: Name of the availability zone for instance
placement.
:param block_device_mapping: (optional) A dict of block
device mappings for this server.
:param block_device_mapping_v2: (optional) A dict of block
device mappings for this server.
:param nics: (optional extension) an ordered list of nics to be
added to this server, with information about connected networks,
fixed IPs, port etc.
:param scheduler_hints: (optional extension) arbitrary key-value pairs
specified by the client to help boot an instance
:param config_drive: (optional extension) value for config drive
either boolean, or volume-id
:param disk_config: (optional extension) control how the disk is
partitioned when the server is created. possible values are 'AUTO'
or 'MANUAL'.
:param admin_pass: (optional extension) add a user supplied admin
password.
:param wait: (optional) Wait for the address to appear as assigned
to the server. Defaults to False.
:param timeout: (optional) Seconds to wait, defaults to 60.
See the ``wait`` parameter.
:param reuse_ips: (optional) Whether to attempt to reuse pre-existing
floating ips should a floating IP be needed (defaults to True)
:param network: (optional) Network dict or name or ID to attach the
server to. Mutually exclusive with the nics parameter. Can also
be a list of network names or IDs or network dicts.
:param boot_from_volume: Whether to boot from volume. 'boot_volume'
implies True, but boot_from_volume=True with no boot_volume is
valid and will create a volume from the image and use that.
:param volume_size: When booting an image from volume, how big should
the created volume be? Defaults to 50.
:param nat_destination: Which network should a created floating IP
be attached to, if it's not possible to infer from the cloud's
configuration. (Optional, defaults to None)
:param group: ServerGroup dict, name or id to boot the server in.
If a group is provided in both scheduler_hints and in the group
param, the group param will win. (Optional, defaults to None)
:returns: The created compute ``Server`` object.
:raises: OpenStackCloudException on operation error.
"""
# TODO(shade) Image is optional but flavor is not - yet flavor comes
# after image in the argument list. Doh.
if not flavor:
raise TypeError(
"create_server() missing 1 required argument: 'flavor'")
if not image and not boot_volume:
raise TypeError(
"create_server() requires either 'image' or 'boot_volume'")
server_json = {'server': kwargs}
# TODO(mordred) Add support for description starting in 2.19
security_groups = kwargs.get('security_groups', [])
if security_groups and not isinstance(kwargs['security_groups'], list):
security_groups = [security_groups]
if security_groups:
kwargs['security_groups'] = []
for sec_group in security_groups:
kwargs['security_groups'].append(dict(name=sec_group))
if 'userdata' in kwargs:
user_data = kwargs.pop('userdata')
if user_data:
kwargs['user_data'] = self._encode_server_userdata(user_data)
for (desired, given) in (
('OS-DCF:diskConfig', 'disk_config'),
('config_drive', 'config_drive'),
('key_name', 'key_name'),
('metadata', 'meta'),
('adminPass', 'admin_pass')):
value = kwargs.pop(given, None)
if value:
kwargs[desired] = value
hints = kwargs.pop('scheduler_hints', {})
if group:
group_obj = self.get_server_group(group)
if not group_obj:
raise exc.OpenStackCloudException(
"Server Group {group} was requested but was not found"
" on the cloud".format(group=group))
hints['group'] = group_obj['id']
if hints:
server_json['os:scheduler_hints'] = hints
kwargs.setdefault('max_count', kwargs.get('max_count', 1))
kwargs.setdefault('min_count', kwargs.get('min_count', 1))
if 'nics' in kwargs and not isinstance(kwargs['nics'], list):
if isinstance(kwargs['nics'], dict):
# Be nice and help the user out
kwargs['nics'] = [kwargs['nics']]
else:
raise exc.OpenStackCloudException(
'nics parameter to create_server takes a list of dicts.'
' Got: {nics}'.format(nics=kwargs['nics']))
if network and ('nics' not in kwargs or not kwargs['nics']):
nics = []
if not isinstance(network, list):
network = [network]
for net_name in network:
if isinstance(net_name, dict) and 'id' in net_name:
network_obj = net_name
else:
network_obj = self.get_network(name_or_id=net_name)
if not network_obj:
raise exc.OpenStackCloudException(
'Network {network} is not a valid network in'
' {cloud}:{region}'.format(
network=network,
cloud=self.name, region=self._compute_region))
nics.append({'net-id': network_obj['id']})
kwargs['nics'] = nics
if not network and ('nics' not in kwargs or not kwargs['nics']):
default_network = self.get_default_network()
if default_network:
kwargs['nics'] = [{'net-id': default_network['id']}]
networks = []
for nic in kwargs.pop('nics', []):
net = {}
if 'net-id' in nic:
# TODO(mordred) Make sure this is in uuid format
net['uuid'] = nic.pop('net-id')
# If there's a net-id, ignore net-name
nic.pop('net-name', None)
elif 'net-name' in nic:
net_name = nic.pop('net-name')
nic_net = self.get_network(net_name)
if not nic_net:
raise exc.OpenStackCloudException(
"Requested network {net} could not be found.".format(
net=net_name))
net['uuid'] = nic_net['id']
for ip_key in ('v4-fixed-ip', 'v6-fixed-ip', 'fixed_ip'):
fixed_ip = nic.pop(ip_key, None)
if fixed_ip and net.get('fixed_ip'):
raise exc.OpenStackCloudException(
"Only one of v4-fixed-ip, v6-fixed-ip or fixed_ip"
" may be given")
if fixed_ip:
net['fixed_ip'] = fixed_ip
for key in ('port', 'port-id'):
if key in nic:
net['port'] = nic.pop(key)
# A tag supported only in server microversion 2.32-2.36 or >= 2.42
# Bumping the version to 2.42 to support the 'tag' implementation
if 'tag' in nic:
utils.require_microversion(
self.compute, '2.42')
net['tag'] = nic.pop('tag')
if nic:
raise exc.OpenStackCloudException(
"Additional unsupported keys given for server network"
" creation: {keys}".format(keys=nic.keys()))
networks.append(net)
if networks:
kwargs['networks'] = networks
else:
# If user has not passed networks - let Nova try the best.
kwargs['networks'] = 'auto'
if image:
if isinstance(image, dict):
kwargs['imageRef'] = image['id']
else:
kwargs['imageRef'] = self.get_image(image).id
if isinstance(flavor, dict):
kwargs['flavorRef'] = flavor['id']
else:
kwargs['flavorRef'] = self.get_flavor(flavor, get_extra=False).id
if volumes is None:
volumes = []
# nova cli calls this boot_volume. Let's be the same
if root_volume and not boot_volume:
boot_volume = root_volume
kwargs = self._get_boot_from_volume_kwargs(
image=image, boot_from_volume=boot_from_volume,
boot_volume=boot_volume, volume_size=str(volume_size),
terminate_volume=terminate_volume,
volumes=volumes, kwargs=kwargs)
kwargs['name'] = name
server = self.compute.create_server(**kwargs)
# TODO(mordred) We're only testing this in functional tests. We need
# to add unit tests for this too.
admin_pass = server.admin_password or kwargs.get('admin_pass')
if not wait:
# This is a direct get call to skip the list_servers
# cache which has absolutely no chance of containing the
# new server.
# Only do this if we're not going to wait for the server
# to complete booting, because the only reason we do it
# is to get a server record that is the return value from
# get/list rather than the return value of create. If we're
# going to do the wait loop below, this is a waste of a call
server = self.compute.get_server(server.id)
if server.status == 'ERROR':
raise exc.OpenStackCloudCreateException(
resource='server', resource_id=server.id)
server = meta.add_server_interfaces(self, server)
else:
server = self.wait_for_server(
server,
auto_ip=auto_ip, ips=ips, ip_pool=ip_pool,
reuse=reuse_ips, timeout=timeout,
nat_destination=nat_destination,
)
server.admin_password = admin_pass
return server
def _get_boot_from_volume_kwargs(
self, image, boot_from_volume, boot_volume, volume_size,
terminate_volume, volumes, kwargs):
"""Return block device mappings
:param image: Image dict, name or id to boot with.
"""
# TODO(mordred) We're only testing this in functional tests. We need
# to add unit tests for this too.
if boot_volume or boot_from_volume or volumes:
kwargs.setdefault('block_device_mapping_v2', [])
else:
return kwargs
# If we have boot_from_volume but no root volume, then we're
# booting an image from volume
if boot_volume:
volume = self.get_volume(boot_volume)
if not volume:
raise exc.OpenStackCloudException(
'Volume {boot_volume} is not a valid volume'
' in {cloud}:{region}'.format(
boot_volume=boot_volume,
cloud=self.name, region=self._compute_region))
block_mapping = {
'boot_index': '0',
'delete_on_termination': terminate_volume,
'destination_type': 'volume',
'uuid': volume['id'],
'source_type': 'volume',
}
kwargs['block_device_mapping_v2'].append(block_mapping)
kwargs['imageRef'] = ''
elif boot_from_volume:
if isinstance(image, dict):
image_obj = image
else:
image_obj = self.get_image(image)
if not image_obj:
raise exc.OpenStackCloudException(
'Image {image} is not a valid image in'
' {cloud}:{region}'.format(
image=image,
cloud=self.name, region=self._compute_region))
block_mapping = {
'boot_index': '0',
'delete_on_termination': terminate_volume,
'destination_type': 'volume',
'uuid': image_obj['id'],
'source_type': 'image',
'volume_size': volume_size,
}
kwargs['imageRef'] = ''
kwargs['block_device_mapping_v2'].append(block_mapping)
if volumes and kwargs['imageRef']:
# If we're attaching volumes on boot but booting from an image,
# we need to specify that in the BDM.
block_mapping = {
u'boot_index': 0,
u'delete_on_termination': True,
u'destination_type': u'local',
u'source_type': u'image',
u'uuid': kwargs['imageRef'],
}
kwargs['block_device_mapping_v2'].append(block_mapping)
for volume in volumes:
volume_obj = self.get_volume(volume)
if not volume_obj:
raise exc.OpenStackCloudException(
'Volume {volume} is not a valid volume'
' in {cloud}:{region}'.format(
volume=volume,
cloud=self.name, region=self._compute_region))
block_mapping = {
'boot_index': '-1',
'delete_on_termination': False,
'destination_type': 'volume',
'uuid': volume_obj['id'],
'source_type': 'volume',
}
kwargs['block_device_mapping_v2'].append(block_mapping)
if boot_volume or boot_from_volume or volumes:
self.list_volumes.invalidate(self)
return kwargs
def wait_for_server(
self, server, auto_ip=True, ips=None, ip_pool=None,
reuse=True, timeout=180, nat_destination=None):
"""
Wait for a server to reach ACTIVE status.
"""
# server = self.compute.wait_for_server(
# server=server, interval=self._SERVER_AGE or 2, wait=timeout
# )
server_id = server['id']
timeout_message = "Timeout waiting for the server to come up."
start_time = time.time()
# There is no point in iterating faster than the list_servers cache
for count in utils.iterate_timeout(
timeout,
timeout_message,
# if _SERVER_AGE is 0 we still want to wait a bit
# to be friendly with the server.
wait=self._SERVER_AGE or 2):
try:
# Use the get_server call so that the list_servers
# cache can be leveraged
server = self.get_server(server_id)
except Exception:
continue
if not server:
continue
# We have more work to do, but the details of that are
# hidden from the user. So, calculate remaining timeout
# and pass it down into the IP stack.
remaining_timeout = timeout - int(time.time() - start_time)
if remaining_timeout <= 0:
raise exc.OpenStackCloudTimeout(timeout_message)
server = self.get_active_server(
server=server, reuse=reuse,
auto_ip=auto_ip, ips=ips, ip_pool=ip_pool,
wait=True, timeout=remaining_timeout,
nat_destination=nat_destination)
if server is not None and server['status'] == 'ACTIVE':
return server
def get_active_server(
self,
server,
auto_ip=True,
ips=None,
ip_pool=None,
reuse=True,
wait=False,
timeout=180,
nat_destination=None,
):
if server['status'] == 'ERROR':
if ('fault' in server and server['fault'] is not None
and 'message' in server['fault']):
raise exc.OpenStackCloudException(
"Error in creating the server."
" Compute service reports fault: {reason}".format(
reason=server['fault']['message']),
extra_data=dict(server=server))
raise exc.OpenStackCloudException(
"Error in creating the server"
" (no further information available)",
extra_data=dict(server=server))
if server['status'] == 'ACTIVE':
if 'addresses' in server and server['addresses']:
return self.add_ips_to_server(
server, auto_ip, ips, ip_pool, reuse=reuse,
nat_destination=nat_destination,
wait=wait, timeout=timeout)
self.log.debug(
'Server %(server)s reached ACTIVE state without'
' being allocated an IP address.'
' Deleting server.', {'server': server['id']})
try:
self._delete_server(
server=server, wait=wait, timeout=timeout)
except Exception as e:
raise exc.OpenStackCloudException(
'Server reached ACTIVE state without being'
' allocated an IP address AND then could not'
' be deleted: {0}'.format(e),
extra_data=dict(server=server))
raise exc.OpenStackCloudException(
'Server reached ACTIVE state without being'
' allocated an IP address.',
extra_data=dict(server=server))
return None
def rebuild_server(
self,
server_id,
image_id,
admin_pass=None,
detailed=False,
bare=False,
wait=False,
timeout=180,
):
"""Rebuild a server.
:param server_id:
:param image_id:
:param admin_pass:
:param detailed:
:param bare:
:param wait:
:param timeout:
:returns: A compute ``Server`` object.
"""
kwargs = {}
if image_id:
kwargs['image'] = image_id
if admin_pass:
kwargs['admin_password'] = admin_pass
server = self.compute.rebuild_server(
server_id,
**kwargs
)
if not wait:
return self._expand_server(
server, bare=bare, detailed=detailed)
admin_pass = server.get('adminPass') or admin_pass
server = self.compute.wait_for_server(
server, wait=timeout)
if server['status'] == 'ACTIVE':
server.adminPass = admin_pass
return self._expand_server(server, detailed=detailed, bare=bare)
def set_server_metadata(self, name_or_id, metadata):
"""Set metadata in a server instance.
:param str name_or_id: The name or ID of the server instance to update.
:param dict metadata: A dictionary with the key=value pairs
to set in the server instance. It only updates the key=value pairs
provided. Existing ones will remain untouched.
:returns: None
:raises: OpenStackCloudException on operation error.
"""
server = self.get_server(name_or_id, bare=True)
if not server:
raise exc.OpenStackCloudException(
'Invalid Server {server}'.format(server=name_or_id))
self.compute.set_server_metadata(server=server.id, **metadata)
def delete_server_metadata(self, name_or_id, metadata_keys):
"""Delete metadata from a server instance.
:param str name_or_id: The name or ID of the server instance
to update.
:param metadata_keys: A list with the keys to be deleted
from the server instance.
:returns: None
:raises: OpenStackCloudException on operation error.
"""
server = self.get_server(name_or_id, bare=True)
if not server:
raise exc.OpenStackCloudException(
'Invalid Server {server}'.format(server=name_or_id))
self.compute.delete_server_metadata(server=server.id,
keys=metadata_keys)
def delete_server(
self,
name_or_id,
wait=False,
timeout=180,
delete_ips=False,
delete_ip_retry=1,
):
"""Delete a server instance.
:param name_or_id: name or ID of the server to delete
:param bool wait: If true, waits for server to be deleted.
:param int timeout: Seconds to wait for server deletion.
:param bool delete_ips: If true, deletes any floating IPs
associated with the instance.
:param int delete_ip_retry: Number of times to retry deleting
any floating ips, should the first try be unsuccessful.
:returns: True if delete succeeded, False otherwise if the
server does not exist.
:raises: OpenStackCloudException on operation error.
"""
# If delete_ips is True, we need the server to not be bare.
server = self.compute.find_server(
name_or_id, ignore_missing=True)
if not server:
return False
# This portion of the code is intentionally left as a separate
# private method in order to avoid an unnecessary API call to get
# a server we already have.
return self._delete_server(
server, wait=wait, timeout=timeout, delete_ips=delete_ips,
delete_ip_retry=delete_ip_retry)
def _delete_server_floating_ips(self, server, delete_ip_retry):
# Does the server have floating ips in its
# addresses dict? If not, skip this.
server_floats = meta.find_nova_interfaces(
server['addresses'], ext_tag='floating')
for fip in server_floats:
try:
ip = self.get_floating_ip(id=None, filters={
'floating_ip_address': fip['addr']})
except exc.OpenStackCloudURINotFound:
# We're deleting. If it doesn't exist - awesome
# NOTE(mordred) If the cloud is a nova FIP cloud but
# floating_ip_source is set to neutron, this
# can lead to a FIP leak.
continue
if not ip:
continue
deleted = self.delete_floating_ip(
ip['id'], retry=delete_ip_retry)
if not deleted:
raise exc.OpenStackCloudException(
"Tried to delete floating ip {floating_ip}"
" associated with server {id} but there was"
" an error deleting it. Not deleting server.".format(
floating_ip=ip['floating_ip_address'],
id=server['id']))
def _delete_server(
self, server, wait=False, timeout=180, delete_ips=False,
delete_ip_retry=1):
if not server:
return False
if delete_ips and self._has_floating_ips() and server['addresses']:
self._delete_server_floating_ips(server, delete_ip_retry)
try:
self.compute.delete_server(
server)
except exceptions.ResourceNotFound:
return False
except Exception:
raise
if not wait:
return True
# If the server has volume attachments, or if it has booted
# from volume, deleting it will change volume state so we will
# need to invalidate the cache. Avoid the extra API call if
# caching is not enabled.
reset_volume_cache = False
if (self.cache_enabled
and self.has_service('volume')
and self.get_volumes(server)):
reset_volume_cache = True
if not isinstance(server, _server.Server):
# We might come here with Munch object (at the moment).
# If this is the case - convert it into real server to be able to
# use wait_for_delete
server = _server.Server(id=server['id'])
self.compute.wait_for_delete(
server, wait=timeout)
if reset_volume_cache:
self.list_volumes.invalidate(self)
# Reset the list servers cache time so that the next list server
# call gets a new list
self._servers_time = self._servers_time - self._SERVER_AGE
return True
@_utils.valid_kwargs(
'name', 'description')
def update_server(self, name_or_id, detailed=False, bare=False, **kwargs):
"""Update a server.
:param name_or_id: Name of the server to be updated.
:param detailed: Whether or not to add detailed additional information.
Defaults to False.
:param bare: Whether to skip adding any additional information to the
server record. Defaults to False, meaning the addresses dict will
be populated as needed from neutron. Setting to True implies
detailed = False.
:param name: New name for the server
:param description: New description for the server
:returns: The updated compute ``Server`` object.
:raises: OpenStackCloudException on operation error.
"""
server = self.compute.find_server(
name_or_id,
ignore_missing=False
)
server = self.compute.update_server(
server, **kwargs)
return self._expand_server(server, bare=bare, detailed=detailed)
def create_server_group(self, name, policies=None, policy=None):
"""Create a new server group.
:param name: Name of the server group being created
:param policies: List of policies for the server group.
:returns: The created compute ``ServerGroup`` object.
:raises: OpenStackCloudException on operation error.
"""
sg_attrs = {
'name': name
}
if policies:
sg_attrs['policies'] = policies
if policy:
sg_attrs['policy'] = policy
return self.compute.create_server_group(
**sg_attrs
)
def delete_server_group(self, name_or_id):
"""Delete a server group.
:param name_or_id: Name or ID of the server group to delete
:returns: True if delete succeeded, False otherwise
:raises: OpenStackCloudException on operation error.
"""
server_group = self.get_server_group(name_or_id)
if not server_group:
self.log.debug("Server group %s not found for deleting",
name_or_id)
return False
self.compute.delete_server_group(server_group, ignore_missing=False)
return True
def create_flavor(
self,
name,
ram,
vcpus,
disk,
flavorid="auto",
ephemeral=0,
swap=0,
rxtx_factor=1.0,
is_public=True,
):
"""Create a new flavor.
:param name: Descriptive name of the flavor
:param ram: Memory in MB for the flavor
:param vcpus: Number of VCPUs for the flavor
:param disk: Size of local disk in GB
:param flavorid: ID for the flavor (optional)
:param ephemeral: Ephemeral space size in GB
:param swap: Swap space in MB
:param rxtx_factor: RX/TX factor
:param is_public: Make flavor accessible to the public
:returns: The created compute ``Flavor`` object.
:raises: OpenStackCloudException on operation error.
"""
attrs = {
'disk': disk,
'ephemeral': ephemeral,
'id': flavorid,
'is_public': is_public,
'name': name,
'ram': ram,
'rxtx_factor': rxtx_factor,
'swap': swap,
'vcpus': vcpus,
}
if flavorid == 'auto':
attrs['id'] = None
return self.compute.create_flavor(**attrs)
def delete_flavor(self, name_or_id):
"""Delete a flavor
:param name_or_id: ID or name of the flavor to delete.
:returns: True if delete succeeded, False otherwise.
:raises: OpenStackCloudException on operation error.
"""
try:
flavor = self.compute.find_flavor(name_or_id)
if not flavor:
self.log.debug(
"Flavor %s not found for deleting", name_or_id)
return False
self.compute.delete_flavor(flavor)
return True
except exceptions.SDKException:
raise exceptions.OpenStackCloudException(
"Unable to delete flavor {name}".format(name=name_or_id))
def set_flavor_specs(self, flavor_id, extra_specs):
"""Add extra specs to a flavor
:param string flavor_id: ID of the flavor to update.
:param dict extra_specs: Dictionary of key-value pairs.
:raises: OpenStackCloudException on operation error.
:raises: OpenStackCloudResourceNotFound if flavor ID is not found.
"""
self.compute.create_flavor_extra_specs(flavor_id, extra_specs)
def unset_flavor_specs(self, flavor_id, keys):
"""Delete extra specs from a flavor
:param string flavor_id: ID of the flavor to update.
:param keys: List of spec keys to delete.
:raises: OpenStackCloudException on operation error.
:raises: OpenStackCloudResourceNotFound if flavor ID is not found.
"""
for key in keys:
self.compute.delete_flavor_extra_specs_property(flavor_id, key)
def add_flavor_access(self, flavor_id, project_id):
"""Grant access to a private flavor for a project/tenant.
:param string flavor_id: ID of the private flavor.
:param string project_id: ID of the project/tenant.
:raises: OpenStackCloudException on operation error.
"""
self.compute.flavor_add_tenant_access(flavor_id, project_id)
def remove_flavor_access(self, flavor_id, project_id):
"""Revoke access from a private flavor for a project/tenant.
:param string flavor_id: ID of the private flavor.
:param string project_id: ID of the project/tenant.
:raises: OpenStackCloudException on operation error.
"""
self.compute.flavor_remove_tenant_access(flavor_id, project_id)
def list_flavor_access(self, flavor_id):
"""List access from a private flavor for a project/tenant.
:param string flavor_id: ID of the private flavor.
:returns: List of dicts with flavor_id and tenant_id attributes.
:raises: OpenStackCloudException on operation error.
"""
return self.compute.get_flavor_access(flavor_id)
def list_hypervisors(self, filters={}):
"""List all hypervisors
:param filters:
:returns: A list of compute ``Hypervisor`` objects.
"""
return list(self.compute.hypervisors(
details=True,
**filters))
def search_aggregates(self, name_or_id=None, filters=None):
"""Seach host aggregates.
:param name: aggregate name or id.
:param filters: a dict containing additional filters to use.
:returns: A list of compute ``Aggregate`` objects matching the search
criteria.
:raises: ``OpenStackCloudException``: if something goes wrong during
the OpenStack API call.
"""
aggregates = self.list_aggregates()
return _utils._filter_list(aggregates, name_or_id, filters)
def list_aggregates(self, filters={}):
"""List all available host aggregates.
:returns: A list of compute ``Aggregate`` objects.
"""
return self.compute.aggregates(**filters)
# TODO(stephenfin): This shouldn't return a munch
def get_aggregate(self, name_or_id, filters=None):
"""Get an aggregate by name or ID.
:param name_or_id: Name or ID of the aggregate.
:param dict filters:
A dictionary of meta data to use for further filtering. Elements
of this dictionary may, themselves, be dictionaries. Example::
{
'availability_zone': 'nova',
'metadata': {
'cpu_allocation_ratio': '1.0'
}
}
:returns: An aggregate dict or None if no matching aggregate is
found.
"""
aggregate = self.compute.find_aggregate(
name_or_id, ignore_missing=True)
if aggregate:
return aggregate._to_munch()
def create_aggregate(self, name, availability_zone=None):
"""Create a new host aggregate.
:param name: Name of the host aggregate being created
:param availability_zone: Availability zone to assign hosts
:returns: The created compute ``Aggregate`` object.
:raises: OpenStackCloudException on operation error.
"""
return self.compute.create_aggregate(
name=name,
availability_zone=availability_zone
)
@_utils.valid_kwargs('name', 'availability_zone')
def update_aggregate(self, name_or_id, **kwargs):
"""Update a host aggregate.
:param name_or_id: Name or ID of the aggregate being updated.
:param name: New aggregate name
:param availability_zone: Availability zone to assign to hosts
:returns: The updated compute ``Aggregate`` object.
:raises: OpenStackCloudException on operation error.
"""
aggregate = self.get_aggregate(name_or_id)
return self.compute.update_aggregate(aggregate, **kwargs)
def delete_aggregate(self, name_or_id):
"""Delete a host aggregate.
:param name_or_id: Name or ID of the host aggregate to delete.
:returns: True if delete succeeded, False otherwise.
:raises: OpenStackCloudException on operation error.
"""
if (
isinstance(name_or_id, (str, bytes))
and not name_or_id.isdigit()
):
aggregate = self.get_aggregate(name_or_id)
if not aggregate:
self.log.debug(
"Aggregate %s not found for deleting", name_or_id)
return False
name_or_id = aggregate.id
try:
self.compute.delete_aggregate(name_or_id, ignore_missing=False)
return True
except exceptions.ResourceNotFound:
self.log.debug("Aggregate %s not found for deleting", name_or_id)
return False
def set_aggregate_metadata(self, name_or_id, metadata):
"""Set aggregate metadata, replacing the existing metadata.
:param name_or_id: Name of the host aggregate to update
:param metadata: Dict containing metadata to replace (Use
{'key': None} to remove a key)
:returns: a dict representing the new host aggregate.
:raises: OpenStackCloudException on operation error.
"""
aggregate = self.get_aggregate(name_or_id)
if not aggregate:
raise exc.OpenStackCloudException(
"Host aggregate %s not found." % name_or_id)
return self.compute.set_aggregate_metadata(aggregate, metadata)
def add_host_to_aggregate(self, name_or_id, host_name):
"""Add a host to an aggregate.
:param name_or_id: Name or ID of the host aggregate.
:param host_name: Host to add.
:raises: OpenStackCloudException on operation error.
"""
aggregate = self.get_aggregate(name_or_id)
if not aggregate:
raise exc.OpenStackCloudException(
"Host aggregate %s not found." % name_or_id)
return self.compute.add_host_to_aggregate(aggregate, host_name)
def remove_host_from_aggregate(self, name_or_id, host_name):
"""Remove a host from an aggregate.
:param name_or_id: Name or ID of the host aggregate.
:param host_name: Host to remove.
:raises: OpenStackCloudException on operation error.
"""
aggregate = self.get_aggregate(name_or_id)
if not aggregate:
raise exc.OpenStackCloudException(
"Host aggregate %s not found." % name_or_id)
return self.compute.remove_host_from_aggregate(aggregate, host_name)
def set_compute_quotas(self, name_or_id, **kwargs):
""" Set a quota in a project
:param name_or_id: project name or id
:param kwargs: key/value pairs of quota name and quota value
:raises: OpenStackCloudException if the resource to set the
quota does not exist.
"""
proj = self.identity.find_project(
name_or_id, ignore_missing=False)
kwargs['force'] = True
self.compute.update_quota_set(
_qs.QuotaSet(project_id=proj.id),
**kwargs
)
def get_compute_quotas(self, name_or_id):
""" Get quota for a project
:param name_or_id: project name or id
:returns: A compute ``QuotaSet`` object if found, else None.
:raises: OpenStackCloudException if it's not a valid project
"""
proj = self.identity.find_project(
name_or_id, ignore_missing=False)
return self.compute.get_quota_set(proj)
def delete_compute_quotas(self, name_or_id):
""" Delete quota for a project
:param name_or_id: project name or id
:raises: OpenStackCloudException if it's not a valid project or the
nova client call failed
:returns: None
"""
proj = self.identity.find_project(
name_or_id, ignore_missing=False)
self.compute.revert_quota_set(proj)
# TODO(stephenfin): Convert to proxy methods
def get_compute_usage(self, name_or_id, start=None, end=None):
""" Get usage for a specific project
:param name_or_id: project name or id
:param start: :class:`datetime.datetime` or string. Start date in UTC
Defaults to 2010-07-06T12:00:00Z (the date the OpenStack project
was started)
:param end: :class:`datetime.datetime` or string. End date in UTC.
Defaults to now
:raises: OpenStackCloudException if it's not a valid project
:returns: Munch object with the usage
"""
def parse_date(date):
try:
return iso8601.parse_date(date)
except iso8601.iso8601.ParseError:
# Yes. This is an exception mask. However,iso8601 is an
# implementation detail - and the error message is actually
# less informative.
raise exc.OpenStackCloudException(
"Date given, {date}, is invalid. Please pass in a date"
" string in ISO 8601 format -"
" YYYY-MM-DDTHH:MM:SS".format(
date=date))
def parse_datetime_for_nova(date):
# Must strip tzinfo from the date- it breaks Nova. Also,
# Nova is expecting this in UTC. If someone passes in an
# ISO8601 date string or a datetime with timzeone data attached,
# strip the timezone data but apply offset math first so that
# the user's well formed perfectly valid date will be used
# correctly.
offset = date.utcoffset()
if offset:
date = date - datetime.timedelta(hours=offset)
return date.replace(tzinfo=None)
if not start:
start = parse_date('2010-07-06')
elif not isinstance(start, datetime.datetime):
start = parse_date(start)
if not end:
end = datetime.datetime.utcnow()
elif not isinstance(start, datetime.datetime):
end = parse_date(end)
start = parse_datetime_for_nova(start)
end = parse_datetime_for_nova(end)
proj = self.get_project(name_or_id)
if not proj:
raise exc.OpenStackCloudException(
"project does not exist: {name}".format(name=proj.id))
data = proxy._json_response(
self.compute.get(
'/os-simple-tenant-usage/{project}'.format(project=proj.id),
params=dict(start=start.isoformat(), end=end.isoformat())),
error_message="Unable to get usage for project: {name}".format(
name=proj.id))
return self._normalize_compute_usage(
self._get_and_munchify('tenant_usage', data))
def _encode_server_userdata(self, userdata):
if hasattr(userdata, 'read'):
userdata = userdata.read()
if not isinstance(userdata, bytes):
# If the userdata passed in is bytes, just send it unmodified
if not isinstance(userdata, str):
raise TypeError("%s can't be encoded" % type(userdata))
# If it's not bytes, make it bytes
userdata = userdata.encode('utf-8', 'strict')
# Once we have base64 bytes, make them into a utf-8 string for REST
return base64.b64encode(userdata).decode('utf-8')
def get_openstack_vars(self, server):
return meta.get_hostvars_from_server(self, server)
def _expand_server_vars(self, server):
# Used by nodepool
# TODO(mordred) remove after these make it into what we
# actually want the API to be.
return meta.expand_server_vars(self, server)