salt/utils/openstack/nova.py
# -*- coding: utf-8 -*-
'''
Nova class
'''
# Import Python libs
from __future__ import absolute_import, with_statement, unicode_literals, print_function
import inspect
import logging
import time
import re
import json
# Import third party libs
from salt.ext import six
HAS_NOVA = False
# pylint: disable=import-error
try:
import novaclient
from novaclient import client
from novaclient.shell import OpenStackComputeShell
import novaclient.utils
import novaclient.exceptions
import novaclient.extension
import novaclient.base
HAS_NOVA = True
except ImportError:
pass
HAS_KEYSTONEAUTH = False
try:
import keystoneauth1.loading
import keystoneauth1.session
HAS_KEYSTONEAUTH = True
except ImportError:
pass
# pylint: enable=import-error
# Import salt libs
import salt.utils.cloud
import salt.utils.files
from salt.exceptions import SaltCloudSystemExit
from salt.utils.versions import LooseVersion as _LooseVersion
# Get logging started
log = logging.getLogger(__name__)
# Version added to novaclient.client.Client function
NOVACLIENT_MINVER = '2.6.1'
NOVACLIENT_MAXVER = '6.0.1'
# dict for block_device_mapping_v2
CLIENT_BDM2_KEYS = {
'id': 'uuid',
'source': 'source_type',
'dest': 'destination_type',
'bus': 'disk_bus',
'device': 'device_name',
'size': 'volume_size',
'format': 'guest_format',
'bootindex': 'boot_index',
'type': 'device_type',
'shutdown': 'delete_on_termination',
}
def check_nova():
'''
Check version of novaclient
'''
if HAS_NOVA:
novaclient_ver = _LooseVersion(novaclient.__version__)
min_ver = _LooseVersion(NOVACLIENT_MINVER)
if min_ver <= novaclient_ver:
return HAS_NOVA
log.debug('Newer novaclient version required. Minimum: %s', NOVACLIENT_MINVER)
return False
if check_nova():
try:
import novaclient.auth_plugin
except ImportError:
log.debug('Using novaclient version 7.0.0 or newer. Authentication '
'plugin auth_plugin.py is not available anymore.')
# kwargs has to be an object instead of a dictionary for the __post_parse_arg__
class KwargsStruct(object):
def __init__(self, **entries):
self.__dict__.update(entries)
def _parse_block_device_mapping_v2(block_device=None, boot_volume=None, snapshot=None, ephemeral=None, swap=None):
bdm = []
if block_device is None:
block_device = []
if ephemeral is None:
ephemeral = []
if boot_volume is not None:
bdm_dict = {'uuid': boot_volume, 'source_type': 'volume',
'destination_type': 'volume', 'boot_index': 0,
'delete_on_termination': False}
bdm.append(bdm_dict)
if snapshot is not None:
bdm_dict = {'uuid': snapshot, 'source_type': 'snapshot',
'destination_type': 'volume', 'boot_index': 0,
'delete_on_termination': False}
bdm.append(bdm_dict)
for device_spec in block_device:
bdm_dict = {}
for key, value in six.iteritems(device_spec):
bdm_dict[CLIENT_BDM2_KEYS[key]] = value
# Convert the delete_on_termination to a boolean or set it to true by
# default for local block devices when not specified.
if 'delete_on_termination' in bdm_dict:
action = bdm_dict['delete_on_termination']
bdm_dict['delete_on_termination'] = (action == 'remove')
elif bdm_dict.get('destination_type') == 'local':
bdm_dict['delete_on_termination'] = True
bdm.append(bdm_dict)
for ephemeral_spec in ephemeral:
bdm_dict = {'source_type': 'blank', 'destination_type': 'local',
'boot_index': -1, 'delete_on_termination': True}
if 'size' in ephemeral_spec:
bdm_dict['volume_size'] = ephemeral_spec['size']
if 'format' in ephemeral_spec:
bdm_dict['guest_format'] = ephemeral_spec['format']
bdm.append(bdm_dict)
if swap is not None:
bdm_dict = {'source_type': 'blank', 'destination_type': 'local',
'boot_index': -1, 'delete_on_termination': True,
'guest_format': 'swap', 'volume_size': swap}
bdm.append(bdm_dict)
return bdm
class NovaServer(object):
def __init__(self, name, server, password=None):
'''
Make output look like libcloud output for consistency
'''
self.name = name
self.id = server['id']
self.image = server.get('image', {}).get('id', 'Boot From Volume')
self.size = server['flavor']['id']
self.state = server['state']
self._uuid = None
self.extra = {
'metadata': server['metadata'],
'access_ip': server['accessIPv4']
}
self.addresses = server.get('addresses', {})
self.public_ips, self.private_ips = [], []
self.fixed_ips, self.floating_ips = [], []
for network in self.addresses.values():
for addr in network:
if salt.utils.cloud.is_public_ip(addr['addr']):
self.public_ips.append(addr['addr'])
else:
self.private_ips.append(addr['addr'])
if addr.get('OS-EXT-IPS:type') == 'floating':
self.floating_ips.append(addr['addr'])
else:
self.fixed_ips.append(addr['addr'])
if password:
self.extra['password'] = password
def __str__(self):
return self.__dict__
def get_entry(dict_, key, value, raise_error=True):
for entry in dict_:
if entry[key] == value:
return entry
if raise_error is True:
raise SaltCloudSystemExit('Unable to find {0} in {1}.'.format(key, dict_))
return {}
def get_entry_multi(dict_, pairs, raise_error=True):
for entry in dict_:
if all([entry[key] == value for key, value in pairs]):
return entry
if raise_error is True:
raise SaltCloudSystemExit('Unable to find {0} in {1}.'.format(pairs, dict_))
return {}
def get_endpoint_url_v3(catalog, service_type, region_name):
for service_entry in catalog:
if service_entry['type'] == service_type:
for endpoint_entry in service_entry['endpoints']:
if (endpoint_entry['region'] == region_name and
endpoint_entry['interface'] == 'public'):
return endpoint_entry['url']
return None
def sanatize_novaclient(kwargs):
variables = (
'username', 'api_key', 'project_id', 'auth_url', 'insecure',
'timeout', 'proxy_tenant_id', 'proxy_token', 'region_name',
'endpoint_type', 'extensions', 'service_type', 'service_name',
'volume_service_name', 'timings', 'bypass_url', 'os_cache',
'no_cache', 'http_log_debug', 'auth_system', 'auth_plugin',
'auth_token', 'cacert', 'tenant_id'
)
ret = {}
for var in kwargs:
if var in variables:
ret[var] = kwargs[var]
return ret
# Function alias to not shadow built-ins
class SaltNova(object):
'''
Class for all novaclient functions
'''
extensions = []
def __init__(
self,
username,
project_id,
auth_url,
region_name=None,
password=None,
os_auth_plugin=None,
use_keystoneauth=False,
**kwargs
):
'''
Set up nova credentials
'''
if all([use_keystoneauth, HAS_KEYSTONEAUTH]):
self._new_init(username=username,
project_id=project_id,
auth_url=auth_url,
region_name=region_name,
password=password,
os_auth_plugin=os_auth_plugin,
**kwargs)
else:
self._old_init(username=username,
project_id=project_id,
auth_url=auth_url,
region_name=region_name,
password=password,
os_auth_plugin=os_auth_plugin,
**kwargs)
def _get_version_from_url(self, url):
'''
Exctract API version from provided URL
'''
regex = re.compile(r"^https?:\/\/.*\/(v[0-9])(\.[0-9])?(\/)?$")
try:
ver = regex.match(url)
if ver.group(1):
retver = ver.group(1)
if ver.group(2):
retver = retver + ver.group(2)
return retver
except AttributeError:
return ''
def _discover_ks_version(self, url):
'''
Keystone API version discovery
'''
result = salt.utils.http.query(url, backend='requests', status=True, decode=True, decode_type='json')
versions = json.loads(result['body'])
try:
links = [ver['links'] for ver in versions['versions']['values'] if ver['status'] == 'stable'][0] \
if result['status'] == 300 else versions['version']['links']
resurl = [link['href'] for link in links if link['rel'] == 'self'][0]
return self._get_version_from_url(resurl)
except KeyError as exc:
raise SaltCloudSystemExit('KeyError: key {0} not found in API response: {1}'.format(exc, versions))
def _new_init(self, username, project_id, auth_url, region_name, password, os_auth_plugin, auth=None, **kwargs):
if auth is None:
auth = {}
ks_version = self._get_version_from_url(auth_url)
if not ks_version:
ks_version = self._discover_ks_version(auth_url)
auth_url = '{0}/{1}'.format(auth_url, ks_version)
loader = keystoneauth1.loading.get_plugin_loader(os_auth_plugin or 'password')
self.client_kwargs = kwargs.copy()
self.kwargs = auth.copy()
if not self.extensions:
if hasattr(OpenStackComputeShell, '_discover_extensions'):
self.extensions = OpenStackComputeShell()._discover_extensions('2.0')
else:
self.extensions = client.discover_extensions('2.0')
for extension in self.extensions:
extension.run_hooks('__pre_parse_args__')
self.client_kwargs['extensions'] = self.extensions
self.kwargs['username'] = username
self.kwargs['project_name'] = project_id
self.kwargs['auth_url'] = auth_url
self.kwargs['password'] = password
if ks_version == 'v3':
self.kwargs['project_id'] = kwargs.get('project_id')
self.kwargs['project_name'] = kwargs.get('project_name')
self.kwargs['user_domain_name'] = kwargs.get('user_domain_name', 'default')
self.kwargs['project_domain_name'] = kwargs.get('project_domain_name', 'default')
self.client_kwargs['region_name'] = region_name
self.client_kwargs['service_type'] = 'compute'
if hasattr(self, 'extensions'):
# needs an object, not a dictionary
self.kwargstruct = KwargsStruct(**self.client_kwargs)
for extension in self.extensions:
extension.run_hooks('__post_parse_args__', self.kwargstruct)
self.client_kwargs = self.kwargstruct.__dict__
# Requires novaclient version >= 2.6.1
self.version = six.text_type(kwargs.get('version', 2))
self.client_kwargs = sanatize_novaclient(self.client_kwargs)
options = loader.load_from_options(**self.kwargs)
self.session = keystoneauth1.session.Session(auth=options)
conn = client.Client(version=self.version, session=self.session, **self.client_kwargs)
self.kwargs['auth_token'] = conn.client.session.get_token()
identity_service_type = kwargs.get('identity_service_type', 'identity')
self.catalog = conn.client.session.get('/' + ks_version + '/auth/catalog',
endpoint_filter={'service_type': identity_service_type}
).json().get('catalog', [])
for ep_type in self.catalog:
if ep_type['type'] == identity_service_type:
for ep_id in ep_type['endpoints']:
ep_ks_version = self._get_version_from_url(ep_id['url'])
if not ep_ks_version:
ep_id['url'] = '{0}/{1}'.format(ep_id['url'], ks_version)
if ks_version == 'v3':
self._v3_setup(region_name)
else:
self._v2_setup(region_name)
def _old_init(self, username, project_id, auth_url, region_name, password, os_auth_plugin, **kwargs):
self.kwargs = kwargs.copy()
if not self.extensions:
if hasattr(OpenStackComputeShell, '_discover_extensions'):
self.extensions = OpenStackComputeShell()._discover_extensions('2.0')
else:
self.extensions = client.discover_extensions('2.0')
for extension in self.extensions:
extension.run_hooks('__pre_parse_args__')
self.kwargs['extensions'] = self.extensions
self.kwargs['username'] = username
self.kwargs['project_id'] = project_id
self.kwargs['auth_url'] = auth_url
self.kwargs['region_name'] = region_name
self.kwargs['service_type'] = 'compute'
# used in novaclient extensions to see if they are rackspace or not, to know if it needs to load
# the hooks for that extension or not. This is cleaned up by sanatize_novaclient
self.kwargs['os_auth_url'] = auth_url
if os_auth_plugin is not None:
novaclient.auth_plugin.discover_auth_systems()
auth_plugin = novaclient.auth_plugin.load_plugin(os_auth_plugin)
self.kwargs['auth_plugin'] = auth_plugin
self.kwargs['auth_system'] = os_auth_plugin
if not self.kwargs.get('api_key', None):
self.kwargs['api_key'] = password
# This has to be run before sanatize_novaclient before extra variables are cleaned out.
if hasattr(self, 'extensions'):
# needs an object, not a dictionary
self.kwargstruct = KwargsStruct(**self.kwargs)
for extension in self.extensions:
extension.run_hooks('__post_parse_args__', self.kwargstruct)
self.kwargs = self.kwargstruct.__dict__
self.kwargs = sanatize_novaclient(self.kwargs)
# Requires novaclient version >= 2.6.1
self.kwargs['version'] = six.text_type(kwargs.get('version', 2))
conn = client.Client(**self.kwargs)
try:
conn.client.authenticate()
except novaclient.exceptions.AmbiguousEndpoints:
raise SaltCloudSystemExit(
"Nova provider requires a 'region_name' to be specified"
)
self.kwargs['auth_token'] = conn.client.auth_token
self.catalog = conn.client.service_catalog.catalog['access']['serviceCatalog']
self._v2_setup(region_name)
def _v3_setup(self, region_name):
if region_name is not None:
self.client_kwargs['bypass_url'] = get_endpoint_url_v3(self.catalog, 'compute', region_name)
log.debug('Using Nova bypass_url: %s', self.client_kwargs['bypass_url'])
self.compute_conn = client.Client(version=self.version, session=self.session, **self.client_kwargs)
volume_endpoints = get_entry(self.catalog, 'type', 'volume', raise_error=False).get('endpoints', {})
if volume_endpoints:
if region_name is not None:
self.client_kwargs['bypass_url'] = get_endpoint_url_v3(self.catalog, 'volume', region_name)
log.debug('Using Cinder bypass_url: %s', self.client_kwargs['bypass_url'])
self.volume_conn = client.Client(version=self.version, session=self.session, **self.client_kwargs)
if hasattr(self, 'extensions'):
self.expand_extensions()
else:
self.volume_conn = None
def _v2_setup(self, region_name):
if region_name is not None:
servers_endpoints = get_entry(self.catalog, 'type', 'compute')['endpoints']
self.kwargs['bypass_url'] = get_entry(
servers_endpoints,
'region',
region_name
)['publicURL']
self.compute_conn = client.Client(**self.kwargs)
volume_endpoints = get_entry(self.catalog, 'type', 'volume', raise_error=False).get('endpoints', {})
if volume_endpoints:
if region_name is not None:
self.kwargs['bypass_url'] = get_entry(
volume_endpoints,
'region',
region_name
)['publicURL']
self.volume_conn = client.Client(**self.kwargs)
if hasattr(self, 'extensions'):
self.expand_extensions()
else:
self.volume_conn = None
def expand_extensions(self):
for connection in (self.compute_conn, self.volume_conn):
if connection is None:
continue
for extension in self.extensions:
for attr in extension.module.__dict__:
if not inspect.isclass(getattr(extension.module, attr)):
continue
for key, value in six.iteritems(connection.__dict__):
if not isinstance(value, novaclient.base.Manager):
continue
if value.__class__.__name__ == attr:
setattr(connection, key, extension.manager_class(connection))
def get_catalog(self):
'''
Return service catalog
'''
return self.catalog
def server_show_libcloud(self, uuid):
'''
Make output look like libcloud output for consistency
'''
server_info = self.server_show(uuid)
server = next(six.itervalues(server_info))
server_name = next(six.iterkeys(server_info))
if not hasattr(self, 'password'):
self.password = None
ret = NovaServer(server_name, server, self.password)
return ret
def boot(self, name, flavor_id=0, image_id=0, timeout=300, **kwargs):
'''
Boot a cloud server.
'''
nt_ks = self.compute_conn
kwargs['name'] = name
kwargs['flavor'] = flavor_id
kwargs['image'] = image_id or None
ephemeral = kwargs.pop('ephemeral', [])
block_device = kwargs.pop('block_device', [])
boot_volume = kwargs.pop('boot_volume', None)
snapshot = kwargs.pop('snapshot', None)
swap = kwargs.pop('swap', None)
kwargs['block_device_mapping_v2'] = _parse_block_device_mapping_v2(
block_device=block_device, boot_volume=boot_volume, snapshot=snapshot,
ephemeral=ephemeral, swap=swap
)
response = nt_ks.servers.create(**kwargs)
self.uuid = response.id
self.password = getattr(response, 'adminPass', None)
start = time.time()
trycount = 0
while True:
trycount += 1
try:
return self.server_show_libcloud(self.uuid)
except Exception as exc:
log.debug(
'Server information not yet available: %s', exc
)
time.sleep(1)
if time.time() - start > timeout:
log.error('Timed out after %s seconds '
'while waiting for data', timeout)
return False
log.debug(
'Retrying server_show() (try %s)', trycount
)
def show_instance(self, name):
'''
Find a server by its name (libcloud)
'''
return self.server_by_name(name)
def root_password(self, server_id, password):
'''
Change server(uuid's) root password
'''
nt_ks = self.compute_conn
nt_ks.servers.change_password(server_id, password)
def server_by_name(self, name):
'''
Find a server by its name
'''
return self.server_show_libcloud(
self.server_list().get(name, {}).get('id', '')
)
def _volume_get(self, volume_id):
'''
Organize information about a volume from the volume_id
'''
if self.volume_conn is None:
raise SaltCloudSystemExit('No cinder endpoint available')
nt_ks = self.volume_conn
volume = nt_ks.volumes.get(volume_id)
response = {'name': volume.display_name,
'size': volume.size,
'id': volume.id,
'description': volume.display_description,
'attachments': volume.attachments,
'status': volume.status
}
return response
def volume_list(self, search_opts=None):
'''
List all block volumes
'''
if self.volume_conn is None:
raise SaltCloudSystemExit('No cinder endpoint available')
nt_ks = self.volume_conn
volumes = nt_ks.volumes.list(search_opts=search_opts)
response = {}
for volume in volumes:
response[volume.display_name] = {
'name': volume.display_name,
'size': volume.size,
'id': volume.id,
'description': volume.display_description,
'attachments': volume.attachments,
'status': volume.status
}
return response
def volume_show(self, name):
'''
Show one volume
'''
if self.volume_conn is None:
raise SaltCloudSystemExit('No cinder endpoint available')
nt_ks = self.volume_conn
volumes = self.volume_list(
search_opts={'display_name': name},
)
volume = volumes[name]
# except Exception as esc:
# # volume doesn't exist
# log.error(esc.strerror)
# return {'name': name, 'status': 'deleted'}
return volume
def volume_create(self, name, size=100, snapshot=None, voltype=None,
availability_zone=None):
'''
Create a block device
'''
if self.volume_conn is None:
raise SaltCloudSystemExit('No cinder endpoint available')
nt_ks = self.volume_conn
response = nt_ks.volumes.create(
size=size,
display_name=name,
volume_type=voltype,
snapshot_id=snapshot,
availability_zone=availability_zone
)
return self._volume_get(response.id)
def volume_delete(self, name):
'''
Delete a block device
'''
if self.volume_conn is None:
raise SaltCloudSystemExit('No cinder endpoint available')
nt_ks = self.volume_conn
try:
volume = self.volume_show(name)
except KeyError as exc:
raise SaltCloudSystemExit('Unable to find {0} volume: {1}'.format(name, exc))
if volume['status'] == 'deleted':
return volume
response = nt_ks.volumes.delete(volume['id'])
return volume
def volume_detach(self,
name,
timeout=300):
'''
Detach a block device
'''
try:
volume = self.volume_show(name)
except KeyError as exc:
raise SaltCloudSystemExit('Unable to find {0} volume: {1}'.format(name, exc))
if not volume['attachments']:
return True
response = self.compute_conn.volumes.delete_server_volume(
volume['attachments'][0]['server_id'],
volume['attachments'][0]['id']
)
trycount = 0
start = time.time()
while True:
trycount += 1
try:
response = self._volume_get(volume['id'])
if response['status'] == 'available':
return response
except Exception as exc:
log.debug('Volume is detaching: %s', name)
time.sleep(1)
if time.time() - start > timeout:
log.error('Timed out after %d seconds '
'while waiting for data', timeout)
return False
log.debug(
'Retrying volume_show() (try %d)', trycount
)
def volume_attach(self,
name,
server_name,
device='/dev/xvdb',
timeout=300):
'''
Attach a block device
'''
try:
volume = self.volume_show(name)
except KeyError as exc:
raise SaltCloudSystemExit('Unable to find {0} volume: {1}'.format(name, exc))
server = self.server_by_name(server_name)
response = self.compute_conn.volumes.create_server_volume(
server.id,
volume['id'],
device=device
)
trycount = 0
start = time.time()
while True:
trycount += 1
try:
response = self._volume_get(volume['id'])
if response['status'] == 'in-use':
return response
except Exception as exc:
log.debug('Volume is attaching: %s', name)
time.sleep(1)
if time.time() - start > timeout:
log.error('Timed out after %s seconds '
'while waiting for data', timeout)
return False
log.debug(
'Retrying volume_show() (try %s)', trycount
)
def suspend(self, instance_id):
'''
Suspend a server
'''
nt_ks = self.compute_conn
response = nt_ks.servers.suspend(instance_id)
return True
def resume(self, instance_id):
'''
Resume a server
'''
nt_ks = self.compute_conn
response = nt_ks.servers.resume(instance_id)
return True
def lock(self, instance_id):
'''
Lock an instance
'''
nt_ks = self.compute_conn
response = nt_ks.servers.lock(instance_id)
return True
def delete(self, instance_id):
'''
Delete a server
'''
nt_ks = self.compute_conn
response = nt_ks.servers.delete(instance_id)
return True
def flavor_list(self, **kwargs):
'''
Return a list of available flavors (nova flavor-list)
'''
nt_ks = self.compute_conn
ret = {}
for flavor in nt_ks.flavors.list(**kwargs):
links = {}
for link in flavor.links:
links[link['rel']] = link['href']
ret[flavor.name] = {
'disk': flavor.disk,
'id': flavor.id,
'name': flavor.name,
'ram': flavor.ram,
'swap': flavor.swap,
'vcpus': flavor.vcpus,
'links': links,
}
if hasattr(flavor, 'rxtx_factor'):
ret[flavor.name]['rxtx_factor'] = flavor.rxtx_factor
return ret
list_sizes = flavor_list
def flavor_create(self,
name, # pylint: disable=C0103
flavor_id=0, # pylint: disable=C0103
ram=0,
disk=0,
vcpus=1,
is_public=True):
'''
Create a flavor
'''
nt_ks = self.compute_conn
nt_ks.flavors.create(
name=name, flavorid=flavor_id, ram=ram, disk=disk, vcpus=vcpus, is_public=is_public
)
return {'name': name,
'id': flavor_id,
'ram': ram,
'disk': disk,
'vcpus': vcpus,
'is_public': is_public}
def flavor_delete(self, flavor_id): # pylint: disable=C0103
'''
Delete a flavor
'''
nt_ks = self.compute_conn
nt_ks.flavors.delete(flavor_id)
return 'Flavor deleted: {0}'.format(flavor_id)
def flavor_access_list(self, **kwargs):
'''
Return a list of project IDs assigned to flavor ID
'''
flavor_id = kwargs.get('flavor_id')
nt_ks = self.compute_conn
ret = {flavor_id: []}
flavor_accesses = nt_ks.flavor_access.list(flavor=flavor_id, **kwargs)
for project in flavor_accesses:
ret[flavor_id].append(project.tenant_id)
return ret
def flavor_access_add(self, flavor_id, project_id):
'''
Add a project to the flavor access list
'''
nt_ks = self.compute_conn
ret = {flavor_id: []}
flavor_accesses = nt_ks.flavor_access.add_tenant_access(flavor_id, project_id)
for project in flavor_accesses:
ret[flavor_id].append(project.tenant_id)
return ret
def flavor_access_remove(self, flavor_id, project_id):
'''
Remove a project from the flavor access list
'''
nt_ks = self.compute_conn
ret = {flavor_id: []}
flavor_accesses = nt_ks.flavor_access.remove_tenant_access(flavor_id, project_id)
for project in flavor_accesses:
ret[flavor_id].append(project.tenant_id)
return ret
def keypair_list(self):
'''
List keypairs
'''
nt_ks = self.compute_conn
ret = {}
for keypair in nt_ks.keypairs.list():
ret[keypair.name] = {
'name': keypair.name,
'fingerprint': keypair.fingerprint,
'public_key': keypair.public_key,
}
return ret
def keypair_add(self, name, pubfile=None, pubkey=None):
'''
Add a keypair
'''
nt_ks = self.compute_conn
if pubfile:
with salt.utils.files.fopen(pubfile, 'r') as fp_:
pubkey = salt.utils.stringutils.to_unicode(fp_.read())
if not pubkey:
return False
nt_ks.keypairs.create(name, public_key=pubkey)
ret = {'name': name, 'pubkey': pubkey}
return ret
def keypair_delete(self, name):
'''
Delete a keypair
'''
nt_ks = self.compute_conn
nt_ks.keypairs.delete(name)
return 'Keypair deleted: {0}'.format(name)
def image_show(self, image_id):
'''
Show image details and metadata
'''
nt_ks = self.compute_conn
image = nt_ks.images.get(image_id)
links = {}
for link in image.links:
links[link['rel']] = link['href']
ret = {
'name': image.name,
'id': image.id,
'status': image.status,
'progress': image.progress,
'created': image.created,
'updated': image.updated,
'metadata': image.metadata,
'links': links,
}
if hasattr(image, 'minDisk'):
ret['minDisk'] = image.minDisk
if hasattr(image, 'minRam'):
ret['minRam'] = image.minRam
return ret
def image_list(self, name=None):
'''
List server images
'''
nt_ks = self.compute_conn
ret = {}
for image in nt_ks.images.list():
links = {}
for link in image.links:
links[link['rel']] = link['href']
ret[image.name] = {
'name': image.name,
'id': image.id,
'status': image.status,
'progress': image.progress,
'created': image.created,
'updated': image.updated,
'metadata': image.metadata,
'links': links,
}
if hasattr(image, 'minDisk'):
ret[image.name]['minDisk'] = image.minDisk
if hasattr(image, 'minRam'):
ret[image.name]['minRam'] = image.minRam
if name:
return {name: ret[name]}
return ret
list_images = image_list
def image_meta_set(self,
image_id=None,
name=None,
**kwargs): # pylint: disable=C0103
'''
Set image metadata
'''
nt_ks = self.compute_conn
if name:
for image in nt_ks.images.list():
if image.name == name:
image_id = image.id # pylint: disable=C0103
if not image_id:
return {'Error': 'A valid image name or id was not specified'}
nt_ks.images.set_meta(image_id, kwargs)
return {image_id: kwargs}
def image_meta_delete(self,
image_id=None, # pylint: disable=C0103
name=None,
keys=None):
'''
Delete image metadata
'''
nt_ks = self.compute_conn
if name:
for image in nt_ks.images.list():
if image.name == name:
image_id = image.id # pylint: disable=C0103
pairs = keys.split(',')
if not image_id:
return {'Error': 'A valid image name or id was not specified'}
nt_ks.images.delete_meta(image_id, pairs)
return {image_id: 'Deleted: {0}'.format(pairs)}
def server_list(self):
'''
List servers
'''
nt_ks = self.compute_conn
ret = {}
for item in nt_ks.servers.list():
try:
ret[item.name] = {
'id': item.id,
'name': item.name,
'state': item.status,
'accessIPv4': item.accessIPv4,
'accessIPv6': item.accessIPv6,
'flavor': {'id': item.flavor['id'],
'links': item.flavor['links']},
'image': {'id': item.image['id'] if item.image else 'Boot From Volume',
'links': item.image['links'] if item.image else ''},
}
except TypeError:
pass
return ret
def server_list_min(self):
'''
List minimal information about servers
'''
nt_ks = self.compute_conn
ret = {}
for item in nt_ks.servers.list(detailed=False):
try:
ret[item.name] = {
'id': item.id,
'state': 'Running'
}
except TypeError:
pass
return ret
def server_list_detailed(self):
'''
Detailed list of servers
'''
nt_ks = self.compute_conn
ret = {}
for item in nt_ks.servers.list():
try:
ret[item.name] = {
'OS-EXT-SRV-ATTR': {},
'OS-EXT-STS': {},
'accessIPv4': item.accessIPv4,
'accessIPv6': item.accessIPv6,
'addresses': item.addresses,
'created': item.created,
'flavor': {'id': item.flavor['id'],
'links': item.flavor['links']},
'hostId': item.hostId,
'id': item.id,
'image': {'id': item.image['id'] if item.image else 'Boot From Volume',
'links': item.image['links'] if item.image else ''},
'key_name': item.key_name,
'links': item.links,
'metadata': item.metadata,
'name': item.name,
'state': item.status,
'tenant_id': item.tenant_id,
'updated': item.updated,
'user_id': item.user_id,
}
except TypeError:
continue
ret[item.name]['progress'] = getattr(item, 'progress', '0')
if hasattr(item.__dict__, 'OS-DCF:diskConfig'):
ret[item.name]['OS-DCF'] = {
'diskConfig': item.__dict__['OS-DCF:diskConfig']
}
if hasattr(item.__dict__, 'OS-EXT-SRV-ATTR:host'):
ret[item.name]['OS-EXT-SRV-ATTR']['host'] = \
item.__dict__['OS-EXT-SRV-ATTR:host']
if hasattr(item.__dict__, 'OS-EXT-SRV-ATTR:hypervisor_hostname'):
ret[item.name]['OS-EXT-SRV-ATTR']['hypervisor_hostname'] = \
item.__dict__['OS-EXT-SRV-ATTR:hypervisor_hostname']
if hasattr(item.__dict__, 'OS-EXT-SRV-ATTR:instance_name'):
ret[item.name]['OS-EXT-SRV-ATTR']['instance_name'] = \
item.__dict__['OS-EXT-SRV-ATTR:instance_name']
if hasattr(item.__dict__, 'OS-EXT-STS:power_state'):
ret[item.name]['OS-EXT-STS']['power_state'] = \
item.__dict__['OS-EXT-STS:power_state']
if hasattr(item.__dict__, 'OS-EXT-STS:task_state'):
ret[item.name]['OS-EXT-STS']['task_state'] = \
item.__dict__['OS-EXT-STS:task_state']
if hasattr(item.__dict__, 'OS-EXT-STS:vm_state'):
ret[item.name]['OS-EXT-STS']['vm_state'] = \
item.__dict__['OS-EXT-STS:vm_state']
if hasattr(item.__dict__, 'security_groups'):
ret[item.name]['security_groups'] = \
item.__dict__['security_groups']
return ret
def server_show(self, server_id):
'''
Show details of one server
'''
ret = {}
try:
servers = self.server_list_detailed()
except AttributeError:
raise SaltCloudSystemExit('Corrupt server in server_list_detailed. Remove corrupt servers.')
for server_name, server in six.iteritems(servers):
if six.text_type(server['id']) == server_id:
ret[server_name] = server
return ret
def secgroup_create(self, name, description):
'''
Create a security group
'''
nt_ks = self.compute_conn
nt_ks.security_groups.create(name, description)
ret = {'name': name, 'description': description}
return ret
def secgroup_delete(self, name):
'''
Delete a security group
'''
nt_ks = self.compute_conn
for item in nt_ks.security_groups.list():
if item.name == name:
nt_ks.security_groups.delete(item.id)
return {name: 'Deleted security group: {0}'.format(name)}
return 'Security group not found: {0}'.format(name)
def secgroup_list(self):
'''
List security groups
'''
nt_ks = self.compute_conn
ret = {}
for item in nt_ks.security_groups.list():
ret[item.name] = {
'name': item.name,
'description': item.description,
'id': item.id,
'tenant_id': item.tenant_id,
'rules': item.rules,
}
return ret
def _item_list(self):
'''
List items
'''
nt_ks = self.compute_conn
ret = []
for item in nt_ks.items.list():
ret.append(item.__dict__)
return ret
def _network_show(self, name, network_lst):
'''
Parse the returned network list
'''
for net in network_lst:
if net.label == name:
return net.__dict__
return {}
def network_show(self, name):
'''
Show network information
'''
nt_ks = self.compute_conn
net_list = nt_ks.networks.list()
return self._network_show(name, net_list)
def network_list(self):
'''
List extra private networks
'''
nt_ks = self.compute_conn
return [network.__dict__ for network in nt_ks.networks.list()]
def _sanatize_network_params(self, kwargs):
'''
Sanatize novaclient network parameters
'''
params = [
'label', 'bridge', 'bridge_interface', 'cidr', 'cidr_v6', 'dns1',
'dns2', 'fixed_cidr', 'gateway', 'gateway_v6', 'multi_host',
'priority', 'project_id', 'vlan_start', 'vpn_start'
]
for variable in six.iterkeys(kwargs): # iterate over a copy, we might delete some
if variable not in params:
del kwargs[variable]
return kwargs
def network_create(self, name, **kwargs):
'''
Create extra private network
'''
nt_ks = self.compute_conn
kwargs['label'] = name
kwargs = self._sanatize_network_params(kwargs)
net = nt_ks.networks.create(**kwargs)
return net.__dict__
def _server_uuid_from_name(self, name):
'''
Get server uuid from name
'''
return self.server_list().get(name, {}).get('id', '')
def virtual_interface_list(self, name):
'''
Get virtual interfaces on slice
'''
nt_ks = self.compute_conn
nets = nt_ks.virtual_interfaces.list(self._server_uuid_from_name(name))
return [network.__dict__ for network in nets]
def virtual_interface_create(self, name, net_name):
'''
Add an interfaces to a slice
'''
nt_ks = self.compute_conn
serverid = self._server_uuid_from_name(name)
networkid = self.network_show(net_name).get('id', None)
if networkid is None:
return {net_name: False}
nets = nt_ks.virtual_interfaces.create(networkid, serverid)
return nets
def floating_ip_pool_list(self):
'''
List all floating IP pools
.. versionadded:: 2016.3.0
'''
nt_ks = self.compute_conn
pools = nt_ks.floating_ip_pools.list()
response = {}
for pool in pools:
response[pool.name] = {
'name': pool.name,
}
return response
def floating_ip_list(self):
'''
List floating IPs
.. versionadded:: 2016.3.0
'''
nt_ks = self.compute_conn
floating_ips = nt_ks.floating_ips.list()
response = {}
for floating_ip in floating_ips:
response[floating_ip.ip] = {
'ip': floating_ip.ip,
'fixed_ip': floating_ip.fixed_ip,
'id': floating_ip.id,
'instance_id': floating_ip.instance_id,
'pool': floating_ip.pool
}
return response
def floating_ip_show(self, ip):
'''
Show info on specific floating IP
.. versionadded:: 2016.3.0
'''
nt_ks = self.compute_conn
floating_ips = nt_ks.floating_ips.list()
for floating_ip in floating_ips:
if floating_ip.ip == ip:
response = {
'ip': floating_ip.ip,
'fixed_ip': floating_ip.fixed_ip,
'id': floating_ip.id,
'instance_id': floating_ip.instance_id,
'pool': floating_ip.pool
}
return response
return {}
def floating_ip_create(self, pool=None):
'''
Allocate a floating IP
.. versionadded:: 2016.3.0
'''
nt_ks = self.compute_conn
floating_ip = nt_ks.floating_ips.create(pool)
response = {
'ip': floating_ip.ip,
'fixed_ip': floating_ip.fixed_ip,
'id': floating_ip.id,
'instance_id': floating_ip.instance_id,
'pool': floating_ip.pool
}
return response
def floating_ip_delete(self, floating_ip):
'''
De-allocate a floating IP
.. versionadded:: 2016.3.0
'''
ip = self.floating_ip_show(floating_ip)
nt_ks = self.compute_conn
return nt_ks.floating_ips.delete(ip)
def floating_ip_associate(self, server_name, floating_ip):
'''
Associate floating IP address to server
.. versionadded:: 2016.3.0
'''
nt_ks = self.compute_conn
server_ = self.server_by_name(server_name)
server = nt_ks.servers.get(server_.__dict__['id'])
server.add_floating_ip(floating_ip)
return self.floating_ip_list()[floating_ip]
def floating_ip_disassociate(self, server_name, floating_ip):
'''
Disassociate a floating IP from server
.. versionadded:: 2016.3.0
'''
nt_ks = self.compute_conn
server_ = self.server_by_name(server_name)
server = nt_ks.servers.get(server_.__dict__['id'])
server.remove_floating_ip(floating_ip)
return self.floating_ip_list()[floating_ip]
# The following is a list of functions that need to be incorporated in the
# nova module. This list should be updated as functions are added.
#
# absolute-limits Print a list of absolute limits for a user
# actions Retrieve server actions.
# add-fixed-ip Add new IP address to network.
# aggregate-add-host Add the host to the specified aggregate.
# aggregate-create Create a new aggregate with the specified details.
# aggregate-delete Delete the aggregate by its id.
# aggregate-details Show details of the specified aggregate.
# aggregate-list Print a list of all aggregates.
# aggregate-remove-host
# Remove the specified host from the specified aggregate.
# aggregate-set-metadata
# Update the metadata associated with the aggregate.
# aggregate-update Update the aggregate's name and optionally
# availability zone.
# cloudpipe-create Create a cloudpipe instance for the given project
# cloudpipe-list Print a list of all cloudpipe instances.
# console-log Get console log output of a server.
# credentials Show user credentials returned from auth
# describe-resource Show details about a resource
# diagnostics Retrieve server diagnostics.
# dns-create Create a DNS entry for domain, name and ip.
# dns-create-private-domain
# Create the specified DNS domain.
# dns-create-public-domain
# Create the specified DNS domain.
# dns-delete Delete the specified DNS entry.
# dns-delete-domain Delete the specified DNS domain.
# dns-domains Print a list of available dns domains.
# dns-list List current DNS entries for domain and ip or domain
# and name.
# endpoints Discover endpoints that get returned from the
# authenticate services
# get-vnc-console Get a vnc console to a server.
# host-action Perform a power action on a host.
# host-update Update host settings.
# image-create Create a new image by taking a snapshot of a running
# server.
# image-delete Delete an image.
# live-migration Migrates a running instance to a new machine.
# meta Set or Delete metadata on a server.
# migrate Migrate a server.
# pause Pause a server.
# rate-limits Print a list of rate limits for a user
# reboot Reboot a server.
# rebuild Shutdown, re-image, and re-boot a server.
# remove-fixed-ip Remove an IP address from a server.
# rename Rename a server.
# rescue Rescue a server.
# resize Resize a server.
# resize-confirm Confirm a previous resize.
# resize-revert Revert a previous resize (and return to the previous
# VM).
# root-password Change the root password for a server.
# secgroup-add-group-rule
# Add a source group rule to a security group.
# secgroup-add-rule Add a rule to a security group.
# secgroup-delete-group-rule
# Delete a source group rule from a security group.
# secgroup-delete-rule
# Delete a rule from a security group.
# secgroup-list-rules
# List rules for a security group.
# ssh SSH into a server.
# unlock Unlock a server.
# unpause Unpause a server.
# unrescue Unrescue a server.
# usage-list List usage data for all tenants
# volume-list List all the volumes.
# volume-snapshot-create
# Add a new snapshot.
# volume-snapshot-delete
# Remove a snapshot.
# volume-snapshot-list
# List all the snapshots.
# volume-snapshot-show
# Show details about a snapshot.
# volume-type-create Create a new volume type.
# volume-type-delete Delete a specific flavor
# volume-type-list Print a list of available 'volume types'.
# x509-create-cert Create x509 cert for a user in tenant
# x509-get-root-cert Fetches the x509 root cert.