Adds Stack Update
[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             if network:
210                 ports = neutron_utils.get_ports(neutron, network, ips)
211                 for port in ports:
212                     out_ports.append(port)
213             else:
214                 raise NovaException(
215                     'Unable to locate network in project {} with '
216                     'name {}'.format(project_name, net_name))
217
218     volumes = None
219     if hasattr(os_server, 'os-extended-volumes:volumes_attached'):
220         volumes = getattr(os_server, 'os-extended-volumes:volumes_attached')
221
222     return VmInst(
223         name=os_server.name, inst_id=os_server.id,
224         image_id=os_server.image['id'], flavor_id=os_server.flavor['id'],
225         ports=out_ports, keypair_name=os_server.key_name,
226         sec_grp_names=sec_grp_names, volume_ids=volumes,
227         compute_host=os_server._info.get('OS-EXT-SRV-ATTR:host'),
228         availability_zone=os_server._info.get('OS-EXT-AZ:availability_zone'))
229
230
231 def __get_latest_server_os_object(nova, server):
232     """
233     Returns a server with a given id
234     :param nova: the Nova client
235     :param server: the domain VmInst object
236     :return: the list of servers or None if not found
237     """
238     return __get_latest_server_os_object_by_id(nova, server.id)
239
240
241 def __get_latest_server_os_object_by_id(nova, server_id):
242     """
243     Returns a server with a given id
244     :param nova: the Nova client
245     :param server_id: the server's ID
246     :return: the list of servers or None if not found
247     """
248     return nova.servers.get(server_id)
249
250
251 def get_server_status(nova, server):
252     """
253     Returns the a VM instance's status from OpenStack
254     :param nova: the Nova client
255     :param server: the domain VmInst object
256     :return: the VM's string status or None if not founc
257     """
258     server = __get_latest_server_os_object(nova, server)
259     if server:
260         return server.status
261     return None
262
263
264 def get_server_console_output(nova, server):
265     """
266     Returns the console object for parsing VM activity
267     :param nova: the Nova client
268     :param server: the domain VmInst object
269     :return: the console output object or None if server object is not found
270     """
271     server = __get_latest_server_os_object(nova, server)
272     if server:
273         return server.get_console_output()
274     return None
275
276
277 def get_latest_server_object(nova, neutron, keystone, server, project_name):
278     """
279     Returns a server with a given id
280     :param nova: the Nova client
281     :param neutron: the Neutron client
282     :param keystone: the Keystone client
283     :param server: the old server object
284     :param project_name: the associated project name
285     :return: the list of servers or None if not found
286     """
287     server = __get_latest_server_os_object(nova, server)
288     return __map_os_server_obj_to_vm_inst(
289         neutron, keystone, server, project_name)
290
291
292 def get_server_object_by_id(nova, neutron, keystone, server_id,
293                             project_name=None):
294     """
295     Returns a server with a given id
296     :param nova: the Nova client
297     :param neutron: the Neutron client
298     :param keystone: the Keystone client
299     :param server_id: the server's id
300     :param project_name: the associated project name
301     :return: an SNAPS-OO VmInst object or None if not found
302     """
303     server = __get_latest_server_os_object_by_id(nova, server_id)
304     return __map_os_server_obj_to_vm_inst(
305         neutron, keystone, server, project_name)
306
307
308 def get_server_security_group_names(nova, server):
309     """
310     Returns a server with a given id
311     :param nova: the Nova client
312     :param server: the old server object
313     :return: the list of security groups associated with a VM
314     """
315     out = list()
316     os_vm_inst = __get_latest_server_os_object(nova, server)
317     if hasattr(os_vm_inst, 'security_groups'):
318         for sec_grp_dict in os_vm_inst.security_groups:
319             out.append(sec_grp_dict['name'])
320     return out
321
322
323 def get_server_info(nova, server):
324     """
325     Returns a dictionary of a VMs info as returned by OpenStack
326     :param nova: the Nova client
327     :param server: the old server object
328     :return: a dict of the info if VM exists else None
329     """
330     vm = __get_latest_server_os_object(nova, server)
331     if vm:
332         return vm._info
333     return None
334
335
336 def reboot_server(nova, server, reboot_type=None):
337     """
338     Returns a dictionary of a VMs info as returned by OpenStack
339     :param nova: the Nova client
340     :param server: the old server object
341     :param reboot_type: Acceptable values 'SOFT', 'HARD'
342                         (api uses SOFT as the default)
343     :return: a dict of the info if VM exists else None
344     """
345     vm = __get_latest_server_os_object(nova, server)
346     if vm:
347         vm.reboot(reboot_type=reboot_type.value)
348     else:
349         raise ServerNotFoundError('Cannot locate server')
350
351
352 def create_keys(key_size=2048):
353     """
354     Generates public and private keys
355     :param key_size: the number of bytes for the key size
356     :return: the cryptography keys
357     """
358     return rsa.generate_private_key(backend=default_backend(),
359                                     public_exponent=65537,
360                                     key_size=key_size)
361
362
363 def public_key_openssh(keys):
364     """
365     Returns the public key for OpenSSH
366     :param keys: the keys generated by create_keys() from cryptography
367     :return: the OpenSSH public key
368     """
369     return keys.public_key().public_bytes(serialization.Encoding.OpenSSH,
370                                           serialization.PublicFormat.OpenSSH)
371
372
373 def save_keys_to_files(keys=None, pub_file_path=None, priv_file_path=None):
374     """
375     Saves the generated RSA generated keys to the filesystem
376     :param keys: the keys to save generated by cryptography
377     :param pub_file_path: the path to the public keys
378     :param priv_file_path: the path to the private keys
379     """
380     if keys:
381         if pub_file_path:
382             # To support '~'
383             pub_expand_file = os.path.expanduser(pub_file_path)
384             pub_dir = os.path.dirname(pub_expand_file)
385
386             if not os.path.isdir(pub_dir):
387                 os.mkdir(pub_dir)
388
389             public_handle = None
390             try:
391                 public_handle = open(pub_expand_file, 'wb')
392                 public_bytes = keys.public_key().public_bytes(
393                     serialization.Encoding.OpenSSH,
394                     serialization.PublicFormat.OpenSSH)
395                 public_handle.write(public_bytes)
396             finally:
397                 if public_handle:
398                     public_handle.close()
399
400             os.chmod(pub_expand_file, 0o600)
401             logger.info("Saved public key to - " + pub_expand_file)
402         if priv_file_path:
403             # To support '~'
404             priv_expand_file = os.path.expanduser(priv_file_path)
405             priv_dir = os.path.dirname(priv_expand_file)
406             if not os.path.isdir(priv_dir):
407                 os.mkdir(priv_dir)
408
409             private_handle = None
410             try:
411                 private_handle = open(priv_expand_file, 'wb')
412                 private_handle.write(
413                     keys.private_bytes(
414                         encoding=serialization.Encoding.PEM,
415                         format=serialization.PrivateFormat.TraditionalOpenSSL,
416                         encryption_algorithm=serialization.NoEncryption()))
417             finally:
418                 if private_handle:
419                     private_handle.close()
420
421             os.chmod(priv_expand_file, 0o600)
422             logger.info("Saved private key to - " + priv_expand_file)
423
424
425 def upload_keypair_file(nova, name, file_path):
426     """
427     Uploads a public key from a file
428     :param nova: the Nova client
429     :param name: the keypair name
430     :param file_path: the path to the public key file
431     :return: the keypair object
432     """
433     fpubkey = None
434     try:
435         with open(os.path.expanduser(file_path), 'rb') as fpubkey:
436             logger.info('Saving keypair to - ' + file_path)
437             return upload_keypair(nova, name, fpubkey.read())
438     finally:
439         if fpubkey:
440             fpubkey.close()
441
442
443 def upload_keypair(nova, name, key):
444     """
445     Uploads a public key from a file
446     :param nova: the Nova client
447     :param name: the keypair name
448     :param key: the public key object
449     :return: the keypair object
450     """
451     logger.info('Creating keypair with name - ' + name)
452     os_kp = nova.keypairs.create(name=name, public_key=key.decode('utf-8'))
453     return Keypair(name=os_kp.name, kp_id=os_kp.id,
454                    public_key=os_kp.public_key, fingerprint=os_kp.fingerprint)
455
456
457 def keypair_exists(nova, keypair_obj):
458     """
459     Returns a copy of the keypair object if found
460     :param nova: the Nova client
461     :param keypair_obj: the keypair object
462     :return: the keypair object or None if not found
463     """
464     try:
465         os_kp = nova.keypairs.get(keypair_obj)
466         return Keypair(name=os_kp.name, kp_id=os_kp.id,
467                        public_key=os_kp.public_key)
468     except:
469         return None
470
471
472 def get_keypair_by_name(nova, name):
473     """
474     Returns a list of all available keypairs
475     :param nova: the Nova client
476     :param name: the name of the keypair to lookup
477     :return: the keypair object or None if not found
478     """
479     keypairs = nova.keypairs.list()
480
481     for keypair in keypairs:
482         if keypair.name == name:
483             return Keypair(name=keypair.name, kp_id=keypair.id,
484                            public_key=keypair.public_key)
485
486     return None
487
488
489 def get_keypair_by_id(nova, kp_id):
490     """
491     Returns a list of all available keypairs
492     :param nova: the Nova client
493     :param kp_id: the ID of the keypair to return
494     :return: the keypair object
495     """
496     keypair = nova.keypairs.get(kp_id)
497     return Keypair(name=keypair.name, kp_id=keypair.id,
498                    public_key=keypair.public_key)
499
500
501 def delete_keypair(nova, key):
502     """
503     Deletes a keypair object from OpenStack
504     :param nova: the Nova client
505     :param key: the SNAPS-OO keypair domain object to delete
506     """
507     logger.debug('Deleting keypair - ' + key.name)
508     nova.keypairs.delete(key.id)
509
510
511 def get_availability_zone_hosts(nova, zone_name='nova'):
512     """
513     Returns the names of all nova active compute servers
514     :param nova: the Nova client
515     :param zone_name: the Nova client
516     :return: a list of compute server names
517     """
518     out = list()
519     zones = nova.availability_zones.list()
520     for zone in zones:
521         if zone.zoneName == zone_name and zone.hosts:
522             for key, host in zone.hosts.items():
523                 if host['nova-compute']['available']:
524                     out.append(zone.zoneName + ':' + key)
525
526     return out
527
528
529 def get_hypervisor_hosts(nova):
530     """
531     Returns the host names of all nova nodes with active hypervisors
532     :param nova: the Nova client
533     :return: a list of hypervisor host names
534     """
535     out = list()
536     hypervisors = nova.hypervisors.list()
537     for hypervisor in hypervisors:
538         if hypervisor.state == "up":
539             out.append(hypervisor.hypervisor_hostname)
540
541     return out
542
543
544 def delete_vm_instance(nova, vm_inst):
545     """
546     Deletes a VM instance
547     :param nova: the nova client
548     :param vm_inst: the snaps.domain.VmInst object
549     """
550     nova.servers.delete(vm_inst.id)
551
552
553 def __get_os_flavor(nova, flavor_id):
554     """
555     Returns to OpenStack flavor object by name
556     :param nova: the Nova client
557     :param flavor_id: the flavor's ID value
558     :return: the OpenStack Flavor object
559     """
560     try:
561         return nova.flavors.get(flavor_id)
562     except NotFound:
563         return None
564
565
566 def get_flavor(nova, flavor):
567     """
568     Returns to OpenStack flavor object by name
569     :param nova: the Nova client
570     :param flavor: the SNAPS flavor domain object
571     :return: the SNAPS Flavor domain object
572     """
573     os_flavor = __get_os_flavor(nova, flavor.id)
574     if os_flavor:
575         return Flavor(
576             name=os_flavor.name, id=os_flavor.id, ram=os_flavor.ram,
577             disk=os_flavor.disk, vcpus=os_flavor.vcpus,
578             ephemeral=os_flavor.ephemeral, swap=os_flavor.swap,
579             rxtx_factor=os_flavor.rxtx_factor, is_public=os_flavor.is_public)
580     try:
581         return nova.flavors.get(flavor.id)
582     except NotFound:
583         return None
584
585
586 def get_flavor_by_id(nova, flavor_id):
587     """
588     Returns to OpenStack flavor object by name
589     :param nova: the Nova client
590     :param flavor_id: the flavor ID value
591     :return: the SNAPS Flavor domain object
592     """
593     os_flavor = __get_os_flavor(nova, flavor_id)
594     if os_flavor:
595         return Flavor(
596             name=os_flavor.name, id=os_flavor.id, ram=os_flavor.ram,
597             disk=os_flavor.disk, vcpus=os_flavor.vcpus,
598             ephemeral=os_flavor.ephemeral, swap=os_flavor.swap,
599             rxtx_factor=os_flavor.rxtx_factor, is_public=os_flavor.is_public)
600
601
602 def __get_os_flavor_by_name(nova, name):
603     """
604     Returns to OpenStack flavor object by name
605     :param nova: the Nova client
606     :param name: the name of the flavor to query
607     :return: OpenStack flavor object
608     """
609     try:
610         return nova.flavors.find(name=name)
611     except NotFound:
612         return None
613
614
615 def get_flavor_by_name(nova, name):
616     """
617     Returns a flavor by name
618     :param nova: the Nova client
619     :param name: the flavor name to return
620     :return: the SNAPS flavor domain object or None if not exists
621     """
622     os_flavor = __get_os_flavor_by_name(nova, name)
623     if os_flavor:
624         return Flavor(
625             name=os_flavor.name, id=os_flavor.id, ram=os_flavor.ram,
626             disk=os_flavor.disk, vcpus=os_flavor.vcpus,
627             ephemeral=os_flavor.ephemeral, swap=os_flavor.swap,
628             rxtx_factor=os_flavor.rxtx_factor, is_public=os_flavor.is_public)
629
630
631 def create_flavor(nova, flavor_settings):
632     """
633     Creates and returns and OpenStack flavor object
634     :param nova: the Nova client
635     :param flavor_settings: the flavor settings
636     :return: the SNAPS flavor domain object
637     """
638     os_flavor = nova.flavors.create(
639         name=flavor_settings.name, flavorid=flavor_settings.flavor_id,
640         ram=flavor_settings.ram, vcpus=flavor_settings.vcpus,
641         disk=flavor_settings.disk, ephemeral=flavor_settings.ephemeral,
642         swap=flavor_settings.swap, rxtx_factor=flavor_settings.rxtx_factor,
643         is_public=flavor_settings.is_public)
644     return Flavor(
645         name=os_flavor.name, id=os_flavor.id, ram=os_flavor.ram,
646         disk=os_flavor.disk, vcpus=os_flavor.vcpus,
647         ephemeral=os_flavor.ephemeral, swap=os_flavor.swap,
648         rxtx_factor=os_flavor.rxtx_factor, is_public=os_flavor.is_public)
649
650
651 def delete_flavor(nova, flavor):
652     """
653     Deletes a flavor
654     :param nova: the Nova client
655     :param flavor: the SNAPS flavor domain object
656     """
657     nova.flavors.delete(flavor.id)
658
659
660 def set_flavor_keys(nova, flavor, metadata):
661     """
662     Sets metadata on the flavor
663     :param nova: the Nova client
664     :param flavor: the SNAPS flavor domain object
665     :param metadata: the metadata to set
666     """
667     os_flavor = __get_os_flavor(nova, flavor.id)
668     if os_flavor:
669         os_flavor.set_keys(metadata)
670
671
672 def get_flavor_keys(nova, flavor):
673     """
674     Sets metadata on the flavor
675     :param nova: the Nova client
676     :param flavor: the SNAPS flavor domain object
677     """
678     os_flavor = __get_os_flavor(nova, flavor.id)
679     if os_flavor:
680         return os_flavor.get_keys()
681
682
683 def add_security_group(nova, vm, security_group_name):
684     """
685     Adds a security group to an existing VM
686     :param nova: the nova client
687     :param vm: the OpenStack server object (VM) to alter
688     :param security_group_name: the name of the security group to add
689     """
690     try:
691         nova.servers.add_security_group(str(vm.id), security_group_name)
692     except ClientException as e:
693         sec_grp_names = get_server_security_group_names(nova, vm)
694         if security_group_name in sec_grp_names:
695             logger.warn('Security group [%s] already added to VM [%s]',
696                         security_group_name, vm.name)
697             return
698
699         logger.error('Unexpected error while adding security group [%s] - %s',
700                      security_group_name, e)
701         raise
702
703
704 def remove_security_group(nova, vm, security_group):
705     """
706     Removes a security group from an existing VM
707     :param nova: the nova client
708     :param vm: the OpenStack server object (VM) to alter
709     :param security_group: the SNAPS SecurityGroup domain object to add
710     """
711     nova.servers.remove_security_group(str(vm.id), security_group.name)
712
713
714 def get_compute_quotas(nova, project_id):
715     """
716     Returns a list of all available keypairs
717     :param nova: the Nova client
718     :param project_id: the project's ID of the quotas to lookup
719     :return: an object of type ComputeQuotas or None if not found
720     """
721     quotas = nova.quotas.get(tenant_id=project_id)
722     if quotas:
723         return ComputeQuotas(quotas)
724
725
726 def update_quotas(nova, project_id, compute_quotas):
727     """
728     Updates the compute quotas for a given project
729     :param nova: the Nova client
730     :param project_id: the project's ID that requires quota updates
731     :param compute_quotas: an object of type ComputeQuotas containing the
732                            values to update
733     :return:
734     """
735     update_values = dict()
736     update_values['metadata_items'] = compute_quotas.metadata_items
737     update_values['cores'] = compute_quotas.cores
738     update_values['instances'] = compute_quotas.instances
739     update_values['injected_files'] = compute_quotas.injected_files
740     update_values['injected_file_content_bytes'] = (
741         compute_quotas.injected_file_content_bytes)
742     update_values['ram'] = compute_quotas.ram
743     update_values['fixed_ips'] = compute_quotas.fixed_ips
744     update_values['key_pairs'] = compute_quotas.key_pairs
745
746     return nova.quotas.update(project_id, **update_values)
747
748
749 def attach_volume(nova, neutron, keystone, server, volume, project_name,
750                   timeout=120):
751     """
752     Attaches a volume to a server. When the timeout parameter is used, a VmInst
753     object with the proper volume updates is returned unless it has not been
754     updated in the allotted amount of time then an Exception will be raised.
755     :param nova: the nova client
756     :param neutron: the neutron client
757     :param keystone: the neutron client
758     :param server: the VMInst domain object
759     :param volume: the Volume domain object
760     :param project_name: the associated project name
761     :param timeout: denotes the amount of time to block to determine if the
762                     has been properly attached.
763     :return: updated VmInst object
764     """
765     nova.volumes.create_server_volume(server.id, volume.id)
766
767     start_time = time.time()
768     while time.time() < start_time + timeout:
769         vm = get_server_object_by_id(
770             nova, neutron, keystone, server.id, project_name)
771         for vol_dict in vm.volume_ids:
772             if volume.id == vol_dict['id']:
773                 return vm
774         time.sleep(POLL_INTERVAL)
775
776     raise NovaException(
777         'Attach failed on volume - {} and server - {}'.format(
778             volume.id, server.id))
779
780
781 def detach_volume(nova, neutron, keystone, server, volume, project_name,
782                   timeout=120):
783     """
784     Detaches a volume to a server. When the timeout parameter is used, a VmInst
785     object with the proper volume updates is returned unless it has not been
786     updated in the allotted amount of time then an Exception will be raised.
787     :param nova: the nova client
788     :param neutron: the neutron client
789     :param keystone: the keystone client
790     :param server: the VMInst domain object
791     :param volume: the Volume domain object
792     :param project_name: the associated project name
793     :param timeout: denotes the amount of time to block to determine if the
794                     has been properly detached.
795     :return: updated VmInst object
796     """
797     nova.volumes.delete_server_volume(server.id, volume.id)
798
799     start_time = time.time()
800     while time.time() < start_time + timeout:
801         vm = get_server_object_by_id(
802             nova, neutron, keystone, server.id, project_name)
803         if len(vm.volume_ids) == 0:
804             return vm
805         else:
806             ids = list()
807             for vol_dict in vm.volume_ids:
808                 ids.append(vol_dict['id'])
809             if volume.id not in ids:
810                 return vm
811         time.sleep(POLL_INTERVAL)
812
813     raise NovaException(
814         'Detach failed on volume - {} server - {}'.format(
815             volume.id, server.id))
816
817
818 class RebootType(enum.Enum):
819     """
820     A rule's direction
821     """
822     soft = 'SOFT'
823     hard = 'HARD'
824
825
826 class NovaException(Exception):
827     """
828     Exception when calls to the Keystone client cannot be served properly
829     """
830
831
832 class ServerNotFoundError(Exception):
833     """
834     Exception when operations to a VM/Server is requested and the OpenStack
835     Server instance cannot be located
836     """