Files
decort-ansible/library/decort_rg.py
2026-06-01 18:27:15 +03:00

524 lines
20 KiB
Python

#!/usr/bin/python
DOCUMENTATION = r'''
---
module: decort_rg
description: See L(Module Documentation,https://repository.basistech.ru/BASIS/decort-ansible/wiki/Home).
'''
from ansible.module_utils.basic import AnsibleModule
from ansible.module_utils.basic import env_fallback
from ansible.module_utils.decort_utils import *
class decort_rg(DecortController):
def __init__(self):
super(decort_rg, self).__init__(AnsibleModule(**self.amodule_init_args))
amodule = self.amodule
self.validated_acc_id = 0
self.rg_id: int = 0
if amodule.params['rg_id'] is None:
if self.amodule.params['account_id']:
self.validated_acc_id, _ = self.account_find("", amodule.params['account_id'])
elif amodule.params['account_name']:
self.validated_acc_id, _ = self.account_find(amodule.params['account_name'])
if not self.validated_acc_id:
# we failed to locate account by either name or ID - abort with an error
self.result['failed'] = True
self.result['msg'] = ("Current user does not have access to the requested account "
"or non-existent account specified.")
self.amodule.fail_json(**self.result)
# Check if the RG with the specified parameters already exists
self.get_info()
if amodule.params['state'] != "absent":
self.rg_should_exist = True
else:
self.rg_should_exist = False
if self.rg_id and (
self.rg_info.status != sdk_types.ResourceGroupStatus.DESTROYED
):
self.check_amodule_args_for_change()
def get_info(self):
# If this is the first getting info
if self._rg_info is None:
self.rg_id, self._rg_info = self.rg_find(
arg_account_id=self.validated_acc_id,
arg_rg_id=self.aparams['rg_id'],
arg_rg_name=self.aparams['rg_name'],
arg_check_state=False,
)
# If this is a repeated getting info
else:
# If check mode is enabled, there is no needed to
# request info again
if self.amodule.check_mode:
return
_, self._rg_info = self.rg_find(arg_rg_id=self.rg_id)
def access(self):
should_change_access = False
acc_granted = False
for rg_item in self.rg_info.acl:
if rg_item.user_name == self.amodule.params['access']['user']:
acc_granted = True
if self.amodule.params['access']['action'] == 'grant':
if rg_item.access_type.value != self.amodule.params['access']['right']:
should_change_access = True
if self.amodule.params['access']['action'] == 'revoke':
should_change_access = True
if acc_granted == False and self.amodule.params['access']['action'] == 'grant':
should_change_access = True
if should_change_access == True:
if self.amodule.params['access']['action'] == "grant":
self.sdk_checkmode(self.api.ca.rg.access_grant)(
access_type=sdk_types.AccessTypeForSet(
self.amodule.params['access']['right']
),
rg_id=self.rg_id,
user_name=self.amodule.params['access']['user'],
)
else:
self.sdk_checkmode(self.api.ca.rg.access_revoke)(
rg_id=self.rg_id,
user_name=self.amodule.params['access']['user'],
)
self.rg_should_exist = True
return
def error(self):
self.result['failed'] = True
self.result['changed'] = False
if self.rg_id:
self.result['msg'] = ("Invalid target state '{}' requested for rg ID {} in the "
"current status '{}'.").format(self.rg_id,
self.amodule.params['state'],
self.rg_info.status.value)
else:
self.result['msg'] = ("Invalid target state '{}' requested for non-existent rg name '{}' "
"in account ID {} ").format(self.amodule.params['state'],
self.amodule.params['rg_name'],
self.validated_acc_id)
return
def update(self):
try:
rg_res_model = (
self.api.cloudapi.rg.get_resource_consumption(rg_id=self.rg_id)
)
except sdk_exceptions.RequestException as e:
self.message(
msg=(
f'Failed to get RG Resources by ID {self.rg_id}: {e}'
)
)
self.exit(fail=True)
reserved_resources = rg_res_model.reserved
incorrect_quota = dict(Requested=dict(),
Reserved=dict(),)
query_key_map = dict(
cpu='cpu_count',
ram='ram_size_mb',
disk='storage_size_gb_by_real_usage',
ext_ips='ext_ip_count',
storage_policies='storage_policies',
)
if self.amodule.params['quotas']:
for quota_item in self.amodule.params['quotas']:
if quota_item == 'storage_policies':
rg_storage_policies = reserved_resources.storage_policies
aparam_storage_policies = self.amodule.params['quotas'][
quota_item
]
for aparam_storage_policy in aparam_storage_policies:
aparam_storage_policy_id = aparam_storage_policy['id']
rg_storage_policy = rg_storage_policies.get(
str(aparam_storage_policy_id)
)
if (
rg_storage_policy
and aparam_storage_policy['storage_size_gb']
< rg_storage_policy.storage_size_gb_by_real_usage
):
incorrect_quota['Requested'][quota_item] = (
self.amodule.params['quotas'][quota_item]
)
incorrect_quota['Reserved'][quota_item] = (
getattr(
reserved_resources,
query_key_map[quota_item]
)
)
elif (
self.amodule.params['quotas'][quota_item]
< getattr(reserved_resources, query_key_map[quota_item])
):
incorrect_quota['Requested'][quota_item] = (
self.amodule.params['quotas'][quota_item]
)
incorrect_quota['Reserved'][quota_item] = (
getattr(reserved_resources, query_key_map[quota_item])
)
if reserved_resources and incorrect_quota['Requested']:
self.result['msg'] = ("Cannot limit less than already reserved'{}'").format(incorrect_quota)
self.result['failed'] = True
if not self.result['failed']:
self.rg_update(
rg_model=self.rg_info,
arg_quotas=self.amodule.params['quotas'],
arg_res_types=self.amodule.params['resType'],
arg_newname=self.amodule.params['rename'],
arg_sep_pools=self.amodule.params['sep_pools'],
arg_desc=self.amodule.params['description'],
)
self.rg_should_exist = True
return
def setDefNet(self):
rg_def_net_type = self.rg_info.default_net_type.value
rg_def_net_id = self.rg_info.default_net_id
aparam_def_net_type = self.aparams['def_netType']
aparam_def_net_id = self.aparams['def_netId']
need_to_reset = (
aparam_def_net_type == sdk_types.RGDefaultNetType.NONE
and rg_def_net_type != aparam_def_net_type
)
need_to_change = False
if aparam_def_net_id is not None:
need_to_change = (aparam_def_net_id != rg_def_net_id
or aparam_def_net_type != rg_def_net_type)
if need_to_reset or need_to_change:
if aparam_def_net_type == sdk_types.RGDefaultNetType.NONE:
self.sdk_checkmode(self.api.ca.rg.remove_def_net)(
rg_id=self.rg_id,
)
else:
if aparam_def_net_type == sdk_types.RGDefaultNetType.PRIVATE:
net_type = sdk_types.RGDefaultNetTypeForSet.PRIVATE
elif aparam_def_net_type == sdk_types.RGDefaultNetType.PUBLIC:
net_type = sdk_types.RGDefaultNetTypeForSet.PUBLIC
self.sdk_checkmode(self.api.ca.rg.set_def_net)(
net_type=net_type,
rg_id=self.rg_id,
net_id=aparam_def_net_id,
)
self.rg_should_exist = True
return
def create(self):
self.rg_id = self.rg_provision(
self.validated_acc_id,
self.amodule.params['rg_name'],
self.amodule.params['owner'],
self.amodule.params['description'],
self.amodule.params['resType'],
self.amodule.params['def_netType'],
self.amodule.params['ipcidr'],
self.amodule.params['extNetId'],
self.amodule.params['extNetIp'],
self.amodule.params['quotas'],
"", # this is location code. TODO: add module argument
sdn_access_group_id=self.aparams['sdn_access_group_id'],
)
if self.rg_id:
self.rg_id, self._rg_info = self.rg_find(
arg_account_id=self.validated_acc_id,
arg_rg_id=self.rg_id,
arg_rg_name="",
arg_check_state=False
)
self.rg_should_exist = True
return
def enable(self):
if self.amodule.params['state'] == "enabled":
self.sdk_checkmode(self.api.ca.rg.enable)(
rg_id=self.rg_id,
)
elif self.amodule.params['state'] == "disabled":
self.sdk_checkmode(self.api.ca.rg.disable)(
rg_id=self.rg_id,
)
self.rg_should_exist = True
return
def restore(self):
self.sdk_checkmode(self.api.ca.rg.restore)(
rg_id=self.rg_id,
)
self.rg_should_exist = True
return
def destroy(self):
self.sdk_checkmode(self.api.ca.rg.delete)(
rg_id=self.rg_id,
permanently=self.amodule.params['permanently'],
recursively=self.aparams['recursive_deletion'],
)
self.rg_should_exist = False
return
def package_facts(self, check_mode=False):
"""Package a dictionary of RG facts according to the decort_rg module specification. This dictionary will
be returned to the upstream Ansible engine at the completion of the module run.
@param arg_rg_facts: dictionary with RG facts as returned by API call to .../rg/get
@param arg_check_mode: boolean that tells if this Ansible module is run in check mode
"""
ret_dict = dict(id=0,
name="none",
state="CHECK_MODE",
)
if check_mode:
# in check mode return immediately with the default values
return ret_dict
#if arg_rg_facts is None:
# # if void facts provided - change state value to ABSENT and return
# ret_dict['state'] = "ABSENT"
# return ret_dict
return self.rg_info.model_dump()
@property
def amodule_init_args(self) -> dict:
return self.pack_amodule_init_args(
argument_spec=dict(
account_id=dict(
type='int',
),
account_name=dict(
type='str',
default='',
),
access=dict(
type='dict',
options=dict(
action=dict(
type='str',
required=True,
choices=[
'grant',
'revoke',
],
),
user=dict(
type='str',
required=True,
),
right=dict(
type='str',
choices=sdk_types.AccessTypeForSet._member_names_,
),
),
),
description=dict(
type='str',
default='',
),
def_netType=dict(
type='str',
choices=sdk_types.RGDefaultNetType._member_names_,
default=sdk_types.RGDefaultNetType.PRIVATE.name,
),
def_netId=dict(
type='int',
),
extNetId=dict(
type='int',
default=0,
),
extNetIp=dict(
type='str',
default='',
),
owner=dict(
type='str',
default='',
),
ipcidr=dict(
type='str',
default='',
),
rename=dict(
type='str',
default='',
),
quotas=dict(
type='dict',
),
resType=dict(
type='list',
),
state=dict(
type='str',
default='present',
choices=[
'absent',
'disabled',
'enabled',
'present',
],
),
permanently=dict(
type='bool',
default='False',
),
rg_name=dict(
type='str',
),
rg_id=dict(
type='int',
),
sep_pools=dict(
type='list',
elements='dict',
options=dict(
sep_id=dict(
type='int',
required=True,
),
pool_names=dict(
type='list',
required=True,
elements='str',
),
),
),
recursive_deletion=dict(
type='bool',
default=False,
),
sdn_access_group_id=dict(
type='str',
),
),
supports_check_mode=True,
)
def check_amodule_args_for_change(self):
check_errors = False
if (
self.aparams['sdn_access_group_id'] is not None
and (
self.aparams['sdn_access_group_id']
!= self.rg_info.sdn_access_group_id
)
):
self.message(
'Check for parameter "sdn_access_group_id" failed: '
'cannot change sdn_access_group_id for an existing resource '
f'group ID {self.rg_id}.'
)
check_errors = True
if check_errors:
self.exit(fail=True)
# Workflow digest:
# 1) authenticate to DECORT controller & validate authentication by issuing API call - done when creating DECORTController
# 2) check if the RG with the specified id or rg_name:name exists
# 3) if RG does not exist -> deploy
# 4) if RG exists: check desired state, desired configuration -> initiate action accordingly
# 5) report result to Ansible
@DecortController.handle_sdk_exceptions
def run(self):
amodule = self.amodule
#amodule.check_mode=True
if self.rg_id:
if self.rg_info.status in [
sdk_types.ResourceGroupStatus.MODELED,
sdk_types.ResourceGroupStatus.DISABLING,
sdk_types.ResourceGroupStatus.ENABLING,
sdk_types.ResourceGroupStatus.DESTROYING,
]:
self.error()
elif self.rg_info.status == sdk_types.ResourceGroupStatus.CREATED:
if amodule.params['state'] == 'absent':
self.destroy()
elif amodule.params['state'] == "disabled":
self.enable()
if amodule.params['state'] in ['present', 'enabled']:
if (
amodule.params['quotas']
or amodule.params['resType']
or amodule.params['rename'] != ""
or amodule.params['sep_pools'] is not None
or amodule.params['description'] is not None
):
self.update()
if amodule.params['access']:
self.access()
if amodule.params['def_netType'] is not None:
self.setDefNet()
elif self.rg_info.status == sdk_types.ResourceGroupStatus.DELETED:
if amodule.params['state'] == 'absent' and amodule.params['permanently'] == True:
self.destroy()
elif (amodule.params['state'] == 'present'
or amodule.params['state'] == 'disabled'):
self.restore()
elif amodule.params['state'] == 'enabled':
self.restore()
self.enable()
elif self.rg_info.status == sdk_types.ResourceGroupStatus.DISABLED:
if amodule.params['state'] == 'absent':
self.destroy()
elif amodule.params['state'] == ("enabled"):
self.enable()
else:
if amodule.params['state'] in ('present', 'enabled'):
if not amodule.params['rg_name']:
self.result['failed'] = True
self.result['msg'] = (
'Resource group could not be created because'
' the "rg_name" parameter was not specified.'
)
else:
self.create()
if amodule.params['access'] and not amodule.check_mode:
self.access()
elif amodule.params['state'] in ('disabled'):
self.error()
if self.result['failed']:
amodule.fail_json(**self.result)
else:
if self.rg_should_exist:
if self.result['changed']:
self.get_info()
self.result['facts'] = self.package_facts(amodule.check_mode)
amodule.exit_json(**self.result)
else:
amodule.exit_json(**self.result)
def main():
decort_rg().run()
if __name__ == '__main__':
main()