
With "extends_documentation_fragment: ['openstack.cloud.openstack']" it is not necessary to list required Python libraries in section 'requirements' of DOCUMENTATION docstring in modules. Ansible will merge requirements from doc fragments and DOCUMENTATION docstring which previously resulted in duplicates such as in server module [0]: * openstacksdk * openstacksdk >= 0.36, < 0.99.0 * python >= 3.6 When removing the 'requirements' section from server module, then Ansible will list openstacksdk once only: * openstacksdk >= 0.36, < 0.99.0 * python >= 3.6 To see what documentation Ansible will produce for server module run: ansible-doc --type module openstack.cloud.server [0] https://docs.ansible.com/ansible/latest/collections/openstack/\ cloud/server_module.html Change-Id: I727ed95ee480bb644b5a533f6a9526973677064c
501 lines
17 KiB
Python
501 lines
17 KiB
Python
#!/usr/bin/python
|
|
# -*- coding: utf-8 -*-
|
|
|
|
# Copyright (c) 2016 Pason System Corporation
|
|
# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt)
|
|
|
|
DOCUMENTATION = '''
|
|
---
|
|
module: quota
|
|
short_description: Manage OpenStack Quotas
|
|
author: OpenStack Ansible SIG
|
|
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:
|
|
backup_gigabytes:
|
|
description: Maximum size of backups in GB's.
|
|
type: int
|
|
backups:
|
|
description: Maximum number of backups allowed.
|
|
type: int
|
|
cores:
|
|
description: Maximum number of CPU's per project.
|
|
type: int
|
|
fixed_ips:
|
|
description:
|
|
- Number of fixed IP's to allow.
|
|
- Available until Nova API version 2.35.
|
|
type: int
|
|
floating_ips:
|
|
description: Number of floating IP's to allow.
|
|
aliases: [compute_floating_ips, floatingip, network_floating_ips]
|
|
type: int
|
|
gigabytes:
|
|
description: Maximum volume storage allowed for project.
|
|
type: int
|
|
groups:
|
|
description: Number of groups that are allowed for the project
|
|
type: int
|
|
injected_file_content_bytes:
|
|
description:
|
|
- Maximum file size in bytes.
|
|
- Available until Nova API version 2.56.
|
|
type: int
|
|
aliases: [injected_file_size]
|
|
injected_files:
|
|
description:
|
|
- Number of injected files to allow.
|
|
- Available until Nova API version 2.56.
|
|
type: int
|
|
injected_file_path_bytes:
|
|
description:
|
|
- Maximum path size.
|
|
- Available until Nova API version 2.56.
|
|
type: int
|
|
aliases: [injected_path_size]
|
|
instances:
|
|
description: Maximum number of instances allowed.
|
|
type: int
|
|
key_pairs:
|
|
description: Number of key pairs to allow.
|
|
type: int
|
|
load_balancers:
|
|
description: The maximum amount of load balancers you can create
|
|
type: int
|
|
aliases: [loadbalancer]
|
|
metadata_items:
|
|
description: Number of metadata items allowed per instance.
|
|
type: int
|
|
name:
|
|
description: Name of the OpenStack Project to manage.
|
|
required: true
|
|
type: str
|
|
networks:
|
|
description: Number of networks to allow.
|
|
type: int
|
|
aliases: [network]
|
|
per_volume_gigabytes:
|
|
description: Maximum size in GB's of individual volumes.
|
|
type: int
|
|
pools:
|
|
description: The maximum number of pools you can create
|
|
type: int
|
|
aliases: [pool]
|
|
ports:
|
|
description: Number of Network ports to allow, this needs to be greater
|
|
than the instances limit.
|
|
type: int
|
|
aliases: [port]
|
|
ram:
|
|
description: Maximum amount of ram in MB to allow.
|
|
type: int
|
|
rbac_policies:
|
|
description: Number of policies to allow.
|
|
type: int
|
|
aliases: [rbac_policy]
|
|
routers:
|
|
description: Number of routers to allow.
|
|
type: int
|
|
aliases: [router]
|
|
security_group_rules:
|
|
description: Number of rules per security group to allow.
|
|
type: int
|
|
aliases: [security_group_rule]
|
|
security_groups:
|
|
description: Number of security groups to allow.
|
|
type: int
|
|
aliases: [security_group]
|
|
server_group_members:
|
|
description: Number of server group members to allow.
|
|
type: int
|
|
server_groups:
|
|
description: Number of server groups to allow.
|
|
type: int
|
|
snapshots:
|
|
description: Number of snapshots to allow.
|
|
type: int
|
|
state:
|
|
description: A value of C(present) sets the quota and a value of
|
|
C(absent) resets the quota to defaults.
|
|
default: present
|
|
type: str
|
|
choices: [absent, present]
|
|
subnets:
|
|
description: Number of subnets to allow.
|
|
type: int
|
|
aliases: [subnet]
|
|
subnet_pools:
|
|
description: Number of subnet pools to allow.
|
|
type: int
|
|
aliases: [subnetpool]
|
|
volumes:
|
|
description: Number of volumes to allow.
|
|
type: int
|
|
extends_documentation_fragment:
|
|
- openstack.cloud.openstack
|
|
'''
|
|
|
|
EXAMPLES = '''
|
|
- name: Fetch current project quota
|
|
openstack.cloud.quota:
|
|
cloud: mycloud
|
|
name: demoproject
|
|
|
|
- name: Reset project quota back to defaults
|
|
openstack.cloud.quota:
|
|
cloud: mycloud
|
|
name: demoproject
|
|
state: absent
|
|
|
|
- name: Change number of cores and volumes
|
|
openstack.cloud.quota:
|
|
cloud: mycloud
|
|
name: demoproject
|
|
cores: 100
|
|
volumes: 20
|
|
|
|
- name: Update quota again
|
|
openstack.cloud.quota:
|
|
cloud: mycloud
|
|
name: demo_project
|
|
floating_ips: 5
|
|
networks: 50
|
|
ports: 300
|
|
rbac_policies: 5
|
|
routers: 5
|
|
subnets: 5
|
|
subnet_pools: 5
|
|
security_group_rules: 5
|
|
security_groups: 5
|
|
backup_gigabytes: 500
|
|
backups: 5
|
|
gigabytes: 500
|
|
groups: 1
|
|
pools: 5
|
|
per_volume_gigabytes: 10
|
|
snapshots: 5
|
|
volumes: 5
|
|
cores: 5
|
|
instances: 5
|
|
key_pairs: 5
|
|
metadata_items: 5
|
|
ram: 5
|
|
server_groups: 5
|
|
server_group_members: 5
|
|
|
|
'''
|
|
|
|
RETURN = '''
|
|
quotas:
|
|
description: Dictionary describing the project quota.
|
|
returned: Regardless if changes where made or not
|
|
type: dict
|
|
contains:
|
|
compute:
|
|
description: Compute service quotas
|
|
type: dict
|
|
contains:
|
|
cores:
|
|
description: Maximum number of CPU's per project.
|
|
type: int
|
|
injected_file_content_bytes:
|
|
description: Maximum file size in bytes.
|
|
type: int
|
|
injected_files:
|
|
description: Number of injected files to allow.
|
|
type: int
|
|
injected_file_path_bytes:
|
|
description: Maximum path size.
|
|
type: int
|
|
instances:
|
|
description: Maximum number of instances allowed.
|
|
type: int
|
|
key_pairs:
|
|
description: Number of key pairs to allow.
|
|
type: int
|
|
metadata_items:
|
|
description: Number of metadata items allowed per instance.
|
|
type: int
|
|
ram:
|
|
description: Maximum amount of ram in MB to allow.
|
|
type: int
|
|
server_group_members:
|
|
description: Number of server group members to allow.
|
|
type: int
|
|
server_groups:
|
|
description: Number of server groups to allow.
|
|
type: int
|
|
network:
|
|
description: Network service quotas
|
|
type: dict
|
|
contains:
|
|
floating_ips:
|
|
description: Number of floating IP's to allow.
|
|
type: int
|
|
load_balancers:
|
|
description: The maximum amount of load balancers one can
|
|
create
|
|
type: int
|
|
networks:
|
|
description: Number of networks to allow.
|
|
type: int
|
|
pools:
|
|
description: The maximum amount of pools one can create.
|
|
type: int
|
|
ports:
|
|
description: Number of Network ports to allow, this needs
|
|
to be greater than the instances limit.
|
|
type: int
|
|
rbac_policies:
|
|
description: Number of policies to allow.
|
|
type: int
|
|
routers:
|
|
description: Number of routers to allow.
|
|
type: int
|
|
security_group_rules:
|
|
description: Number of rules per security group to allow.
|
|
type: int
|
|
security_groups:
|
|
description: Number of security groups to allow.
|
|
type: int
|
|
subnet_pools:
|
|
description: Number of subnet pools to allow.
|
|
type: int
|
|
subnets:
|
|
description: Number of subnets to allow.
|
|
type: int
|
|
volume:
|
|
description: Block storage service quotas
|
|
type: dict
|
|
contains:
|
|
backup_gigabytes:
|
|
description: Maximum size of backups in GB's.
|
|
type: int
|
|
backups:
|
|
description: Maximum number of backups allowed.
|
|
type: int
|
|
gigabytes:
|
|
description: Maximum volume storage allowed for project.
|
|
type: int
|
|
groups:
|
|
description: Number of groups that are allowed for the
|
|
project
|
|
type: int
|
|
per_volume_gigabytes:
|
|
description: Maximum size in GB's of individual volumes.
|
|
type: int
|
|
snapshots:
|
|
description: Number of snapshots to allow.
|
|
type: int
|
|
volumes:
|
|
description: Number of volumes to allow.
|
|
type: int
|
|
sample:
|
|
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,
|
|
networks: -1,
|
|
ram: 153600,
|
|
security_group_rules: -1,
|
|
security_groups: -1,
|
|
server_group_members: 10,
|
|
server_groups: 10,
|
|
network:
|
|
floating_ips: 50,
|
|
load_balancers: 10,
|
|
networks: 10,
|
|
pools: 10,
|
|
ports: 160,
|
|
rbac_policies: 10,
|
|
routers: 10,
|
|
security_group_rules: 100,
|
|
security_groups: 10,
|
|
subnet_pools: -1,
|
|
subnets: 10,
|
|
volume:
|
|
backup_gigabytes: 1000,
|
|
backups: 10,
|
|
gigabytes: 1000,
|
|
groups: 10,
|
|
per_volume_gigabytes: -1,
|
|
snapshots: 10,
|
|
volumes: 10,
|
|
'''
|
|
|
|
from ansible_collections.openstack.cloud.plugins.module_utils.openstack import OpenStackModule
|
|
from collections import defaultdict
|
|
|
|
|
|
class QuotaModule(OpenStackModule):
|
|
# TODO: Add missing network quota options 'check_limit', 'health_monitors',
|
|
# 'l7_policies', 'listeners' to argument_spec, DOCUMENTATION and
|
|
# RETURN docstrings
|
|
argument_spec = dict(
|
|
backup_gigabytes=dict(type='int'),
|
|
backups=dict(type='int'),
|
|
cores=dict(type='int'),
|
|
fixed_ips=dict(type='int'),
|
|
floating_ips=dict(
|
|
type='int', aliases=['floatingip', 'compute_floating_ips',
|
|
'network_floating_ips']),
|
|
gigabytes=dict(type='int'),
|
|
groups=dict(type='int'),
|
|
injected_file_content_bytes=dict(type='int',
|
|
aliases=['injected_file_size']),
|
|
injected_file_path_bytes=dict(type='int',
|
|
aliases=['injected_path_size']),
|
|
injected_files=dict(type='int'),
|
|
instances=dict(type='int'),
|
|
key_pairs=dict(type='int', no_log=False),
|
|
load_balancers=dict(type='int', aliases=['loadbalancer']),
|
|
metadata_items=dict(type='int'),
|
|
name=dict(required=True),
|
|
networks=dict(type='int', aliases=['network']),
|
|
per_volume_gigabytes=dict(type='int'),
|
|
pools=dict(type='int', aliases=['pool']),
|
|
ports=dict(type='int', aliases=['port']),
|
|
ram=dict(type='int'),
|
|
rbac_policies=dict(type='int', aliases=['rbac_policy']),
|
|
routers=dict(type='int', aliases=['router']),
|
|
security_group_rules=dict(type='int', aliases=['security_group_rule']),
|
|
security_groups=dict(type='int', aliases=['security_group']),
|
|
server_group_members=dict(type='int'),
|
|
server_groups=dict(type='int'),
|
|
snapshots=dict(type='int'),
|
|
state=dict(default='present', choices=['absent', 'present']),
|
|
subnet_pools=dict(type='int', aliases=['subnetpool']),
|
|
subnets=dict(type='int', aliases=['subnet']),
|
|
volumes=dict(type='int'),
|
|
)
|
|
|
|
module_kwargs = dict(
|
|
supports_check_mode=True
|
|
)
|
|
|
|
# Some attributes in quota resources don't exist in the api anymore, mostly
|
|
# compute quotas that were simply network proxies. This map allows marking
|
|
# them to be skipped.
|
|
exclusion_map = {
|
|
'compute': {
|
|
# 'fixed_ips', # Available until Nova API version 2.35
|
|
'floating_ips', # Available until Nova API version 2.35
|
|
'name',
|
|
'networks', # Available until Nova API version 2.35
|
|
'security_group_rules', # Available until Nova API version 2.35
|
|
'security_groups', # Available until Nova API version 2.35
|
|
# 'injected_file_content_bytes', # Available until
|
|
# 'injected_file_path_bytes', # Nova API
|
|
# 'injected_files', # version 2.56
|
|
},
|
|
'network': {'name'},
|
|
'volume': {'name'},
|
|
}
|
|
|
|
def _get_quotas(self, project):
|
|
quota = {}
|
|
if self.conn.has_service('block-storage'):
|
|
quota['volume'] = self.conn.block_storage.get_quota_set(project)
|
|
else:
|
|
self.warn('Block storage service aka volume service is not'
|
|
' supported by your cloud. Ignoring volume quotas.')
|
|
|
|
if self.conn.has_service('network'):
|
|
quota['network'] = self.conn.network.get_quota(project.id)
|
|
else:
|
|
self.warn('Network service is not supported by your cloud.'
|
|
' Ignoring network quotas.')
|
|
|
|
quota['compute'] = self.conn.compute.get_quota_set(project.id)
|
|
|
|
return quota
|
|
|
|
def _build_update(self, quotas):
|
|
changes = defaultdict(dict)
|
|
|
|
for quota_type in quotas.keys():
|
|
exclusions = self.exclusion_map[quota_type]
|
|
for attr in quotas[quota_type].keys():
|
|
if attr in exclusions:
|
|
continue
|
|
if (attr in self.params and self.params[attr] is not None
|
|
and quotas[quota_type][attr] != self.params[attr]):
|
|
changes[quota_type][attr] = self.params[attr]
|
|
|
|
return changes
|
|
|
|
def _system_state_change(self, 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.
|
|
"""
|
|
|
|
if self.params['state'] == 'absent':
|
|
return True
|
|
|
|
return bool(self._build_update(project_quota_output))
|
|
|
|
def run(self):
|
|
project = self.conn.identity.find_project(
|
|
self.params['name'], ignore_missing=False)
|
|
|
|
# Get current quota values
|
|
quotas = self._get_quotas(project)
|
|
|
|
changed = False
|
|
|
|
if self.ansible.check_mode:
|
|
self.exit_json(changed=self._system_state_change(quotas))
|
|
|
|
if self.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.
|
|
changed = True
|
|
self.conn.compute.revert_quota_set(project)
|
|
if 'network' in quotas:
|
|
self.conn.network.delete_quota(project.id)
|
|
if 'volume' in quotas:
|
|
self.conn.block_storage.revert_quota_set(project)
|
|
|
|
# Necessary since we can't tell what the default quotas are
|
|
quotas = self._get_quotas(project)
|
|
|
|
elif self.params['state'] == 'present':
|
|
changes = self._build_update(quotas)
|
|
|
|
if changes:
|
|
if 'volume' in changes:
|
|
self.conn.block_storage.update_quota_set(
|
|
quotas['volume'], **changes['volume'])
|
|
if 'compute' in changes:
|
|
self.conn.compute.update_quota_set(
|
|
quotas['compute'], **changes['compute'])
|
|
if 'network' in changes:
|
|
quotas['network'] = self.conn.network.update_quota(
|
|
project.id, **changes['network'])
|
|
changed = True
|
|
|
|
quotas = {k: v.to_dict(computed=False) for k, v in quotas.items()}
|
|
self.exit_json(changed=changed, quotas=quotas)
|
|
|
|
|
|
def main():
|
|
module = QuotaModule()
|
|
module()
|
|
|
|
|
|
if __name__ == '__main__':
|
|
main()
|