Created domain classes for security groups.
[snaps.git] / snaps / openstack / utils / nova_utils.py
1 # Copyright (c) 2017 Cable Television Laboratories, Inc. ("CableLabs")
2 #                    and others.  All rights reserved.
3 #
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:
7 #
8 #     http://www.apache.org/licenses/LICENSE-2.0
9 #
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.
15
16 import logging
17
18 import os
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
24
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
29
30 __author__ = 'spisarski'
31
32 logger = logging.getLogger('nova_utils')
33
34 """
35 Utilities for basic OpenStack Nova API calls
36 """
37
38
39 def nova_client(os_creds):
40     """
41     Instantiates and returns a client for communications with OpenStack's Nova
42     server
43     :param os_creds: The connection credentials to the OpenStack API
44     :return: the client object
45     """
46     logger.debug('Retrieving Nova Client')
47     return Client(os_creds.compute_api_version,
48                   session=keystone_utils.keystone_session(os_creds))
49
50
51 def create_server(nova, neutron, glance, instance_settings, image_settings,
52                   keypair_settings=None):
53     """
54     Creates a VM instance
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
62     """
63
64     ports = list()
65
66     for port_setting in instance_settings.port_settings:
67         ports.append(neutron_utils.get_port_by_name(
68             neutron, port_setting.name))
69     nics = []
70     for port in ports:
71         kv = dict()
72         kv['port-id'] = port['port']['id']
73         nics.append(kv)
74
75     logger.info('Creating VM with name - ' + instance_settings.name)
76     keypair_name = None
77     if keypair_settings:
78         keypair_name = keypair_settings.name
79
80     flavor = get_flavor_by_name(nova, instance_settings.flavor)
81     if not flavor:
82         raise Exception(
83             'Flavor not found with name - %s',
84             instance_settings.flavor)
85
86     image = glance_utils.get_image(glance, image_settings.name)
87     if image:
88         args = {'name': instance_settings.name,
89                 'flavor': flavor,
90                 'image': image,
91                 'nics': nics,
92                 'key_name': keypair_name,
93                 'security_groups':
94                     instance_settings.security_group_names,
95                 'userdata': instance_settings.userdata,
96                 'availability_zone':
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)
101     else:
102         raise Exception(
103             'Cannot create instance, image cannot be located with name %s',
104             image_settings.name)
105
106
107 def get_servers_by_name(nova, name):
108     """
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
113     """
114     out = list()
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))
119     return out
120
121
122 def get_latest_server_os_object(nova, server):
123     """
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
128     """
129     return nova.servers.get(server.id)
130
131
132 def get_latest_server_object(nova, server):
133     """
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
138     """
139     server = get_latest_server_os_object(nova, server)
140     return VmInst(name=server.name, inst_id=server.id,
141                   networks=server.networks)
142
143
144 def create_keys(key_size=2048):
145     """
146     Generates public and private keys
147     :param key_size: the number of bytes for the key size
148     :return: the cryptography keys
149     """
150     return rsa.generate_private_key(backend=default_backend(),
151                                     public_exponent=65537,
152                                     key_size=key_size)
153
154
155 def public_key_openssh(keys):
156     """
157     Returns the public key for OpenSSH
158     :param keys: the keys generated by create_keys() from cryptography
159     :return: the OpenSSH public key
160     """
161     return keys.public_key().public_bytes(serialization.Encoding.OpenSSH,
162                                           serialization.PublicFormat.OpenSSH)
163
164
165 def save_keys_to_files(keys=None, pub_file_path=None, priv_file_path=None):
166     """
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
171     :return: None
172     """
173     if keys:
174         if pub_file_path:
175             # To support '~'
176             pub_expand_file = os.path.expanduser(pub_file_path)
177             pub_dir = os.path.dirname(pub_expand_file)
178
179             if not os.path.isdir(pub_dir):
180                 os.mkdir(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)
189         if priv_file_path:
190             # To support '~'
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):
194                 os.mkdir(priv_dir)
195             private_handle = open(priv_expand_file, 'wb')
196             private_handle.write(
197                 keys.private_bytes(
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)
204
205
206 def upload_keypair_file(nova, name, file_path):
207     """
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
213     """
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())
217
218
219 def upload_keypair(nova, name, key):
220     """
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
226     """
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)
230
231
232 def keypair_exists(nova, keypair_obj):
233     """
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
238     """
239     try:
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)
243     except:
244         return None
245
246
247 def get_keypair_by_name(nova, name):
248     """
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
253     """
254     keypairs = nova.keypairs.list()
255
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)
260
261     return None
262
263
264 def delete_keypair(nova, key):
265     """
266     Deletes a keypair object from OpenStack
267     :param nova: the Nova client
268     :param key: the SNAPS-OO keypair domain object to delete
269     """
270     logger.debug('Deleting keypair - ' + key.name)
271     nova.keypairs.delete(key.id)
272
273
274 def get_nova_availability_zones(nova):
275     """
276     Returns the names of all nova active compute servers
277     :param nova: the Nova client
278     :return: a list of compute server names
279     """
280     out = list()
281     zones = nova.availability_zones.list()
282     for zone in zones:
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)
287
288     return out
289
290
291 def delete_vm_instance(nova, vm_inst):
292     """
293     Deletes a VM instance
294     :param nova: the nova client
295     :param vm_inst: the snaps.domain.VmInst object
296     """
297     nova.servers.delete(vm_inst.id)
298
299
300 def get_os_flavor(nova, flavor):
301     """
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
306     """
307     try:
308         return nova.flavors.get(flavor.id)
309     except NotFound:
310         return None
311
312
313 def get_flavor(nova, flavor):
314     """
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
319     """
320     os_flavor = get_os_flavor(nova, flavor)
321     if os_flavor:
322         return 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)
327     try:
328         return nova.flavors.get(flavor.id)
329     except NotFound:
330         return None
331
332
333 def get_os_flavor_by_name(nova, name):
334     """
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
339     """
340     try:
341         return nova.flavors.find(name=name)
342     except NotFound:
343         return None
344
345
346 def get_flavor_by_name(nova, name):
347     """
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
352     """
353     os_flavor = get_os_flavor_by_name(nova, name)
354     if os_flavor:
355         return Flavor(
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)
360
361
362 def create_flavor(nova, flavor_settings):
363     """
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
368     """
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)
375     return Flavor(
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)
380
381
382 def delete_flavor(nova, flavor):
383     """
384     Deletes a flavor
385     :param nova: the Nova client
386     :param flavor: the SNAPS flavor domain object
387     """
388     nova.flavors.delete(flavor.id)
389
390
391 def set_flavor_keys(nova, flavor, metadata):
392     """
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
397     """
398     os_flavor = get_os_flavor(nova, flavor)
399     if os_flavor:
400         os_flavor.set_keys(metadata)
401
402
403 def add_security_group(nova, vm, security_group_name):
404     """
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
409     """
410     nova.servers.add_security_group(str(vm.id), security_group_name)
411
412
413 def remove_security_group(nova, vm, security_group):
414     """
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
419     """
420     nova.servers.remove_security_group(str(vm.id), security_group.name)
421
422
423 def add_floating_ip_to_server(nova, vm, floating_ip, ip_addr):
424     """
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
430     """
431     vm = get_latest_server_os_object(nova, vm)
432     vm.add_floating_ip(floating_ip.ip, ip_addr)