community.general/lib/ansible/modules/cloud/openstack/os_quota.py
Monty Taylor 0f893027c4 Add a module_utils OpenStack Cloud constructor (#20974)
Start using this to construct shade OpenStack Cloud objects in a
consistent manner. This will let us centralize things like dealing with
password arguments and whatnot. It also allows us to introduce the
ability to pass a fully formed config dict directly to the module.

Migrate all OpenStack modules to use openstack_cloud_from_module.

Have it return the shade library since it's responsible for
importing shade and shade is needed for the exceptions.

Only pull specific OpenStack arguments for the constructor

Rather than passing **module.params to the shade constructor, pull out
only the values that make sense. This should prevent the issues with
module parameters stepping on shade parameters.

Replace module.params.pop with module.params.get

We don't need to pop these anymore since the shade constructor is now
using opt-in values.

Using real urls is ungood. Use example.com domains. Also, get rid of the
antiquated port numbers.
2018-02-15 15:20:49 +01:00

515 lines
17 KiB
Python

#!/usr/bin/python
# Copyright (c) 2016 Pason System Corporation
# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt)
from __future__ import absolute_import, division, print_function
__metaclass__ = type
ANSIBLE_METADATA = {'metadata_version': '1.1',
'status': ['preview'],
'supported_by': 'community'}
DOCUMENTATION = '''
---
module: os_quota
short_description: Manage OpenStack Quotas
extends_documentation_fragment: openstack
version_added: "2.3"
author: "Michael Gale (gale.michael@gmail.com)"
description:
- Manage OpenStack Quotas. Quotas can be created,
updated or deleted using this module. A quota will be updated
if matches an existing project and is present.
options:
name:
description:
- Name of the OpenStack Project to manage.
required: true
state:
description:
- A value of present sets the quota and a value of absent resets the quota to system defaults.
required: False
default: present
backup_gigabytes:
required: False
default: None
description: Maximum size of backups in GB's.
backups:
required: False
default: None
description: Maximum number of backups allowed.
cores:
required: False
default: None
description: Maximum number of CPU's per project.
fixed_ips:
required: False
default: None
description: Number of fixed IP's to allow.
floating_ips:
required: False
default: None
description: Number of floating IP's to allow in Compute.
aliases: ['compute_floating_ips']
floatingip:
required: False
default: None
description: Number of floating IP's to allow in Network.
aliases: ['network_floating_ips']
gigabytes:
required: False
default: None
description: Maximum volume storage allowed for project.
gigabytes_lvm:
required: False
default: None
description: Maximum size in GB's of individual lvm volumes.
injected_file_size:
required: False
default: None
description: Maximum file size in bytes.
injected_files:
required: False
default: None
description: Number of injected files to allow.
injected_path_size:
required: False
default: None
description: Maximum path size.
instances:
required: False
default: None
description: Maximum number of instances allowed.
key_pairs:
required: False
default: None
description: Number of key pairs to allow.
loadbalancer:
required: False
default: None
description: Number of load balancers to allow.
version_added: "2.4"
network:
required: False
default: None
description: Number of networks to allow.
per_volume_gigabytes:
required: False
default: None
description: Maximum size in GB's of individual volumes.
pool:
required: False
default: None
description: Number of load balancer pools to allow.
version_added: "2.4"
port:
required: False
default: None
description: Number of Network ports to allow, this needs to be greater than the instances limit.
properties:
required: False
default: None
description: Number of properties to allow.
ram:
required: False
default: None
description: Maximum amount of ram in MB to allow.
rbac_policy:
required: False
default: None
description: Number of policies to allow.
router:
required: False
default: None
description: Number of routers to allow.
security_group_rule:
required: False
default: None
description: Number of rules per security group to allow.
security_group:
required: False
default: None
description: Number of security groups to allow.
server_group_members:
required: False
default: None
description: Number of server group members to allow.
server_groups:
required: False
default: None
description: Number of server groups to allow.
snapshots:
required: False
default: None
description: Number of snapshots to allow.
snapshots_lvm:
required: False
default: None
description: Number of LVM snapshots to allow.
subnet:
required: False
default: None
description: Number of subnets to allow.
subnetpool:
required: False
default: None
description: Number of subnet pools to allow.
volumes:
required: False
default: None
description: Number of volumes to allow.
volumes_lvm:
required: False
default: None
description: Number of LVM volumes to allow.
availability_zone:
description:
- Ignored. Present for backwards compatibility
required: false
requirements:
- "python >= 2.6"
- "shade > 1.9.0"
'''
EXAMPLES = '''
# List a Project Quota
- os_quota:
cloud: mycloud
name: demoproject
# Set a Project back to the defaults
- os_quota:
cloud: mycloud
name: demoproject
state: absent
# Update a Project Quota for cores
- os_quota:
cloud: mycloud
name: demoproject
cores: 100
# Update a Project Quota
- os_quota:
name: demoproject
cores: 1000
volumes: 20
volumes_type:
- volume_lvm: 10
# Complete example based on list of projects
- name: Update quotas
os_quota:
name: "{{ item.name }}"
backup_gigabytes: "{{ item.backup_gigabytes }}"
backups: "{{ item.backups }}"
cores: "{{ item.cores }}"
fixed_ips: "{{ item.fixed_ips }}"
floating_ips: "{{ item.floating_ips }}"
floatingip: "{{ item.floatingip }}"
gigabytes: "{{ item.gigabytes }}"
injected_file_size: "{{ item.injected_file_size }}"
injected_files: "{{ item.injected_files }}"
injected_path_size: "{{ item.injected_path_size }}"
instances: "{{ item.instances }}"
key_pairs: "{{ item.key_pairs }}"
loadbalancer: "{{ item.loadbalancer }}"
per_volume_gigabytes: "{{ item.per_volume_gigabytes }}"
pool: "{{ item.pool }}"
port: "{{ item.port }}"
properties: "{{ item.properties }}"
ram: "{{ item.ram }}"
security_group_rule: "{{ item.security_group_rule }}"
security_group: "{{ item.security_group }}"
server_group_members: "{{ item.server_group_members }}"
server_groups: "{{ item.server_groups }}"
snapshots: "{{ item.snapshots }}"
volumes: "{{ item.volumes }}"
volumes_types:
volumes_lvm: "{{ item.volumes_lvm }}"
snapshots_types:
snapshots_lvm: "{{ item.snapshots_lvm }}"
gigabytes_types:
gigabytes_lvm: "{{ item.gigabytes_lvm }}"
with_items:
- "{{ projects }}"
when: item.state == "present"
'''
RETURN = '''
openstack_quotas:
description: Dictionary describing the project quota.
returned: Regardless if changes where made or note
type: complex
contains:
openstack_quotas: {
compute: {
cores: 150,
fixed_ips: -1,
floating_ips: 10,
injected_file_content_bytes: 10240,
injected_file_path_bytes: 255,
injected_files: 5,
instances: 100,
key_pairs: 100,
metadata_items: 128,
ram: 153600,
security_group_rules: 20,
security_groups: 10,
server_group_members: 10,
server_groups: 10
},
network: {
floatingip: 50,
loadbalancer: 10,
network: 10,
pool: 10,
port: 160,
rbac_policy: 10,
router: 10,
security_group: 10,
security_group_rule: 100,
subnet: 10,
subnetpool: -1
},
volume: {
backup_gigabytes: 1000,
backups: 10,
gigabytes: 1000,
gigabytes_lvm: -1,
per_volume_gigabytes: -1,
snapshots: 10,
snapshots_lvm: -1,
volumes: 10,
volumes_lvm: -1
}
}
'''
from ansible.module_utils.basic import AnsibleModule
from ansible.module_utils.openstack import openstack_full_argument_spec, openstack_module_kwargs, openstack_cloud_from_module
def _get_volume_quotas(cloud, project):
return cloud.get_volume_quotas(project)
def _get_network_quotas(cloud, project):
return cloud.get_network_quotas(project)
def _get_compute_quotas(cloud, project):
return cloud.get_compute_quotas(project)
def _get_quotas(shade, module, cloud, project):
quota = {}
try:
quota['volume'] = _get_volume_quotas(cloud, project)
except shade.OpenStackCloudURINotFound:
module.warn("No public endpoint for volumev2 service was found. Ignoring volume quotas.")
try:
quota['network'] = _get_network_quotas(cloud, project)
except shade.OpenStackCloudURINotFound:
module.warn("No public endpoint for network service was found. Ignoring network quotas.")
quota['compute'] = _get_compute_quotas(cloud, project)
for quota_type in quota.keys():
quota[quota_type] = _scrub_results(quota[quota_type])
return quota
def _scrub_results(quota):
filter_attr = [
'HUMAN_ID',
'NAME_ATTR',
'human_id',
'request_ids',
'x_openstack_request_ids',
]
for attr in filter_attr:
if attr in quota:
del quota[attr]
return quota
def _system_state_change_details(module, project_quota_output):
quota_change_request = {}
changes_required = False
for quota_type in project_quota_output.keys():
for quota_option in project_quota_output[quota_type].keys():
if quota_option in module.params and module.params[quota_option] is not None:
if project_quota_output[quota_type][quota_option] != module.params[quota_option]:
changes_required = True
if quota_type not in quota_change_request:
quota_change_request[quota_type] = {}
quota_change_request[quota_type][quota_option] = module.params[quota_option]
return (changes_required, quota_change_request)
def _system_state_change(module, project_quota_output):
"""
Determine if changes are required to the current project quota.
This is done by comparing the current project_quota_output against
the desired quota settings set on the module params.
"""
changes_required, quota_change_request = _system_state_change_details(
module,
project_quota_output
)
if changes_required:
return True
else:
return False
def main():
argument_spec = openstack_full_argument_spec(
name=dict(required=True),
state=dict(default='present', choices=['absent', 'present']),
backup_gigabytes=dict(required=False, type='int', default=None),
backups=dict(required=False, type='int', default=None),
cores=dict(required=False, type='int', default=None),
fixed_ips=dict(required=False, type='int', default=None),
floating_ips=dict(required=False, type='int', default=None, aliases=['compute_floating_ips']),
floatingip=dict(required=False, type='int', default=None, aliases=['network_floating_ips']),
gigabytes=dict(required=False, type='int', default=None),
gigabytes_types=dict(required=False, type='dict', default={}),
injected_file_size=dict(required=False, type='int', default=None),
injected_files=dict(required=False, type='int', default=None),
injected_path_size=dict(required=False, type='int', default=None),
instances=dict(required=False, type='int', default=None),
key_pairs=dict(required=False, type='int', default=None),
loadbalancer=dict(required=False, type='int', default=None),
network=dict(required=False, type='int', default=None),
per_volume_gigabytes=dict(required=False, type='int', default=None),
pool=dict(required=False, type='int', default=None),
port=dict(required=False, type='int', default=None),
project=dict(required=False, type='int', default=None),
properties=dict(required=False, type='int', default=None),
ram=dict(required=False, type='int', default=None),
rbac_policy=dict(required=False, type='int', default=None),
router=dict(required=False, type='int', default=None),
security_group_rule=dict(required=False, type='int', default=None),
security_group=dict(required=False, type='int', default=None),
server_group_members=dict(required=False, type='int', default=None),
server_groups=dict(required=False, type='int', default=None),
snapshots=dict(required=False, type='int', default=None),
snapshots_types=dict(required=False, type='dict', default={}),
subnet=dict(required=False, type='int', default=None),
subnetpool=dict(required=False, type='int', default=None),
volumes=dict(required=False, type='int', default=None),
volumes_types=dict(required=False, type='dict', default={})
)
module = AnsibleModule(argument_spec,
supports_check_mode=True
)
shade, cloud = openstack_cloud_from_module(module)
try:
cloud_params = dict(module.params)
# In order to handle the different volume types we update module params after.
dynamic_types = [
'gigabytes_types',
'snapshots_types',
'volumes_types',
]
for dynamic_type in dynamic_types:
for k, v in module.params[dynamic_type].items():
module.params[k] = int(v)
# Get current quota values
project_quota_output = _get_quotas(
shade, module, cloud, cloud_params['name'])
changes_required = False
if module.params['state'] == "absent":
# If a quota state is set to absent we should assume there will be changes.
# The default quota values are not accessible so we can not determine if
# no changes will occur or not.
if module.check_mode:
module.exit_json(changed=True)
# Calling delete_network_quotas when a quota has not been set results
# in an error, according to the shade docs it should return the
# current quota.
# The following error string is returned:
# network client call failed: Quota for tenant 69dd91d217e949f1a0b35a4b901741dc could not be found.
neutron_msg1 = "network client call failed: Quota for tenant"
neutron_msg2 = "could not be found"
for quota_type in project_quota_output.keys():
quota_call = getattr(cloud, 'delete_%s_quotas' % (quota_type))
try:
quota_call(cloud_params['name'])
except shade.OpenStackCloudException as e:
error_msg = str(e)
if error_msg.find(neutron_msg1) > -1 and error_msg.find(neutron_msg2) > -1:
pass
else:
module.fail_json(msg=str(e), extra_data=e.extra_data)
project_quota_output = _get_quotas(
shade, module, cloud, cloud_params['name'])
changes_required = True
elif module.params['state'] == "present":
if module.check_mode:
module.exit_json(changed=_system_state_change(module, project_quota_output))
changes_required, quota_change_request = _system_state_change_details(
module,
project_quota_output
)
if changes_required:
for quota_type in quota_change_request.keys():
quota_call = getattr(cloud, 'set_%s_quotas' % (quota_type))
quota_call(cloud_params['name'], **quota_change_request[quota_type])
# Get quota state post changes for validation
project_quota_update = _get_quotas(
shade, module, cloud, cloud_params['name'])
if project_quota_output == project_quota_update:
module.fail_json(msg='Could not apply quota update')
project_quota_output = project_quota_update
module.exit_json(changed=changes_required,
openstack_quotas=project_quota_output
)
except shade.OpenStackCloudException as e:
module.fail_json(msg=str(e), extra_data=e.extra_data)
if __name__ == '__main__':
main()