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))
51 def create_server(nova, neutron, glance, instance_settings, image_settings,
52 keypair_settings=None):
55 :param nova: the nova client (required)
56 :param neutron: the neutron client for retrieving ports (required)
57 :param glance: the glance client (required)
58 :param instance_settings: the VM instance settings object (required)
59 :param image_settings: the VM's image settings object (required)
60 :param keypair_settings: the VM's keypair settings object (optional)
61 :return: a snaps.domain.VmInst object
66 for port_setting in instance_settings.port_settings:
67 ports.append(neutron_utils.get_port_by_name(
68 neutron, port_setting.name))
72 kv['port-id'] = port.id
75 logger.info('Creating VM with name - ' + instance_settings.name)
78 keypair_name = keypair_settings.name
80 flavor = get_flavor_by_name(nova, instance_settings.flavor)
83 'Flavor not found with name - %s', instance_settings.flavor)
85 image = glance_utils.get_image(glance, image_settings.name)
87 args = {'name': instance_settings.name,
91 'key_name': keypair_name,
93 instance_settings.security_group_names,
94 'userdata': instance_settings.userdata,
96 instance_settings.availability_zone}
97 server = nova.servers.create(**args)
98 return VmInst(name=server.name, inst_id=server.id,
99 networks=server.networks)
102 'Cannot create instance, image cannot be located with name %s',
106 def get_servers_by_name(nova, name):
108 Returns a list of servers with a given name
109 :param nova: the Nova client
110 :param name: the server name
111 :return: the list of snaps.domain.VmInst objects
114 servers = nova.servers.list(search_opts={'name': name})
115 for server in servers:
116 out.append(VmInst(name=server.name, inst_id=server.id,
117 networks=server.networks))
121 def __get_latest_server_os_object(nova, server):
123 Returns a server with a given id
124 :param nova: the Nova client
125 :param server: the domain VmInst object
126 :return: the list of servers or None if not found
128 return nova.servers.get(server.id)
131 def get_server_status(nova, server):
133 Returns the a VM instance's status from OpenStack
134 :param nova: the Nova client
135 :param server: the domain VmInst object
136 :return: the VM's string status or None if not founc
138 server = __get_latest_server_os_object(nova, server)
144 def get_server_console_output(nova, server):
146 Returns the console object for parsing VM activity
147 :param nova: the Nova client
148 :param server: the domain VmInst object
149 :return: the console output object or None if server object is not found
151 server = __get_latest_server_os_object(nova, server)
153 return server.get_console_output()
157 def get_latest_server_object(nova, server):
159 Returns a server with a given id
160 :param nova: the Nova client
161 :param server: the old server object
162 :return: the list of servers or None if not found
164 server = __get_latest_server_os_object(nova, server)
165 return VmInst(name=server.name, inst_id=server.id,
166 networks=server.networks)
169 def get_server_security_group_names(nova, server):
171 Returns a server with a given id
172 :param nova: the Nova client
173 :param server: the old server object
174 :return: the list of security groups associated with a VM
177 os_vm_inst = __get_latest_server_os_object(nova, server)
178 for sec_grp_dict in os_vm_inst.security_groups:
179 out.append(sec_grp_dict['name'])
183 def get_server_info(nova, server):
185 Returns a dictionary of a VMs info as returned by OpenStack
186 :param nova: the Nova client
187 :param server: the old server object
188 :return: a dict of the info if VM exists else None
190 vm = __get_latest_server_os_object(nova, server)
196 def create_keys(key_size=2048):
198 Generates public and private keys
199 :param key_size: the number of bytes for the key size
200 :return: the cryptography keys
202 return rsa.generate_private_key(backend=default_backend(),
203 public_exponent=65537,
207 def public_key_openssh(keys):
209 Returns the public key for OpenSSH
210 :param keys: the keys generated by create_keys() from cryptography
211 :return: the OpenSSH public key
213 return keys.public_key().public_bytes(serialization.Encoding.OpenSSH,
214 serialization.PublicFormat.OpenSSH)
217 def save_keys_to_files(keys=None, pub_file_path=None, priv_file_path=None):
219 Saves the generated RSA generated keys to the filesystem
220 :param keys: the keys to save generated by cryptography
221 :param pub_file_path: the path to the public keys
222 :param priv_file_path: the path to the private keys
227 pub_expand_file = os.path.expanduser(pub_file_path)
228 pub_dir = os.path.dirname(pub_expand_file)
230 if not os.path.isdir(pub_dir):
232 public_handle = open(pub_expand_file, 'wb')
233 public_bytes = keys.public_key().public_bytes(
234 serialization.Encoding.OpenSSH,
235 serialization.PublicFormat.OpenSSH)
236 public_handle.write(public_bytes)
237 public_handle.close()
238 os.chmod(pub_expand_file, 0o400)
239 logger.info("Saved public key to - " + pub_expand_file)
242 priv_expand_file = os.path.expanduser(priv_file_path)
243 priv_dir = os.path.dirname(priv_expand_file)
244 if not os.path.isdir(priv_dir):
246 private_handle = open(priv_expand_file, 'wb')
247 private_handle.write(
249 encoding=serialization.Encoding.PEM,
250 format=serialization.PrivateFormat.TraditionalOpenSSL,
251 encryption_algorithm=serialization.NoEncryption()))
252 private_handle.close()
253 os.chmod(priv_expand_file, 0o400)
254 logger.info("Saved private key to - " + priv_expand_file)
257 def upload_keypair_file(nova, name, file_path):
259 Uploads a public key from a file
260 :param nova: the Nova client
261 :param name: the keypair name
262 :param file_path: the path to the public key file
263 :return: the keypair object
265 with open(os.path.expanduser(file_path), 'rb') as fpubkey:
266 logger.info('Saving keypair to - ' + file_path)
267 return upload_keypair(nova, name, fpubkey.read())
270 def upload_keypair(nova, name, key):
272 Uploads a public key from a file
273 :param nova: the Nova client
274 :param name: the keypair name
275 :param key: the public key object
276 :return: the keypair object
278 logger.info('Creating keypair with name - ' + name)
279 os_kp = nova.keypairs.create(name=name, public_key=key.decode('utf-8'))
280 return Keypair(name=os_kp.name, id=os_kp.id, public_key=os_kp.public_key)
283 def keypair_exists(nova, keypair_obj):
285 Returns a copy of the keypair object if found
286 :param nova: the Nova client
287 :param keypair_obj: the keypair object
288 :return: the keypair object or None if not found
291 os_kp = nova.keypairs.get(keypair_obj)
292 return Keypair(name=os_kp.name, id=os_kp.id,
293 public_key=os_kp.public_key)
298 def get_keypair_by_name(nova, name):
300 Returns a list of all available keypairs
301 :param nova: the Nova client
302 :param name: the name of the keypair to lookup
303 :return: the keypair object or None if not found
305 keypairs = nova.keypairs.list()
307 for keypair in keypairs:
308 if keypair.name == name:
309 return Keypair(name=keypair.name, id=keypair.id,
310 public_key=keypair.public_key)
315 def delete_keypair(nova, key):
317 Deletes a keypair object from OpenStack
318 :param nova: the Nova client
319 :param key: the SNAPS-OO keypair domain object to delete
321 logger.debug('Deleting keypair - ' + key.name)
322 nova.keypairs.delete(key.id)
325 def get_nova_availability_zones(nova):
327 Returns the names of all nova active compute servers
328 :param nova: the Nova client
329 :return: a list of compute server names
332 zones = nova.availability_zones.list()
334 if zone.zoneName == 'nova':
335 for key, host in zone.hosts.items():
336 if host['nova-compute']['available']:
337 out.append(zone.zoneName + ':' + key)
342 def delete_vm_instance(nova, vm_inst):
344 Deletes a VM instance
345 :param nova: the nova client
346 :param vm_inst: the snaps.domain.VmInst object
348 nova.servers.delete(vm_inst.id)
351 def __get_os_flavor(nova, flavor):
353 Returns to OpenStack flavor object by name
354 :param nova: the Nova client
355 :param flavor: the SNAPS flavor domain object
356 :return: the OpenStack Flavor object
359 return nova.flavors.get(flavor.id)
364 def get_flavor(nova, flavor):
366 Returns to OpenStack flavor object by name
367 :param nova: the Nova client
368 :param flavor: the SNAPS flavor domain object
369 :return: the SNAPS Flavor domain object
371 os_flavor = __get_os_flavor(nova, flavor)
374 name=os_flavor.name, id=os_flavor.id, ram=os_flavor.ram,
375 disk=os_flavor.disk, vcpus=os_flavor.vcpus,
376 ephemeral=os_flavor.ephemeral, swap=os_flavor.swap,
377 rxtx_factor=os_flavor.rxtx_factor, is_public=os_flavor.is_public)
379 return nova.flavors.get(flavor.id)
384 def __get_os_flavor_by_name(nova, name):
386 Returns to OpenStack flavor object by name
387 :param nova: the Nova client
388 :param name: the name of the flavor to query
389 :return: OpenStack flavor object
392 return nova.flavors.find(name=name)
397 def get_flavor_by_name(nova, name):
399 Returns a flavor by name
400 :param nova: the Nova client
401 :param name: the flavor name to return
402 :return: the SNAPS flavor domain object or None if not exists
404 os_flavor = __get_os_flavor_by_name(nova, name)
407 name=os_flavor.name, id=os_flavor.id, ram=os_flavor.ram,
408 disk=os_flavor.disk, vcpus=os_flavor.vcpus,
409 ephemeral=os_flavor.ephemeral, swap=os_flavor.swap,
410 rxtx_factor=os_flavor.rxtx_factor, is_public=os_flavor.is_public)
413 def create_flavor(nova, flavor_settings):
415 Creates and returns and OpenStack flavor object
416 :param nova: the Nova client
417 :param flavor_settings: the flavor settings
418 :return: the SNAPS flavor domain object
420 os_flavor = nova.flavors.create(
421 name=flavor_settings.name, flavorid=flavor_settings.flavor_id,
422 ram=flavor_settings.ram, vcpus=flavor_settings.vcpus,
423 disk=flavor_settings.disk, ephemeral=flavor_settings.ephemeral,
424 swap=flavor_settings.swap, rxtx_factor=flavor_settings.rxtx_factor,
425 is_public=flavor_settings.is_public)
427 name=os_flavor.name, id=os_flavor.id, ram=os_flavor.ram,
428 disk=os_flavor.disk, vcpus=os_flavor.vcpus,
429 ephemeral=os_flavor.ephemeral, swap=os_flavor.swap,
430 rxtx_factor=os_flavor.rxtx_factor, is_public=os_flavor.is_public)
433 def delete_flavor(nova, flavor):
436 :param nova: the Nova client
437 :param flavor: the SNAPS flavor domain object
439 nova.flavors.delete(flavor.id)
442 def set_flavor_keys(nova, flavor, metadata):
444 Sets metadata on the flavor
445 :param nova: the Nova client
446 :param flavor: the SNAPS flavor domain object
447 :param metadata: the metadata to set
449 os_flavor = __get_os_flavor(nova, flavor)
451 os_flavor.set_keys(metadata)
454 def add_security_group(nova, vm, security_group_name):
456 Adds a security group to an existing VM
457 :param nova: the nova client
458 :param vm: the OpenStack server object (VM) to alter
459 :param security_group_name: the name of the security group to add
461 nova.servers.add_security_group(str(vm.id), security_group_name)
464 def remove_security_group(nova, vm, security_group):
466 Removes a security group from an existing VM
467 :param nova: the nova client
468 :param vm: the OpenStack server object (VM) to alter
469 :param security_group: the SNAPS SecurityGroup domain object to add
471 nova.servers.remove_security_group(str(vm.id), security_group.name)
474 def add_floating_ip_to_server(nova, vm, floating_ip, ip_addr):
476 Adds a floating IP to a server instance
477 :param nova: the nova client
478 :param vm: VmInst domain object
479 :param floating_ip: FloatingIp domain object
480 :param ip_addr: the IP to which to bind the floating IP to
482 vm = __get_latest_server_os_object(nova, vm)
483 vm.add_floating_ip(floating_ip.ip, ip_addr)
486 class NovaException(Exception):
488 Exception when calls to the Keystone client cannot be served properly