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['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 servers
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_latest_server_object(nova, server):
134 Returns a server with a given id
135 :param nova: the Nova client
136 :param server: the old server object
137 :return: the list of servers or None if not found
139 server = get_latest_server_os_object(nova, server)
140 return VmInst(name=server.name, inst_id=server.id,
141 networks=server.networks)
144 def create_keys(key_size=2048):
146 Generates public and private keys
147 :param key_size: the number of bytes for the key size
148 :return: the cryptography keys
150 return rsa.generate_private_key(backend=default_backend(),
151 public_exponent=65537,
155 def public_key_openssh(keys):
157 Returns the public key for OpenSSH
158 :param keys: the keys generated by create_keys() from cryptography
159 :return: the OpenSSH public key
161 return keys.public_key().public_bytes(serialization.Encoding.OpenSSH,
162 serialization.PublicFormat.OpenSSH)
165 def save_keys_to_files(keys=None, pub_file_path=None, priv_file_path=None):
167 Saves the generated RSA generated keys to the filesystem
168 :param keys: the keys to save generated by cryptography
169 :param pub_file_path: the path to the public keys
170 :param priv_file_path: the path to the private keys
176 pub_expand_file = os.path.expanduser(pub_file_path)
177 pub_dir = os.path.dirname(pub_expand_file)
179 if not os.path.isdir(pub_dir):
181 public_handle = open(pub_expand_file, 'wb')
182 public_bytes = keys.public_key().public_bytes(
183 serialization.Encoding.OpenSSH,
184 serialization.PublicFormat.OpenSSH)
185 public_handle.write(public_bytes)
186 public_handle.close()
187 os.chmod(pub_expand_file, 0o400)
188 logger.info("Saved public key to - " + pub_expand_file)
191 priv_expand_file = os.path.expanduser(priv_file_path)
192 priv_dir = os.path.dirname(priv_expand_file)
193 if not os.path.isdir(priv_dir):
195 private_handle = open(priv_expand_file, 'wb')
196 private_handle.write(
198 encoding=serialization.Encoding.PEM,
199 format=serialization.PrivateFormat.TraditionalOpenSSL,
200 encryption_algorithm=serialization.NoEncryption()))
201 private_handle.close()
202 os.chmod(priv_expand_file, 0o400)
203 logger.info("Saved private key to - " + priv_expand_file)
206 def upload_keypair_file(nova, name, file_path):
208 Uploads a public key from a file
209 :param nova: the Nova client
210 :param name: the keypair name
211 :param file_path: the path to the public key file
212 :return: the keypair object
214 with open(os.path.expanduser(file_path), 'rb') as fpubkey:
215 logger.info('Saving keypair to - ' + file_path)
216 return upload_keypair(nova, name, fpubkey.read())
219 def upload_keypair(nova, name, key):
221 Uploads a public key from a file
222 :param nova: the Nova client
223 :param name: the keypair name
224 :param key: the public key object
225 :return: the keypair object
227 logger.info('Creating keypair with name - ' + name)
228 os_kp = nova.keypairs.create(name=name, public_key=key.decode('utf-8'))
229 return Keypair(name=os_kp.name, id=os_kp.id, public_key=os_kp.public_key)
232 def keypair_exists(nova, keypair_obj):
234 Returns a copy of the keypair object if found
235 :param nova: the Nova client
236 :param keypair_obj: the keypair object
237 :return: the keypair object or None if not found
240 os_kp = nova.keypairs.get(keypair_obj)
241 return Keypair(name=os_kp.name, id=os_kp.id,
242 public_key=os_kp.public_key)
247 def get_keypair_by_name(nova, name):
249 Returns a list of all available keypairs
250 :param nova: the Nova client
251 :param name: the name of the keypair to lookup
252 :return: the keypair object or None if not found
254 keypairs = nova.keypairs.list()
256 for keypair in keypairs:
257 if keypair.name == name:
258 return Keypair(name=keypair.name, id=keypair.id,
259 public_key=keypair.public_key)
264 def delete_keypair(nova, key):
266 Deletes a keypair object from OpenStack
267 :param nova: the Nova client
268 :param key: the SNAPS-OO keypair domain object to delete
270 logger.debug('Deleting keypair - ' + key.name)
271 nova.keypairs.delete(key.id)
274 def get_nova_availability_zones(nova):
276 Returns the names of all nova active compute servers
277 :param nova: the Nova client
278 :return: a list of compute server names
281 zones = nova.availability_zones.list()
283 if zone.zoneName == 'nova':
284 for key, host in zone.hosts.items():
285 if host['nova-compute']['available']:
286 out.append(zone.zoneName + ':' + key)
291 def delete_vm_instance(nova, vm_inst):
293 Deletes a VM instance
294 :param nova: the nova client
295 :param vm_inst: the snaps.domain.VmInst object
297 nova.servers.delete(vm_inst.id)
300 def get_os_flavor(nova, flavor):
302 Returns to OpenStack flavor object by name
303 :param nova: the Nova client
304 :param flavor: the SNAPS flavor domain object
305 :return: the OpenStack Flavor object
308 return nova.flavors.get(flavor.id)
313 def get_flavor(nova, flavor):
315 Returns to OpenStack flavor object by name
316 :param nova: the Nova client
317 :param flavor: the SNAPS flavor domain object
318 :return: the SNAPS Flavor domain object
320 os_flavor = get_os_flavor(nova, flavor)
323 name=os_flavor.name, id=os_flavor.id, ram=os_flavor.ram,
324 disk=os_flavor.disk, vcpus=os_flavor.vcpus,
325 ephemeral=os_flavor.ephemeral, swap=os_flavor.swap,
326 rxtx_factor=os_flavor.rxtx_factor, is_public=os_flavor.is_public)
328 return nova.flavors.get(flavor.id)
333 def get_os_flavor_by_name(nova, name):
335 Returns to OpenStack flavor object by name
336 :param nova: the Nova client
337 :param name: the name of the flavor to query
338 :return: OpenStack flavor object
341 return nova.flavors.find(name=name)
346 def get_flavor_by_name(nova, name):
348 Returns a flavor by name
349 :param nova: the Nova client
350 :param name: the flavor name to return
351 :return: the SNAPS flavor domain object or None if not exists
353 os_flavor = get_os_flavor_by_name(nova, name)
356 name=os_flavor.name, id=os_flavor.id, ram=os_flavor.ram,
357 disk=os_flavor.disk, vcpus=os_flavor.vcpus,
358 ephemeral=os_flavor.ephemeral, swap=os_flavor.swap,
359 rxtx_factor=os_flavor.rxtx_factor, is_public=os_flavor.is_public)
362 def create_flavor(nova, flavor_settings):
364 Creates and returns and OpenStack flavor object
365 :param nova: the Nova client
366 :param flavor_settings: the flavor settings
367 :return: the SNAPS flavor domain object
369 os_flavor = nova.flavors.create(
370 name=flavor_settings.name, flavorid=flavor_settings.flavor_id,
371 ram=flavor_settings.ram, vcpus=flavor_settings.vcpus,
372 disk=flavor_settings.disk, ephemeral=flavor_settings.ephemeral,
373 swap=flavor_settings.swap, rxtx_factor=flavor_settings.rxtx_factor,
374 is_public=flavor_settings.is_public)
376 name=os_flavor.name, id=os_flavor.id, ram=os_flavor.ram,
377 disk=os_flavor.disk, vcpus=os_flavor.vcpus,
378 ephemeral=os_flavor.ephemeral, swap=os_flavor.swap,
379 rxtx_factor=os_flavor.rxtx_factor, is_public=os_flavor.is_public)
382 def delete_flavor(nova, flavor):
385 :param nova: the Nova client
386 :param flavor: the SNAPS flavor domain object
388 nova.flavors.delete(flavor.id)
391 def set_flavor_keys(nova, flavor, metadata):
393 Sets metadata on the flavor
394 :param nova: the Nova client
395 :param flavor: the SNAPS flavor domain object
396 :param metadata: the metadata to set
398 os_flavor = get_os_flavor(nova, flavor)
400 os_flavor.set_keys(metadata)
403 def add_security_group(nova, vm, security_group_name):
405 Adds a security group to an existing VM
406 :param nova: the nova client
407 :param vm: the OpenStack server object (VM) to alter
408 :param security_group_name: the name of the security group to add
410 nova.servers.add_security_group(str(vm.id), security_group_name)
413 def remove_security_group(nova, vm, security_group):
415 Removes a security group from an existing VM
416 :param nova: the nova client
417 :param vm: the OpenStack server object (VM) to alter
418 :param security_group: the SNAPS SecurityGroup domain object to add
420 nova.servers.remove_security_group(str(vm.id), security_group.name)
423 def add_floating_ip_to_server(nova, vm, floating_ip, ip_addr):
425 Adds a floating IP to a server instance
426 :param nova: the nova client
427 :param vm: VmInst domain object
428 :param floating_ip: FloatingIp domain object
429 :param ip_addr: the IP to which to bind the floating IP to
431 vm = get_latest_server_os_object(nova, vm)
432 vm.add_floating_ip(floating_ip.ip, ip_addr)