Miscellaneous minor stability fixes
[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 enum
19 import os
20 import time
21 from cryptography.hazmat.backends import default_backend
22 from cryptography.hazmat.primitives import serialization
23 from cryptography.hazmat.primitives.asymmetric import rsa
24 from novaclient.client import Client
25 from novaclient.exceptions import NotFound, ClientException
26
27 from snaps import file_utils
28 from snaps.domain.flavor import Flavor
29 from snaps.domain.keypair import Keypair
30 from snaps.domain.project import ComputeQuotas
31 from snaps.domain.vm_inst import VmInst
32 from snaps.openstack.utils import keystone_utils, glance_utils, neutron_utils
33
34 __author__ = 'spisarski'
35
36 logger = logging.getLogger('nova_utils')
37
38 POLL_INTERVAL = 3
39
40 """
41 Utilities for basic OpenStack Nova API calls
42 """
43
44
45 def nova_client(os_creds, session=None):
46     """
47     Instantiates and returns a client for communications with OpenStack's Nova
48     server
49     :param os_creds: The connection credentials to the OpenStack API
50     :param session: the keystone session object (optional)
51     :return: the client object
52     """
53     logger.debug('Retrieving Nova Client')
54     if not session:
55         session = keystone_utils.keystone_session(os_creds)
56
57     return Client(os_creds.compute_api_version,
58                   session=session,
59                   region_name=os_creds.region_name)
60
61
62 def create_server(nova, keystone, neutron, glance, instance_config,
63                   image_config, project_name, keypair_config=None):
64     """
65     Creates a VM instance
66     :param nova: the nova client (required)
67     :param keystone: the keystone client for retrieving projects (required)
68     :param neutron: the neutron client for retrieving ports (required)
69     :param glance: the glance client (required)
70     :param instance_config: the VMInstConfig object (required)
71     :param image_config: the VM's ImageConfig object (required)
72     :param project_name: the associated project name (required)
73     :param keypair_config: the VM's KeypairConfig object (optional)
74     :return: a snaps.domain.VmInst object
75     """
76
77     ports = list()
78
79     for port_setting in instance_config.port_settings:
80         port = neutron_utils.get_port(
81             neutron, keystone, port_settings=port_setting,
82             project_name=project_name)
83         if port:
84             ports.append(port)
85         else:
86             raise Exception('Cannot find port named - ' + port_setting.name)
87     nics = []
88     for port in ports:
89         kv = dict()
90         kv['port-id'] = port.id
91         nics.append(kv)
92
93     logger.info('Creating VM with name - ' + instance_config.name)
94     keypair_name = None
95     if keypair_config:
96         keypair_name = keypair_config.name
97
98     flavor = get_flavor_by_name(nova, instance_config.flavor)
99     if not flavor:
100         raise NovaException(
101             'Flavor not found with name - %s', instance_config.flavor)
102
103     image = glance_utils.get_image(glance, image_settings=image_config)
104     if image:
105         userdata = None
106         if instance_config.userdata:
107             if isinstance(instance_config.userdata, str):
108                 userdata = instance_config.userdata + '\n'
109             elif (isinstance(instance_config.userdata, dict) and
110                   'script_file' in instance_config.userdata):
111                 try:
112                     userdata = file_utils.read_file(
113                         instance_config.userdata['script_file'])
114                 except Exception as e:
115                     logger.warn('error reading userdata file %s - %s',
116                                 instance_config.userdata, e)
117         args = {'name': instance_config.name,
118                 'flavor': flavor,
119                 'image': image,
120                 'nics': nics,
121                 'key_name': keypair_name,
122                 'security_groups':
123                     instance_config.security_group_names,
124                 'userdata': userdata}
125
126         if instance_config.availability_zone:
127             args['availability_zone'] = instance_config.availability_zone
128
129         server = nova.servers.create(**args)
130
131         return __map_os_server_obj_to_vm_inst(
132             neutron, keystone, server, project_name)
133     else:
134         raise NovaException(
135             'Cannot create instance, image cannot be located with name %s',
136             image_config.name)
137
138
139 def get_server(nova, neutron, keystone, vm_inst_settings=None,
140                server_name=None, project_id=None):
141     """
142     Returns a VmInst object for the first server instance found.
143     :param nova: the Nova client
144     :param neutron: the Neutron client
145     :param keystone: the Keystone client
146     :param vm_inst_settings: the VmInstanceConfig object from which to build
147                              the query if not None
148     :param server_name: the server with this name to return if vm_inst_settings
149                         is not None
150     :param project_id: the assocaited project ID
151     :return: a snaps.domain.VmInst object or None if not found
152     """
153     search_opts = dict()
154     if vm_inst_settings:
155         search_opts['name'] = vm_inst_settings.name
156     elif server_name:
157         search_opts['name'] = server_name
158
159     servers = nova.servers.list(search_opts=search_opts)
160     for server in servers:
161         return __map_os_server_obj_to_vm_inst(
162             neutron, keystone, server, project_id)
163
164
165 def get_server_connection(nova, vm_inst_settings=None, server_name=None):
166     """
167     Returns a VmInst object for the first server instance found.
168     :param nova: the Nova client
169     :param vm_inst_settings: the VmInstanceConfig object from which to build
170                              the query if not None
171     :param server_name: the server with this name to return if vm_inst_settings
172                         is not None
173     :return: a snaps.domain.VmInst object or None if not found
174     """
175     search_opts = dict()
176     if vm_inst_settings:
177         search_opts['name'] = vm_inst_settings.name
178     elif server_name:
179         search_opts['name'] = server_name
180
181     servers = nova.servers.list(search_opts=search_opts)
182     for server in servers:
183         return server.links[0]
184
185
186 def __map_os_server_obj_to_vm_inst(neutron, keystone, os_server,
187                                    project_name=None):
188     """
189     Returns a VmInst object for an OpenStack Server object
190     :param neutron: the Neutron client
191     :param keystone: the Keystone client
192     :param os_server: the OpenStack server object
193     :param project_name: the associated project name
194     :return: an equivalent SNAPS-OO VmInst domain object
195     """
196     sec_grp_names = list()
197     # VM must be active for 'security_groups' attr to be initialized
198     if hasattr(os_server, 'security_groups'):
199         for sec_group in os_server.security_groups:
200             if sec_group.get('name'):
201                 sec_grp_names.append(sec_group.get('name'))
202
203     out_ports = list()
204     if len(os_server.networks) > 0:
205         for net_name, ips in os_server.networks.items():
206             network = neutron_utils.get_network(
207                 neutron, keystone, network_name=net_name,
208                 project_name=project_name)
209             ports = neutron_utils.get_ports(neutron, network, ips)
210             for port in ports:
211                 out_ports.append(port)
212
213     volumes = None
214     if hasattr(os_server, 'os-extended-volumes:volumes_attached'):
215         volumes = getattr(os_server, 'os-extended-volumes:volumes_attached')
216
217     return VmInst(
218         name=os_server.name, inst_id=os_server.id,
219         image_id=os_server.image['id'], flavor_id=os_server.flavor['id'],
220         ports=out_ports, keypair_name=os_server.key_name,
221         sec_grp_names=sec_grp_names, volume_ids=volumes,
222         compute_host=os_server._info.get('OS-EXT-SRV-ATTR:host'),
223         availability_zone=os_server._info.get('OS-EXT-AZ:availability_zone'))
224
225
226 def __get_latest_server_os_object(nova, server):
227     """
228     Returns a server with a given id
229     :param nova: the Nova client
230     :param server: the domain VmInst object
231     :return: the list of servers or None if not found
232     """
233     return __get_latest_server_os_object_by_id(nova, server.id)
234
235
236 def __get_latest_server_os_object_by_id(nova, server_id):
237     """
238     Returns a server with a given id
239     :param nova: the Nova client
240     :param server_id: the server's ID
241     :return: the list of servers or None if not found
242     """
243     return nova.servers.get(server_id)
244
245
246 def get_server_status(nova, server):
247     """
248     Returns the a VM instance's status from OpenStack
249     :param nova: the Nova client
250     :param server: the domain VmInst object
251     :return: the VM's string status or None if not founc
252     """
253     server = __get_latest_server_os_object(nova, server)
254     if server:
255         return server.status
256     return None
257
258
259 def get_server_console_output(nova, server):
260     """
261     Returns the console object for parsing VM activity
262     :param nova: the Nova client
263     :param server: the domain VmInst object
264     :return: the console output object or None if server object is not found
265     """
266     server = __get_latest_server_os_object(nova, server)
267     if server:
268         return server.get_console_output()
269     return None
270
271
272 def get_latest_server_object(nova, neutron, keystone, server, project_name):
273     """
274     Returns a server with a given id
275     :param nova: the Nova client
276     :param neutron: the Neutron client
277     :param keystone: the Keystone client
278     :param server: the old server object
279     :param project_name: the associated project name
280     :return: the list of servers or None if not found
281     """
282     server = __get_latest_server_os_object(nova, server)
283     return __map_os_server_obj_to_vm_inst(
284         neutron, keystone, server, project_name)
285
286
287 def get_server_object_by_id(nova, neutron, keystone, server_id,
288                             project_name=None):
289     """
290     Returns a server with a given id
291     :param nova: the Nova client
292     :param neutron: the Neutron client
293     :param keystone: the Keystone client
294     :param server_id: the server's id
295     :param project_name: the associated project name
296     :return: an SNAPS-OO VmInst object or None if not found
297     """
298     server = __get_latest_server_os_object_by_id(nova, server_id)
299     return __map_os_server_obj_to_vm_inst(
300         neutron, keystone, server, project_name)
301
302
303 def get_server_security_group_names(nova, server):
304     """
305     Returns a server with a given id
306     :param nova: the Nova client
307     :param server: the old server object
308     :return: the list of security groups associated with a VM
309     """
310     out = list()
311     os_vm_inst = __get_latest_server_os_object(nova, server)
312     if hasattr(os_vm_inst, 'security_groups'):
313         for sec_grp_dict in os_vm_inst.security_groups:
314             out.append(sec_grp_dict['name'])
315     return out
316
317
318 def get_server_info(nova, server):
319     """
320     Returns a dictionary of a VMs info as returned by OpenStack
321     :param nova: the Nova client
322     :param server: the old server object
323     :return: a dict of the info if VM exists else None
324     """
325     vm = __get_latest_server_os_object(nova, server)
326     if vm:
327         return vm._info
328     return None
329
330
331 def reboot_server(nova, server, reboot_type=None):
332     """
333     Returns a dictionary of a VMs info as returned by OpenStack
334     :param nova: the Nova client
335     :param server: the old server object
336     :param reboot_type: Acceptable values 'SOFT', 'HARD'
337                         (api uses SOFT as the default)
338     :return: a dict of the info if VM exists else None
339     """
340     vm = __get_latest_server_os_object(nova, server)
341     if vm:
342         vm.reboot(reboot_type=reboot_type.value)
343     else:
344         raise ServerNotFoundError('Cannot locate server')
345
346
347 def create_keys(key_size=2048):
348     """
349     Generates public and private keys
350     :param key_size: the number of bytes for the key size
351     :return: the cryptography keys
352     """
353     return rsa.generate_private_key(backend=default_backend(),
354                                     public_exponent=65537,
355                                     key_size=key_size)
356
357
358 def public_key_openssh(keys):
359     """
360     Returns the public key for OpenSSH
361     :param keys: the keys generated by create_keys() from cryptography
362     :return: the OpenSSH public key
363     """
364     return keys.public_key().public_bytes(serialization.Encoding.OpenSSH,
365                                           serialization.PublicFormat.OpenSSH)
366
367
368 def save_keys_to_files(keys=None, pub_file_path=None, priv_file_path=None):
369     """
370     Saves the generated RSA generated keys to the filesystem
371     :param keys: the keys to save generated by cryptography
372     :param pub_file_path: the path to the public keys
373     :param priv_file_path: the path to the private keys
374     """
375     if keys:
376         if pub_file_path:
377             # To support '~'
378             pub_expand_file = os.path.expanduser(pub_file_path)
379             pub_dir = os.path.dirname(pub_expand_file)
380
381             if not os.path.isdir(pub_dir):
382                 os.mkdir(pub_dir)
383
384             public_handle = None
385             try:
386                 public_handle = open(pub_expand_file, 'wb')
387                 public_bytes = keys.public_key().public_bytes(
388                     serialization.Encoding.OpenSSH,
389                     serialization.PublicFormat.OpenSSH)
390                 public_handle.write(public_bytes)
391             finally:
392                 if public_handle:
393                     public_handle.close()
394
395             os.chmod(pub_expand_file, 0o600)
396             logger.info("Saved public key to - " + pub_expand_file)
397         if priv_file_path:
398             # To support '~'
399             priv_expand_file = os.path.expanduser(priv_file_path)
400             priv_dir = os.path.dirname(priv_expand_file)
401             if not os.path.isdir(priv_dir):
402                 os.mkdir(priv_dir)
403
404             private_handle = None
405             try:
406                 private_handle = open(priv_expand_file, 'wb')
407                 private_handle.write(
408                     keys.private_bytes(
409                         encoding=serialization.Encoding.PEM,
410                         format=serialization.PrivateFormat.TraditionalOpenSSL,
411                         encryption_algorithm=serialization.NoEncryption()))
412             finally:
413                 if private_handle:
414                     private_handle.close()
415
416             os.chmod(priv_expand_file, 0o600)
417             logger.info("Saved private key to - " + priv_expand_file)
418
419
420 def upload_keypair_file(nova, name, file_path):
421     """
422     Uploads a public key from a file
423     :param nova: the Nova client
424     :param name: the keypair name
425     :param file_path: the path to the public key file
426     :return: the keypair object
427     """
428     fpubkey = None
429     try:
430         with open(os.path.expanduser(file_path), 'rb') as fpubkey:
431             logger.info('Saving keypair to - ' + file_path)
432             return upload_keypair(nova, name, fpubkey.read())
433     finally:
434         if fpubkey:
435             fpubkey.close()
436
437
438 def upload_keypair(nova, name, key):
439     """
440     Uploads a public key from a file
441     :param nova: the Nova client
442     :param name: the keypair name
443     :param key: the public key object
444     :return: the keypair object
445     """
446     logger.info('Creating keypair with name - ' + name)
447     os_kp = nova.keypairs.create(name=name, public_key=key.decode('utf-8'))
448     return Keypair(name=os_kp.name, kp_id=os_kp.id,
449                    public_key=os_kp.public_key, fingerprint=os_kp.fingerprint)
450
451
452 def keypair_exists(nova, keypair_obj):
453     """
454     Returns a copy of the keypair object if found
455     :param nova: the Nova client
456     :param keypair_obj: the keypair object
457     :return: the keypair object or None if not found
458     """
459     try:
460         os_kp = nova.keypairs.get(keypair_obj)
461         return Keypair(name=os_kp.name, kp_id=os_kp.id,
462                        public_key=os_kp.public_key)
463     except:
464         return None
465
466
467 def get_keypair_by_name(nova, name):
468     """
469     Returns a list of all available keypairs
470     :param nova: the Nova client
471     :param name: the name of the keypair to lookup
472     :return: the keypair object or None if not found
473     """
474     keypairs = nova.keypairs.list()
475
476     for keypair in keypairs:
477         if keypair.name == name:
478             return Keypair(name=keypair.name, kp_id=keypair.id,
479                            public_key=keypair.public_key)
480
481     return None
482
483
484 def get_keypair_by_id(nova, kp_id):
485     """
486     Returns a list of all available keypairs
487     :param nova: the Nova client
488     :param kp_id: the ID of the keypair to return
489     :return: the keypair object
490     """
491     keypair = nova.keypairs.get(kp_id)
492     return Keypair(name=keypair.name, kp_id=keypair.id,
493                    public_key=keypair.public_key)
494
495
496 def delete_keypair(nova, key):
497     """
498     Deletes a keypair object from OpenStack
499     :param nova: the Nova client
500     :param key: the SNAPS-OO keypair domain object to delete
501     """
502     logger.debug('Deleting keypair - ' + key.name)
503     nova.keypairs.delete(key.id)
504
505
506 def get_availability_zone_hosts(nova, zone_name='nova'):
507     """
508     Returns the names of all nova active compute servers
509     :param nova: the Nova client
510     :param zone_name: the Nova client
511     :return: a list of compute server names
512     """
513     out = list()
514     zones = nova.availability_zones.list()
515     for zone in zones:
516         if zone.zoneName == zone_name and zone.hosts:
517             for key, host in zone.hosts.items():
518                 if host['nova-compute']['available']:
519                     out.append(zone.zoneName + ':' + key)
520
521     return out
522
523
524 def get_hypervisor_hosts(nova):
525     """
526     Returns the host names of all nova nodes with active hypervisors
527     :param nova: the Nova client
528     :return: a list of hypervisor host names
529     """
530     out = list()
531     hypervisors = nova.hypervisors.list()
532     for hypervisor in hypervisors:
533         if hypervisor.state == "up":
534             out.append(hypervisor.hypervisor_hostname)
535
536     return out
537
538
539 def delete_vm_instance(nova, vm_inst):
540     """
541     Deletes a VM instance
542     :param nova: the nova client
543     :param vm_inst: the snaps.domain.VmInst object
544     """
545     nova.servers.delete(vm_inst.id)
546
547
548 def __get_os_flavor(nova, flavor_id):
549     """
550     Returns to OpenStack flavor object by name
551     :param nova: the Nova client
552     :param flavor_id: the flavor's ID value
553     :return: the OpenStack Flavor object
554     """
555     try:
556         return nova.flavors.get(flavor_id)
557     except NotFound:
558         return None
559
560
561 def get_flavor(nova, flavor):
562     """
563     Returns to OpenStack flavor object by name
564     :param nova: the Nova client
565     :param flavor: the SNAPS flavor domain object
566     :return: the SNAPS Flavor domain object
567     """
568     os_flavor = __get_os_flavor(nova, flavor.id)
569     if os_flavor:
570         return Flavor(
571             name=os_flavor.name, id=os_flavor.id, ram=os_flavor.ram,
572             disk=os_flavor.disk, vcpus=os_flavor.vcpus,
573             ephemeral=os_flavor.ephemeral, swap=os_flavor.swap,
574             rxtx_factor=os_flavor.rxtx_factor, is_public=os_flavor.is_public)
575     try:
576         return nova.flavors.get(flavor.id)
577     except NotFound:
578         return None
579
580
581 def get_flavor_by_id(nova, flavor_id):
582     """
583     Returns to OpenStack flavor object by name
584     :param nova: the Nova client
585     :param flavor_id: the flavor ID value
586     :return: the SNAPS Flavor domain object
587     """
588     os_flavor = __get_os_flavor(nova, flavor_id)
589     if os_flavor:
590         return Flavor(
591             name=os_flavor.name, id=os_flavor.id, ram=os_flavor.ram,
592             disk=os_flavor.disk, vcpus=os_flavor.vcpus,
593             ephemeral=os_flavor.ephemeral, swap=os_flavor.swap,
594             rxtx_factor=os_flavor.rxtx_factor, is_public=os_flavor.is_public)
595
596
597 def __get_os_flavor_by_name(nova, name):
598     """
599     Returns to OpenStack flavor object by name
600     :param nova: the Nova client
601     :param name: the name of the flavor to query
602     :return: OpenStack flavor object
603     """
604     try:
605         return nova.flavors.find(name=name)
606     except NotFound:
607         return None
608
609
610 def get_flavor_by_name(nova, name):
611     """
612     Returns a flavor by name
613     :param nova: the Nova client
614     :param name: the flavor name to return
615     :return: the SNAPS flavor domain object or None if not exists
616     """
617     os_flavor = __get_os_flavor_by_name(nova, name)
618     if os_flavor:
619         return Flavor(
620             name=os_flavor.name, id=os_flavor.id, ram=os_flavor.ram,
621             disk=os_flavor.disk, vcpus=os_flavor.vcpus,
622             ephemeral=os_flavor.ephemeral, swap=os_flavor.swap,
623             rxtx_factor=os_flavor.rxtx_factor, is_public=os_flavor.is_public)
624
625
626 def create_flavor(nova, flavor_settings):
627     """
628     Creates and returns and OpenStack flavor object
629     :param nova: the Nova client
630     :param flavor_settings: the flavor settings
631     :return: the SNAPS flavor domain object
632     """
633     os_flavor = nova.flavors.create(
634         name=flavor_settings.name, flavorid=flavor_settings.flavor_id,
635         ram=flavor_settings.ram, vcpus=flavor_settings.vcpus,
636         disk=flavor_settings.disk, ephemeral=flavor_settings.ephemeral,
637         swap=flavor_settings.swap, rxtx_factor=flavor_settings.rxtx_factor,
638         is_public=flavor_settings.is_public)
639     return Flavor(
640         name=os_flavor.name, id=os_flavor.id, ram=os_flavor.ram,
641         disk=os_flavor.disk, vcpus=os_flavor.vcpus,
642         ephemeral=os_flavor.ephemeral, swap=os_flavor.swap,
643         rxtx_factor=os_flavor.rxtx_factor, is_public=os_flavor.is_public)
644
645
646 def delete_flavor(nova, flavor):
647     """
648     Deletes a flavor
649     :param nova: the Nova client
650     :param flavor: the SNAPS flavor domain object
651     """
652     nova.flavors.delete(flavor.id)
653
654
655 def set_flavor_keys(nova, flavor, metadata):
656     """
657     Sets metadata on the flavor
658     :param nova: the Nova client
659     :param flavor: the SNAPS flavor domain object
660     :param metadata: the metadata to set
661     """
662     os_flavor = __get_os_flavor(nova, flavor.id)
663     if os_flavor:
664         os_flavor.set_keys(metadata)
665
666
667 def get_flavor_keys(nova, flavor):
668     """
669     Sets metadata on the flavor
670     :param nova: the Nova client
671     :param flavor: the SNAPS flavor domain object
672     """
673     os_flavor = __get_os_flavor(nova, flavor.id)
674     if os_flavor:
675         return os_flavor.get_keys()
676
677
678 def add_security_group(nova, vm, security_group_name):
679     """
680     Adds a security group to an existing VM
681     :param nova: the nova client
682     :param vm: the OpenStack server object (VM) to alter
683     :param security_group_name: the name of the security group to add
684     """
685     try:
686         nova.servers.add_security_group(str(vm.id), security_group_name)
687     except ClientException as e:
688         sec_grp_names = get_server_security_group_names(nova, vm)
689         if security_group_name in sec_grp_names:
690             logger.warn('Security group [%s] already added to VM [%s]',
691                         security_group_name, vm.name)
692             return
693
694         logger.error('Unexpected error while adding security group [%s] - %s',
695                      security_group_name, e)
696         raise
697
698
699 def remove_security_group(nova, vm, security_group):
700     """
701     Removes a security group from an existing VM
702     :param nova: the nova client
703     :param vm: the OpenStack server object (VM) to alter
704     :param security_group: the SNAPS SecurityGroup domain object to add
705     """
706     nova.servers.remove_security_group(str(vm.id), security_group.name)
707
708
709 def get_compute_quotas(nova, project_id):
710     """
711     Returns a list of all available keypairs
712     :param nova: the Nova client
713     :param project_id: the project's ID of the quotas to lookup
714     :return: an object of type ComputeQuotas or None if not found
715     """
716     quotas = nova.quotas.get(tenant_id=project_id)
717     if quotas:
718         return ComputeQuotas(quotas)
719
720
721 def update_quotas(nova, project_id, compute_quotas):
722     """
723     Updates the compute quotas for a given project
724     :param nova: the Nova client
725     :param project_id: the project's ID that requires quota updates
726     :param compute_quotas: an object of type ComputeQuotas containing the
727                            values to update
728     :return:
729     """
730     update_values = dict()
731     update_values['metadata_items'] = compute_quotas.metadata_items
732     update_values['cores'] = compute_quotas.cores
733     update_values['instances'] = compute_quotas.instances
734     update_values['injected_files'] = compute_quotas.injected_files
735     update_values['injected_file_content_bytes'] = (
736         compute_quotas.injected_file_content_bytes)
737     update_values['ram'] = compute_quotas.ram
738     update_values['fixed_ips'] = compute_quotas.fixed_ips
739     update_values['key_pairs'] = compute_quotas.key_pairs
740
741     return nova.quotas.update(project_id, **update_values)
742
743
744 def attach_volume(nova, neutron, keystone, server, volume, project_name,
745                   timeout=120):
746     """
747     Attaches a volume to a server. When the timeout parameter is used, a VmInst
748     object with the proper volume updates is returned unless it has not been
749     updated in the allotted amount of time then an Exception will be raised.
750     :param nova: the nova client
751     :param neutron: the neutron client
752     :param keystone: the neutron client
753     :param server: the VMInst domain object
754     :param volume: the Volume domain object
755     :param project_name: the associated project name
756     :param timeout: denotes the amount of time to block to determine if the
757                     has been properly attached.
758     :return: updated VmInst object
759     """
760     nova.volumes.create_server_volume(server.id, volume.id)
761
762     start_time = time.time()
763     while time.time() < start_time + timeout:
764         vm = get_server_object_by_id(
765             nova, neutron, keystone, server.id, project_name)
766         for vol_dict in vm.volume_ids:
767             if volume.id == vol_dict['id']:
768                 return vm
769         time.sleep(POLL_INTERVAL)
770
771     raise NovaException(
772         'Attach failed on volume - {} and server - {}'.format(
773             volume.id, server.id))
774
775
776 def detach_volume(nova, neutron, keystone, server, volume, project_name,
777                   timeout=120):
778     """
779     Detaches a volume to a server. When the timeout parameter is used, a VmInst
780     object with the proper volume updates is returned unless it has not been
781     updated in the allotted amount of time then an Exception will be raised.
782     :param nova: the nova client
783     :param neutron: the neutron client
784     :param keystone: the keystone client
785     :param server: the VMInst domain object
786     :param volume: the Volume domain object
787     :param project_name: the associated project name
788     :param timeout: denotes the amount of time to block to determine if the
789                     has been properly detached.
790     :return: updated VmInst object
791     """
792     nova.volumes.delete_server_volume(server.id, volume.id)
793
794     start_time = time.time()
795     while time.time() < start_time + timeout:
796         vm = get_server_object_by_id(
797             nova, neutron, keystone, server.id, project_name)
798         if len(vm.volume_ids) == 0:
799             return vm
800         else:
801             ids = list()
802             for vol_dict in vm.volume_ids:
803                 ids.append(vol_dict['id'])
804             if volume.id not in ids:
805                 return vm
806         time.sleep(POLL_INTERVAL)
807
808     raise NovaException(
809         'Detach failed on volume - {} server - {}'.format(
810             volume.id, server.id))
811
812
813 class RebootType(enum.Enum):
814     """
815     A rule's direction
816     """
817     soft = 'SOFT'
818     hard = 'HARD'
819
820
821 class NovaException(Exception):
822     """
823     Exception when calls to the Keystone client cannot be served properly
824     """
825
826
827 class ServerNotFoundError(Exception):
828     """
829     Exception when operations to a VM/Server is requested and the OpenStack
830     Server instance cannot be located
831     """