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 if instance_settings.availability_zone:
97 args['availability_zone'] = instance_settings.availability_zone
99 server = nova.servers.create(**args)
100 return VmInst(name=server.name, inst_id=server.id,
101 networks=server.networks)
104 'Cannot create instance, image cannot be located with name %s',
108 def get_servers_by_name(nova, name):
110 Returns a list of servers with a given name
111 :param nova: the Nova client
112 :param name: the server name
113 :return: the list of snaps.domain.VmInst objects
116 servers = nova.servers.list(search_opts={'name': name})
117 for server in servers:
118 out.append(VmInst(name=server.name, inst_id=server.id,
119 networks=server.networks))
123 def __get_latest_server_os_object(nova, server):
125 Returns a server with a given id
126 :param nova: the Nova client
127 :param server: the domain VmInst object
128 :return: the list of servers or None if not found
130 return nova.servers.get(server.id)
133 def get_server_status(nova, server):
135 Returns the a VM instance's status from OpenStack
136 :param nova: the Nova client
137 :param server: the domain VmInst object
138 :return: the VM's string status or None if not founc
140 server = __get_latest_server_os_object(nova, server)
146 def get_server_console_output(nova, server):
148 Returns the console object for parsing VM activity
149 :param nova: the Nova client
150 :param server: the domain VmInst object
151 :return: the console output object or None if server object is not found
153 server = __get_latest_server_os_object(nova, server)
155 return server.get_console_output()
159 def get_latest_server_object(nova, server):
161 Returns a server with a given id
162 :param nova: the Nova client
163 :param server: the old server object
164 :return: the list of servers or None if not found
166 server = __get_latest_server_os_object(nova, server)
167 return VmInst(name=server.name, inst_id=server.id,
168 networks=server.networks)
171 def get_server_security_group_names(nova, server):
173 Returns a server with a given id
174 :param nova: the Nova client
175 :param server: the old server object
176 :return: the list of security groups associated with a VM
179 os_vm_inst = __get_latest_server_os_object(nova, server)
180 for sec_grp_dict in os_vm_inst.security_groups:
181 out.append(sec_grp_dict['name'])
185 def get_server_info(nova, server):
187 Returns a dictionary of a VMs info as returned by OpenStack
188 :param nova: the Nova client
189 :param server: the old server object
190 :return: a dict of the info if VM exists else None
192 vm = __get_latest_server_os_object(nova, server)
198 def create_keys(key_size=2048):
200 Generates public and private keys
201 :param key_size: the number of bytes for the key size
202 :return: the cryptography keys
204 return rsa.generate_private_key(backend=default_backend(),
205 public_exponent=65537,
209 def public_key_openssh(keys):
211 Returns the public key for OpenSSH
212 :param keys: the keys generated by create_keys() from cryptography
213 :return: the OpenSSH public key
215 return keys.public_key().public_bytes(serialization.Encoding.OpenSSH,
216 serialization.PublicFormat.OpenSSH)
219 def save_keys_to_files(keys=None, pub_file_path=None, priv_file_path=None):
221 Saves the generated RSA generated keys to the filesystem
222 :param keys: the keys to save generated by cryptography
223 :param pub_file_path: the path to the public keys
224 :param priv_file_path: the path to the private keys
229 pub_expand_file = os.path.expanduser(pub_file_path)
230 pub_dir = os.path.dirname(pub_expand_file)
232 if not os.path.isdir(pub_dir):
234 public_handle = open(pub_expand_file, 'wb')
235 public_bytes = keys.public_key().public_bytes(
236 serialization.Encoding.OpenSSH,
237 serialization.PublicFormat.OpenSSH)
238 public_handle.write(public_bytes)
239 public_handle.close()
240 os.chmod(pub_expand_file, 0o400)
241 logger.info("Saved public key to - " + pub_expand_file)
244 priv_expand_file = os.path.expanduser(priv_file_path)
245 priv_dir = os.path.dirname(priv_expand_file)
246 if not os.path.isdir(priv_dir):
248 private_handle = open(priv_expand_file, 'wb')
249 private_handle.write(
251 encoding=serialization.Encoding.PEM,
252 format=serialization.PrivateFormat.TraditionalOpenSSL,
253 encryption_algorithm=serialization.NoEncryption()))
254 private_handle.close()
255 os.chmod(priv_expand_file, 0o400)
256 logger.info("Saved private key to - " + priv_expand_file)
259 def upload_keypair_file(nova, name, file_path):
261 Uploads a public key from a file
262 :param nova: the Nova client
263 :param name: the keypair name
264 :param file_path: the path to the public key file
265 :return: the keypair object
267 with open(os.path.expanduser(file_path), 'rb') as fpubkey:
268 logger.info('Saving keypair to - ' + file_path)
269 return upload_keypair(nova, name, fpubkey.read())
272 def upload_keypair(nova, name, key):
274 Uploads a public key from a file
275 :param nova: the Nova client
276 :param name: the keypair name
277 :param key: the public key object
278 :return: the keypair object
280 logger.info('Creating keypair with name - ' + name)
281 os_kp = nova.keypairs.create(name=name, public_key=key.decode('utf-8'))
282 return Keypair(name=os_kp.name, id=os_kp.id, public_key=os_kp.public_key)
285 def keypair_exists(nova, keypair_obj):
287 Returns a copy of the keypair object if found
288 :param nova: the Nova client
289 :param keypair_obj: the keypair object
290 :return: the keypair object or None if not found
293 os_kp = nova.keypairs.get(keypair_obj)
294 return Keypair(name=os_kp.name, id=os_kp.id,
295 public_key=os_kp.public_key)
300 def get_keypair_by_name(nova, name):
302 Returns a list of all available keypairs
303 :param nova: the Nova client
304 :param name: the name of the keypair to lookup
305 :return: the keypair object or None if not found
307 keypairs = nova.keypairs.list()
309 for keypair in keypairs:
310 if keypair.name == name:
311 return Keypair(name=keypair.name, id=keypair.id,
312 public_key=keypair.public_key)
317 def delete_keypair(nova, key):
319 Deletes a keypair object from OpenStack
320 :param nova: the Nova client
321 :param key: the SNAPS-OO keypair domain object to delete
323 logger.debug('Deleting keypair - ' + key.name)
324 nova.keypairs.delete(key.id)
327 def get_availability_zone_hosts(nova, zone_name='nova'):
329 Returns the names of all nova active compute servers
330 :param nova: the Nova client
331 :param zone_name: the Nova client
332 :return: a list of compute server names
335 zones = nova.availability_zones.list()
337 if zone.zoneName == zone_name and zone.hosts:
338 for key, host in zone.hosts.items():
339 if host['nova-compute']['available']:
340 out.append(zone.zoneName + ':' + key)
345 def delete_vm_instance(nova, vm_inst):
347 Deletes a VM instance
348 :param nova: the nova client
349 :param vm_inst: the snaps.domain.VmInst object
351 nova.servers.delete(vm_inst.id)
354 def __get_os_flavor(nova, flavor):
356 Returns to OpenStack flavor object by name
357 :param nova: the Nova client
358 :param flavor: the SNAPS flavor domain object
359 :return: the OpenStack Flavor object
362 return nova.flavors.get(flavor.id)
367 def get_flavor(nova, flavor):
369 Returns to OpenStack flavor object by name
370 :param nova: the Nova client
371 :param flavor: the SNAPS flavor domain object
372 :return: the SNAPS Flavor domain object
374 os_flavor = __get_os_flavor(nova, flavor)
377 name=os_flavor.name, id=os_flavor.id, ram=os_flavor.ram,
378 disk=os_flavor.disk, vcpus=os_flavor.vcpus,
379 ephemeral=os_flavor.ephemeral, swap=os_flavor.swap,
380 rxtx_factor=os_flavor.rxtx_factor, is_public=os_flavor.is_public)
382 return nova.flavors.get(flavor.id)
387 def __get_os_flavor_by_name(nova, name):
389 Returns to OpenStack flavor object by name
390 :param nova: the Nova client
391 :param name: the name of the flavor to query
392 :return: OpenStack flavor object
395 return nova.flavors.find(name=name)
400 def get_flavor_by_name(nova, name):
402 Returns a flavor by name
403 :param nova: the Nova client
404 :param name: the flavor name to return
405 :return: the SNAPS flavor domain object or None if not exists
407 os_flavor = __get_os_flavor_by_name(nova, name)
410 name=os_flavor.name, id=os_flavor.id, ram=os_flavor.ram,
411 disk=os_flavor.disk, vcpus=os_flavor.vcpus,
412 ephemeral=os_flavor.ephemeral, swap=os_flavor.swap,
413 rxtx_factor=os_flavor.rxtx_factor, is_public=os_flavor.is_public)
416 def create_flavor(nova, flavor_settings):
418 Creates and returns and OpenStack flavor object
419 :param nova: the Nova client
420 :param flavor_settings: the flavor settings
421 :return: the SNAPS flavor domain object
423 os_flavor = nova.flavors.create(
424 name=flavor_settings.name, flavorid=flavor_settings.flavor_id,
425 ram=flavor_settings.ram, vcpus=flavor_settings.vcpus,
426 disk=flavor_settings.disk, ephemeral=flavor_settings.ephemeral,
427 swap=flavor_settings.swap, rxtx_factor=flavor_settings.rxtx_factor,
428 is_public=flavor_settings.is_public)
430 name=os_flavor.name, id=os_flavor.id, ram=os_flavor.ram,
431 disk=os_flavor.disk, vcpus=os_flavor.vcpus,
432 ephemeral=os_flavor.ephemeral, swap=os_flavor.swap,
433 rxtx_factor=os_flavor.rxtx_factor, is_public=os_flavor.is_public)
436 def delete_flavor(nova, flavor):
439 :param nova: the Nova client
440 :param flavor: the SNAPS flavor domain object
442 nova.flavors.delete(flavor.id)
445 def set_flavor_keys(nova, flavor, metadata):
447 Sets metadata on the flavor
448 :param nova: the Nova client
449 :param flavor: the SNAPS flavor domain object
450 :param metadata: the metadata to set
452 os_flavor = __get_os_flavor(nova, flavor)
454 os_flavor.set_keys(metadata)
457 def add_security_group(nova, vm, security_group_name):
459 Adds a security group to an existing VM
460 :param nova: the nova client
461 :param vm: the OpenStack server object (VM) to alter
462 :param security_group_name: the name of the security group to add
464 nova.servers.add_security_group(str(vm.id), security_group_name)
467 def remove_security_group(nova, vm, security_group):
469 Removes a security group from an existing VM
470 :param nova: the nova client
471 :param vm: the OpenStack server object (VM) to alter
472 :param security_group: the SNAPS SecurityGroup domain object to add
474 nova.servers.remove_security_group(str(vm.id), security_group.name)
477 def add_floating_ip_to_server(nova, vm, floating_ip, ip_addr):
479 Adds a floating IP to a server instance
480 :param nova: the nova client
481 :param vm: VmInst domain object
482 :param floating_ip: FloatingIp domain object
483 :param ip_addr: the IP to which to bind the floating IP to
485 vm = __get_latest_server_os_object(nova, vm)
486 vm.add_floating_ip(floating_ip.ip, ip_addr)
489 class NovaException(Exception):
491 Exception when calls to the Keystone client cannot be served properly