This commit is contained in:
2026-06-01 18:27:15 +03:00
parent ae986fa9e6
commit 1ab446e05d
34 changed files with 5135 additions and 2113 deletions

View File

@@ -20,7 +20,6 @@ class decort_vins(DecortController):
self.vins_id = 0
self.vins_level = "" # "ID" if specified by ID, "RG" - at resource group, "ACC" - at account level
vins_facts = None # will hold ViNS facts
validated_rg_id = 0
rg_facts = None # will hold RG facts
validated_acc_id = 0
@@ -28,15 +27,28 @@ class decort_vins(DecortController):
if arg_amodule.params['vins_id']:
# expect existing ViNS with the specified ID
# This call to vins_find will abort the module if no ViNS with such ID is present
self.vins_id, self.vins_facts = self.vins_find(arg_amodule.params['vins_id'],check_state=False)
self.vins_id, self._vins_info = self.vins_find(
arg_amodule.params['vins_id'],
check_state=False,
)
if self.vins_id == 0:
if arg_amodule.params['state'] == 'absent':
self.exit()
else:
self.message(
self.MESSAGES.obj_not_found(
obj='VINS',
id=arg_amodule.params['vins_id'],
)
)
self.exit(fail=True)
if self._vins_info is None:
self.result['failed'] = True
self.result['msg'] = "Specified ViNS ID {} not found.".format(arg_amodule.params['vins_id'])
self.amodule.fail_json(**self.result)
self.vins_level = "ID"
#raise Exception(self.vins_facts)
validated_acc_id = self.vins_facts['accountId']
validated_rg_id = self.vins_facts['rgId']
validated_acc_id = self._vins_info.account_id
validated_rg_id = self._vins_info.rg_id
elif arg_amodule.params['rg_id']:
# expect ViNS @ RG level in the RG with specified ID
@@ -44,14 +56,16 @@ 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']
validated_acc_id = rg_facts.account_id
# 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'],
account_id=0,
rg_id=arg_amodule.params['rg_id'],
rg_facts=rg_facts,
check_state=False)
self.vins_id, self._vins_info = self.vins_find(
vins_id=0,
vins_name=arg_amodule.params['vins_name'],
account_id=0,
rg_id=arg_amodule.params['rg_id'],
check_state=False,
)
# TODO: add checks and setup ViNS presence flags accordingly
pass
elif arg_amodule.params['account_id'] or arg_amodule.params['account_name'] != "":
@@ -66,27 +80,39 @@ class decort_vins(DecortController):
# expect ViNS @ RG level in the RG with specified name under specified account
# RG with the specified name must be present under the account, otherwise abort the module
validated_rg_id, rg_facts = self.rg_find(validated_acc_id, 0, arg_amodule.params['rg_name'])
if (not validated_rg_id or
rg_facts['status'] in ["DESTROYING", "DESTROYED", "DELETING", "DELETED", "DISABLING", "ENABLING"]):
if (not validated_rg_id or rg_facts is None or
rg_facts.status in [
sdk_types.ResourceGroupStatus.DESTROYING,
sdk_types.ResourceGroupStatus.DESTROYED,
sdk_types.ResourceGroupStatus.DELETED,
sdk_types.ResourceGroupStatus.DISABLING,
sdk_types.ResourceGroupStatus.ENABLING,
]
):
self.result['failed'] = True
self.result['msg'] = "RG name '{}' not found or has invalid state.".format(arg_amodule.params['rg_name'])
self.amodule.fail_json(**self.result)
# This call to vins_find may return vins_id=0 if no ViNS with this name found under specified RG
self.vins_id, self.vins_facts = self.vins_find(vins_id=0, vins_name=arg_amodule.params['vins_name'],
account_id=0, # set to 0, as we are looking for ViNS under RG
rg_id=validated_rg_id,
rg_facts=rg_facts,
check_state=False)
# (account_id) set to 0, as we are looking for ViNS under RG
self.vins_id, self._vins_info = self.vins_find(
vins_id=0,
vins_name=arg_amodule.params['vins_name'],
account_id=0,
rg_id=validated_rg_id,
check_state=False,
)
self.vins_level = "RG"
# TODO: add checks and setup ViNS presence flags accordingly
else: # At this point we know for sure that rg_name="" and rg_id=0
# So we expect ViNS @ account level
# 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'],
account_id=validated_acc_id,
rg_id=0,
rg_facts=rg_facts,
check_state=False)
self.vins_id, self._vins_info = self.vins_find(
vins_id=0,
vins_name=arg_amodule.params['vins_name'],
account_id=validated_acc_id,
rg_id=0,
check_state=False,
)
self.vins_level = "ACC"
# TODO: add checks and setup ViNS presence flags accordingly
else:
@@ -106,73 +132,121 @@ class decort_vins(DecortController):
self.rg_id = validated_rg_id
self.acc_id = validated_acc_id
if self.vins_id and self.vins_facts['status'] != 'DESTROYED':
if (
self._vins_info
and self._vins_info.status != sdk_types.VINSStatus.DESTROYED
):
self.check_amodule_args_for_change()
else:
self.check_amodule_args_for_create()
return
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'],
zone_id=self.amodule.params['zone_id'],
security_group_mode = self.amodule.params['security_group_mode']
if security_group_mode is None:
security_group_mode = False
self.message(
msg=self.MESSAGES.default_value_used(
param_name='security_group_mode',
default_value=security_group_mode
),
warning=True,
)
self.vins_id = self.vins_provision(
vins_name=self.amodule.params['vins_name'],
account_id=self.acc_id,
rg_id=self.rg_id,
ipcidr=self.amodule.params['ipcidr'],
ext_net_id=self.amodule.params['ext_net_id'],
ext_ip_addr=self.amodule.params['ext_ip_addr'],
desc=self.amodule.params['description'],
zone_id=self.amodule.params['zone_id'],
security_group_mode=security_group_mode,
)
if self.amodule.params['mgmtaddr'] or self.amodule.params['connect_to']:
_, self.vins_facts = self.vins_find(self.vins_id)
if self.vins_id:
self._vins_info = self._vins_get_by_id(vins_id=self.vins_id)
if self.amodule.params['connect_to']:
self.vins_update_ifaces(self.vins_facts,self.amodule.params['connect_to'],)
self.vins_update_ifaces(
self.vins_info,
self.amodule.params['connect_to'],
)
if self.amodule.params['mgmtaddr']:
self.vins_update_mgmt(self.vins_facts,self.amodule.params['mgmtaddr'])
self.vins_update_mgmt(
self.vins_info,
self.amodule.params['mgmtaddr'],
)
return
def action(self,d_state='',restore=False):
if restore == True:
self.vins_restore(arg_vins_id=self.vins_id)
self.vins_state(self.vins_facts, 'enabled')
self.vins_facts['status'] = "ENABLED"
self.vins_facts['VNFDev']['techStatus'] = "STARTED"
self.vins_update_extnet(self.vins_facts,
self.amodule.params['ext_net_id'],
self.amodule.params['ext_ip_addr'],
)
if d_state == 'enabled' and self.vins_facts['status'] == "DISABLED":
self.vins_state(self.vins_facts, d_state)
self.vins_facts['status'] = "ENABLED"
self.vins_facts['VNFDev']['techStatus'] = "STARTED"
def action(self, d_state='', restore=False):
if restore:
self.sdk_checkmode(self.api.cloudapi.vins.restore)(
vins_id=self.vins_info.id,
)
self._vins_info = self._vins_get_by_id(vins_id=self.vins_id)
self.vins_state(self.vins_info, 'enabled')
self._vins_info = self._vins_get_by_id(vins_id=self.vins_id)
if (
self.amodule.params['ext_net_id'] is not None
or self.amodule.params['ext_ip_addr'] is not None
):
self.vins_update_extnet(
self.vins_info,
self.amodule.params['ext_net_id'],
self.amodule.params['ext_ip_addr'],
)
if (
d_state == 'enabled'
and self.vins_info.status == sdk_types.VINSStatus.DISABLED
):
self.vins_state(self.vins_info, d_state)
self._vins_info = self._vins_get_by_id(vins_id=self.vins_id)
d_state = ''
if self.vins_facts['status'] == "ENABLED" and self.vins_facts['VNFDev']['techStatus'] == "STARTED":
self.vins_update_ifaces(self.vins_facts,
self.amodule.params['connect_to'],
)
if (
self.vins_info.status == sdk_types.VINSStatus.ENABLED
and self.vins_info.vnfdev.tech_status == (
sdk_types.VNFDevTechStatus.STARTED
)
):
self.vins_update_ifaces(
self.vins_info,
self.amodule.params['connect_to'],
)
if self.result['changed']:
_, self.vins_facts = self.vins_find(self.vins_id)
self.vins_update_mgmt(self.vins_facts,
self.amodule.params['mgmtaddr'],
)
self._vins_info = self._vins_get_by_id(vins_id=self.vins_id)
self.vins_update_mgmt(
self.vins_info,
self.amodule.params['mgmtaddr'],
)
if d_state != '':
self.vins_state(self.vins_facts, d_state)
self.vins_state(self.vins_info, d_state)
aparam_zone_id = self.aparams['zone_id']
if aparam_zone_id is not None and aparam_zone_id != self.vins_facts['zoneId']:
if (
aparam_zone_id is not None
and aparam_zone_id != self.vins_info.zone_id
):
self.vins_migrate_to_zone(
net_id=self.vins_id,
net_id=self.vins_info.id,
zone_id=aparam_zone_id,
)
return
def delete(self):
self.vins_delete(self.vins_id, self.amodule.params['permanently'])
self.vins_facts['status'] = 'DESTROYED'
self.sdk_checkmode(self.api.cloudapi.vins.delete)(
vins_id=self.vins_info.id,
permanently=self.amodule.params['permanently'],
)
return
def nop(self):
"""No operation (NOP) handler for ViNS management by decort_vins module.
This function is intended to be called from the main switch construct of the module
@@ -181,23 +255,27 @@ class decort_vins(DecortController):
"""
self.result['failed'] = False
self.result['changed'] = False
if self.vins_id:
self.result['msg'] = ("No state change required for ViNS ID {} because of its "
"current status '{}'.").format(self.vins_id, self.vins_facts['status'])
if self._vins_info:
self.result['msg'] = (
f'No state change required for ViNS ID {self._vins_info.id} '
f'because of its "current status "{self._vins_info.status}".'
)
else:
self.result['msg'] = ("No state change to '{}' can be done for "
"non-existent ViNS instance.").format(self.amodule.params['state'])
return
def error(self):
self.result['failed'] = True
self.result['changed'] = False
if self.vins_id:
if self._vins_info:
self.result['failed'] = True
self.result['changed'] = False
self.result['msg'] = ("Invalid target state '{}' requested for ViNS ID {} in the "
"current status '{}'").format(self.vins_id,
self.amodule.params['state'],
self.vins_facts['status'])
self.result['msg'] = (
f'Invalid target state "{self.amodule.params['state']}" '
f'requested for ViNS ID {self._vins_info.id} in the '
f'current status "{self._vins_info.status}"'
)
else:
self.result['failed'] = True
self.result['changed'] = False
@@ -205,6 +283,7 @@ class decort_vins(DecortController):
"ViNS name '{}'").format(self.amodule.params['state'],
self.amodule.params['vins_name'])
return
def package_facts(self, arg_check_mode=False):
"""Package a dictionary of ViNS facts according to the decort_vins module specification.
This dictionary will be returned to the upstream Ansible engine at the completion of
@@ -222,40 +301,12 @@ class decort_vins(DecortController):
# in check mode return immediately with the default values
return ret_dict
if self.vins_facts is None:
if self._vins_info is None:
# if void facts provided - change state value to ABSENT and return
ret_dict['state'] = "ABSENT"
return ret_dict
ret_dict['id'] = self.vins_facts['id']
ret_dict['name'] = self.vins_facts['name']
ret_dict['state'] = self.vins_facts['status']
ret_dict['account_id'] = self.vins_facts['accountId']
ret_dict['rg_id'] = self.vins_facts['rgId']
ret_dict['int_net_addr'] = self.vins_facts['network']
ret_dict['gid'] = self.vins_facts['gid']
custom_interfaces = list(filter(lambda i: i['type']=="CUSTOM",self.vins_facts['VNFDev']['interfaces']))
if custom_interfaces:
ret_dict['custom_net_addr'] = []
for runner in custom_interfaces:
ret_dict['custom_net_addr'].append(runner['ipAddress'])
mgmt_interfaces = list(filter(lambda i: i['listenSsh'] and i['name']!="ens9",self.vins_facts['VNFDev']['interfaces']))
if mgmt_interfaces:
ret_dict['ssh_ipaddr'] = []
for runner in mgmt_interfaces:
ret_dict['ssh_ipaddr'].append(runner['ipAddress'])
ret_dict['ssh_password'] = self.vins_facts['VNFDev']['config']['mgmt']['password']
ret_dict['ssh_port'] = 9022
if self.vins_facts['vnfs'].get('GW'):
gw_config = self.vins_facts['vnfs']['GW']['config']
ret_dict['ext_ip_addr'] = gw_config['ext_net_ip']
ret_dict['ext_net_id'] = gw_config['ext_net_id']
else:
ret_dict['ext_ip_addr'] = ""
ret_dict['ext_net_id'] = -1
ret_dict['zone_id'] = self.vins_facts['zoneId']
return ret_dict
return self._vins_info.model_dump()
@property
@@ -276,11 +327,9 @@ class decort_vins(DecortController):
),
ext_net_id=dict(
type='int',
default=-1,
),
ext_ip_addr=dict(
type='str',
default='',
),
ipcidr=dict(
type='str',
@@ -304,7 +353,6 @@ class decort_vins(DecortController):
),
state=dict(
type='str',
default='present',
choices=[
'absent',
'disabled',
@@ -335,6 +383,9 @@ class decort_vins(DecortController):
zone_id=dict(
type=int,
),
security_group_mode=dict(
type='bool',
),
),
supports_check_mode=True,
required_one_of=[
@@ -347,6 +398,34 @@ class decort_vins(DecortController):
if self.check_aparam_zone_id() is False:
check_errors = True
if (
self.amodule.params['ext_ip_addr']
and self.amodule.params['ext_net_id'] is None
and self.vins_info.vnfs.gw is None
):
self.message(
msg=(
'Check for parameter "ext_net_id" failed: '
'the "ext_net_id" parameter must be specified '
'if the "ext_ip_addr" parameter is passed and '
'VINS is not connected to an external network.'
)
)
check_errors = True
if (
self.aparams['security_group_mode'] is not None
and self.vins_info.security_group_mode != self.aparams['security_group_mode']
):
self.message(
msg=(
'Check for parameter "security_group_mode" failed: '
'"security_group_mode" cannot be changed '
'for existing ViNS'
)
)
check_errors = True
if check_errors:
self.exit(fail=True)
@@ -384,36 +463,54 @@ class decort_vins(DecortController):
# if cconfig_save is true, only config save without other updates
vins_should_exist = False
if self.vins_id:
if self._vins_info:
vins_should_exist = True
if self.vins_facts['status'] in ["MODELED", "DISABLING", "ENABLING", "DELETING", "DESTROYING"]:
if self._vins_info.status in [
sdk_types.VINSStatus.MODELED,
sdk_types.VINSStatus.DISABLING,
sdk_types.VINSStatus.ENABLING,
sdk_types.VINSStatus.DELETING,
sdk_types.VINSStatus.DESTROYING,
]:
# error: nothing can be done to existing ViNS in the listed statii regardless of
# the requested state
self.result['failed'] = True
self.result['changed'] = False
self.result['msg'] = ("No change can be done for existing ViNS ID {} because of its current "
"status '{}'").format(self.vins_id, self.vins_facts['status'])
elif self.vins_facts['status'] == "DISABLED":
self.result['msg'] = (
f'No change can be done for existing '
f'ViNS ID {self.vins_id} because of its '
f'current status {self._vins_info.status}'
)
elif self._vins_info.status == sdk_types.VINSStatus.DISABLED:
if amodule.params['state'] == 'absent':
self.delete()
vins_should_exist = False
elif amodule.params['state'] in ('present', 'disabled'):
elif (
amodule.params['state'] is None
or amodule.params['state'] in ('present', 'disabled')
):
# update ViNS, leave in disabled state
self.action()
elif amodule.params['state'] == 'enabled':
# update ViNS and enable
self.action('enabled')
elif self.vins_facts['status'] in ["CREATED", "ENABLED"]:
elif self._vins_info.status in [
sdk_types.VINSStatus.CREATED,
sdk_types.VINSStatus.ENABLED,
]:
if amodule.params['state'] == 'absent':
self.delete()
vins_should_exist = False
elif amodule.params['state'] in ('present', 'enabled'):
elif (
amodule.params['state'] is None
or amodule.params['state'] in ('present', 'enabled')
):
# update ViNS
self.action()
elif amodule.params['state'] == 'disabled':
# disable and update ViNS
self.action('disabled')
elif self.vins_facts['status'] == "DELETED":
elif self._vins_info.status == sdk_types.VINSStatus.DELETED:
if amodule.params['state'] in ['present', 'enabled']:
# restore and enable
self.action(restore=True)
@@ -426,28 +523,48 @@ class decort_vins(DecortController):
elif amodule.params['state'] == 'disabled':
self.error()
vins_should_exist = False
elif self.vins_facts['status'] == "DESTROYED":
if amodule.params['state'] in ('present', 'enabled'):
elif self._vins_info.status == sdk_types.VINSStatus.DESTROYED:
state = amodule.params['state']
if state is None:
state = 'present'
self.message(
msg=(
f'State not specified, '
f'default value "{state}" will be used.'
),
warning=True,
)
if state in ('present', 'enabled'):
# need to re-provision ViNS;
self.create()
vins_should_exist = True
elif amodule.params['state'] == 'absent':
elif state == 'absent':
self.nop()
vins_should_exist = False
elif amodule.params['state'] == 'disabled':
elif state == 'disabled':
self.error()
else:
state = amodule.params['state']
if state is None:
state = 'present'
self.message(
msg=(
f'State not specified, '
f'default value "{state}" will be used.'
),
warning=True,
)
# Preexisting ViNS was not found.
vins_should_exist = False # we will change it back to True if ViNS is created or restored
# If requested state is 'absent' - nothing to do
if amodule.params['state'] == 'absent':
if state == 'absent':
self.nop()
elif amodule.params['state'] in ('present', 'enabled'):
elif state in ('present', 'enabled'):
self.check_amodule_argument('vins_name')
# as we already have account ID and RG ID we can create ViNS and get vins_id on success
self.create()
vins_should_exist = True
elif amodule.params['state'] == 'disabled':
elif state == 'disabled':
self.error()
#
# conditional switch end - complete module run
@@ -457,7 +574,7 @@ class decort_vins(DecortController):
else:
# prepare ViNS facts to be returned as part of self.result and then call exit_json(...)
if self.result['changed']:
_, self.vins_facts = self.vins_find(self.vins_id)
self._vins_info = self._vins_get_by_id(vins_id=self.vins_id)
self.result['facts'] = self.package_facts(amodule.check_mode)
amodule.exit_json(**self.result)