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_by_name(
69 neutron, port_setting.name))
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_servers_by_name(nova, name):
111 Returns a list of servers with a given name
112 :param nova: the Nova client
113 :param name: the server name
114 :return: the list of snaps.domain.VmInst objects
117 servers = nova.servers.list(search_opts={'name': name})
118 for server in servers:
119 out.append(VmInst(name=server.name, inst_id=server.id,
120 networks=server.networks))
124 def __get_latest_server_os_object(nova, server):
126 Returns a server with a given id
127 :param nova: the Nova client
128 :param server: the domain VmInst object
129 :return: the list of servers or None if not found
131 return nova.servers.get(server.id)
134 def get_server_status(nova, server):
136 Returns the a VM instance's status from OpenStack
137 :param nova: the Nova client
138 :param server: the domain VmInst object
139 :return: the VM's string status or None if not founc
141 server = __get_latest_server_os_object(nova, server)
147 def get_server_console_output(nova, server):
149 Returns the console object for parsing VM activity
150 :param nova: the Nova client
151 :param server: the domain VmInst object
152 :return: the console output object or None if server object is not found
154 server = __get_latest_server_os_object(nova, server)
156 return server.get_console_output()
160 def get_latest_server_object(nova, server):
162 Returns a server with a given id
163 :param nova: the Nova client
164 :param server: the old server object
165 :return: the list of servers or None if not found
167 server = __get_latest_server_os_object(nova, server)
168 return VmInst(name=server.name, inst_id=server.id,
169 networks=server.networks)
172 def get_server_security_group_names(nova, server):
174 Returns a server with a given id
175 :param nova: the Nova client
176 :param server: the old server object
177 :return: the list of security groups associated with a VM
180 os_vm_inst = __get_latest_server_os_object(nova, server)
181 for sec_grp_dict in os_vm_inst.security_groups:
182 out.append(sec_grp_dict['name'])
186 def get_server_info(nova, server):
188 Returns a dictionary of a VMs info as returned by OpenStack
189 :param nova: the Nova client
190 :param server: the old server object
191 :return: a dict of the info if VM exists else None
193 vm = __get_latest_server_os_object(nova, server)
199 def create_keys(key_size=2048):
201 Generates public and private keys
202 :param key_size: the number of bytes for the key size
203 :return: the cryptography keys
205 return rsa.generate_private_key(backend=default_backend(),
206 public_exponent=65537,
210 def public_key_openssh(keys):
212 Returns the public key for OpenSSH
213 :param keys: the keys generated by create_keys() from cryptography
214 :return: the OpenSSH public key
216 return keys.public_key().public_bytes(serialization.Encoding.OpenSSH,
217 serialization.PublicFormat.OpenSSH)
220 def save_keys_to_files(keys=None, pub_file_path=None, priv_file_path=None):
222 Saves the generated RSA generated keys to the filesystem
223 :param keys: the keys to save generated by cryptography
224 :param pub_file_path: the path to the public keys
225 :param priv_file_path: the path to the private keys
230 pub_expand_file = os.path.expanduser(pub_file_path)
231 pub_dir = os.path.dirname(pub_expand_file)
233 if not os.path.isdir(pub_dir):
238 public_handle = open(pub_expand_file, 'wb')
239 public_bytes = keys.public_key().public_bytes(
240 serialization.Encoding.OpenSSH,
241 serialization.PublicFormat.OpenSSH)
242 public_handle.write(public_bytes)
245 public_handle.close()
247 os.chmod(pub_expand_file, 0o400)
248 logger.info("Saved public key to - " + pub_expand_file)
251 priv_expand_file = os.path.expanduser(priv_file_path)
252 priv_dir = os.path.dirname(priv_expand_file)
253 if not os.path.isdir(priv_dir):
256 private_handle = None
258 private_handle = open(priv_expand_file, 'wb')
259 private_handle.write(
261 encoding=serialization.Encoding.PEM,
262 format=serialization.PrivateFormat.TraditionalOpenSSL,
263 encryption_algorithm=serialization.NoEncryption()))
266 private_handle.close()
268 os.chmod(priv_expand_file, 0o400)
269 logger.info("Saved private key to - " + priv_expand_file)
272 def upload_keypair_file(nova, name, file_path):
274 Uploads a public key from a file
275 :param nova: the Nova client
276 :param name: the keypair name
277 :param file_path: the path to the public key file
278 :return: the keypair object
282 with open(os.path.expanduser(file_path), 'rb') as fpubkey:
283 logger.info('Saving keypair to - ' + file_path)
284 return upload_keypair(nova, name, fpubkey.read())
290 def upload_keypair(nova, name, key):
292 Uploads a public key from a file
293 :param nova: the Nova client
294 :param name: the keypair name
295 :param key: the public key object
296 :return: the keypair object
298 logger.info('Creating keypair with name - ' + name)
299 os_kp = nova.keypairs.create(name=name, public_key=key.decode('utf-8'))
300 return Keypair(name=os_kp.name, id=os_kp.id, public_key=os_kp.public_key)
303 def keypair_exists(nova, keypair_obj):
305 Returns a copy of the keypair object if found
306 :param nova: the Nova client
307 :param keypair_obj: the keypair object
308 :return: the keypair object or None if not found
311 os_kp = nova.keypairs.get(keypair_obj)
312 return Keypair(name=os_kp.name, id=os_kp.id,
313 public_key=os_kp.public_key)
318 def get_keypair_by_name(nova, name):
320 Returns a list of all available keypairs
321 :param nova: the Nova client
322 :param name: the name of the keypair to lookup
323 :return: the keypair object or None if not found
325 keypairs = nova.keypairs.list()
327 for keypair in keypairs:
328 if keypair.name == name:
329 return Keypair(name=keypair.name, id=keypair.id,
330 public_key=keypair.public_key)
335 def delete_keypair(nova, key):
337 Deletes a keypair object from OpenStack
338 :param nova: the Nova client
339 :param key: the SNAPS-OO keypair domain object to delete
341 logger.debug('Deleting keypair - ' + key.name)
342 nova.keypairs.delete(key.id)
345 def get_availability_zone_hosts(nova, zone_name='nova'):
347 Returns the names of all nova active compute servers
348 :param nova: the Nova client
349 :param zone_name: the Nova client
350 :return: a list of compute server names
353 zones = nova.availability_zones.list()
355 if zone.zoneName == zone_name and zone.hosts:
356 for key, host in zone.hosts.items():
357 if host['nova-compute']['available']:
358 out.append(zone.zoneName + ':' + key)
363 def delete_vm_instance(nova, vm_inst):
365 Deletes a VM instance
366 :param nova: the nova client
367 :param vm_inst: the snaps.domain.VmInst object
369 nova.servers.delete(vm_inst.id)
372 def __get_os_flavor(nova, flavor):
374 Returns to OpenStack flavor object by name
375 :param nova: the Nova client
376 :param flavor: the SNAPS flavor domain object
377 :return: the OpenStack Flavor object
380 return nova.flavors.get(flavor.id)
385 def get_flavor(nova, flavor):
387 Returns to OpenStack flavor object by name
388 :param nova: the Nova client
389 :param flavor: the SNAPS flavor domain object
390 :return: the SNAPS Flavor domain object
392 os_flavor = __get_os_flavor(nova, flavor)
395 name=os_flavor.name, id=os_flavor.id, ram=os_flavor.ram,
396 disk=os_flavor.disk, vcpus=os_flavor.vcpus,
397 ephemeral=os_flavor.ephemeral, swap=os_flavor.swap,
398 rxtx_factor=os_flavor.rxtx_factor, is_public=os_flavor.is_public)
400 return nova.flavors.get(flavor.id)
405 def __get_os_flavor_by_name(nova, name):
407 Returns to OpenStack flavor object by name
408 :param nova: the Nova client
409 :param name: the name of the flavor to query
410 :return: OpenStack flavor object
413 return nova.flavors.find(name=name)
418 def get_flavor_by_name(nova, name):
420 Returns a flavor by name
421 :param nova: the Nova client
422 :param name: the flavor name to return
423 :return: the SNAPS flavor domain object or None if not exists
425 os_flavor = __get_os_flavor_by_name(nova, name)
428 name=os_flavor.name, id=os_flavor.id, ram=os_flavor.ram,
429 disk=os_flavor.disk, vcpus=os_flavor.vcpus,
430 ephemeral=os_flavor.ephemeral, swap=os_flavor.swap,
431 rxtx_factor=os_flavor.rxtx_factor, is_public=os_flavor.is_public)
434 def create_flavor(nova, flavor_settings):
436 Creates and returns and OpenStack flavor object
437 :param nova: the Nova client
438 :param flavor_settings: the flavor settings
439 :return: the SNAPS flavor domain object
441 os_flavor = nova.flavors.create(
442 name=flavor_settings.name, flavorid=flavor_settings.flavor_id,
443 ram=flavor_settings.ram, vcpus=flavor_settings.vcpus,
444 disk=flavor_settings.disk, ephemeral=flavor_settings.ephemeral,
445 swap=flavor_settings.swap, rxtx_factor=flavor_settings.rxtx_factor,
446 is_public=flavor_settings.is_public)
448 name=os_flavor.name, id=os_flavor.id, ram=os_flavor.ram,
449 disk=os_flavor.disk, vcpus=os_flavor.vcpus,
450 ephemeral=os_flavor.ephemeral, swap=os_flavor.swap,
451 rxtx_factor=os_flavor.rxtx_factor, is_public=os_flavor.is_public)
454 def delete_flavor(nova, flavor):
457 :param nova: the Nova client
458 :param flavor: the SNAPS flavor domain object
460 nova.flavors.delete(flavor.id)
463 def set_flavor_keys(nova, flavor, metadata):
465 Sets metadata on the flavor
466 :param nova: the Nova client
467 :param flavor: the SNAPS flavor domain object
468 :param metadata: the metadata to set
470 os_flavor = __get_os_flavor(nova, flavor)
472 os_flavor.set_keys(metadata)
475 def get_flavor_keys(nova, flavor):
477 Sets metadata on the flavor
478 :param nova: the Nova client
479 :param flavor: the SNAPS flavor domain object
481 os_flavor = __get_os_flavor(nova, flavor)
483 return os_flavor.get_keys()
486 def add_security_group(nova, vm, security_group_name):
488 Adds a security group to an existing VM
489 :param nova: the nova client
490 :param vm: the OpenStack server object (VM) to alter
491 :param security_group_name: the name of the security group to add
493 nova.servers.add_security_group(str(vm.id), security_group_name)
496 def remove_security_group(nova, vm, security_group):
498 Removes a security group from an existing VM
499 :param nova: the nova client
500 :param vm: the OpenStack server object (VM) to alter
501 :param security_group: the SNAPS SecurityGroup domain object to add
503 nova.servers.remove_security_group(str(vm.id), security_group.name)
506 def add_floating_ip_to_server(nova, vm, floating_ip, ip_addr):
508 Adds a floating IP to a server instance
509 :param nova: the nova client
510 :param vm: VmInst domain object
511 :param floating_ip: FloatingIp domain object
512 :param ip_addr: the IP to which to bind the floating IP to
514 vm = __get_latest_server_os_object(nova, vm)
515 vm.add_floating_ip(floating_ip.ip, ip_addr)
518 class NovaException(Exception):
520 Exception when calls to the Keystone client cannot be served properly