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.vm_inst import VmInst
27 from snaps.openstack.utils import keystone_utils, glance_utils, neutron_utils
29 __author__ = 'spisarski'
31 logger = logging.getLogger('nova_utils')
34 Utilities for basic OpenStack Nova API calls
38 def nova_client(os_creds):
40 Instantiates and returns a client for communications with OpenStack's Nova
42 :param os_creds: The connection credentials to the OpenStack API
43 :return: the client object
45 logger.debug('Retrieving Nova Client')
46 return Client(os_creds.compute_api_version,
47 session=keystone_utils.keystone_session(os_creds))
50 def create_server(nova, neutron, glance, instance_settings, image_settings,
51 keypair_settings=None):
54 :param nova: the nova client (required)
55 :param neutron: the neutron client for retrieving ports (required)
56 :param glance: the glance client (required)
57 :param instance_settings: the VM instance settings object (required)
58 :param image_settings: the VM's image settings object (required)
59 :param keypair_settings: the VM's keypair settings object (optional)
60 :return: a snaps.domain.VmInst object
65 for port_setting in instance_settings.port_settings:
66 ports.append(neutron_utils.get_port_by_name(
67 neutron, port_setting.name))
71 kv['port-id'] = port['port']['id']
74 logger.info('Creating VM with name - ' + instance_settings.name)
77 keypair_name = keypair_settings.name
79 flavor = get_flavor_by_name(nova, instance_settings.flavor)
82 'Flavor not found with name - %s',
83 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 return nova.keypairs.create(name=name, public_key=key.decode('utf-8'))
230 def keypair_exists(nova, keypair_obj):
232 Returns a copy of the keypair object if found
233 :param nova: the Nova client
234 :param keypair_obj: the keypair object
235 :return: the keypair object or None if not found
238 return nova.keypairs.get(keypair_obj)
243 def get_keypair_by_name(nova, name):
245 Returns a list of all available keypairs
246 :param nova: the Nova client
247 :param name: the name of the keypair to lookup
248 :return: the keypair object or None if not found
250 keypairs = nova.keypairs.list()
252 for keypair in keypairs:
253 if keypair.name == name:
259 def delete_keypair(nova, key):
261 Deletes a keypair object from OpenStack
262 :param nova: the Nova client
263 :param key: the keypair object to delete
265 logger.debug('Deleting keypair - ' + key.name)
266 nova.keypairs.delete(key)
269 def get_nova_availability_zones(nova):
271 Returns the names of all nova active compute servers
272 :param nova: the Nova client
273 :return: a list of compute server names
276 zones = nova.availability_zones.list()
278 if zone.zoneName == 'nova':
279 for key, host in zone.hosts.items():
280 if host['nova-compute']['available']:
281 out.append(zone.zoneName + ':' + key)
286 def delete_vm_instance(nova, vm_inst):
288 Deletes a VM instance
289 :param nova: the nova client
290 :param vm_inst: the snaps.domain.VmInst object
292 nova.servers.delete(vm_inst.id)
295 def get_os_flavor(nova, flavor):
297 Returns to OpenStack flavor object by name
298 :param nova: the Nova client
299 :param flavor: the SNAPS flavor domain object
300 :return: the OpenStack Flavor object
303 return nova.flavors.get(flavor.id)
308 def get_flavor(nova, flavor):
310 Returns to OpenStack flavor object by name
311 :param nova: the Nova client
312 :param flavor: the SNAPS flavor domain object
313 :return: the SNAPS Flavor domain object
315 os_flavor = get_os_flavor(nova, flavor)
318 name=os_flavor.name, id=os_flavor.id, ram=os_flavor.ram,
319 disk=os_flavor.disk, vcpus=os_flavor.vcpus,
320 ephemeral=os_flavor.ephemeral, swap=os_flavor.swap,
321 rxtx_factor=os_flavor.rxtx_factor, is_public=os_flavor.is_public)
323 return nova.flavors.get(flavor.id)
328 def get_os_flavor_by_name(nova, name):
330 Returns to OpenStack flavor object by name
331 :param nova: the Nova client
332 :param name: the name of the flavor to query
333 :return: OpenStack flavor object
336 return nova.flavors.find(name=name)
341 def get_flavor_by_name(nova, name):
343 Returns a flavor by name
344 :param nova: the Nova client
345 :param name: the flavor name to return
346 :return: the SNAPS flavor domain object or None if not exists
348 os_flavor = get_os_flavor_by_name(nova, name)
351 name=os_flavor.name, id=os_flavor.id, ram=os_flavor.ram,
352 disk=os_flavor.disk, vcpus=os_flavor.vcpus,
353 ephemeral=os_flavor.ephemeral, swap=os_flavor.swap,
354 rxtx_factor=os_flavor.rxtx_factor, is_public=os_flavor.is_public)
357 def create_flavor(nova, flavor_settings):
359 Creates and returns and OpenStack flavor object
360 :param nova: the Nova client
361 :param flavor_settings: the flavor settings
362 :return: the SNAPS flavor domain object
364 os_flavor = nova.flavors.create(
365 name=flavor_settings.name, flavorid=flavor_settings.flavor_id,
366 ram=flavor_settings.ram, vcpus=flavor_settings.vcpus,
367 disk=flavor_settings.disk, ephemeral=flavor_settings.ephemeral,
368 swap=flavor_settings.swap, rxtx_factor=flavor_settings.rxtx_factor,
369 is_public=flavor_settings.is_public)
371 name=os_flavor.name, id=os_flavor.id, ram=os_flavor.ram,
372 disk=os_flavor.disk, vcpus=os_flavor.vcpus,
373 ephemeral=os_flavor.ephemeral, swap=os_flavor.swap,
374 rxtx_factor=os_flavor.rxtx_factor, is_public=os_flavor.is_public)
377 def delete_flavor(nova, flavor):
380 :param nova: the Nova client
381 :param flavor: the SNAPS flavor domain object
383 nova.flavors.delete(flavor.id)
386 def set_flavor_keys(nova, flavor, metadata):
388 Sets metadata on the flavor
389 :param nova: the Nova client
390 :param flavor: the SNAPS flavor domain object
391 :param metadata: the metadata to set
393 os_flavor = get_os_flavor(nova, flavor)
395 os_flavor.set_keys(metadata)
398 def add_security_group(nova, vm, security_group_name):
400 Adds a security group to an existing VM
401 :param nova: the nova client
402 :param vm: the OpenStack server object (VM) to alter
403 :param security_group_name: the name of the security group to add
405 nova.servers.add_security_group(str(vm.id), security_group_name)
408 def remove_security_group(nova, vm, security_group):
410 Removes a security group from an existing VM
411 :param nova: the nova client
412 :param vm: the OpenStack server object (VM) to alter
413 :param security_group: the OpenStack security group object to add
415 nova.servers.remove_security_group(
416 str(vm.id), security_group['security_group']['name'])
419 def add_floating_ip_to_server(nova, vm, floating_ip, ip_addr):
421 Adds a floating IP to a server instance
422 :param nova: the nova client
423 :param vm: VmInst domain object
424 :param floating_ip: FloatingIp domain object
425 :param ip_addr: the IP to which to bind the floating IP to
427 vm = get_latest_server_os_object(nova, vm)
428 vm.add_floating_ip(floating_ip.ip, ip_addr)