1 # Copyright (c) 2017 Cable Television Laboratories, Inc. ("CableLabs")
2 # and others. All rights reserved.
4 # Licensed under the Apache License, Version 2.0 (the "License");
5 # you may not use this file except in compliance with the License.
6 # You may obtain a copy of the License at:
8 # http://www.apache.org/licenses/LICENSE-2.0
10 # Unless required by applicable law or agreed to in writing, software
11 # distributed under the License is distributed on an "AS IS" BASIS,
12 # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 # See the License for the specific language governing permissions and
14 # limitations under the License.
19 from cryptography.hazmat.backends import default_backend
20 from cryptography.hazmat.primitives import serialization
21 from cryptography.hazmat.primitives.asymmetric import rsa
22 from novaclient.client import Client
23 from novaclient.exceptions import NotFound
25 from snaps.domain.flavor import Flavor
26 from snaps.domain.keypair import Keypair
27 from snaps.domain.project import ComputeQuotas
28 from snaps.domain.vm_inst import VmInst
29 from snaps.openstack.utils import keystone_utils, glance_utils, neutron_utils
31 __author__ = 'spisarski'
33 logger = logging.getLogger('nova_utils')
36 Utilities for basic OpenStack Nova API calls
40 def nova_client(os_creds):
42 Instantiates and returns a client for communications with OpenStack's Nova
44 :param os_creds: The connection credentials to the OpenStack API
45 :return: the client object
47 logger.debug('Retrieving Nova Client')
48 return Client(os_creds.compute_api_version,
49 session=keystone_utils.keystone_session(os_creds),
50 region_name=os_creds.region_name)
53 def create_server(nova, neutron, glance, instance_settings, image_settings,
54 keypair_settings=None):
57 :param nova: the nova client (required)
58 :param neutron: the neutron client for retrieving ports (required)
59 :param glance: the glance client (required)
60 :param instance_settings: the VM instance settings object (required)
61 :param image_settings: the VM's image settings object (required)
62 :param keypair_settings: the VM's keypair settings object (optional)
63 :return: a snaps.domain.VmInst object
68 for port_setting in instance_settings.port_settings:
69 ports.append(neutron_utils.get_port(
70 neutron, port_settings=port_setting))
74 kv['port-id'] = port.id
77 logger.info('Creating VM with name - ' + instance_settings.name)
80 keypair_name = keypair_settings.name
82 flavor = get_flavor_by_name(nova, instance_settings.flavor)
85 'Flavor not found with name - %s', instance_settings.flavor)
87 image = glance_utils.get_image(glance, image_settings=image_settings)
89 args = {'name': instance_settings.name,
93 'key_name': keypair_name,
95 instance_settings.security_group_names,
96 'userdata': instance_settings.userdata}
98 if instance_settings.availability_zone:
99 args['availability_zone'] = instance_settings.availability_zone
101 server = nova.servers.create(**args)
102 return VmInst(name=server.name, inst_id=server.id,
103 networks=server.networks)
106 'Cannot create instance, image cannot be located with name %s',
110 def get_server(nova, vm_inst_settings=None, server_name=None):
112 Returns a VmInst object for the first server instance found.
113 :param nova: the Nova client
114 :param vm_inst_settings: the VmInstanceSettings object from which to build
115 the query if not None
116 :param server_name: the server with this name to return if vm_inst_settings
118 :return: a snaps.domain.VmInst object or None if not found
122 search_opts['name'] = vm_inst_settings.name
124 search_opts['name'] = server_name
126 servers = nova.servers.list(search_opts=search_opts)
127 for server in servers:
128 return VmInst(name=server.name, inst_id=server.id,
129 networks=server.networks)
132 def __get_latest_server_os_object(nova, server):
134 Returns a server with a given id
135 :param nova: the Nova client
136 :param server: the domain VmInst object
137 :return: the list of servers or None if not found
139 return nova.servers.get(server.id)
142 def get_server_status(nova, server):
144 Returns the a VM instance's status from OpenStack
145 :param nova: the Nova client
146 :param server: the domain VmInst object
147 :return: the VM's string status or None if not founc
149 server = __get_latest_server_os_object(nova, server)
155 def get_server_console_output(nova, server):
157 Returns the console object for parsing VM activity
158 :param nova: the Nova client
159 :param server: the domain VmInst object
160 :return: the console output object or None if server object is not found
162 server = __get_latest_server_os_object(nova, server)
164 return server.get_console_output()
168 def get_latest_server_object(nova, server):
170 Returns a server with a given id
171 :param nova: the Nova client
172 :param server: the old server object
173 :return: the list of servers or None if not found
175 server = __get_latest_server_os_object(nova, server)
176 return VmInst(name=server.name, inst_id=server.id,
177 networks=server.networks)
180 def get_server_security_group_names(nova, server):
182 Returns a server with a given id
183 :param nova: the Nova client
184 :param server: the old server object
185 :return: the list of security groups associated with a VM
188 os_vm_inst = __get_latest_server_os_object(nova, server)
189 for sec_grp_dict in os_vm_inst.security_groups:
190 out.append(sec_grp_dict['name'])
194 def get_server_info(nova, server):
196 Returns a dictionary of a VMs info as returned by OpenStack
197 :param nova: the Nova client
198 :param server: the old server object
199 :return: a dict of the info if VM exists else None
201 vm = __get_latest_server_os_object(nova, server)
207 def create_keys(key_size=2048):
209 Generates public and private keys
210 :param key_size: the number of bytes for the key size
211 :return: the cryptography keys
213 return rsa.generate_private_key(backend=default_backend(),
214 public_exponent=65537,
218 def public_key_openssh(keys):
220 Returns the public key for OpenSSH
221 :param keys: the keys generated by create_keys() from cryptography
222 :return: the OpenSSH public key
224 return keys.public_key().public_bytes(serialization.Encoding.OpenSSH,
225 serialization.PublicFormat.OpenSSH)
228 def save_keys_to_files(keys=None, pub_file_path=None, priv_file_path=None):
230 Saves the generated RSA generated keys to the filesystem
231 :param keys: the keys to save generated by cryptography
232 :param pub_file_path: the path to the public keys
233 :param priv_file_path: the path to the private keys
238 pub_expand_file = os.path.expanduser(pub_file_path)
239 pub_dir = os.path.dirname(pub_expand_file)
241 if not os.path.isdir(pub_dir):
246 public_handle = open(pub_expand_file, 'wb')
247 public_bytes = keys.public_key().public_bytes(
248 serialization.Encoding.OpenSSH,
249 serialization.PublicFormat.OpenSSH)
250 public_handle.write(public_bytes)
253 public_handle.close()
255 os.chmod(pub_expand_file, 0o400)
256 logger.info("Saved public key to - " + pub_expand_file)
259 priv_expand_file = os.path.expanduser(priv_file_path)
260 priv_dir = os.path.dirname(priv_expand_file)
261 if not os.path.isdir(priv_dir):
264 private_handle = None
266 private_handle = open(priv_expand_file, 'wb')
267 private_handle.write(
269 encoding=serialization.Encoding.PEM,
270 format=serialization.PrivateFormat.TraditionalOpenSSL,
271 encryption_algorithm=serialization.NoEncryption()))
274 private_handle.close()
276 os.chmod(priv_expand_file, 0o400)
277 logger.info("Saved private key to - " + priv_expand_file)
280 def upload_keypair_file(nova, name, file_path):
282 Uploads a public key from a file
283 :param nova: the Nova client
284 :param name: the keypair name
285 :param file_path: the path to the public key file
286 :return: the keypair object
290 with open(os.path.expanduser(file_path), 'rb') as fpubkey:
291 logger.info('Saving keypair to - ' + file_path)
292 return upload_keypair(nova, name, fpubkey.read())
298 def upload_keypair(nova, name, key):
300 Uploads a public key from a file
301 :param nova: the Nova client
302 :param name: the keypair name
303 :param key: the public key object
304 :return: the keypair object
306 logger.info('Creating keypair with name - ' + name)
307 os_kp = nova.keypairs.create(name=name, public_key=key.decode('utf-8'))
308 return Keypair(name=os_kp.name, id=os_kp.id, public_key=os_kp.public_key)
311 def keypair_exists(nova, keypair_obj):
313 Returns a copy of the keypair object if found
314 :param nova: the Nova client
315 :param keypair_obj: the keypair object
316 :return: the keypair object or None if not found
319 os_kp = nova.keypairs.get(keypair_obj)
320 return Keypair(name=os_kp.name, id=os_kp.id,
321 public_key=os_kp.public_key)
326 def get_keypair_by_name(nova, name):
328 Returns a list of all available keypairs
329 :param nova: the Nova client
330 :param name: the name of the keypair to lookup
331 :return: the keypair object or None if not found
333 keypairs = nova.keypairs.list()
335 for keypair in keypairs:
336 if keypair.name == name:
337 return Keypair(name=keypair.name, id=keypair.id,
338 public_key=keypair.public_key)
343 def delete_keypair(nova, key):
345 Deletes a keypair object from OpenStack
346 :param nova: the Nova client
347 :param key: the SNAPS-OO keypair domain object to delete
349 logger.debug('Deleting keypair - ' + key.name)
350 nova.keypairs.delete(key.id)
353 def get_availability_zone_hosts(nova, zone_name='nova'):
355 Returns the names of all nova active compute servers
356 :param nova: the Nova client
357 :param zone_name: the Nova client
358 :return: a list of compute server names
361 zones = nova.availability_zones.list()
363 if zone.zoneName == zone_name and zone.hosts:
364 for key, host in zone.hosts.items():
365 if host['nova-compute']['available']:
366 out.append(zone.zoneName + ':' + key)
371 def delete_vm_instance(nova, vm_inst):
373 Deletes a VM instance
374 :param nova: the nova client
375 :param vm_inst: the snaps.domain.VmInst object
377 nova.servers.delete(vm_inst.id)
380 def __get_os_flavor(nova, flavor):
382 Returns to OpenStack flavor object by name
383 :param nova: the Nova client
384 :param flavor: the SNAPS flavor domain object
385 :return: the OpenStack Flavor object
388 return nova.flavors.get(flavor.id)
393 def get_flavor(nova, flavor):
395 Returns to OpenStack flavor object by name
396 :param nova: the Nova client
397 :param flavor: the SNAPS flavor domain object
398 :return: the SNAPS Flavor domain object
400 os_flavor = __get_os_flavor(nova, flavor)
403 name=os_flavor.name, id=os_flavor.id, ram=os_flavor.ram,
404 disk=os_flavor.disk, vcpus=os_flavor.vcpus,
405 ephemeral=os_flavor.ephemeral, swap=os_flavor.swap,
406 rxtx_factor=os_flavor.rxtx_factor, is_public=os_flavor.is_public)
408 return nova.flavors.get(flavor.id)
413 def __get_os_flavor_by_name(nova, name):
415 Returns to OpenStack flavor object by name
416 :param nova: the Nova client
417 :param name: the name of the flavor to query
418 :return: OpenStack flavor object
421 return nova.flavors.find(name=name)
426 def get_flavor_by_name(nova, name):
428 Returns a flavor by name
429 :param nova: the Nova client
430 :param name: the flavor name to return
431 :return: the SNAPS flavor domain object or None if not exists
433 os_flavor = __get_os_flavor_by_name(nova, name)
436 name=os_flavor.name, id=os_flavor.id, ram=os_flavor.ram,
437 disk=os_flavor.disk, vcpus=os_flavor.vcpus,
438 ephemeral=os_flavor.ephemeral, swap=os_flavor.swap,
439 rxtx_factor=os_flavor.rxtx_factor, is_public=os_flavor.is_public)
442 def create_flavor(nova, flavor_settings):
444 Creates and returns and OpenStack flavor object
445 :param nova: the Nova client
446 :param flavor_settings: the flavor settings
447 :return: the SNAPS flavor domain object
449 os_flavor = nova.flavors.create(
450 name=flavor_settings.name, flavorid=flavor_settings.flavor_id,
451 ram=flavor_settings.ram, vcpus=flavor_settings.vcpus,
452 disk=flavor_settings.disk, ephemeral=flavor_settings.ephemeral,
453 swap=flavor_settings.swap, rxtx_factor=flavor_settings.rxtx_factor,
454 is_public=flavor_settings.is_public)
456 name=os_flavor.name, id=os_flavor.id, ram=os_flavor.ram,
457 disk=os_flavor.disk, vcpus=os_flavor.vcpus,
458 ephemeral=os_flavor.ephemeral, swap=os_flavor.swap,
459 rxtx_factor=os_flavor.rxtx_factor, is_public=os_flavor.is_public)
462 def delete_flavor(nova, flavor):
465 :param nova: the Nova client
466 :param flavor: the SNAPS flavor domain object
468 nova.flavors.delete(flavor.id)
471 def set_flavor_keys(nova, flavor, metadata):
473 Sets metadata on the flavor
474 :param nova: the Nova client
475 :param flavor: the SNAPS flavor domain object
476 :param metadata: the metadata to set
478 os_flavor = __get_os_flavor(nova, flavor)
480 os_flavor.set_keys(metadata)
483 def get_flavor_keys(nova, flavor):
485 Sets metadata on the flavor
486 :param nova: the Nova client
487 :param flavor: the SNAPS flavor domain object
489 os_flavor = __get_os_flavor(nova, flavor)
491 return os_flavor.get_keys()
494 def add_security_group(nova, vm, security_group_name):
496 Adds a security group to an existing VM
497 :param nova: the nova client
498 :param vm: the OpenStack server object (VM) to alter
499 :param security_group_name: the name of the security group to add
501 nova.servers.add_security_group(str(vm.id), security_group_name)
504 def remove_security_group(nova, vm, security_group):
506 Removes a security group from an existing VM
507 :param nova: the nova client
508 :param vm: the OpenStack server object (VM) to alter
509 :param security_group: the SNAPS SecurityGroup domain object to add
511 nova.servers.remove_security_group(str(vm.id), security_group.name)
514 def add_floating_ip_to_server(nova, vm, floating_ip, ip_addr):
516 Adds a floating IP to a server instance
517 :param nova: the nova client
518 :param vm: VmInst domain object
519 :param floating_ip: FloatingIp domain object
520 :param ip_addr: the IP to which to bind the floating IP to
522 vm = __get_latest_server_os_object(nova, vm)
523 vm.add_floating_ip(floating_ip.ip, ip_addr)
526 def get_compute_quotas(nova, project_id):
528 Returns a list of all available keypairs
529 :param nova: the Nova client
530 :param project_id: the project's ID of the quotas to lookup
531 :return: an object of type ComputeQuotas or None if not found
533 quotas = nova.quotas.get(tenant_id=project_id)
535 return ComputeQuotas(quotas)
538 def update_quotas(nova, project_id, compute_quotas):
540 Updates the compute quotas for a given project
541 :param nova: the Nova client
542 :param project_id: the project's ID that requires quota updates
543 :param compute_quotas: an object of type ComputeQuotas containing the
547 update_values = dict()
548 update_values['metadata_items'] = compute_quotas.metadata_items
549 update_values['cores'] = compute_quotas.cores
550 update_values['instances'] = compute_quotas.instances
551 update_values['injected_files'] = compute_quotas.injected_files
552 update_values['injected_file_content_bytes'] = compute_quotas.injected_file_content_bytes
553 update_values['ram'] = compute_quotas.ram
554 update_values['fixed_ips'] = compute_quotas.fixed_ips
555 update_values['key_pairs'] = compute_quotas.key_pairs
557 return nova.quotas.update(project_id, **update_values)
560 class NovaException(Exception):
562 Exception when calls to the Keystone client cannot be served properly