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.vm_inst import VmInst
28 from snaps.openstack.utils import keystone_utils, glance_utils, neutron_utils
30 __author__ = 'spisarski'
32 logger = logging.getLogger('nova_utils')
35 Utilities for basic OpenStack Nova API calls
39 def nova_client(os_creds):
41 Instantiates and returns a client for communications with OpenStack's Nova
43 :param os_creds: The connection credentials to the OpenStack API
44 :return: the client object
46 logger.debug('Retrieving Nova Client')
47 return Client(os_creds.compute_api_version,
48 session=keystone_utils.keystone_session(os_creds),
49 region_name=os_creds.region_name)
52 def create_server(nova, neutron, glance, instance_settings, image_settings,
53 keypair_settings=None):
56 :param nova: the nova client (required)
57 :param neutron: the neutron client for retrieving ports (required)
58 :param glance: the glance client (required)
59 :param instance_settings: the VM instance settings object (required)
60 :param image_settings: the VM's image settings object (required)
61 :param keypair_settings: the VM's keypair settings object (optional)
62 :return: a snaps.domain.VmInst object
67 for port_setting in instance_settings.port_settings:
68 ports.append(neutron_utils.get_port(
69 neutron, port_settings=port_setting))
73 kv['port-id'] = port.id
76 logger.info('Creating VM with name - ' + instance_settings.name)
79 keypair_name = keypair_settings.name
81 flavor = get_flavor_by_name(nova, instance_settings.flavor)
84 'Flavor not found with name - %s', instance_settings.flavor)
86 image = glance_utils.get_image(glance, image_settings=image_settings)
88 args = {'name': instance_settings.name,
92 'key_name': keypair_name,
94 instance_settings.security_group_names,
95 'userdata': instance_settings.userdata}
97 if instance_settings.availability_zone:
98 args['availability_zone'] = instance_settings.availability_zone
100 server = nova.servers.create(**args)
101 return VmInst(name=server.name, inst_id=server.id,
102 networks=server.networks)
105 'Cannot create instance, image cannot be located with name %s',
109 def get_server(nova, vm_inst_settings=None, server_name=None):
111 Returns a VmInst object for the first server instance found.
112 :param nova: the Nova client
113 :param vm_inst_settings: the VmInstanceSettings object from which to build
114 the query if not None
115 :param server_name: the server with this name to return if vm_inst_settings
117 :return: a snaps.domain.VmInst object or None if not found
121 search_opts['name'] = vm_inst_settings.name
123 search_opts['name'] = server_name
125 servers = nova.servers.list(search_opts=search_opts)
126 for server in servers:
127 return VmInst(name=server.name, inst_id=server.id,
128 networks=server.networks)
131 def __get_latest_server_os_object(nova, server):
133 Returns a server with a given id
134 :param nova: the Nova client
135 :param server: the domain VmInst object
136 :return: the list of servers or None if not found
138 return nova.servers.get(server.id)
141 def get_server_status(nova, server):
143 Returns the a VM instance's status from OpenStack
144 :param nova: the Nova client
145 :param server: the domain VmInst object
146 :return: the VM's string status or None if not founc
148 server = __get_latest_server_os_object(nova, server)
154 def get_server_console_output(nova, server):
156 Returns the console object for parsing VM activity
157 :param nova: the Nova client
158 :param server: the domain VmInst object
159 :return: the console output object or None if server object is not found
161 server = __get_latest_server_os_object(nova, server)
163 return server.get_console_output()
167 def get_latest_server_object(nova, server):
169 Returns a server with a given id
170 :param nova: the Nova client
171 :param server: the old server object
172 :return: the list of servers or None if not found
174 server = __get_latest_server_os_object(nova, server)
175 return VmInst(name=server.name, inst_id=server.id,
176 networks=server.networks)
179 def get_server_security_group_names(nova, server):
181 Returns a server with a given id
182 :param nova: the Nova client
183 :param server: the old server object
184 :return: the list of security groups associated with a VM
187 os_vm_inst = __get_latest_server_os_object(nova, server)
188 for sec_grp_dict in os_vm_inst.security_groups:
189 out.append(sec_grp_dict['name'])
193 def get_server_info(nova, server):
195 Returns a dictionary of a VMs info as returned by OpenStack
196 :param nova: the Nova client
197 :param server: the old server object
198 :return: a dict of the info if VM exists else None
200 vm = __get_latest_server_os_object(nova, server)
206 def create_keys(key_size=2048):
208 Generates public and private keys
209 :param key_size: the number of bytes for the key size
210 :return: the cryptography keys
212 return rsa.generate_private_key(backend=default_backend(),
213 public_exponent=65537,
217 def public_key_openssh(keys):
219 Returns the public key for OpenSSH
220 :param keys: the keys generated by create_keys() from cryptography
221 :return: the OpenSSH public key
223 return keys.public_key().public_bytes(serialization.Encoding.OpenSSH,
224 serialization.PublicFormat.OpenSSH)
227 def save_keys_to_files(keys=None, pub_file_path=None, priv_file_path=None):
229 Saves the generated RSA generated keys to the filesystem
230 :param keys: the keys to save generated by cryptography
231 :param pub_file_path: the path to the public keys
232 :param priv_file_path: the path to the private keys
237 pub_expand_file = os.path.expanduser(pub_file_path)
238 pub_dir = os.path.dirname(pub_expand_file)
240 if not os.path.isdir(pub_dir):
245 public_handle = open(pub_expand_file, 'wb')
246 public_bytes = keys.public_key().public_bytes(
247 serialization.Encoding.OpenSSH,
248 serialization.PublicFormat.OpenSSH)
249 public_handle.write(public_bytes)
252 public_handle.close()
254 os.chmod(pub_expand_file, 0o400)
255 logger.info("Saved public key to - " + pub_expand_file)
258 priv_expand_file = os.path.expanduser(priv_file_path)
259 priv_dir = os.path.dirname(priv_expand_file)
260 if not os.path.isdir(priv_dir):
263 private_handle = None
265 private_handle = open(priv_expand_file, 'wb')
266 private_handle.write(
268 encoding=serialization.Encoding.PEM,
269 format=serialization.PrivateFormat.TraditionalOpenSSL,
270 encryption_algorithm=serialization.NoEncryption()))
273 private_handle.close()
275 os.chmod(priv_expand_file, 0o400)
276 logger.info("Saved private key to - " + priv_expand_file)
279 def upload_keypair_file(nova, name, file_path):
281 Uploads a public key from a file
282 :param nova: the Nova client
283 :param name: the keypair name
284 :param file_path: the path to the public key file
285 :return: the keypair object
289 with open(os.path.expanduser(file_path), 'rb') as fpubkey:
290 logger.info('Saving keypair to - ' + file_path)
291 return upload_keypair(nova, name, fpubkey.read())
297 def upload_keypair(nova, name, key):
299 Uploads a public key from a file
300 :param nova: the Nova client
301 :param name: the keypair name
302 :param key: the public key object
303 :return: the keypair object
305 logger.info('Creating keypair with name - ' + name)
306 os_kp = nova.keypairs.create(name=name, public_key=key.decode('utf-8'))
307 return Keypair(name=os_kp.name, id=os_kp.id, public_key=os_kp.public_key)
310 def keypair_exists(nova, keypair_obj):
312 Returns a copy of the keypair object if found
313 :param nova: the Nova client
314 :param keypair_obj: the keypair object
315 :return: the keypair object or None if not found
318 os_kp = nova.keypairs.get(keypair_obj)
319 return Keypair(name=os_kp.name, id=os_kp.id,
320 public_key=os_kp.public_key)
325 def get_keypair_by_name(nova, name):
327 Returns a list of all available keypairs
328 :param nova: the Nova client
329 :param name: the name of the keypair to lookup
330 :return: the keypair object or None if not found
332 keypairs = nova.keypairs.list()
334 for keypair in keypairs:
335 if keypair.name == name:
336 return Keypair(name=keypair.name, id=keypair.id,
337 public_key=keypair.public_key)
342 def delete_keypair(nova, key):
344 Deletes a keypair object from OpenStack
345 :param nova: the Nova client
346 :param key: the SNAPS-OO keypair domain object to delete
348 logger.debug('Deleting keypair - ' + key.name)
349 nova.keypairs.delete(key.id)
352 def get_availability_zone_hosts(nova, zone_name='nova'):
354 Returns the names of all nova active compute servers
355 :param nova: the Nova client
356 :param zone_name: the Nova client
357 :return: a list of compute server names
360 zones = nova.availability_zones.list()
362 if zone.zoneName == zone_name and zone.hosts:
363 for key, host in zone.hosts.items():
364 if host['nova-compute']['available']:
365 out.append(zone.zoneName + ':' + key)
370 def delete_vm_instance(nova, vm_inst):
372 Deletes a VM instance
373 :param nova: the nova client
374 :param vm_inst: the snaps.domain.VmInst object
376 nova.servers.delete(vm_inst.id)
379 def __get_os_flavor(nova, flavor):
381 Returns to OpenStack flavor object by name
382 :param nova: the Nova client
383 :param flavor: the SNAPS flavor domain object
384 :return: the OpenStack Flavor object
387 return nova.flavors.get(flavor.id)
392 def get_flavor(nova, flavor):
394 Returns to OpenStack flavor object by name
395 :param nova: the Nova client
396 :param flavor: the SNAPS flavor domain object
397 :return: the SNAPS Flavor domain object
399 os_flavor = __get_os_flavor(nova, flavor)
402 name=os_flavor.name, id=os_flavor.id, ram=os_flavor.ram,
403 disk=os_flavor.disk, vcpus=os_flavor.vcpus,
404 ephemeral=os_flavor.ephemeral, swap=os_flavor.swap,
405 rxtx_factor=os_flavor.rxtx_factor, is_public=os_flavor.is_public)
407 return nova.flavors.get(flavor.id)
412 def __get_os_flavor_by_name(nova, name):
414 Returns to OpenStack flavor object by name
415 :param nova: the Nova client
416 :param name: the name of the flavor to query
417 :return: OpenStack flavor object
420 return nova.flavors.find(name=name)
425 def get_flavor_by_name(nova, name):
427 Returns a flavor by name
428 :param nova: the Nova client
429 :param name: the flavor name to return
430 :return: the SNAPS flavor domain object or None if not exists
432 os_flavor = __get_os_flavor_by_name(nova, name)
435 name=os_flavor.name, id=os_flavor.id, ram=os_flavor.ram,
436 disk=os_flavor.disk, vcpus=os_flavor.vcpus,
437 ephemeral=os_flavor.ephemeral, swap=os_flavor.swap,
438 rxtx_factor=os_flavor.rxtx_factor, is_public=os_flavor.is_public)
441 def create_flavor(nova, flavor_settings):
443 Creates and returns and OpenStack flavor object
444 :param nova: the Nova client
445 :param flavor_settings: the flavor settings
446 :return: the SNAPS flavor domain object
448 os_flavor = nova.flavors.create(
449 name=flavor_settings.name, flavorid=flavor_settings.flavor_id,
450 ram=flavor_settings.ram, vcpus=flavor_settings.vcpus,
451 disk=flavor_settings.disk, ephemeral=flavor_settings.ephemeral,
452 swap=flavor_settings.swap, rxtx_factor=flavor_settings.rxtx_factor,
453 is_public=flavor_settings.is_public)
455 name=os_flavor.name, id=os_flavor.id, ram=os_flavor.ram,
456 disk=os_flavor.disk, vcpus=os_flavor.vcpus,
457 ephemeral=os_flavor.ephemeral, swap=os_flavor.swap,
458 rxtx_factor=os_flavor.rxtx_factor, is_public=os_flavor.is_public)
461 def delete_flavor(nova, flavor):
464 :param nova: the Nova client
465 :param flavor: the SNAPS flavor domain object
467 nova.flavors.delete(flavor.id)
470 def set_flavor_keys(nova, flavor, metadata):
472 Sets metadata on the flavor
473 :param nova: the Nova client
474 :param flavor: the SNAPS flavor domain object
475 :param metadata: the metadata to set
477 os_flavor = __get_os_flavor(nova, flavor)
479 os_flavor.set_keys(metadata)
482 def get_flavor_keys(nova, flavor):
484 Sets metadata on the flavor
485 :param nova: the Nova client
486 :param flavor: the SNAPS flavor domain object
488 os_flavor = __get_os_flavor(nova, flavor)
490 return os_flavor.get_keys()
493 def add_security_group(nova, vm, security_group_name):
495 Adds a security group to an existing VM
496 :param nova: the nova client
497 :param vm: the OpenStack server object (VM) to alter
498 :param security_group_name: the name of the security group to add
500 nova.servers.add_security_group(str(vm.id), security_group_name)
503 def remove_security_group(nova, vm, security_group):
505 Removes a security group from an existing VM
506 :param nova: the nova client
507 :param vm: the OpenStack server object (VM) to alter
508 :param security_group: the SNAPS SecurityGroup domain object to add
510 nova.servers.remove_security_group(str(vm.id), security_group.name)
513 def add_floating_ip_to_server(nova, vm, floating_ip, ip_addr):
515 Adds a floating IP to a server instance
516 :param nova: the nova client
517 :param vm: VmInst domain object
518 :param floating_ip: FloatingIp domain object
519 :param ip_addr: the IP to which to bind the floating IP to
521 vm = __get_latest_server_os_object(nova, vm)
522 vm.add_floating_ip(floating_ip.ip, ip_addr)
525 class NovaException(Exception):
527 Exception when calls to the Keystone client cannot be served properly