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 servers
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_latest_server_object(nova, server):
133 Returns a server with a given id
134 :param nova: the Nova client
135 :param server: the old server object
136 :return: the list of servers or None if not found
138 server = get_latest_server_os_object(nova, server)
139 return VmInst(name=server.name, inst_id=server.id,
140 networks=server.networks)
143 def create_keys(key_size=2048):
145 Generates public and private keys
146 :param key_size: the number of bytes for the key size
147 :return: the cryptography keys
149 return rsa.generate_private_key(backend=default_backend(),
150 public_exponent=65537,
154 def public_key_openssh(keys):
156 Returns the public key for OpenSSH
157 :param keys: the keys generated by create_keys() from cryptography
158 :return: the OpenSSH public key
160 return keys.public_key().public_bytes(serialization.Encoding.OpenSSH,
161 serialization.PublicFormat.OpenSSH)
164 def save_keys_to_files(keys=None, pub_file_path=None, priv_file_path=None):
166 Saves the generated RSA generated keys to the filesystem
167 :param keys: the keys to save generated by cryptography
168 :param pub_file_path: the path to the public keys
169 :param priv_file_path: the path to the private keys
175 pub_expand_file = os.path.expanduser(pub_file_path)
176 pub_dir = os.path.dirname(pub_expand_file)
178 if not os.path.isdir(pub_dir):
180 public_handle = open(pub_expand_file, 'wb')
181 public_bytes = keys.public_key().public_bytes(
182 serialization.Encoding.OpenSSH,
183 serialization.PublicFormat.OpenSSH)
184 public_handle.write(public_bytes)
185 public_handle.close()
186 os.chmod(pub_expand_file, 0o400)
187 logger.info("Saved public key to - " + pub_expand_file)
190 priv_expand_file = os.path.expanduser(priv_file_path)
191 priv_dir = os.path.dirname(priv_expand_file)
192 if not os.path.isdir(priv_dir):
194 private_handle = open(priv_expand_file, 'wb')
195 private_handle.write(
197 encoding=serialization.Encoding.PEM,
198 format=serialization.PrivateFormat.TraditionalOpenSSL,
199 encryption_algorithm=serialization.NoEncryption()))
200 private_handle.close()
201 os.chmod(priv_expand_file, 0o400)
202 logger.info("Saved private key to - " + priv_expand_file)
205 def upload_keypair_file(nova, name, file_path):
207 Uploads a public key from a file
208 :param nova: the Nova client
209 :param name: the keypair name
210 :param file_path: the path to the public key file
211 :return: the keypair object
213 with open(os.path.expanduser(file_path), 'rb') as fpubkey:
214 logger.info('Saving keypair to - ' + file_path)
215 return upload_keypair(nova, name, fpubkey.read())
218 def upload_keypair(nova, name, key):
220 Uploads a public key from a file
221 :param nova: the Nova client
222 :param name: the keypair name
223 :param key: the public key object
224 :return: the keypair object
226 logger.info('Creating keypair with name - ' + name)
227 os_kp = nova.keypairs.create(name=name, public_key=key.decode('utf-8'))
228 return Keypair(name=os_kp.name, id=os_kp.id, public_key=os_kp.public_key)
231 def keypair_exists(nova, keypair_obj):
233 Returns a copy of the keypair object if found
234 :param nova: the Nova client
235 :param keypair_obj: the keypair object
236 :return: the keypair object or None if not found
239 os_kp = nova.keypairs.get(keypair_obj)
240 return Keypair(name=os_kp.name, id=os_kp.id,
241 public_key=os_kp.public_key)
246 def get_keypair_by_name(nova, name):
248 Returns a list of all available keypairs
249 :param nova: the Nova client
250 :param name: the name of the keypair to lookup
251 :return: the keypair object or None if not found
253 keypairs = nova.keypairs.list()
255 for keypair in keypairs:
256 if keypair.name == name:
257 return Keypair(name=keypair.name, id=keypair.id,
258 public_key=keypair.public_key)
263 def delete_keypair(nova, key):
265 Deletes a keypair object from OpenStack
266 :param nova: the Nova client
267 :param key: the SNAPS-OO keypair domain object to delete
269 logger.debug('Deleting keypair - ' + key.name)
270 nova.keypairs.delete(key.id)
273 def get_nova_availability_zones(nova):
275 Returns the names of all nova active compute servers
276 :param nova: the Nova client
277 :return: a list of compute server names
280 zones = nova.availability_zones.list()
282 if zone.zoneName == 'nova':
283 for key, host in zone.hosts.items():
284 if host['nova-compute']['available']:
285 out.append(zone.zoneName + ':' + key)
290 def delete_vm_instance(nova, vm_inst):
292 Deletes a VM instance
293 :param nova: the nova client
294 :param vm_inst: the snaps.domain.VmInst object
296 nova.servers.delete(vm_inst.id)
299 def get_os_flavor(nova, flavor):
301 Returns to OpenStack flavor object by name
302 :param nova: the Nova client
303 :param flavor: the SNAPS flavor domain object
304 :return: the OpenStack Flavor object
307 return nova.flavors.get(flavor.id)
312 def get_flavor(nova, flavor):
314 Returns to OpenStack flavor object by name
315 :param nova: the Nova client
316 :param flavor: the SNAPS flavor domain object
317 :return: the SNAPS Flavor domain object
319 os_flavor = get_os_flavor(nova, flavor)
322 name=os_flavor.name, id=os_flavor.id, ram=os_flavor.ram,
323 disk=os_flavor.disk, vcpus=os_flavor.vcpus,
324 ephemeral=os_flavor.ephemeral, swap=os_flavor.swap,
325 rxtx_factor=os_flavor.rxtx_factor, is_public=os_flavor.is_public)
327 return nova.flavors.get(flavor.id)
332 def get_os_flavor_by_name(nova, name):
334 Returns to OpenStack flavor object by name
335 :param nova: the Nova client
336 :param name: the name of the flavor to query
337 :return: OpenStack flavor object
340 return nova.flavors.find(name=name)
345 def get_flavor_by_name(nova, name):
347 Returns a flavor by name
348 :param nova: the Nova client
349 :param name: the flavor name to return
350 :return: the SNAPS flavor domain object or None if not exists
352 os_flavor = get_os_flavor_by_name(nova, name)
355 name=os_flavor.name, id=os_flavor.id, ram=os_flavor.ram,
356 disk=os_flavor.disk, vcpus=os_flavor.vcpus,
357 ephemeral=os_flavor.ephemeral, swap=os_flavor.swap,
358 rxtx_factor=os_flavor.rxtx_factor, is_public=os_flavor.is_public)
361 def create_flavor(nova, flavor_settings):
363 Creates and returns and OpenStack flavor object
364 :param nova: the Nova client
365 :param flavor_settings: the flavor settings
366 :return: the SNAPS flavor domain object
368 os_flavor = nova.flavors.create(
369 name=flavor_settings.name, flavorid=flavor_settings.flavor_id,
370 ram=flavor_settings.ram, vcpus=flavor_settings.vcpus,
371 disk=flavor_settings.disk, ephemeral=flavor_settings.ephemeral,
372 swap=flavor_settings.swap, rxtx_factor=flavor_settings.rxtx_factor,
373 is_public=flavor_settings.is_public)
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)
381 def delete_flavor(nova, flavor):
384 :param nova: the Nova client
385 :param flavor: the SNAPS flavor domain object
387 nova.flavors.delete(flavor.id)
390 def set_flavor_keys(nova, flavor, metadata):
392 Sets metadata on the flavor
393 :param nova: the Nova client
394 :param flavor: the SNAPS flavor domain object
395 :param metadata: the metadata to set
397 os_flavor = get_os_flavor(nova, flavor)
399 os_flavor.set_keys(metadata)
402 def add_security_group(nova, vm, security_group_name):
404 Adds a security group to an existing VM
405 :param nova: the nova client
406 :param vm: the OpenStack server object (VM) to alter
407 :param security_group_name: the name of the security group to add
409 nova.servers.add_security_group(str(vm.id), security_group_name)
412 def remove_security_group(nova, vm, security_group):
414 Removes a security group from an existing VM
415 :param nova: the nova client
416 :param vm: the OpenStack server object (VM) to alter
417 :param security_group: the SNAPS SecurityGroup domain object to add
419 nova.servers.remove_security_group(str(vm.id), security_group.name)
422 def add_floating_ip_to_server(nova, vm, floating_ip, ip_addr):
424 Adds a floating IP to a server instance
425 :param nova: the nova client
426 :param vm: VmInst domain object
427 :param floating_ip: FloatingIp domain object
428 :param ip_addr: the IP to which to bind the floating IP to
430 vm = get_latest_server_os_object(nova, vm)
431 vm.add_floating_ip(floating_ip.ip, ip_addr)
434 class NovaException(Exception):
436 Exception when calls to the Keystone client cannot be served properly