This commit is contained in:
2025-07-21 13:31:14 +03:00
parent 4113719334
commit 06336697a6
201 changed files with 2228 additions and 80456 deletions

View File

@@ -4,7 +4,7 @@ DOCUMENTATION = r'''
---
module: decort_account
description: See L(Module Documentation,https://repository.basistech.ru/BASIS/decort-ansible/wiki/Home).
description: See L(Module Documentation,https://repository.basistech.ru/BASIS/decort-ansible/wiki/Home). # noqa: E501
'''
from typing import Iterable
@@ -114,6 +114,9 @@ class DecortAccount(DecortController):
description=dict(
type='str',
),
default_zone_id=dict(
type='int',
),
),
required_one_of=[
('id', 'name')
@@ -164,15 +167,24 @@ class DecortAccount(DecortController):
if check_error:
self.exit(fail=True)
def check_amodule_args_for_change(self):
check_error = False
if self.check_aparam_default_zone_id() is False:
check_error = True
if check_error:
self.exit(fail=True)
def run(self):
self.get_info()
self.check_amodule_args_for_change()
self.change()
self.exit()
def get_info(self):
# If this is the first getting info
if not getattr(self, 'id', None):
self.id, self.facts = self.account_find(
if self._acc_info is None:
self.acc_id, self._acc_info = self.account_find(
account_name=self.aparams['name'],
account_id=self.aparams['id'],
)
@@ -181,7 +193,10 @@ class DecortAccount(DecortController):
# If check mode is enabled, there is no needed to
# request info again
if not self.amodule.check_mode:
self.id, self.facts = self.account_find(account_id=self.id)
self.acc_id, self._acc_info = self.account_find(
account_id=self.acc_id,
)
self.facts = self.acc_info
def change(self):
self.change_state()
@@ -189,12 +204,12 @@ class DecortAccount(DecortController):
self.change_acl()
if self.account_update_args:
self.account_update(account_id=self.id,
self.account_update(account_id=self.acc_id,
**self.account_update_args)
self.get_info()
def change_state(self):
match self.facts:
match self._acc_info:
case None:
self.message(self.MESSAGES.obj_not_found(obj=self.OBJ))
match self.aparams:
@@ -208,7 +223,7 @@ class DecortAccount(DecortController):
self.message(
self.MESSAGES.obj_deleted(
obj=self.OBJ,
id=self.id,
id=self.acc_id,
permanently=True,
already=True,
)
@@ -216,7 +231,7 @@ class DecortAccount(DecortController):
case {'state': 'confirmed' | 'disabled' | 'present'}:
self.message(
self.MESSAGES.obj_not_restored(obj=self.OBJ,
id=self.id)
id=self.acc_id)
)
self.exit(fail=True)
case {'status': 'DELETED'}:
@@ -225,7 +240,7 @@ class DecortAccount(DecortController):
self.message(
self.MESSAGES.obj_deleted(
obj=self.OBJ,
id=self.id,
id=self.acc_id,
permanently=False,
already=True,
)
@@ -259,26 +274,28 @@ class DecortAccount(DecortController):
pass
def delete(self, permanently=False):
self.account_delete(account_id=self.id, permanently=permanently)
self.account_delete(account_id=self.acc_id, permanently=permanently)
self.get_info()
def disable(self):
self.account_disable(account_id=self.id)
self.account_disable(account_id=self.acc_id)
self.get_info()
def enable(self):
self.account_enable(account_id=self.id)
self.account_enable(account_id=self.acc_id)
self.get_info()
def restore(self):
self.account_restore(account_id=self.id)
self.account_restore(account_id=self.acc_id)
self.get_info()
def change_acl(self):
if not self.aparams['acl']:
return
actual_users = {u['userGroupId']: u['right'] for u in self.facts['acl']}
actual_users = {
u['userGroupId']: u['right'] for u in self.acc_info['acl']
}
actual_users_ids = set(actual_users.keys())
aparams_acl = self.aparams['acl']
@@ -291,9 +308,13 @@ class DecortAccount(DecortController):
match aparams_acl:
case {'mode': 'revoke'}:
del_users_ids = aparams_users_ids.intersection(actual_users_ids)
del_users_ids = aparams_users_ids.intersection(
actual_users_ids,
)
case {'mode': 'update' | 'match' as mode}:
new_users_ids = aparams_users_ids.difference(actual_users_ids)
new_users_ids = aparams_users_ids.difference(
actual_users_ids,
)
new_users = dict(
u for u in aparams_users.items() if u[0] in new_users_ids
)
@@ -315,7 +336,7 @@ class DecortAccount(DecortController):
actual_users_ids.difference(aparams_users_ids)
if del_users_ids or new_users or upd_users:
self.account_change_acl(account_id=self.id,
self.account_change_acl(account_id=self.acc_id,
del_users=del_users_ids,
add_users=new_users,
upd_users=upd_users)
@@ -327,12 +348,12 @@ class DecortAccount(DecortController):
aparam_access_emails = self.aparams['access_emails']
if (aparam_access_emails is not None
and self.facts['sendAccessEmails'] != aparam_access_emails):
and self.acc_info['sendAccessEmails'] != aparam_access_emails):
result_args['access_emails'] = aparam_access_emails
aparam_name = self.aparams['name']
if (self.aparams['id'] and aparam_name
and self.facts['name'] != aparam_name):
and self.acc_info['name'] != aparam_name):
result_args['name'] = aparam_name
aparam_quotas = self.aparams['quotas']
@@ -346,7 +367,7 @@ class DecortAccount(DecortController):
['ram', 'CU_M', 'ram_quota'],
]
for aparam, info_key, result_arg in quotas_naming:
current_value = int(self.facts['resourceLimits'][info_key])
current_value = int(self.acc_info['resourceLimits'][info_key])
if (aparam_quotas[aparam] is not None
and current_value != aparam_quotas[aparam]):
result_args[result_arg] = aparam_quotas[aparam]
@@ -359,18 +380,38 @@ class DecortAccount(DecortController):
sep_pools.add(
f'{sep["sep_id"]}_{pool_name}'
)
if set(self.facts['uniqPools']) != sep_pools:
if set(self.acc_info['uniqPools']) != sep_pools:
result_args['sep_pools'] = sep_pools
aparam_desc = self.aparams['description']
if (
aparam_desc is not None
and self.facts['description'] != aparam_desc
and self.acc_info['description'] != aparam_desc
):
result_args['description'] = aparam_desc
aparam_default_zone_id = self.aparams['default_zone_id']
if (
aparam_default_zone_id is not None
and self.acc_info['defaultZoneId'] != aparam_default_zone_id
):
result_args['default_zone_id'] = aparam_default_zone_id
return result_args
def check_aparam_default_zone_id(self) -> bool | None:
aparam_default_zone_id = self.aparams['default_zone_id']
if aparam_default_zone_id is not None:
if aparam_default_zone_id in self.acc_zone_ids:
return True
else:
self.message(
'Check for parameter "default_zone_id" failed: '
f'zone ID {aparam_default_zone_id} not available '
f'for account ID {self.acc_id}.'
)
return False
def main():
DecortAccount().run()

View File

@@ -4,7 +4,7 @@ DOCUMENTATION = r'''
---
module: decort_account_info
description: See L(Module Documentation,https://repository.basistech.ru/BASIS/decort-ansible/wiki/Home).
description: See L(Module Documentation,https://repository.basistech.ru/BASIS/decort-ansible/wiki/Home). # noqa: E501
'''
from ansible.module_utils.basic import AnsibleModule
@@ -78,8 +78,7 @@ class DecortAccountInfo(DecortController):
),
field=dict(
type='str',
choices=\
self.FIELDS_FOR_SORTING_ACCOUNT_COMPUTE_LIST,
choices=self.FIELDS_FOR_SORTING_ACCOUNT_COMPUTE_LIST, # noqa: E501
required=True,
),
),
@@ -129,8 +128,7 @@ class DecortAccountInfo(DecortController):
),
field=dict(
type='str',
choices=\
self.FIELDS_FOR_SORTING_ACCOUNT_DISK_LIST,
choices=self.FIELDS_FOR_SORTING_ACCOUNT_DISK_LIST, # noqa: E501
required=True,
),
),
@@ -221,8 +219,7 @@ class DecortAccountInfo(DecortController):
),
field=dict(
type='str',
choices=\
self.FIELDS_FOR_SORTING_ACCOUNT_IMAGE_LIST,
choices=self.FIELDS_FOR_SORTING_ACCOUNT_IMAGE_LIST, # noqa: E501
required=True,
),
),
@@ -278,8 +275,7 @@ class DecortAccountInfo(DecortController):
),
field=dict(
type='str',
choices=\
self.FIELDS_FOR_SORTING_ACCOUNT_RG_LIST,
choices=self.FIELDS_FOR_SORTING_ACCOUNT_RG_LIST, # noqa: E501
required=True,
),
),
@@ -332,8 +328,7 @@ class DecortAccountInfo(DecortController):
),
field=dict(
type='str',
choices=\
self.FIELDS_FOR_SORTING_ACCOUNT_VINS_LIST,
choices=self.FIELDS_FOR_SORTING_ACCOUNT_VINS_LIST, # noqa: E501
required=True,
),
),

View File

@@ -20,6 +20,7 @@ class decort_bservice(DecortController):
validated_acc_id = 0
validated_rg_id = 0
self.bservice_info = None
self.is_bservice_stopped_or_will_be_stopped: None | bool = None
if arg_amodule.params['name'] == "" and arg_amodule.params['id'] == 0:
self.result['failed'] = True
self.result['changed'] = False
@@ -27,7 +28,7 @@ class decort_bservice(DecortController):
self.fail_json(**self.result)
if not arg_amodule.params['id']:
if not arg_amodule.params['rg_id']: # RG ID is not set -> locate RG by name -> need account ID
validated_acc_id, _ = self.account_find(arg_amodule.params['account_name'],
validated_acc_id, self.acc_info = self.account_find(arg_amodule.params['account_name'],
arg_amodule.params['account_id'])
if not validated_acc_id:
self.result['failed'] = True
@@ -53,7 +54,7 @@ class decort_bservice(DecortController):
arg_amodule.params['rg_id'] = validated_rg_id
arg_amodule.params['rg_name'] = validated_rg_facts['name']
validated_acc_id = validated_rg_facts['accountId']
self.bservice_id, self.bservice_info = self.bservice_find(
validated_acc_id,
validated_rg_id,
@@ -61,10 +62,14 @@ class decort_bservice(DecortController):
arg_amodule.params['id']
)
if self.bservice_id == 0:
self.bservice_should_exist = False
else:
self.acc_id = validated_acc_id or self.bservice_info['accountId']
if self.bservice_id and self.bservice_info['status'] != 'DESTROYED':
self.bservice_should_exist = True
self.check_amodule_args_for_change()
else:
self.bservice_should_exist = False
self.check_amodule_args_for_create()
def nop(self):
"""No operation (NOP) handler for B-service.
@@ -103,15 +108,23 @@ class decort_bservice(DecortController):
self.amodule.params['name'],
self.amodule.params['rg_id'],
self.amodule.params['sshuser'],
self.amodule.params['sshkey']
self.amodule.params['sshkey'],
zone_id=self.aparams['zone_id'],
)
if self.bservice_id:
_, self.bservice_info = self.bservice_get_by_id(self.bservice_id)
self.bservice_state(self.bservice_info,'enabled',self.amodule.params['started'])
self.bservice_state(self.bservice_info,'enabled')
return
def action(self,d_state,started=False):
self.bservice_state(self.bservice_info,d_state,started)
def action(self,d_state):
self.bservice_state(self.bservice_info,d_state)
aparam_zone_id = self.aparams['zone_id']
if aparam_zone_id is not None and aparam_zone_id != self.bservice_info['zoneId']:
self.bservice_migrate_to_zone(
bs_id=self.bservice_id,
zone_id=aparam_zone_id,
)
return
def restore(self):
@@ -147,6 +160,7 @@ class decort_bservice(DecortController):
ret_dict['rg_id'] = self.bservice_info['rgId']
ret_dict['account_id'] = self.bservice_info['accountId']
ret_dict['groups'] = self.bservice_info['groups']
ret_dict['zone_id'] = self.bservice_info['zoneId']
return ret_dict
@property
@@ -168,13 +182,10 @@ class decort_bservice(DecortController):
'disabled',
'enabled',
'present',
'check',
'started',
'stopped',
],
),
started=dict(
type='bool',
default=True,
),
name=dict(
type='str',
default='',
@@ -197,6 +208,9 @@ class decort_bservice(DecortController):
type='str',
default='',
),
zone_id=dict(
type='int',
),
),
supports_check_mode=True,
required_one_of=[
@@ -204,12 +218,54 @@ class decort_bservice(DecortController):
('rg_id', 'rg_name'),
],
)
def check_amodule_args_for_change(self):
check_errors = False
self.is_bservice_stopped_or_will_be_stopped = (
(
self.bservice_info['techStatus'] == 'STOPPED'
and (
self.aparams['state'] is None
or self.aparams['state'] in ('present', 'stopped')
)
)
or (
self.bservice_info['techStatus'] != 'STOPPED'
and self.aparams['state'] == 'stopped'
)
)
if self.check_aparam_zone_id() is False:
check_errors = True
if (
self.aparams['zone_id'] is not None
and self.aparams['zone_id'] != self.bservice_info['zoneId']
and not self.is_bservice_stopped_or_will_be_stopped
):
check_errors = True
self.message(
'Check for parameter "zone_id" failed: '
'Basic Service must be stopped to migrate to a zone.'
)
if check_errors:
self.exit(fail=True)
def check_amodule_args_for_create(self):
check_errors = False
if self.check_aparam_zone_id() is False:
check_errors = True
if check_errors:
self.exit(fail=True)
def main():
subj = decort_bservice()
amodule = subj.amodule
if amodule.params['state'] == 'check':
if subj.amodule.check_mode:
subj.result['changed'] = False
if subj.bservice_id:
subj.result['failed'] = False
@@ -235,30 +291,23 @@ def main():
"ENABLING","DISABLING","RESTORING","MODELED"):
subj.error()
elif subj.bservice_info['status'] == "DELETED":
if amodule.params['state'] in ('disabled', 'enabled', 'present'):
if amodule.params['state'] in (
'disabled', 'enabled', 'present', 'started', 'stopped'
):
subj.restore(subj.bservice_id)
subj.action(amodule.params['state'],amodule.params['started'])
subj.action(amodule.params['state'])
if amodule.params['state'] == 'absent':
subj.nop()
elif subj.bservice_info['techStatus'] in ("STARTED","STOPPED"):
if amodule.params['state'] == 'disabled':
subj.action(amodule.params['state'],amodule.params['started'])
elif amodule.params['state'] == 'absent':
subj.destroy()
else:
subj.action(amodule.params['state'],amodule.params['started'])
elif subj.bservice_info['status'] == "DISABLED":
elif subj.bservice_info['status'] in ('ENABLED', 'DISABLED'):
if amodule.params['state'] == 'absent':
subj.destroy()
elif amodule.params['state'] in ('present','enabled'):
subj.action(amodule.params['state'],amodule.params['started'])
else:
subj.nop()
subj.action(amodule.params['state'])
elif subj.bservice_info['status'] == "DESTROED":
if amodule.params['state'] in ('present','enabled'):
subj.create()
subj.action(amodule.params['state'],amodule.params['started'])
if amodule.params['state'] == 'absent':
subj.action(amodule.params['state'])
if amodule.params['state'] == 'absent':
subj.nop()
else:
if amodule.params['state'] == 'absent':
@@ -272,9 +321,10 @@ def main():
amodule.fail_json(**subj.result)
else:
if subj.bservice_should_exist:
_, subj.bservice_info = subj.bservice_get_by_id(subj.bservice_id)
subj.result['facts'] = subj.package_facts(amodule.check_mode)
amodule.exit_json(**subj.result)
else:
amodule.exit_json(**subj.result)
if __name__ == "__main__":
main()
main()

View File

@@ -52,7 +52,7 @@ class decort_disk(DecortController):
self.amodule.fail_json(**self.result)
self.acc_id = validated_acc_id
self.acc_info = validated_acc_info
self._acc_info = validated_acc_info
validated_disk_id, validated_disk_facts = self.disk_find(
disk_id=arg_amodule.params['id'],
name=arg_amodule.params['name'] if "name" in arg_amodule.params else "",
@@ -192,7 +192,6 @@ class decort_disk(DecortController):
),
description=dict(
type='str',
default='Disk by decort_disk',
),
id=dict(
type='int',
@@ -348,4 +347,4 @@ def main():
if __name__ == "__main__":
main()
#SHARE
#SHARE

View File

@@ -389,4 +389,4 @@ def main():
amodule.exit_json(**subj.result)
if __name__ == "__main__":
main()
main()

View File

@@ -4,7 +4,7 @@ DOCUMENTATION = r'''
---
module: decort_jwt
description: See L(Module Documentation,https://repository.basistech.ru/BASIS/decort-ansible/wiki/Home).
description: See L(Module Documentation,https://repository.basistech.ru/BASIS/decort-ansible/wiki/Home). # noqa: E501
'''
from ansible.module_utils.basic import AnsibleModule

View File

@@ -23,6 +23,7 @@ class decort_k8s(DecortController):
validated_rg_facts = None
validated_k8ci_id = 0
self.k8s_should_exist = False
self.is_k8s_stopped_or_will_be_stopped: None | bool = None
self.wg_default_params = {
'num': 1,
@@ -77,62 +78,12 @@ class decort_k8s(DecortController):
rg_id=validated_rg_id,
check_state=False)
if self.k8s_id:
if self.k8s_id and self.k8s_info['status'] != 'DESTROYED':
self.k8s_should_exist = True
self.acc_id = self.k8s_info['accountId']
# check workers and groups for add or remove?
aparam_sysctl = arg_amodule.params['lb_sysctl']
if aparam_sysctl is not None:
_, lb_info = self._lb_get_by_id(lb_id=self.k8s_info['lbId'])
sysctl_with_str_values = {
k: str(v) for k, v in aparam_sysctl.items()
}
if sysctl_with_str_values != lb_info['sysctlParams']:
self.message(
'Check for parameter "lb_sysctl" failed: '
'cannot change lb_sysctl for an existing cluster '
'load balancer.'
)
self.exit(fail=True)
if not self.k8s_id:
validated_k8ci_id = self.k8s_k8ci_find(arg_amodule.params['k8ci_id'])
if not validated_k8ci_id:
self.result['failed'] = True
self.result['changed'] = False
self.result['msg'] = "Cannot find K8CI ID {}.".format(arg_amodule.params['k8ci_id'])
self.amodule.fail_json(**self.result)
if not arg_amodule.params['workers']:
self.result['failed'] = True
self.result['changed'] = False
self.result['msg'] = "At least one worker group must be present"
self.amodule.fail_json(**self.result)
if (
arg_amodule.params['lb_sysctl'] is not None
and not arg_amodule.params['with_lb']
):
self.message(
'Check for parameter "lb_sysctl" failed: '
'"lb_sysctl" can only be set if the parameter "with_lb" '
'is set to True.'
)
self.exit(fail=True)
if (
self.aparams['master_count'] is not None
and self.aparams['master_count'] > 1
and not self.aparams['with_lb']
):
self.message(
'Check for parameter "master_count" failed: '
'master_count can be more than 1 only if the parameter '
'"with_lb" is set to True.'
)
self.exit(fail=True)
self.check_amodule_args_for_change()
else:
self.check_amodule_args_for_create()
return
def package_facts(self,check_mode=False):
@@ -168,6 +119,7 @@ class decort_k8s(DecortController):
ret_dict['k8s_Workers'] = self.k8s_info['k8sGroups']['workers']
ret_dict['lb_id'] = self.k8s_info['lbId']
ret_dict['description'] = self.k8s_info['desc']
ret_dict['zone_id'] = self.k8s_info['zoneId']
return ret_dict
@@ -240,6 +192,7 @@ class decort_k8s(DecortController):
self.amodule.params['extnet_only'],
master_chipset,
lb_sysctl=self.amodule.params['lb_sysctl'],
zone_id=self.aparams['zone_id'],
)
if not k8s_id:
@@ -270,7 +223,7 @@ class decort_k8s(DecortController):
self.k8s_should_exist = False
return
def action(self, disared_state, started=True, preupdate: bool = False):
def action(self, disared_state, preupdate: bool = False):
if self.amodule.params['master_chipset'] is not None:
for master_node in self.k8s_info['k8sGroups']['masters'][
'detailedInfo'
@@ -292,14 +245,19 @@ class decort_k8s(DecortController):
# K8s info updating
self.k8s_info = self.k8s_get_by_id(k8s_id=self.k8s_id)
#k8s state
self.k8s_state(self.k8s_info, disared_state, started)
self.k8s_state(self.k8s_info, disared_state)
self.k8s_info = self.k8s_get_by_id(k8s_id=self.k8s_id)
if started == True and self.k8s_info['techStatus'] == "STOPPED":
self.k8s_state(self.k8s_info, disared_state,started)
self.k8s_info['techStatus'] == "STARTED"
#check groups and modify if needed
if self.aparams['workers'] is not None:
self.k8s_workers_modify(self.k8s_info, self.amodule.params['workers'])
aparam_zone_id = self.aparams['zone_id']
if aparam_zone_id is not None and aparam_zone_id != self.k8s_info['zoneId']:
self.k8s_migrate_to_zone(
k8s_id=self.k8s_id,
zone_id=aparam_zone_id,
)
if self.result['changed'] == True:
self.k8s_info = self.k8s_get_by_id(k8s_id=self.k8s_id)
#TODO check workers metadata and modify if needed
@@ -328,17 +286,14 @@ class decort_k8s(DecortController):
'disabled',
'enabled',
'present',
'check',
'started',
'stopped',
],
),
permanent=dict(
type='bool',
default=False,
),
started=dict(
type='bool',
default=True,
),
name=dict(
type='str',
default='',
@@ -490,18 +445,110 @@ class decort_k8s(DecortController):
lb_sysctl=dict(
type='dict',
),
zone_id=dict(
type='int',
),
),
supports_check_mode=True,
required_one_of=[
('id', 'name'),
],
)
def check_amodule_args_for_change(self):
check_errors = False
self.is_k8s_stopped_or_will_be_stopped = (
(
self.k8s_info['techStatus'] == 'STOPPED'
and (
self.aparams['state'] is None
or self.aparams['state'] in ('present', 'stopped')
)
)
or (
self.k8s_info['techStatus'] != 'STOPPED'
and self.aparams['state'] == 'stopped'
)
)
aparam_sysctl = self.aparams['lb_sysctl']
if aparam_sysctl is not None:
_, lb_info = self._lb_get_by_id(lb_id=self.k8s_info['lbId'])
sysctl_with_str_values = {
k: str(v) for k, v in aparam_sysctl.items()
}
if sysctl_with_str_values != lb_info['sysctlParams']:
self.message(
'Check for parameter "lb_sysctl" failed: '
'cannot change lb_sysctl for an existing cluster '
'load balancer.'
)
check_errors = True
if self.check_aparam_zone_id() is False:
check_errors = True
if (
self.aparams['zone_id'] is not None
and self.aparams['zone_id'] != self.k8s_info['zoneId']
and not self.is_k8s_stopped_or_will_be_stopped
):
check_errors = True
self.message(
'Check for parameter "zone_id" failed: '
'K8s cluster must be stopped to migrate to a zone.'
)
if check_errors:
self.exit(fail=True)
def check_amodule_args_for_create(self):
check_errors = False
validated_k8ci_id = self.k8s_k8ci_find(self.aparams['k8ci_id'])
if not validated_k8ci_id:
self.message(f'Cannot find K8CI ID {"k8ci_id"}.')
check_errors = True
if not self.aparams['workers']:
self.message('At least one worker group must be present.')
check_errors = True
if (
self.aparams['lb_sysctl'] is not None
and not self.aparams['with_lb']
):
self.message(
'Check for parameter "lb_sysctl" failed: '
'"lb_sysctl" can only be set if the parameter "with_lb" '
'is set to True.'
)
check_errors = True
if (
self.aparams['master_count'] is not None
and self.aparams['master_count'] > 1
and not self.aparams['with_lb']
):
self.message(
'Check for parameter "master_count" failed: '
'master_count can be more than 1 only if the parameter '
'"with_lb" is set to True.'
)
check_errors = True
if self.check_aparam_zone_id() is False:
check_errors = True
if check_errors:
self.exit(fail=True)
def main():
subj = decort_k8s()
amodule = subj.amodule
if amodule.params['state'] == 'check':
if subj.amodule.check_mode:
subj.result['changed'] = False
if subj.k8s_id:
# cluster is found - package facts and report success to Ansible
@@ -521,7 +568,9 @@ def main():
"ENABLING","DISABLING","RESTORING","MODELED"):
subj.error()
elif subj.k8s_info['status'] == "DELETED":
if amodule.params['state'] in ('disabled', 'enabled', 'present'):
if amodule.params['state'] in (
'disabled', 'enabled', 'present', 'started', 'stopped'
):
subj.k8s_restore(subj.k8s_id)
subj.action(disared_state=amodule.params['state'],
preupdate=True)
@@ -530,24 +579,15 @@ def main():
subj.destroy()
else:
subj.nop()
elif subj.k8s_info['techStatus'] in ("STARTED","STOPPED"):
if amodule.params['state'] == 'disabled':
subj.action(amodule.params['state'])
elif amodule.params['state'] == 'absent':
subj.destroy()
else:
subj.action(amodule.params['state'],amodule.params['started'])
elif subj.k8s_info['status'] == "DISABLED":
elif subj.k8s_info['status'] in ('ENABLED', 'DISABLED'):
if amodule.params['state'] == 'absent':
subj.destroy()
elif amodule.params['state'] in ('present','enabled'):
subj.action(amodule.params['state'],amodule.params['started'])
else:
subj.nop()
elif subj.k8s_info['status'] == "DESTROED":
subj.action(disared_state=amodule.params['state'])
elif subj.k8s_info['status'] == "DESTROYED":
if amodule.params['state'] in ('present','enabled'):
subj.create()
if amodule.params['state'] == 'absent':
if amodule.params['state'] == 'absent':
subj.nop()
else:
if amodule.params['state'] == 'absent':

View File

@@ -18,6 +18,9 @@ DefaultT = TypeVar('DefaultT')
class decort_kvmvm(DecortController):
is_vm_stopped_or_will_be_stopped: None | bool = None
guest_agent_exec_result: None | str = None
def __init__(self):
# call superclass constructor first
super(decort_kvmvm, self).__init__(AnsibleModule(**self.amodule_init_args))
@@ -31,20 +34,22 @@ class decort_kvmvm(DecortController):
# This following flag is used to avoid extra (and unnecessary) get of compute details prior to
# packaging facts before the module completes. As ""
self.skip_final_get = False
self.force_final_get = False
self.comp_id = 0
self.comp_info = None
self.acc_id = 0
self.rg_id = 0
self.aparam_image = None
validated_acc_id =0
validated_acc_id = 0
validated_rg_id = 0
validated_rg_facts = None
self.vm_to_clone_id = 0
self.vm_to_clone_info = None
if self.aparams['get_snapshot_merge_status']:
self.force_final_get = True
if arg_amodule.params['clone_from'] is not None:
self.vm_to_clone_id, self.vm_to_clone_info, _ = (
self._compute_get_by_id(
@@ -101,7 +106,7 @@ class decort_kvmvm(DecortController):
if not comp_id: # manage Compute by name -> need RG identity
if not arg_amodule.params['rg_id']: # RG ID is not set -> locate RG by name -> need account ID
validated_acc_id, _ = self.account_find(arg_amodule.params['account_name'],
validated_acc_id, self._acc_info = self.account_find(arg_amodule.params['account_name'],
arg_amodule.params['account_id'])
if not validated_acc_id:
self.result['failed'] = True
@@ -142,6 +147,7 @@ class decort_kvmvm(DecortController):
if self.comp_id:
self.comp_should_exist = True
self.acc_id = self.comp_info['accountId']
self.rg_id = self.comp_info['rgId']
self.check_amodule_args_for_change()
else:
if self.amodule.params['state'] != 'absent':
@@ -186,18 +192,82 @@ class decort_kvmvm(DecortController):
'to a DPDK network.'
)
for net in aparam_nets:
# MTU for non-DPDK networks
net_type = net['type']
if (
net['type'] != self.VMNetType.DPDK.value
and net['mtu'] is not None
net['type'] not in (
self.VMNetType.SDN.value,
self.VMNetType.EMPTY.value,
)
and not isinstance(net['id'], int)
):
check_error = True
self.message(
'Check for parameter "networks" failed:'
' MTU can be specifed only for DPDK network'
' (remove parameter "mtu" for network'
f' {net["type"]} with ID {net["id"]}).'
'Check for parameter "networks" failed: '
'Type of parameter "id" must be integer for '
f'{net["type"]} network type'
)
# MTU
net_mtu = net['mtu']
if net_mtu is not None:
mtu_net_types = (
self.VMNetType.DPDK.value,
self.VMNetType.EXTNET.value,
)
# Allowed network types for set MTU
if net_type not in mtu_net_types:
check_error = True
self.message(
'Check for parameter "networks" failed:'
' MTU can be specifed'
' only for DPDK or EXTNET network'
' (remove parameter "mtu" for network'
f' {net["type"]} with ID {net["id"]}).'
)
# Maximum MTU
MAX_MTU = 9216
if net_type in mtu_net_types and net_mtu > MAX_MTU:
check_error = True
self.message(
'Check for parameter "networks" failed:'
f' MTU must be no more than {MAX_MTU}'
' (change value for parameter "mtu" for network'
f' {net["type"]} with ID {net["id"]}).'
)
# EXTNET minimum MTU
EXTNET_MIN_MTU = 1500
if (
net_type == self.VMNetType.EXTNET.value
and net_mtu < EXTNET_MIN_MTU
):
check_error = True
self.message(
'Check for parameter "networks" failed:'
f' MTU for {self.VMNetType.EXTNET.value} network'
f' must be at least {EXTNET_MIN_MTU}'
' (change value for parameter "mtu" for network'
f' {net["type"]} with ID {net["id"]}).'
)
# DPDK minimum MTU
DPDK_MIN_MTU = 1
if (
net_type == self.VMNetType.DPDK.value
and net_mtu < DPDK_MIN_MTU
):
check_error = True
self.message(
'Check for parameter "networks" failed:'
f' MTU for {self.VMNetType.DPDK.value} network'
f' must be at least {DPDK_MIN_MTU}'
' (change value for parameter "mtu" for network'
f' {net["type"]} with ID {net["id"]}).'
)
# MAC address
if net['mac'] is not None:
if net['type'] == self.VMNetType.EMPTY.value:
@@ -220,7 +290,20 @@ class decort_kvmvm(DecortController):
'specified in quotes and in the format '
'"XX:XX:XX:XX:XX:XX".'
)
if self.VMNetType.SDN.value in net_types:
if not net_types.issubset(
{
self.VMNetType.SDN.value,
self.VMNetType.EMPTY.value,
self.VMNetType.VFNIC.value,
}
):
check_error = True
self.message(
'Check for parameter "networks" failed: '
'a compute can be connected to a SDN network and '
'only to VFNIC, EMPTY networks at the same time.'
)
aparam_custom_fields = self.aparams['custom_fields']
if aparam_custom_fields is not None:
if (
@@ -458,7 +541,8 @@ class decort_kvmvm(DecortController):
boot_mode=boot_mode,
boot_loader_type=loader_type,
network_interface_naming=network_interface_naming,
hot_resize=hot_resize,)
hot_resize=hot_resize,
zone_id=self.aparams['zone_id'],)
self.comp_should_exist = True
# Originally we would have had to re-read comp_info after VM was provisioned
@@ -636,6 +720,40 @@ class decort_kvmvm(DecortController):
custom_fields=aparam_custom_fields['fields'],
)
aparam_zone_id = self.aparams['zone_id']
if aparam_zone_id is not None and aparam_zone_id != self.comp_info['zoneId']:
self.compute_migrate_to_zone(
compute_id=self.comp_id,
zone_id=aparam_zone_id,
)
aparam_guest_agent = self.aparams['guest_agent']
if aparam_guest_agent is not None:
if aparam_guest_agent['enabled'] is not None:
if (
aparam_guest_agent['enabled']
and not self.comp_info['qemu_guest']['enabled']
):
self.compute_guest_agent_enable(vm_id=self.comp_id)
elif (
aparam_guest_agent['enabled'] is False
and self.comp_info['qemu_guest']['enabled']
):
self.compute_guest_agent_disable(vm_id=self.comp_id)
if aparam_guest_agent['update_available_commands']:
self.compute_guest_agent_feature_update(vm_id=self.comp_id)
aparam_guest_agent_exec = aparam_guest_agent['exec']
if aparam_guest_agent_exec is not None:
self.guest_agent_exec_result = (
self.compute_guest_agent_execute(
vm_id=self.comp_id,
cmd=aparam_guest_agent_exec['cmd'],
args=aparam_guest_agent_exec['args'],
)
)
return
@property
@@ -779,7 +897,7 @@ class decort_kvmvm(DecortController):
# If it does - save public IP address of GW VNF in ret_dict['nat_ip']
elif iface['connType'] == "VLAN": # This is direct external network connection
ret_dict['public_ips'].append(iface['ipAddress'])
ret_dict['cpu'] = self.comp_info['cpus']
ret_dict['ram'] = self.comp_info['ram']
@@ -830,6 +948,18 @@ class decort_kvmvm(DecortController):
ret_dict['affinity_rules'] = self.comp_info['affinityRules']
ret_dict['anti_affinity_rules'] = self.comp_info['antiAffinityRules']
ret_dict['zone_id'] = self.comp_info['zoneId']
ret_dict['guest_agent'] = self.comp_info['qemu_guest']
if self.guest_agent_exec_result:
ret_dict['guest_agent']['exec_result'] = self.guest_agent_exec_result # noqa: E501
if self.amodule.params['get_snapshot_merge_status']:
ret_dict['snapshot_merge_status'] = (
self.comp_info['snapshot_merge_status']
)
return ret_dict
def check_amodule_args_for_create(self):
@@ -918,6 +1048,30 @@ class decort_kvmvm(DecortController):
' to a DPDK network.'
)
if self.check_aparam_zone_id() is False:
check_errors = True
if self.aparams['guest_agent'] is not None:
check_errors = True
self.message(
'Check for parameter "guest_agent" failed: '
'guest_agent can be specified only for existing VM.'
)
if self.aparams['get_snapshot_merge_status']:
check_errors = True
self.message(
'Check for parameter "get_snapshot_merge_status" failed: '
'snapshot merge status can be retrieved only for existing VM.'
)
aparam_networks = self.aparams['networks']
if aparam_networks is not None:
net_types = {net['type'] for net in aparam_networks}
if self.VMNetType.TRUNK.value in net_types:
if self.check_aparam_networks_trunk() is False:
check_errors = True
if check_errors:
self.exit(fail=True)
@@ -1019,11 +1173,13 @@ class decort_kvmvm(DecortController):
'EXTNET',
'VFNIC',
'DPDK',
'TRUNK',
'SDN',
'EMPTY',
],
),
id=dict(
type='int',
type='raw',
),
ip_addr=dict(
type='str',
@@ -1040,6 +1196,8 @@ class decort_kvmvm(DecortController):
('type', 'EXTNET', ('id',)),
('type', 'VFNIC', ('id',)),
('type', 'DPDK', ('id',)),
('type', 'TRUNK', ('id',)),
('type', 'SDN', ('id', 'mac')),
],
),
network_order_changing=dict(
@@ -1180,6 +1338,36 @@ class decort_kvmvm(DecortController):
hot_resize=dict(
type='bool',
),
zone_id=dict(
type='int',
),
guest_agent=dict(
type='dict',
options=dict(
enabled=dict(
type='bool',
),
exec=dict(
type='dict',
options=dict(
cmd=dict(
type='str',
required=True,
),
args=dict(
type='dict',
default={},
),
),
),
update_available_commands=dict(
type='bool',
),
),
),
get_snapshot_merge_status=dict(
type='bool',
),
),
supports_check_mode=True,
required_one_of=[
@@ -1196,6 +1384,24 @@ class decort_kvmvm(DecortController):
comp_info = self.vm_to_clone_info or self.comp_info
comp_id = comp_info['id']
self.is_vm_stopped_or_will_be_stopped = (
(
comp_info['techStatus'] == 'STOPPED'
and (
self.amodule.params['state'] is None
or self.amodule.params['state'] in (
'halted', 'poweredoff', 'present', 'stopped',
)
)
)
or (
comp_info['techStatus'] != 'STOPPED'
and self.amodule.params['state'] in (
'halted', 'poweredoff', 'stopped',
)
)
)
aparam_boot = self.amodule.params['boot']
if aparam_boot is not None:
new_boot_disk_size = aparam_boot['disk_size']
@@ -1285,26 +1491,8 @@ class decort_kvmvm(DecortController):
'state for a blank Compute can not be "started" or "paused".'
)
is_vm_stopped_or_will_be_stopped = (
(
comp_info['techStatus'] == 'STOPPED'
and (
self.amodule.params['state'] is None
or self.amodule.params['state'] in (
'halted', 'poweredoff', 'present', 'stopped',
)
)
)
or (
comp_info['techStatus'] != 'STOPPED'
and self.amodule.params['state'] in (
'halted', 'poweredoff', 'stopped',
)
)
)
if self.amodule.params['rollback_to'] is not None:
if not is_vm_stopped_or_will_be_stopped:
if not self.is_vm_stopped_or_will_be_stopped:
check_errors = True
self.message(
'Check for parameter "rollback_to" failed: '
@@ -1334,7 +1522,7 @@ class decort_kvmvm(DecortController):
if (
self.aparams[param_name] is not None
and comp_info[comp_field_name] != self.aparams[param_name]
and not is_vm_stopped_or_will_be_stopped
and not self.is_vm_stopped_or_will_be_stopped
):
check_errors = True
self.message(
@@ -1343,7 +1531,7 @@ class decort_kvmvm(DecortController):
)
if self.aparams['preferred_cpu_cores'] is not None:
if not is_vm_stopped_or_will_be_stopped:
if not self.is_vm_stopped_or_will_be_stopped:
check_errors = True
self.message(
'Check for parameter "preferred_cpu_cores" failed: '
@@ -1407,7 +1595,7 @@ class decort_kvmvm(DecortController):
if (
comp_boot_disk_id is not None
and comp_boot_disk_id in disks_to_detach
and not is_vm_stopped_or_will_be_stopped
and not self.is_vm_stopped_or_will_be_stopped
):
check_errors = True
self.message(
@@ -1438,6 +1626,22 @@ class decort_kvmvm(DecortController):
'Hot resize must be enabled to change CPU or RAM.'
)
if self.check_aparam_zone_id() is False:
check_errors = True
if self.check_aparam_guest_agent() is False:
check_errors = True
if self.check_aparam_get_snapshot_merge_status() is False:
check_errors = True
aparam_networks = self.aparams['networks']
if aparam_networks is not None:
net_types = {net['type'] for net in aparam_networks}
if self.VMNetType.TRUNK.value in net_types:
if self.check_aparam_networks_trunk() is False:
check_errors = True
if check_errors:
self.exit(fail=True)
@@ -1530,6 +1734,211 @@ class decort_kvmvm(DecortController):
)
return clone_id
def check_aparam_guest_agent(self) -> bool:
check_errors = False
aparam_guest_agent = self.aparams['guest_agent']
if aparam_guest_agent:
if self.is_vm_stopped_or_will_be_stopped:
if aparam_guest_agent['update_available_commands']:
check_errors = True
self.message(
'Check for parameter '
'"guest_agent.update_available_commands" failed: '
f'VM ID {self.comp_id} must be started to update '
'available commands.'
)
is_guest_agent_enabled_or_will_be_enabled = (
(
self.comp_info['qemu_guest']['enabled']
and aparam_guest_agent['enabled'] is not False
)
or (
self.comp_info['qemu_guest']['enabled'] is False
and aparam_guest_agent['enabled']
)
)
aparam_guest_agent_exec = aparam_guest_agent['exec']
if aparam_guest_agent_exec is not None:
if self.is_vm_stopped_or_will_be_stopped:
check_errors = True
self.message(
'Check for parameter "guest_agent.exec" failed: '
f'VM ID {self.comp_id} must be started '
'to execute commands.'
)
if not is_guest_agent_enabled_or_will_be_enabled:
check_errors = True
self.message(
'Check for parameter "guest_agent.exec" failed: '
f'Guest agent for VM ID {self.comp_id} must be enabled'
' to execute commands.'
)
aparam_exec_cmd = aparam_guest_agent_exec['cmd']
available_commands = (
self.comp_info['qemu_guest']['enabled_agent_features']
)
if aparam_exec_cmd not in available_commands:
check_errors = True
self.message(
'Check for parameter "guest_agent.exec.cmd" failed: '
f'Command "{aparam_exec_cmd}" is not '
f'available for VM ID {self.comp_id}.'
)
return not check_errors
def check_aparam_get_snapshot_merge_status(self) -> bool | None:
check_errors = False
if self.aparams['get_snapshot_merge_status']:
vm_has_shared_sep_disk = False
vm_disk_ids = [disk['id'] for disk in self.comp_info['disks']]
for disk_id in vm_disk_ids:
_, disk_info = self._disk_get_by_id(disk_id=disk_id)
if disk_info['sepType'] == 'SHARED':
vm_has_shared_sep_disk = True
break
if not vm_has_shared_sep_disk:
check_errors = True
self.message(
'Check for parameter "get_snapshot_merge_status" failed: '
f'VM ID {self.comp_id} must have at least one disk with '
'SEP type SHARED to retrieve snapshot merge status.'
)
return not check_errors
def find_networks_tags_intersections(
self,
trunk_networks: list,
extnet_networks: list,
) -> bool:
has_intersections = False
def parse_trunk_tags(trunk_tags_string: str):
trunk_tags = set()
for part in trunk_tags_string.split(','):
if '-' in part:
start, end = part.split('-')
trunk_tags.update(range(int(start), int(end) + 1))
else:
trunk_tags.add(int(part))
return trunk_tags
trunk_tags_dicts = []
for trunk_network in trunk_networks:
trunk_tags_dicts.append({
'id': trunk_network['id'],
'tags_str': trunk_network['trunkTags'],
'tags': parse_trunk_tags(
trunk_tags_string=trunk_network['trunkTags']
),
'native_vlan_id': trunk_network['nativeVlanId'],
})
# find for trunk tags intersections with other networks
for i in range(len(trunk_tags_dicts)):
for j in range(i + 1, len(trunk_tags_dicts)):
intersection = (
trunk_tags_dicts[i]['tags']
& trunk_tags_dicts[j]['tags']
)
if intersection:
has_intersections = True
self.message(
'Check for parameter "networks" failed: '
f'Trunk tags {trunk_tags_dicts[i]["tags_str"]} '
f'of trunk ID {trunk_tags_dicts[i]["id"]} '
f'overlaps with trunk tags '
f'{trunk_tags_dicts[j]["tags_str"]} of trunk ID '
f'{trunk_tags_dicts[j]["id"]}'
)
for extnet in extnet_networks:
if extnet['vlanId'] in trunk_tags_dicts[i]['tags']:
has_intersections = True
self.message(
'Check for parameter "networks" failed: '
f'Trunk tags {trunk_tags_dicts[i]["tags_str"]} '
f'of trunk ID {trunk_tags_dicts[i]["id"]} '
f'overlaps with tag {extnet["vlanId"]} of extnet ID '
f'{extnet["id"]}'
)
if extnet['vlanId'] == trunk_tags_dicts[i]['native_vlan_id']:
has_intersections = True
self.message(
'Check for parameter "networks" failed: '
f'Trunk native vlan ID '
f'{trunk_tags_dicts[i]["native_vlan_id"]} of trunk ID '
f'{trunk_tags_dicts[i]["id"]} '
f'overlaps with vlan ID {extnet["vlanId"]} of extnet '
f'ID {extnet["id"]}'
)
return has_intersections
def check_aparam_networks_trunk(self) -> bool | None:
check_errors = False
# check if account has vm feature “trunk”
if not self.check_account_vm_features(vm_feature=self.VMFeature.trunk):
check_errors = True
self.message(
'Check for parameter "networks" failed: '
f'Account ID {self.acc_id} must have feature "trunk" to use '
'trunk type networks '
)
# check if rg has vm feature “trunk”
if not self.check_rg_vm_features(vm_feature=self.VMFeature.trunk):
check_errors = True
self.message(
'Check for parameter "networks" failed: '
f'RG ID {self.rg_id} must have feature "trunk" to use '
'trunk type networks '
)
aparam_trunk_networks = []
aparam_extnet_networks = []
for net in self.aparams['networks']:
if net['type'] == self.VMNetType.TRUNK.value:
aparam_trunk_networks.append(net)
elif net['type'] == self.VMNetType.EXTNET.value:
aparam_extnet_networks.append(net)
trunk_networks_info = []
# check that account has access to all specified trunks
for trunk_network in aparam_trunk_networks:
trunk_info = self.trunk_get(id=trunk_network['id'])
trunk_networks_info.append(trunk_info)
if (
trunk_info['accountIds'] is None
or self.acc_id not in trunk_info['accountIds']
):
check_errors = True
self.message(
'Check for parameter "networks" failed: '
f'Account ID {self.acc_id} does not have access to '
f'trunk ID {trunk_info['id']}'
)
extnet_networks_info = []
for extnet_network in aparam_extnet_networks:
extnet_networks_info.append(
self.extnet_get(id=extnet_network['id'])
)
# check that trunk tags do not overlap with each other
# and with extnets vlan id
if self.find_networks_tags_intersections(
trunk_networks=trunk_networks_info,
extnet_networks=extnet_networks_info,
):
check_errors = True
return not check_errors
# Workflow digest:
# 1) authenticate to DECORT controller & validate authentication by issuing API call - done when creating DECSController
# 2) check if the VM with the specified id or rg_name:name exists
@@ -1617,12 +2026,16 @@ def main():
# prepare Compute facts to be returned as part of decon.result and then call exit_json(...)
rg_facts = None
if subj.comp_should_exist:
if subj.result['changed'] and not subj.skip_final_get:
if (
(subj.result['changed'] and not subj.skip_final_get)
or subj.force_final_get
):
# There were changes to the Compute - refresh Compute facts.
_, subj.comp_info, _ = subj.compute_find(
comp_id=subj.comp_id,
need_custom_fields=True,
need_console_url=amodule.params['get_console_url'],
need_snapshot_merge_status=amodule.params['get_snapshot_merge_status'], # noqa: E501
)
#
# We no longer need to re-read RG facts, as all network info is now available inside

View File

@@ -23,8 +23,6 @@ class decort_lb(DecortController):
self.vins_facts = None
self.rg_id = 0
self.rg_facts = None
self.acc_id = 0
self.acc_facts = None
self.default_server_check = "enabled"
self.default_alg = "roundrobin"
self.default_settings = {
@@ -37,6 +35,8 @@ class decort_lb(DecortController):
"slowstart": 60000,
"weight": 100,
}
self.is_lb_stopped_or_will_be_stopped: None | bool = None
if arg_amodule.params['lb_id']:
self.lb_id, self.lb_facts = self.lb_find(arg_amodule.params['lb_id'])
if not self.lb_id:
@@ -46,14 +46,14 @@ class decort_lb(DecortController):
self.fail_json(**self.result)
self.rg_id = self.lb_facts['rgId']
self.vins_id = self.lb_facts['vinsId']
return
if arg_amodule.params['rg_id']:
elif arg_amodule.params['rg_id']:
self.rg_id, self.rg_facts = self.rg_find(0,arg_amodule.params['rg_id'], arg_rg_name="")
if not self.rg_id:
self.result['failed'] = True
self.result['msg'] = "Specified RG ID {} not found.".format(arg_amodule.params['vins_id'])
self.amodule.fail_json(**self.result)
self.acc_id = self.rg_facts['accountId']
elif arg_amodule.params['account_id'] or arg_amodule.params['account_name'] != "":
@@ -61,7 +61,7 @@ class decort_lb(DecortController):
self.result['failed'] = True
self.result['msg'] = ("RG name must be specified with account present")
self.amodule.fail_json(**self.result)
self.acc_id, self.acc_facts = self.account_find(arg_amodule.params['account_name'],
self.acc_id, self._acc_info = self.account_find(arg_amodule.params['account_name'],
arg_amodule.params['account_id'])
if not self.acc_id:
self.result['failed'] = True
@@ -96,15 +96,26 @@ class decort_lb(DecortController):
if self.rg_id and arg_amodule.params['lb_name']:
self.lb_id, self.lb_facts = self.lb_find(0,arg_amodule.params['lb_name'],self.rg_id)
if self.lb_id and self.lb_facts['status'] != 'DESTROYED':
self.acc_id = self.lb_facts['accountId']
self.check_amodule_args_for_change()
else:
self.check_amodule_args_for_create()
return
def create(self):
start_after_create = self.aparams['state'] != 'stopped'
self.lb_id = self.lb_provision(self.amodule.params['lb_name'],
self.rg_id,self.vins_id,
self.amodule.params['ext_net_id'],
self.amodule.params['ha_lb'],
self.amodule.params['description'],
sysctl=self.amodule.params['sysctl'],)
sysctl=self.amodule.params['sysctl'],
zone_id=self.aparams['zone_id'],
start=start_after_create,
)
if self.lb_id and (self.amodule.params['backends'] or
self.amodule.params['frontends']):
self.lb_id, self.lb_facts = self.lb_find(0,self.amodule.params['lb_name'],self.rg_id)
@@ -141,6 +152,13 @@ class decort_lb(DecortController):
self.lb_state(self.lb_facts, 'started')
_, self.lb_facts = self._lb_get_by_id(lb_id=self.lb_id)
aparam_zone_id = self.aparams['zone_id']
if aparam_zone_id is not None and aparam_zone_id != self.lb_facts['zoneId']:
self.lb_migrate_to_zone(
lb_id=self.lb_id,
zone_id=aparam_zone_id,
)
return
def delete(self):
@@ -205,13 +223,15 @@ class decort_lb(DecortController):
ret_dict['id'] = self.lb_facts['id']
ret_dict['name'] = self.lb_facts['name']
ret_dict['state'] = self.lb_facts['status']
#ret_dict['account_id'] = self.lb_facts['accountId']
ret_dict['account_id'] = self.lb_facts['accountId']
ret_dict['rg_id'] = self.lb_facts['rgId']
ret_dict['gid'] = self.lb_facts['gid']
if self.amodule.params['state']!="absent":
ret_dict['backends'] = self.lb_facts['backends']
ret_dict['frontends'] = self.lb_facts['frontends']
ret_dict['sysctl'] = self.lb_facts['sysctlParams']
ret_dict['zone_id'] = self.lb_facts['zoneId']
ret_dict['tech_status'] = self.lb_facts['techStatus']
return ret_dict
@property
@@ -227,11 +247,10 @@ class decort_lb(DecortController):
),
description=dict(
type='str',
default='Managed by Ansible module decort_lb',
),
ext_net_id=dict(
type='int',
default=-1,
default=0,
),
ext_ip_addr=dict(
type='str',
@@ -239,13 +258,14 @@ class decort_lb(DecortController):
),
state=dict(
type='str',
default='present',
choices=[
'absent',
'disabled',
'enabled',
'present',
'restart',
'started',
'stopped',
],
),
rg_id=dict(
@@ -291,6 +311,9 @@ class decort_lb(DecortController):
sysctl=dict(
type='dict',
),
zone_id=dict(
type=int,
),
),
supports_check_mode=True,
required_one_of=[
@@ -300,6 +323,49 @@ class decort_lb(DecortController):
],
)
def check_amodule_args_for_change(self):
check_errors = False
lb_info: dict = self.lb_facts
self.is_lb_stopped_or_will_be_stopped = (
(
lb_info['techStatus'] == 'STOPPED'
and (
self.aparams['state'] is None
or self.aparams['state'] in ('present', 'stopped')
)
)
or (
lb_info['techStatus'] != 'STOPPED'
and self.aparams['state'] == 'stopped'
)
)
if self.check_aparam_zone_id() is False:
check_errors = True
if (
self.aparams['zone_id'] is not None
and self.aparams['zone_id'] != lb_info['zoneId']
and not self.is_lb_stopped_or_will_be_stopped
):
check_errors = True
self.message(
'Check for parameter "zone_id" failed: '
'Load balancer must be stopped to migrate to a zone.'
)
if check_errors:
self.exit(fail=True)
def check_amodule_args_for_create(self):
check_errors = False
if self.check_aparam_zone_id() is False:
check_errors = True
if check_errors:
self.exit(fail=True)
def main():
decon = decort_lb()
amodule = decon.amodule
@@ -309,24 +375,15 @@ def main():
decon.result['changed'] = False
decon.result['msg'] = ("No change can be done for existing LB ID {} because of its current "
"status '{}'").format(decon.lb_id, decon.lb_facts['status'])
elif decon.lb_facts['status'] == "DISABLED":
if amodule.params['state'] == 'absent':
decon.delete()
elif amodule.params['state'] == 'disabled':
decon.action()
elif amodule.params['state'] in ('enabled', 'present'):
decon.action('enabled')
elif decon.lb_facts['status'] in ["CREATED", "ENABLED"]:
if amodule.params['state'] == 'absent':
decon.delete()
elif amodule.params['state'] in ('present', 'enabled'):
decon.action(d_state='enabled')
elif amodule.params['state'] == 'disabled':
decon.action('disabled')
elif amodule.params['state'] in ('stopped', 'started','restart'):
decon.action(amodule.params['state'])
elif decon.lb_facts['status'] in ('DISABLED', 'ENABLED', 'CREATED'):
if amodule.params['state'] == 'absent':
decon.delete()
else:
decon.action(d_state=amodule.params['state'])
elif decon.lb_facts['status'] == "DELETED":
if amodule.params['state'] in ['present', 'enabled']:
if amodule.params['state'] == 'present':
decon.action(restore=True)
elif amodule.params['state'] == 'enabled':
decon.action(d_state='enabled', restore=True)
elif (amodule.params['state'] == 'absent' and
amodule.params['permanently']):
@@ -341,11 +398,14 @@ def main():
elif amodule.params['state'] == 'disabled':
decon.error()
else:
if amodule.params['state'] == 'absent':
state = amodule.params['state']
if state is None:
state = 'present'
if state == 'absent':
decon.nop()
elif amodule.params['state'] in ('present', 'enabled'):
elif state in ('present', 'enabled', 'stopped', 'started'):
decon.create()
elif amodule.params['state'] == 'disabled':
elif state == 'disabled':
decon.error()
if decon.result['failed']:
@@ -357,4 +417,4 @@ def main():
decon.result['facts'] = decon.package_facts(amodule.check_mode)
amodule.exit_json(**decon.result)
if __name__ == "__main__":
main()
main()

View File

@@ -26,7 +26,7 @@ class decort_osimage(DecortController):
if amodule.params['account_name']:
self.validated_account_id, _ = self.account_find(amodule.params['account_name'])
else:
self.validated_account_id = amodule.params['account_Id']
self.validated_account_id = amodule.params['account_id']
if self.validated_account_id == 0:
# we failed either to find or access the specified account - fail the module
@@ -129,7 +129,7 @@ class decort_osimage(DecortController):
hot_resize=hot_resize,
username=amodule.params['image_username'],
password=amodule.params['image_password'],
account_Id=self.validated_account_id,
account_id=self.validated_account_id,
usernameDL=amodule.params['usernameDL'],
passwordDL=amodule.params['passwordDL'],
sepId=amodule.params['sepId'],
@@ -156,7 +156,11 @@ class decort_osimage(DecortController):
def decort_virt_image_create(self,amodule):
# function that creates a virtual image
image_facts = self.virt_image_create(name=amodule.params['virt_name'], targetId=self.target_image_id)
image_facts = self.virt_image_create(
name=amodule.params['virt_name'],
target_id=self.target_image_id,
account_id=self.aparams['account_id'],
)
image_id, image_facts = decort_osimage.decort_virt_image_find(self, amodule)
self.result['facts'] = decort_osimage.decort_osimage_package_facts(image_facts, amodule.check_mode)
return image_id, image_facts
@@ -244,7 +248,7 @@ class decort_osimage(DecortController):
account_name=dict(
type='str',
),
account_Id=dict(
account_id=dict(
type='int',
),
image_name=dict(

View File

@@ -42,6 +42,9 @@ class decort_rg(DecortController):
else:
self.rg_should_exist = False
if self.validated_rg_id and self.rg_facts['status'] != 'DESTROYED':
self.check_amodule_args_for_change()
def get_info(self):
# If this is the first getting info
if not self.validated_rg_id:
@@ -150,18 +153,20 @@ class decort_rg(DecortController):
return
def create(self):
self.validated_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
)
self.validated_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.validated_rg_id:
self.validated_rg_id, self.rg_facts = self.rg_find(
@@ -237,6 +242,7 @@ class decort_rg(DecortController):
ret_dict['computes'] = self.rg_facts['vms']
ret_dict['uniqPools'] = self.rg_facts['uniqPools']
ret_dict['description'] = self.rg_facts['desc']
ret_dict['sdn_access_group_id'] = self.rg_facts['sdn_access_group_id']
return ret_dict
@@ -334,11 +340,34 @@ class decort_rg(DecortController):
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_facts['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.validated_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

View File

@@ -4,7 +4,7 @@ DOCUMENTATION = r'''
---
module: decort_snapshot
description: See L(Module Documentation,https://repository.basistech.ru/BASIS/decort-ansible/wiki/Home).
description: See L(Module Documentation,https://repository.basistech.ru/BASIS/decort-ansible/wiki/Home). # noqa: E501
'''
import time
@@ -17,33 +17,24 @@ class DecortSnapshot(DecortController):
super().__init__(AnsibleModule(**self.amodule_init_args))
self.check_amodule_args()
self.vm_id: int
self.vm_facts: dict
self.aparams_label = self.aparams['label']
self.aparams_vm_id = self.aparams['vm_id']
vm_id, vm_facts, _ = self._compute_get_by_id(
self.vm_id, self.vm_facts, _ = self._compute_get_by_id(
comp_id=self.aparams_vm_id,
)
if not vm_id:
if not self.vm_id:
self.message(f'VM {self.aparams_vm_id} not found')
self.exit(fail=True)
self.vm_name = vm_facts['name']
self.vm_snapshots = vm_facts['snapSets']
self.vm_name = self.vm_facts['name']
self.vm_snapshots = self.vm_facts['snapSets']
self.vm_snapshot_labels = [
snapshot['label'] for snapshot in self.vm_snapshots
]
if (
self.aparams_label is not None
and self.aparams_label not in self.vm_snapshot_labels
and self.aparams['state'] is None
):
self.message(
f'Snapshot {self.aparams_label} '
f'not found for VM {self.aparams_vm_id}'
)
self.exit(fail=True)
self.new_snapshot_label = None
if self.aparams['state'] == 'present':
if self.aparams_label is None:
@@ -53,6 +44,17 @@ class DecortSnapshot(DecortController):
elif self.aparams_label not in self.vm_snapshot_labels:
self.new_snapshot_label = self.aparams_label
if (
self.new_snapshot_label is None
and self.aparams_label is not None
and self.aparams_label not in self.vm_snapshot_labels
):
self.message(
f'Snapshot {self.aparams_label} '
f'not found for VM {self.aparams_vm_id}'
)
self.exit(fail=True)
@property
def amodule_init_args(self) -> dict:
return self.pack_amodule_init_args(
@@ -65,6 +67,7 @@ class DecortSnapshot(DecortController):
choices=(
'absent',
'present',
'merge_aborted',
),
),
usage=dict(
@@ -101,6 +104,7 @@ class DecortSnapshot(DecortController):
def run(self):
self.get_info(first_run=True)
self.check_amodule_args_for_change()
self.change()
self.exit()
@@ -126,6 +130,8 @@ class DecortSnapshot(DecortController):
case 'absent':
if self.aparams_label in self.vm_snapshot_labels:
self.delete()
case 'merge_aborted':
self.abort_merge()
def create(self):
self.snapshot_create(
@@ -141,6 +147,13 @@ class DecortSnapshot(DecortController):
)
self.facts = {}
def abort_merge(self):
self.snapshot_abort_merge(
vm_id=self.aparams_vm_id,
label=self.aparams_label,
)
self.get_info()
def get_snapshot_usage(self) -> int:
label = self.new_snapshot_label or self.aparams_label
common_snapshots_usage_info, _ = self.snapshot_usage(
@@ -148,6 +161,24 @@ class DecortSnapshot(DecortController):
label=label,
)
return common_snapshots_usage_info['stored']
def check_amodule_args_for_change(self):
check_errors = False
if (
self.aparams['state'] == 'merge_aborted'
and self.vm_facts['techStatus'] != 'MERGE'
):
check_errors = True
self.message(
f'Check for parameter "state" failed: '
'Merge can be aborted only for VM in "MERGE" tech status.'
)
if check_errors:
self.exit(fail=True)
def main():
DecortSnapshot().run()

51
library/decort_trunk.py Normal file
View File

@@ -0,0 +1,51 @@
#!/usr/bin/python
DOCUMENTATION = r'''
---
module: decort_trunk
description: See L(Module Documentation,https://repository.basistech.ru/BASIS/decort-ansible/wiki/Home). # noqa: E501
'''
from ansible.module_utils.basic import AnsibleModule
from ansible.module_utils.decort_utils import DecortController
class DecortTrunk(DecortController):
def __init__(self):
super().__init__(AnsibleModule(**self.amodule_init_args))
self.id: int = self.aparams['id']
@property
def amodule_init_args(self) -> dict:
return self.pack_amodule_init_args(
argument_spec=dict(
id=dict(
type='int',
required=True,
),
),
supports_check_mode=True,
)
def run(self):
self.get_info()
self.exit()
def get_info(self):
self.facts = self.trunk_get(id=self.id)
self.facts['account_ids'] = self.facts.pop('accountIds')
self.facts['created_timestamp'] = self.facts.pop('created_at')
self.facts['deleted_timestamp'] = self.facts.pop('deleted_at')
self.facts['updated_timestamp'] = self.facts.pop('updated_at')
self.facts['native_vlan_id'] = self.facts.pop('nativeVlanId')
self.facts['ovs_bridge'] = self.facts.pop('ovsBridge')
self.facts['vlan_ids'] = self.facts.pop('trunkTags')
def main():
DecortTrunk().run()
if __name__ == '__main__':
main()

View File

@@ -4,7 +4,7 @@ DOCUMENTATION = r'''
---
module: decort_user_info
description: See L(Module Documentation,https://repository.basistech.ru/BASIS/decort-ansible/wiki/Home).
description: See L(Module Documentation,https://repository.basistech.ru/BASIS/decort-ansible/wiki/Home). # noqa: E501
'''
from ansible.module_utils.basic import AnsibleModule
@@ -33,9 +33,8 @@ class DecortUserInfo(DecortController):
rights=dict(
type='str',
choices=[
e.value
for e in self.AccountUserRights
],
e.value for e in self.AccountUserRights
],
),
id=dict(
type='int',
@@ -47,7 +46,7 @@ class DecortUserInfo(DecortController):
type='str',
choices=[
e.value for e in self.AccountStatus
],
],
),
),
),
@@ -79,8 +78,8 @@ class DecortUserInfo(DecortController):
type='str',
choices=[
e.value
for e in self.AccountSortableField
],
for e in self.AccountSortableField
],
required=True,
),
),
@@ -186,6 +185,124 @@ class DecortUserInfo(DecortController):
type='bool',
default=False,
),
zones=dict(
type='dict',
options=dict(
filter=dict(
type='dict',
options=dict(
deletable=dict(
type='bool',
),
description=dict(
type='str',
),
grid_id=dict(
type='int',
),
id=dict(
type='int',
),
name=dict(
type='str',
),
node_id=dict(
type='int',
),
status=dict(
type='str',
choices=[
e.value for e in self.ZoneStatus
],
),
),
),
pagination=dict(
type='dict',
apply_defaults=True,
options=dict(
number=dict(
type='int',
default=1,
),
size=dict(
type='int',
default=50,
),
),
),
sorting=dict(
type='dict',
options=dict(
asc=dict(
type='bool',
default=True,
),
field=dict(
type='str',
choices=self.ZoneField._member_names_,
required=True,
),
),
),
),
),
trunks=dict(
type='dict',
options=dict(
filter=dict(
type='dict',
options=dict(
ids=dict(
type='list',
),
account_ids=dict(
type='list',
),
status=dict(
type='str',
choices=[
e.value for e in self.TrunkStatus
],
),
vlan_ids=dict(
type='list',
),
),
),
pagination=dict(
type='dict',
apply_defaults=True,
options=dict(
number=dict(
type='int',
default=1,
),
size=dict(
type='int',
default=50,
),
),
),
sorting=dict(
type='dict',
options=dict(
asc=dict(
type='bool',
default=True,
),
field=dict(
type='str',
choices=[
e.value
for e in self.TrunksSortableField
],
required=True,
),
),
),
),
),
),
supports_check_mode=True,
)
@@ -200,22 +317,38 @@ class DecortUserInfo(DecortController):
check_error = False
match self.aparams['audits']:
case {'filter': {'time':
{'start': {'datetime': str() as dt_str}}
}
}:
case {
'filter': {'time': {'start': {'datetime': str() as dt_str}}}
}:
if self.dt_str_to_sec(dt_str=dt_str) is None:
self.message(self.MESSAGES.str_not_parsed(string=dt_str))
check_error = True
match self.aparams['audits']:
case {'filter': {'time':
{'end': {'datetime': str() as dt_str}}
}
}:
case {
'filter': {'time': {'end': {'datetime': str() as dt_str}}}
}:
if self.dt_str_to_sec(dt_str=dt_str) is None:
self.message(self.MESSAGES.str_not_parsed(string=dt_str))
check_error = True
aparam_trunks = self.aparams['trunks']
if (
aparam_trunks is not None
and aparam_trunks['filter'] is not None
and aparam_trunks['filter']['vlan_ids'] is not None
):
for vlan_id in aparam_trunks['filter']['vlan_ids']:
if not (
self.TRUNK_VLAN_ID_MIN_VALUE
<= vlan_id
<= self.TRUNK_VLAN_ID_MAX_VALUE
):
check_error = True
self.message(
'Check for parameter "trunks.filter.vlan_ids" failed: '
f'VLAN ID {vlan_id} must be in range 1-4095.'
)
if check_error:
self.exit(fail=True)
@@ -331,6 +464,94 @@ class DecortUserInfo(DecortController):
return mapped_args
@property
def mapped_zones_args(self):
"""
Map the module argument `zones` to
arguments dictionary for the method
`DecortController.user_zones`.
"""
input_args = self.aparams['zones']
if not input_args:
return input_args
mapped_args = {}
input_args_filter = input_args['filter']
if input_args_filter:
mapped_args.update(input_args_filter)
input_args_filter_status = input_args_filter['status']
if input_args_filter_status:
mapped_args['status'] = (
self.ZoneStatus(input_args_filter_status)
)
input_args_pagination = input_args['pagination']
if input_args_pagination:
mapped_args['page_number'] = input_args_pagination['number']
mapped_args['page_size'] = input_args_pagination['size']
input_args_sorting = input_args['sorting']
if input_args_sorting:
mapped_args['sort_by_asc'] = input_args_sorting['asc']
input_args_sorting_field = input_args_sorting['field']
if input_args_sorting_field:
mapped_args['sort_by_field'] = (
self.ZoneField._member_map_[input_args_sorting_field]
)
return mapped_args
@property
def mapped_trunks_args(self):
"""
Map the module argument `trunks` to
arguments dictionary for the method
`DecortController.user_trunks`.
"""
input_args = self.aparams['trunks']
if not input_args:
return input_args
mapped_args = {}
input_args_filter = input_args['filter']
if input_args_filter:
mapped_args.update(input_args_filter)
input_args_filter_status = input_args_filter['status']
if input_args_filter_status:
mapped_args['status'] = (
self.TrunkStatus(input_args_filter_status)
)
input_args_filter_vlan_ids = input_args_filter['vlan_ids']
if input_args_filter_vlan_ids is not None:
mapped_args['vlan_ids'] = ', '.join(
map(str, input_args_filter_vlan_ids)
)
input_args_pagination = input_args['pagination']
if input_args_pagination:
mapped_args['page_number'] = input_args_pagination['number']
mapped_args['page_size'] = input_args_pagination['size']
input_args_sorting = input_args['sorting']
if input_args_sorting:
mapped_args['sort_by_asc'] = input_args_sorting['asc']
input_args_sorting_field = input_args_sorting['field']
if input_args_sorting_field:
mapped_args['sort_by_field'] = (
self.TrunksSortableField(input_args_sorting_field)
)
return mapped_args
def run(self):
self.get_info()
self.exit()
@@ -357,13 +578,34 @@ class DecortUserInfo(DecortController):
if self.aparams['api_methods']:
self.facts['api_methods'] = self.user_api_methods(id=self.id)
search_string = self.aparams['objects_search']
if search_string:
self.facts['objects_search'] = self.user_objects_search(
search_string=search_string,
)
if self.aparams['zones']:
self.facts['zones'] = self.user_zones(**self.mapped_zones_args)
if self.aparams['trunks']:
self.facts['trunks'] = self.user_trunks(**self.mapped_trunks_args)
for trunk_facts in self.facts['trunks']:
trunk_facts['account_ids'] = trunk_facts.pop('accountIds')
trunk_facts['created_timestamp'] = trunk_facts.pop(
'created_at'
)
trunk_facts['deleted_timestamp'] = trunk_facts.pop(
'deleted_at'
)
trunk_facts['updated_timestamp'] = trunk_facts.pop(
'updated_at'
)
trunk_facts['native_vlan_id'] = trunk_facts.pop(
'nativeVlanId'
)
trunk_facts['ovs_bridge'] = trunk_facts.pop('ovsBridge')
trunk_facts['vlan_ids'] = trunk_facts.pop('trunkTags')
def main():
DecortUserInfo().run()

View File

@@ -24,7 +24,6 @@ class decort_vins(DecortController):
validated_rg_id = 0
rg_facts = None # will hold RG facts
validated_acc_id = 0
acc_facts = None # will hold Account facts
if arg_amodule.params['vins_id']:
# expect existing ViNS with the specified ID
@@ -45,6 +44,7 @@ class decort_vins(DecortController):
# This call to rg_find will abort the module if no RG with such ID is present
validated_rg_id, rg_facts = self.rg_find(0, # account ID set to 0 as we search for RG by RG ID
arg_amodule.params['rg_id'], arg_rg_name="")
validated_acc_id = rg_facts['accountId']
# This call to vins_find may return vins_id=0 if no ViNS found
self.vins_id, self.vins_facts = self.vins_find(vins_id=0, vins_name=arg_amodule.params['vins_name'],
@@ -56,7 +56,7 @@ class decort_vins(DecortController):
pass
elif arg_amodule.params['account_id'] or arg_amodule.params['account_name'] != "":
# Specified account must be present and accessible by the user, otherwise abort the module
validated_acc_id, acc_facts = self.account_find(arg_amodule.params['account_name'], arg_amodule.params['account_id'])
validated_acc_id, self._acc_info = self.account_find(arg_amodule.params['account_name'], arg_amodule.params['account_id'])
if not validated_acc_id:
self.result['failed'] = True
self.result['msg'] = ("Current user does not have access to the requested account "
@@ -102,15 +102,25 @@ class decort_vins(DecortController):
self.amodule.fail_json(**self.result)
return
self.rg_id = validated_rg_id
self.acc_id = validated_acc_id
if self.vins_id and self.vins_facts['status'] != 'DESTROYED':
self.check_amodule_args_for_change()
else:
self.check_amodule_args_for_create()
return
def create(self):
self.vins_id = self.vins_provision(self.amodule.params['vins_name'],
self.acc_id, self.rg_id,
self.amodule.params['ipcidr'],
self.amodule.params['ext_net_id'], self.amodule.params['ext_ip_addr'],
self.amodule.params['description'])
self.amodule.params['description'],
zone_id=self.amodule.params['zone_id'],
)
if self.amodule.params['mgmtaddr'] or self.amodule.params['connect_to']:
_, self.vins_facts = self.vins_find(self.vins_id)
@@ -150,6 +160,14 @@ class decort_vins(DecortController):
if d_state != '':
self.vins_state(self.vins_facts, d_state)
aparam_zone_id = self.aparams['zone_id']
if aparam_zone_id is not None and aparam_zone_id != self.vins_facts['zoneId']:
self.vins_migrate_to_zone(
net_id=self.vins_id,
zone_id=aparam_zone_id,
)
return
def delete(self):
self.vins_delete(self.vins_id, permanently=True)
@@ -235,6 +253,7 @@ class decort_vins(DecortController):
else:
ret_dict['ext_ip_addr'] = ""
ret_dict['ext_net_id'] = -1
ret_dict['zone_id'] = self.vins_facts['zoneId']
return ret_dict
@@ -309,12 +328,31 @@ class decort_vins(DecortController):
type='str',
default='',
),
zone_id=dict(
type=int,
),
),
supports_check_mode=True,
required_one_of=[
('vins_id', 'vins_name'),
],
)
def check_amodule_args_for_change(self):
check_errors = False
if self.check_aparam_zone_id() is False:
check_errors = True
if check_errors:
self.exit(fail=True)
def check_amodule_args_for_create(self):
check_errors = False
if self.check_aparam_zone_id() is False:
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

48
library/decort_zone.py Normal file
View File

@@ -0,0 +1,48 @@
#!/usr/bin/python
DOCUMENTATION = r'''
---
module: decort_zone
description: See L(Module Documentation,https://repository.basistech.ru/BASIS/decort-ansible/wiki/Home). # noqa: E501
'''
from ansible.module_utils.basic import AnsibleModule
from ansible.module_utils.decort_utils import DecortController
class DecortZone(DecortController):
def __init__(self):
super().__init__(AnsibleModule(**self.amodule_init_args))
self.id: int = self.aparams['id']
@property
def amodule_init_args(self) -> dict:
return self.pack_amodule_init_args(
argument_spec=dict(
id=dict(
type='int',
required=True,
),
),
supports_check_mode=True,
)
def run(self):
self.get_info()
self.exit()
def get_info(self):
self.facts = self.zone_get(id=self.id)
self.facts['grid_id'] = self.facts.pop('gid')
self.facts['created_timestamp'] = self.facts.pop('createdTime')
self.facts['updated_timestamp'] = self.facts.pop('updatedTime')
self.facts['node_ids'] = self.facts.pop('nodeIds')
def main():
DecortZone().run()
if __name__ == '__main__':
main()