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',
84 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 instance_settings.availability_zone}
98 server = nova.servers.create(**args)
99 return VmInst(name=server.name, inst_id=server.id,
100 networks=server.networks)
103 'Cannot create instance, image cannot be located with name %s',
107 def get_servers_by_name(nova, name):
109 Returns a list of servers with a given name
110 :param nova: the Nova client
111 :param name: the server name
112 :return: the list of snaps.domain.VmInst objects
115 servers = nova.servers.list(search_opts={'name': name})
116 for server in servers:
117 out.append(VmInst(name=server.name, inst_id=server.id,
118 networks=server.networks))
122 def __get_latest_server_os_object(nova, server):
124 Returns a server with a given id
125 :param nova: the Nova client
126 :param server: the domain VmInst object
127 :return: the list of servers or None if not found
129 return nova.servers.get(server.id)
132 def get_server_status(nova, server):
134 Returns the a VM instance's status from OpenStack
135 :param nova: the Nova client
136 :param server: the domain VmInst object
137 :return: the VM's string status or None if not founc
139 server = __get_latest_server_os_object(nova, server)
145 def get_server_console_output(nova, server):
147 Returns the console object for parsing VM activity
148 :param nova: the Nova client
149 :param server: the domain VmInst object
150 :return: the console output object or None if server object is not found
152 server = __get_latest_server_os_object(nova, server)
154 return server.get_console_output()
158 def get_latest_server_object(nova, server):
160 Returns a server with a given id
161 :param nova: the Nova client
162 :param server: the old server object
163 :return: the list of servers or None if not found
165 server = __get_latest_server_os_object(nova, server)
166 return VmInst(name=server.name, inst_id=server.id,
167 networks=server.networks)
170 def get_server_security_group_names(nova, server):
172 Returns a server with a given id
173 :param nova: the Nova client
174 :param server: the old server object
175 :return: the list of security groups associated with a VM
178 os_vm_inst = __get_latest_server_os_object(nova, server)
179 for sec_grp_dict in os_vm_inst.security_groups:
180 out.append(sec_grp_dict['name'])
184 def get_server_info(nova, server):
186 Returns a dictionary of a VMs info as returned by OpenStack
187 :param nova: the Nova client
188 :param server: the old server object
189 :return: a dict of the info if VM exists else None
191 vm = __get_latest_server_os_object(nova, server)
197 def create_keys(key_size=2048):
199 Generates public and private keys
200 :param key_size: the number of bytes for the key size
201 :return: the cryptography keys
203 return rsa.generate_private_key(backend=default_backend(),
204 public_exponent=65537,
208 def public_key_openssh(keys):
210 Returns the public key for OpenSSH
211 :param keys: the keys generated by create_keys() from cryptography
212 :return: the OpenSSH public key
214 return keys.public_key().public_bytes(serialization.Encoding.OpenSSH,
215 serialization.PublicFormat.OpenSSH)
218 def save_keys_to_files(keys=None, pub_file_path=None, priv_file_path=None):
220 Saves the generated RSA generated keys to the filesystem
221 :param keys: the keys to save generated by cryptography
222 :param pub_file_path: the path to the public keys
223 :param priv_file_path: the path to the private keys
228 pub_expand_file = os.path.expanduser(pub_file_path)
229 pub_dir = os.path.dirname(pub_expand_file)
231 if not os.path.isdir(pub_dir):
233 public_handle = open(pub_expand_file, 'wb')
234 public_bytes = keys.public_key().public_bytes(
235 serialization.Encoding.OpenSSH,
236 serialization.PublicFormat.OpenSSH)
237 public_handle.write(public_bytes)
238 public_handle.close()
239 os.chmod(pub_expand_file, 0o400)
240 logger.info("Saved public key to - " + pub_expand_file)
243 priv_expand_file = os.path.expanduser(priv_file_path)
244 priv_dir = os.path.dirname(priv_expand_file)
245 if not os.path.isdir(priv_dir):
247 private_handle = open(priv_expand_file, 'wb')
248 private_handle.write(
250 encoding=serialization.Encoding.PEM,
251 format=serialization.PrivateFormat.TraditionalOpenSSL,
252 encryption_algorithm=serialization.NoEncryption()))
253 private_handle.close()
254 os.chmod(priv_expand_file, 0o400)
255 logger.info("Saved private key to - " + priv_expand_file)
258 def upload_keypair_file(nova, name, file_path):
260 Uploads a public key from a file
261 :param nova: the Nova client
262 :param name: the keypair name
263 :param file_path: the path to the public key file
264 :return: the keypair object
266 with open(os.path.expanduser(file_path), 'rb') as fpubkey:
267 logger.info('Saving keypair to - ' + file_path)
268 return upload_keypair(nova, name, fpubkey.read())
271 def upload_keypair(nova, name, key):
273 Uploads a public key from a file
274 :param nova: the Nova client
275 :param name: the keypair name
276 :param key: the public key object
277 :return: the keypair object
279 logger.info('Creating keypair with name - ' + name)
280 os_kp = nova.keypairs.create(name=name, public_key=key.decode('utf-8'))
281 return Keypair(name=os_kp.name, id=os_kp.id, public_key=os_kp.public_key)
284 def keypair_exists(nova, keypair_obj):
286 Returns a copy of the keypair object if found
287 :param nova: the Nova client
288 :param keypair_obj: the keypair object
289 :return: the keypair object or None if not found
292 os_kp = nova.keypairs.get(keypair_obj)
293 return Keypair(name=os_kp.name, id=os_kp.id,
294 public_key=os_kp.public_key)
299 def get_keypair_by_name(nova, name):
301 Returns a list of all available keypairs
302 :param nova: the Nova client
303 :param name: the name of the keypair to lookup
304 :return: the keypair object or None if not found
306 keypairs = nova.keypairs.list()
308 for keypair in keypairs:
309 if keypair.name == name:
310 return Keypair(name=keypair.name, id=keypair.id,
311 public_key=keypair.public_key)
316 def delete_keypair(nova, key):
318 Deletes a keypair object from OpenStack
319 :param nova: the Nova client
320 :param key: the SNAPS-OO keypair domain object to delete
322 logger.debug('Deleting keypair - ' + key.name)
323 nova.keypairs.delete(key.id)
326 def get_nova_availability_zones(nova):
328 Returns the names of all nova active compute servers
329 :param nova: the Nova client
330 :return: a list of compute server names
333 zones = nova.availability_zones.list()
335 if zone.zoneName == 'nova':
336 for key, host in zone.hosts.items():
337 if host['nova-compute']['available']:
338 out.append(zone.zoneName + ':' + key)
343 def delete_vm_instance(nova, vm_inst):
345 Deletes a VM instance
346 :param nova: the nova client
347 :param vm_inst: the snaps.domain.VmInst object
349 nova.servers.delete(vm_inst.id)
352 def __get_os_flavor(nova, flavor):
354 Returns to OpenStack flavor object by name
355 :param nova: the Nova client
356 :param flavor: the SNAPS flavor domain object
357 :return: the OpenStack Flavor object
360 return nova.flavors.get(flavor.id)
365 def get_flavor(nova, flavor):
367 Returns to OpenStack flavor object by name
368 :param nova: the Nova client
369 :param flavor: the SNAPS flavor domain object
370 :return: the SNAPS Flavor domain object
372 os_flavor = __get_os_flavor(nova, flavor)
375 name=os_flavor.name, id=os_flavor.id, ram=os_flavor.ram,
376 disk=os_flavor.disk, vcpus=os_flavor.vcpus,
377 ephemeral=os_flavor.ephemeral, swap=os_flavor.swap,
378 rxtx_factor=os_flavor.rxtx_factor, is_public=os_flavor.is_public)
380 return nova.flavors.get(flavor.id)
385 def __get_os_flavor_by_name(nova, name):
387 Returns to OpenStack flavor object by name
388 :param nova: the Nova client
389 :param name: the name of the flavor to query
390 :return: OpenStack flavor object
393 return nova.flavors.find(name=name)
398 def get_flavor_by_name(nova, name):
400 Returns a flavor by name
401 :param nova: the Nova client
402 :param name: the flavor name to return
403 :return: the SNAPS flavor domain object or None if not exists
405 os_flavor = __get_os_flavor_by_name(nova, name)
408 name=os_flavor.name, id=os_flavor.id, ram=os_flavor.ram,
409 disk=os_flavor.disk, vcpus=os_flavor.vcpus,
410 ephemeral=os_flavor.ephemeral, swap=os_flavor.swap,
411 rxtx_factor=os_flavor.rxtx_factor, is_public=os_flavor.is_public)
414 def create_flavor(nova, flavor_settings):
416 Creates and returns and OpenStack flavor object
417 :param nova: the Nova client
418 :param flavor_settings: the flavor settings
419 :return: the SNAPS flavor domain object
421 os_flavor = nova.flavors.create(
422 name=flavor_settings.name, flavorid=flavor_settings.flavor_id,
423 ram=flavor_settings.ram, vcpus=flavor_settings.vcpus,
424 disk=flavor_settings.disk, ephemeral=flavor_settings.ephemeral,
425 swap=flavor_settings.swap, rxtx_factor=flavor_settings.rxtx_factor,
426 is_public=flavor_settings.is_public)
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 delete_flavor(nova, flavor):
437 :param nova: the Nova client
438 :param flavor: the SNAPS flavor domain object
440 nova.flavors.delete(flavor.id)
443 def set_flavor_keys(nova, flavor, metadata):
445 Sets metadata on the flavor
446 :param nova: the Nova client
447 :param flavor: the SNAPS flavor domain object
448 :param metadata: the metadata to set
450 os_flavor = __get_os_flavor(nova, flavor)
452 os_flavor.set_keys(metadata)
455 def add_security_group(nova, vm, security_group_name):
457 Adds a security group to an existing VM
458 :param nova: the nova client
459 :param vm: the OpenStack server object (VM) to alter
460 :param security_group_name: the name of the security group to add
462 nova.servers.add_security_group(str(vm.id), security_group_name)
465 def remove_security_group(nova, vm, security_group):
467 Removes a security group from an existing VM
468 :param nova: the nova client
469 :param vm: the OpenStack server object (VM) to alter
470 :param security_group: the SNAPS SecurityGroup domain object to add
472 nova.servers.remove_security_group(str(vm.id), security_group.name)
475 def add_floating_ip_to_server(nova, vm, floating_ip, ip_addr):
477 Adds a floating IP to a server instance
478 :param nova: the nova client
479 :param vm: VmInst domain object
480 :param floating_ip: FloatingIp domain object
481 :param ip_addr: the IP to which to bind the floating IP to
483 vm = __get_latest_server_os_object(nova, vm)
484 vm.add_floating_ip(floating_ip.ip, ip_addr)