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.name)
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):
235 public_handle = open(pub_expand_file, 'wb')
236 public_bytes = keys.public_key().public_bytes(
237 serialization.Encoding.OpenSSH,
238 serialization.PublicFormat.OpenSSH)
239 public_handle.write(public_bytes)
240 public_handle.close()
241 os.chmod(pub_expand_file, 0o400)
242 logger.info("Saved public key to - " + pub_expand_file)
245 priv_expand_file = os.path.expanduser(priv_file_path)
246 priv_dir = os.path.dirname(priv_expand_file)
247 if not os.path.isdir(priv_dir):
249 private_handle = open(priv_expand_file, 'wb')
250 private_handle.write(
252 encoding=serialization.Encoding.PEM,
253 format=serialization.PrivateFormat.TraditionalOpenSSL,
254 encryption_algorithm=serialization.NoEncryption()))
255 private_handle.close()
256 os.chmod(priv_expand_file, 0o400)
257 logger.info("Saved private key to - " + priv_expand_file)
260 def upload_keypair_file(nova, name, file_path):
262 Uploads a public key from a file
263 :param nova: the Nova client
264 :param name: the keypair name
265 :param file_path: the path to the public key file
266 :return: the keypair object
268 with open(os.path.expanduser(file_path), 'rb') as fpubkey:
269 logger.info('Saving keypair to - ' + file_path)
270 return upload_keypair(nova, name, fpubkey.read())
273 def upload_keypair(nova, name, key):
275 Uploads a public key from a file
276 :param nova: the Nova client
277 :param name: the keypair name
278 :param key: the public key object
279 :return: the keypair object
281 logger.info('Creating keypair with name - ' + name)
282 os_kp = nova.keypairs.create(name=name, public_key=key.decode('utf-8'))
283 return Keypair(name=os_kp.name, id=os_kp.id, public_key=os_kp.public_key)
286 def keypair_exists(nova, keypair_obj):
288 Returns a copy of the keypair object if found
289 :param nova: the Nova client
290 :param keypair_obj: the keypair object
291 :return: the keypair object or None if not found
294 os_kp = nova.keypairs.get(keypair_obj)
295 return Keypair(name=os_kp.name, id=os_kp.id,
296 public_key=os_kp.public_key)
301 def get_keypair_by_name(nova, name):
303 Returns a list of all available keypairs
304 :param nova: the Nova client
305 :param name: the name of the keypair to lookup
306 :return: the keypair object or None if not found
308 keypairs = nova.keypairs.list()
310 for keypair in keypairs:
311 if keypair.name == name:
312 return Keypair(name=keypair.name, id=keypair.id,
313 public_key=keypair.public_key)
318 def delete_keypair(nova, key):
320 Deletes a keypair object from OpenStack
321 :param nova: the Nova client
322 :param key: the SNAPS-OO keypair domain object to delete
324 logger.debug('Deleting keypair - ' + key.name)
325 nova.keypairs.delete(key.id)
328 def get_availability_zone_hosts(nova, zone_name='nova'):
330 Returns the names of all nova active compute servers
331 :param nova: the Nova client
332 :param zone_name: the Nova client
333 :return: a list of compute server names
336 zones = nova.availability_zones.list()
338 if zone.zoneName == zone_name and zone.hosts:
339 for key, host in zone.hosts.items():
340 if host['nova-compute']['available']:
341 out.append(zone.zoneName + ':' + key)
346 def delete_vm_instance(nova, vm_inst):
348 Deletes a VM instance
349 :param nova: the nova client
350 :param vm_inst: the snaps.domain.VmInst object
352 nova.servers.delete(vm_inst.id)
355 def __get_os_flavor(nova, flavor):
357 Returns to OpenStack flavor object by name
358 :param nova: the Nova client
359 :param flavor: the SNAPS flavor domain object
360 :return: the OpenStack Flavor object
363 return nova.flavors.get(flavor.id)
368 def get_flavor(nova, flavor):
370 Returns to OpenStack flavor object by name
371 :param nova: the Nova client
372 :param flavor: the SNAPS flavor domain object
373 :return: the SNAPS Flavor domain object
375 os_flavor = __get_os_flavor(nova, flavor)
378 name=os_flavor.name, id=os_flavor.id, ram=os_flavor.ram,
379 disk=os_flavor.disk, vcpus=os_flavor.vcpus,
380 ephemeral=os_flavor.ephemeral, swap=os_flavor.swap,
381 rxtx_factor=os_flavor.rxtx_factor, is_public=os_flavor.is_public)
383 return nova.flavors.get(flavor.id)
388 def __get_os_flavor_by_name(nova, name):
390 Returns to OpenStack flavor object by name
391 :param nova: the Nova client
392 :param name: the name of the flavor to query
393 :return: OpenStack flavor object
396 return nova.flavors.find(name=name)
401 def get_flavor_by_name(nova, name):
403 Returns a flavor by name
404 :param nova: the Nova client
405 :param name: the flavor name to return
406 :return: the SNAPS flavor domain object or None if not exists
408 os_flavor = __get_os_flavor_by_name(nova, name)
411 name=os_flavor.name, id=os_flavor.id, ram=os_flavor.ram,
412 disk=os_flavor.disk, vcpus=os_flavor.vcpus,
413 ephemeral=os_flavor.ephemeral, swap=os_flavor.swap,
414 rxtx_factor=os_flavor.rxtx_factor, is_public=os_flavor.is_public)
417 def create_flavor(nova, flavor_settings):
419 Creates and returns and OpenStack flavor object
420 :param nova: the Nova client
421 :param flavor_settings: the flavor settings
422 :return: the SNAPS flavor domain object
424 os_flavor = nova.flavors.create(
425 name=flavor_settings.name, flavorid=flavor_settings.flavor_id,
426 ram=flavor_settings.ram, vcpus=flavor_settings.vcpus,
427 disk=flavor_settings.disk, ephemeral=flavor_settings.ephemeral,
428 swap=flavor_settings.swap, rxtx_factor=flavor_settings.rxtx_factor,
429 is_public=flavor_settings.is_public)
431 name=os_flavor.name, id=os_flavor.id, ram=os_flavor.ram,
432 disk=os_flavor.disk, vcpus=os_flavor.vcpus,
433 ephemeral=os_flavor.ephemeral, swap=os_flavor.swap,
434 rxtx_factor=os_flavor.rxtx_factor, is_public=os_flavor.is_public)
437 def delete_flavor(nova, flavor):
440 :param nova: the Nova client
441 :param flavor: the SNAPS flavor domain object
443 nova.flavors.delete(flavor.id)
446 def set_flavor_keys(nova, flavor, metadata):
448 Sets metadata on the flavor
449 :param nova: the Nova client
450 :param flavor: the SNAPS flavor domain object
451 :param metadata: the metadata to set
453 os_flavor = __get_os_flavor(nova, flavor)
455 os_flavor.set_keys(metadata)
458 def get_flavor_keys(nova, flavor):
460 Sets metadata on the flavor
461 :param nova: the Nova client
462 :param flavor: the SNAPS flavor domain object
464 os_flavor = __get_os_flavor(nova, flavor)
466 return os_flavor.get_keys()
469 def add_security_group(nova, vm, security_group_name):
471 Adds a security group to an existing VM
472 :param nova: the nova client
473 :param vm: the OpenStack server object (VM) to alter
474 :param security_group_name: the name of the security group to add
476 nova.servers.add_security_group(str(vm.id), security_group_name)
479 def remove_security_group(nova, vm, security_group):
481 Removes a security group from an existing VM
482 :param nova: the nova client
483 :param vm: the OpenStack server object (VM) to alter
484 :param security_group: the SNAPS SecurityGroup domain object to add
486 nova.servers.remove_security_group(str(vm.id), security_group.name)
489 def add_floating_ip_to_server(nova, vm, floating_ip, ip_addr):
491 Adds a floating IP to a server instance
492 :param nova: the nova client
493 :param vm: VmInst domain object
494 :param floating_ip: FloatingIp domain object
495 :param ip_addr: the IP to which to bind the floating IP to
497 vm = __get_latest_server_os_object(nova, vm)
498 vm.add_floating_ip(floating_ip.ip, ip_addr)
501 class NovaException(Exception):
503 Exception when calls to the Keystone client cannot be served properly