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

@@ -1,11 +1,174 @@
# Список изменений в версии 11.0.3 # Список изменений в версии 12.0.0
## Добавлено ## Добавлено
### Глобально
| Идентификатор<br>задачи | Описание |
| --- | --- |
| BANS-1164 | Добавлен модуль `decort_sdn_hypervisor_list`, позволяющий получить список гипервизоров SDN.|
| BANS-1162 | Добавлен модуль `decort_sdn_access_group_list`, позволяющий получить список групп доступа SDN.|
| BANS-1161 | Добавлен модуль `decort_sdn_segment_list`, позволяющий получить список доступных сегментов SDN. |
| BANS-1154 | Добавлен модуль `decort_sdn_segment`, позволяющий привести к целевому состоянию и получить информацию о сегментах SDN. |
| BANS-1158 | Добавлен модуль `decort_sdn_hypervisor`, позволяющий привести к целевому состоянию и получить информацию о гипервизоре SDN. |
| BANS-1156 | Добавлен модуль `decort_sdn_access_group`, позволяющий создать, привести к целевому состоянию и получить информацию о группе доступа SDN. |
| BANS-1163 | Добавлен модуль `decort_sdn_logical_port_list`, позволяющий получить список доступных логических портов SDN. |
| BANS-1159 | Добавлен модуль `decort_sdn_network_object_group`, позволяющий привести к целевому состоянию и получить информацию о группах сетевых объектов. |
| BANS-1157 | Добавлен модуль `decort_sdn_logical_port`, позволяющий привести к целевому состоянию и получить информацию о логическом порте. |
| BANS-1184 | Добавлен модуль `decort_sdn_logical_port_address`, позволяющий привести к целевому состоянию и получить информацию об адресе логического порта SDN. |
| BANS-1186 | Добавлен модуль `decort_sdn_network_object_group_logical_port`, позволяющий привести к целевому состоянию логические порты сетевых групп объектов SDN. |
| BANS-1185 | Добавлен модуль `decort_sdn_network_object_group_ip_range`, позволяющий привести к целевому состоянию и получить информацию о диапазоне IP-адресов в группе сетевых объектов SDN. |
| BANS-1193 | Добавлен модуль `decort_sdn_network_object_group_mac`, позволяющий привести к целевому состоянию и получить информацию о MAC-адресе в группе сетевых объектов SDN. |
## Удалено ### Модуль decort_disk
| Идентификатор<br>задачи | Описание |
| --- | --- |
| BANS-1011 | Добавлены возвращаемые значения `account_name`, `acl`, `created_by`, `created_datetime`, `created_timestamp`, `deleted_by`, `deleted_datetime`, `deleted_timestamp`, `description`, `destruction_datetime`, `destruction_timestamp`, `device_name`, `image_id`, `image_ids`, `milestones`, `params`, `parent_id`, `present_to`, `purge_datetime`, `purge_timestamp`, `replication`, `res_id`, `res_name`, `role`, `sep_type`, `shared`, `snapshots`, `tech_status`, `updated_by`, `updated_datetime`, `updated_timestamp`. |
| BANS-1104 | Добавлено возвращаемое значение `provision`. |
## Исправлено
### Модуль decort_k8s ### Модуль decort_k8s
| Идентификатор<br>задачи | Описание | | Идентификатор<br>задачи | Описание |
| --- | --- | | --- | --- |
| BANS-1208 | Модуль завершал работу ошибкой запроса к API при попытке модуля пересоздать группу воркеров. | | BANS-238 | Добавлена возможность изменить описание объекта. |
| BANS-1134 | Добавлены возвращаемые значения `account_name`, `acl`, `acl.account`, `acl.k8s`, `acl.rg`, `bservice_id`, `created_by`, `created_datetime`, `created_timestamp`, `deleted_by`, `deleted_datetime`, `deleted_timestamp`, `extnet_only`, `k8ci_id`, `k8ci_name`, `lb_ha_ips`, `lb_ha_ips.backend`, `lb_ha_ips.frontend`, `lb_ha_mode`, `network_plugin`, `node_groups`, `node_groups.master`, `node_groups.master.id`, `node_groups.master.name`, `node_groups.master.annotations`, `node_groups.master.labels`, `node_groups.master.taints`, `node_groups.master.vms.ext_ip`, `node_groups.master.vms.name`, `node_groups.master.vms.status`, `node_groups.worker`, `node_groups.worker.id`, `node_groups.worker.name`, `node_groups.worker.annotations`, `node_groups.worker.labels`, `node_groups.worker.taints`, `node_groups.worker.vms.ext_ip`, `node_groups.worker.vms.name`, `node_groups.worker.vms.status`, `rg_name`, `updated_by`, `updated_datetime`, `updated_timestamp`, `with_lb`. |
### Модуль decort_rg
| Идентификатор<br>задачи | Описание |
| --- | --- |
| BANS-1063 | Добавлены возвращаемые значения `account_name`, `acl`, `cpu_allocation_parameter`, `cpu_allocation_ratio`, `created_by`, `created_timestamp`, `created_datetime`, `deleted_by`, `deleted_timestamp`, `deleted_datetime`, `dirty`, `guid`, `lock_status`, `milestones`, `secret`, `updated_by`, `updated_timestamp`, `updated_datetime`, `vm_features`. |
### Модуль decort_vins
| Идентификатор<br>задачи | Описание |
| --- | --- |
| BANS-1061 | Добавлены возвращаемые значения `account_name`, `created_by`, `created_datetime`, `created_timestamp`, `default_gw`, `default_qos`, `deleted_by`, `deleted_datetime`, `deleted_timestamp`, `description`, `guid`, `lock_status`, `manager_id`, `manager_type`, `milestones`, `net_prefix`, `pre_reservation_count`, `redundant`, `rg_name`, `secondary_vnfdev_id`, `security_group_mode`, `updated_by`, `updated_datetime`, `updated_timestamp`, `user_managed`, `vms`, `vnfdev`, `vnfs`, `vxlan_id`. |
| BANS-1132 | Добавлен параметр `security_group_mode`. |
### Модуль decort_vm
| Идентификатор<br>задачи | Описание |
| --- | --- |
| BANS-1088 | Добавлено возвращаемое значение `weight`. |
### Модуль decort_disk_list
| Идентификатор<br>задачи | Описание |
| --- | --- |
| BANS-1083 | Добавлен параметр `filter.vm_id`. |
| BANS-1084 | Добавлен параметр `filter.rg_id`. |
| BANS-1096 | Добавлено возвращаемое значение `independent`. |
| BANS-1103 | Добавлено возвращаемое значение `provision`. |
### Модуль decort_image
| Идентификатор<br>задачи | Описание |
| --- | --- |
| BANS-1094 | Добавлено возвращаемое значение `independent`. |
| BANS-1108 | Добавлено возвращаемое значение `links_to`. |
### Модуль decort_zone_list
| Идентификатор<br>задачи | Описание |
| --- | --- |
| BANS-1116 | Добавлено возвращаемое значение `drs_dx_app_id`. |
| BANS-1117 | Добавлено возвращаемое значение `drs_dx_url`. |
| BANS-1118 | Добавлено возвращаемое значение `drs`. |
| BANS-1119 | Добавлено возвращаемое значение `drs_name`. |
| BANS-1120 | Добавлено возвращаемое значение `drs_uid`. |
| BANS-1121 | Добавлено возвращаемое значение `drs_dx_sso_url`. |
| BANS-1147 | Добавлено возвращаемое значение `drs_dx_ssl_skip_verify`. |
| BANS-1148 | Добавлено возвращаемое значение `drs_bvs_domain`. |
| BANS-1149 | Добавлено возвращаемое значение `drs_broadcast_ip_addr`. |
| BANS-1166 | Добавлено возвращаемое значение `drs_dx_sso_type`. |
| BANS-1150 | Добавлено возвращаемое значение `drs_ping_ip_addr`. |
### Модуль decort_zone
| Идентификатор<br>задачи | Описание |
| --- | --- |
| BANS-1124 | Добавлено возвращаемое значение `drs_dx_app_id`. |
| BANS-1122 | Добавлено возвращаемое значение `drs`. |
| BANS-1125 | Добавлено возвращаемое значение `drs_dx_url`. |
| BANS-1123 | Добавлено возвращаемое значение `drs_uid`. |
| BANS-1126 | Добавлено возвращаемое значение `drs_name`. |
| BANS-1127 | Добавлено возвращаемое значение `drs_dx_sso_url`. |
| BANS-1143 | Добавлено возвращаемое значение `drs_dx_ssl_skip_verify`. |
| BANS-1144 | Добавлено возвращаемое значение `drs_bvs_domain`. |
| BANS-1145 | Добавлено возвращаемое значение `drs_broadcast_ip_addr`. |
| BANS-1146 | Добавлено возвращаемое значение `drs_ping_ip_addr`. |
| BANS-1165 | Добавлено возвращаемое значение `drs_dx_sso_type`. |
### Модуль decort_lb
| Идентификатор<br>задачи | Описание |
| --- | --- |
| BANS-1112 | Добавлены возвращаемые значения `acl`, `backend_ha_ip_addr`, `created_by`, `created_datetime`, `created_timestamp`, `deleted_by`, `deleted_datetime`, `deleted_timestamp`, `description`, `dp_api_user`, `ext_net_id`, `frontend_ha_ip_addr`, `guid`, `ha_mode`, `manager_id`, `manager_type`, `milestones`, `part_of_k8s`, `primary_node.backend_ip_addr`, `primary_node.frontend_ip_addr`, `primary_node.guid`, `primary_node.mgmt_ip`, `primary_node.net_id`, `primary_node.vm_id`, `rg_name`, `secondary_node.backend_ip_addr`, `secondary_node.frontend_ip_addr`, `secondary_node.guid`, `secondary_node.mgmt_ip`, `secondary_node.net_id`, `secondary_node.vm_id`, `updated_by`, `updated_datetime`, `updated_timestamp`, `user_managed`, `vins_id`. |
### Модуль decort_user
| Идентификатор<br>задачи | Описание |
| --- | --- |
| BANS-1176 | Добавлены возвращаемые значения `consumed.storage_policies.storage_size_quota_gb`, `reserved.storage_policies.storage_size_quota_gb`. |
## Удалено
### Глобально
| Идентификатор<br>задачи | Описание |
| --- | --- |
### Модуль decort_account
| Идентификатор<br>задачи | Описание |
| --- | --- |
| BANS-1040 | Удалены возвращаемые значения `computes_amount` , `createdTime`, `createdTime_readable`, `deactivationTime`, `deactivationTime_readable`, `deletedTime`, `deletedTime_readable`, `resourceLimits`, `resourceLimits.CU_C`, `resourceLimits.CU_D`, `resourceLimits.CU_DM`, `resourceLimits.CU_I`, `resourceLimits.CU_M`, `resourceLimits.gpu_units`, `updatedTime`, `updatedTime_readable`, `vinses_amount` в связи с переименованием в `vm_counts` , `created_timestamp`, `created_datetime`, `deactivation_timestamp`, `deactivation_datetime`, `deleted_timestamp`, `deleted_datetime`, `quotas`, `quotas.cpu_count`, `quotas.disk_size_gb`, `quotas.storage_size_gb`, `quotas.ext_ip_count`, `quotas.ram_size_mb`, `quotas.gpu_count`, `updated_timestamp`, `updated_datetime`, `vins_count`. |
| BANS-1067 | Удалены параметры `access_emails`, `quotas.cpu`, `quotas.disk_size`, `quotas.gpu`, `quotas.public_ip`, `quotas.ram` в связи с переименованием в `send_access_emails`, `quotas.cpu_count`, `quotas.storage_size_gb`, `quotas.gpu_count`, `quotas.ext_ip_count`, `quotas.ram_size_mb`. |
### Модуль decort_disk
| Идентификатор<br>задачи | Описание |
| --- | --- |
| BANS-1011 | Удалены возвращаемые значения `computes`, `gid`, `iotune`, `pool`, `size`, `size_available`, `size_used`, `state` в связи с переименованием в `vms`, `grid_id`, `io_tune`, `sep_pool_name`, `size_max_gb`, `size_available_gb`, `size_used_gb`, `status` |
### Модуль decort_k8s
| Идентификатор<br>задачи | Описание |
| --- | --- |
| BANS-1068 | Для параметра `description` удалено значение по умолчанию. |
| BANS-1134 | Удалены возвращаемые значения `techStatus`, `state`, `k8s_Masters`, `k8s_Masters.cpu`, `k8s_Masters.ram`, `k8s_Masters.disk`, `k8s_Masters.num`, `k8s_Masters.detailedInfo`, `k8s_Masters.detailedInfo.techStatus`, `k8s_Workers`, `k8s_Workers.cpu`, `k8s_Workers.ram`, `k8s_Workers.disk`, `k8s_Workers.num`, `k8s_Workers.detailedInfo`, `k8s_Workers.detailedInfo.techStatus` в связи с переименованием в `tech_status`, `status`, `node_groups.master`, `node_groups.master.node_cpu_count`, `node_groups.master.node_ram_size_mb`, `node_groups.master.node_boot_disk_size_gb`, `node_groups.master.node_count`, `node_groups.master.vms`, `node_groups.master.vms.tech_status`, `node_groups.worker`, `node_groups.worker.node_cpu_count`, `node_groups.worker.node_ram_size_mb`, `node_groups.worker.node_boot_disk_size_gb`, `node_groups.worker.node_count`, `node_groups.worker.vms`, `node_groups.worker.vms.tech_status`. |
### Модуль decort_rg
| Идентификатор<br>задачи | Описание |
| --- | --- |
| BANS-1063 | Удалены возвращаемые значения `computes`, `defNetId`, `defNetType`, `gid`, `quota`, `resTypes`, `uniqPools`, `ViNS` в связи с переименованием в `vm_ids`, `default_net_id`, `default_net_type`, `grid_id`, `quotas`, `resource_types`, `sep_pools`, `vins_ids` |
### Модуль decort_vins
| Идентификатор<br>задачи | Описание |
| --- | --- |
| BANS-1061 | Удалены возвращаемые значения `int_net_addr`, `gid`, `state` в связи с переименованием в `net_ip`, `grid_id`, `status`. |
| BANS-1061 | Удалены возвращаемые значения `custom_net_addr`, `ext_ip_addr`, `ssh_ipaddr`, `ssh_password`, `ssh_port`. |
| BANS-1054 | Для параметра `state` значение по умолчанию изменено на значение по умолчанию если объект не существует или безвозвратно удалён. |
### Модуль decort_disk_list
| Идентификатор<br>задачи | Описание |
| --- | --- |
| BANS-1085 | Удалён параметр `filter.type`. |
| BANS-1086 | Удалено возвращаемое значение `type`. |
### Модуль decort_user
| Идентификатор<br>задачи | Описание |
| --- | --- |
| BANS-1080 | Удалено возвращаемое значение `emailaddresses` в связи с переименованием в `email_addresses`. |
| BANS-1176 | Удалены возвращаемые значения `resource_consumed`, `resource_consumed.cpu`, `resource_consumed.disksize`, `resource_consumed.disksizemax`, `resource_consumed.extips`, `resource_consumed.gpu`, `resource_consumed.ram`, `resource_consumed.policies`, `resource_consumed.policies.disksize`, `resource_consumed.policies.disksizemax`, `resource_consumed.policies.seps.disksize`, `resource_consumed.policies.seps.disksizemax`, `resource_consumed.seps`, `resource_consumed.seps.disksize`, `resource_consumed.seps.disksizemax`, `resource_reserved`, `resource_reserved.cpu`, `resource_reserved.disksize`, `resource_reserved.disksizemax`, `resource_reserved.extips`, `resource_reserved.gpu`, `resource_reserved.ram`, `resource_reserved.policies`, `resource_reserved.policies.disksize`, `resource_reserved.policies.disksizemax`, `resource_reserved.policies.seps.disksize`, `resource_reserved.policies.seps.disksizemax`, `resource_reserved.seps`, `resource_reserved.seps.disksize`, `resource_reserved.seps.disksizemax` в связи с переименованием в `consumed`, `consumed.cpu_count`, `consumed.storage_size_gb_by_real_usage`, `consumed.storage_size_gb_by_disk_max`, `consumed.ext_ip_count`, `consumed.gpu_count`, `consumed.ram_size_mb`, `consumed.storage_policies`, `consumed.storage_policies.storage_size_gb_by_real_usage`, `consumed.storage_policies.storage_size_gb_by_disk_max`, `consumed.storage_policies.sep_pools.storage_size_gb_by_real_usage`, `consumed.storage_policies.sep_pools.storage_size_gb_by_disk_max`, `consumed.sep_pools`, `consumed.sep_pools.storage_size_gb_by_real_usage`, `consumed.sep_pools.storage_size_gb_by_disk_max`, `reserved`, `reserved.cpu_count`, `reserved.storage_size_gb_by_real_usage`, `reserved.storage_size_gb_by_disk_max`, `reserved.ext_ip_count`, `reserved.gpu_count`, `reserved.ram_size_mb`, `reserved.storage_policies`, `reserved.storage_policies.storage_size_gb_by_real_usage`, `reserved.storage_policies.storage_size_gb_by_disk_max`, `reserved.storage_policies.sep_pools.storage_size_gb_by_real_usage`, `reserved.storage_policies.sep_pools.storage_size_gb_by_disk_max`, `reserved.sep_pools`, `reserved.sep_pools.storage_size_gb_by_real_usage`, `reserved.sep_pools.storage_size_gb_by_disk_max`. |
### Модуль decort_lb
| Идентификатор<br>задачи | Описание |
| --- | --- |
| BANS-1112 | Удалены возвращаемые значения `backends.serverDefaultSettings`, `backends.servers.address`, `backends.servers.serverSettings`, `frontends.backend`, `frontends.bindings.address`, `gid`, `state`, `sysctl` в связи с переименованием в `backends.server_default_settings`, `backends.servers.ip_addr`, `backends.servers.server_settings`, `frontends.backend_name`, `frontends.bindings.ip_addr`, `grid_id`, `status`, `sysctl_params`. |
## Исправлено
### Модуль decort_account
| Идентификатор<br>задачи | Описание |
| --- | --- |
| BANS-1055 | При передаче несуществующего ID аккаунта модуль завершал свою работу с некорректным текстом ошибки. Также модуль повторно выполнял запрос к API `cloudapi/account/get` без необходимости. |
### Модуль decort_vins
| Идентификатор<br>задачи | Описание |
| --- | --- |
| BANS-1053 | Модуль завершал свою работу ошибкой Python при выполнении восстановления внутренней сети из корзины. |
| BANS-1079 | Модуль отключал внешнюю сеть при незаданном параметре `ext_net_id`. |
| BANS-937 | Модуль завершал работу ошибкой запроса к API при `state: absent` для несуществующего объекта. |
### Модуль decort_disk
| Идентификатор<br>задачи | Описание |
| --- | --- |
| BANS-1062 | Модуль завершал работу ошибкой при `state: absent` для несуществующего объекта. |
### Модуль decort_k8s
| Идентификатор<br>задачи | Описание |
| --- | --- |
| BANS-1188 | Модуль завершал работу ошибкой при `state: absent` для объекта, который не найден или безвозвратно удален. |

View File

@@ -5,6 +5,7 @@
| Версия платформы | Версия модулей Ansible | | Версия платформы | Версия модулей Ansible |
|:----------------:|:--------------------------:| |:----------------:|:--------------------------:|
| 4.6.0 | 12.0.x |
| 4.5.0 | 11.0.x | | 4.5.0 | 11.0.x |
| 4.4.0 | 10.0.x | | 4.4.0 | 10.0.x |
| 4.4.0 build 963 | 9.0.x | | 4.4.0 build 963 | 9.0.x |

View File

@@ -7,9 +7,9 @@ module: decort_account
description: See L(Module Documentation,https://repository.basistech.ru/BASIS/decort-ansible/wiki/Home). # noqa: E501 description: See L(Module Documentation,https://repository.basistech.ru/BASIS/decort-ansible/wiki/Home). # noqa: E501
''' '''
from typing import Iterable from typing import Any, Iterable
from ansible.module_utils.basic import AnsibleModule from ansible.module_utils.basic import AnsibleModule
from ansible.module_utils.decort_utils import DecortController from ansible.module_utils.decort_utils import DecortController, sdk_types
class DecortAccount(DecortController): class DecortAccount(DecortController):
@@ -23,7 +23,7 @@ class DecortAccount(DecortController):
def amodule_init_args(self) -> dict: def amodule_init_args(self) -> dict:
return self.pack_amodule_init_args( return self.pack_amodule_init_args(
argument_spec=dict( argument_spec=dict(
access_emails=dict( send_access_emails=dict(
type='bool', type='bool',
), ),
acl=dict( acl=dict(
@@ -45,8 +45,13 @@ class DecortAccount(DecortController):
options=dict( options=dict(
rights=dict( rights=dict(
type='str', type='str',
choices=['R', 'RCX', 'ARCXDU'], choices=(
default='R', sdk_types.AccessTypeForSet
._member_names_
),
default=(
sdk_types.AccessTypeForSet.R.name
),
), ),
id=dict( id=dict(
type='str', type='str',
@@ -69,19 +74,19 @@ class DecortAccount(DecortController):
quotas=dict( quotas=dict(
type='dict', type='dict',
options=dict( options=dict(
cpu=dict( cpu_count=dict(
type='int', type='int',
), ),
disks_size=dict( storage_size_gb=dict(
type='int', type='int',
), ),
gpu=dict( gpu_count=dict(
type='int', type='int',
), ),
public_ip=dict( ext_ip_count=dict(
type='int', type='int',
), ),
ram=dict( ram_size_mb=dict(
type='int', type='int',
), ),
), ),
@@ -135,7 +140,7 @@ class DecortAccount(DecortController):
# Parameters or combinations of parameters that can # Parameters or combinations of parameters that can
# cause changing the object. # cause changing the object.
changing_params = [ changing_params = [
'access_emails', 'send_access_emails',
'acl', 'acl',
['id', 'name'], ['id', 'name'],
'quotas', 'quotas',
@@ -188,110 +193,308 @@ class DecortAccount(DecortController):
self.acc_id, self._acc_info = self.account_find( self.acc_id, self._acc_info = self.account_find(
account_name=self.aparams['name'], account_name=self.aparams['name'],
account_id=self.aparams['id'], account_id=self.aparams['id'],
resource_consumption=self.aparams['get_resource_consumption'], )
)
# If this is a repeated getting info # If this is a repeated getting info
else: else:
# If check mode is enabled, there is no needed to # If check mode is enabled, there is no needed to
# request info again # request info again
if not self.amodule.check_mode: if not self.amodule.check_mode:
self.acc_id, self._acc_info = self.account_find( self.acc_id, self._acc_info = self.account_find(
account_id=self.acc_id, account_id=self._acc_info.id,
resource_consumption=(
self.aparams['get_resource_consumption']
),
) )
self.facts = self.acc_info
if self._acc_info is not None:
acc_info_dict = self.acc_info.model_dump()
if self.aparams['get_resource_consumption']:
resource_consumption_dict = (
self.api.cloudapi.account.get_resource_consumption(
account_id=self.acc_info.id,
).model_dump()
)
resource_consumption_dict.pop('id')
resource_consumption_dict.pop('quotas')
acc_info_dict['resource_consumption'] = (
resource_consumption_dict
)
self.facts = acc_info_dict
def change(self): def change(self):
self.change_state() self.change_state()
self.change_acl() self.change_acl()
if self.account_update_args: need_account_update_api_call: bool = False
self.account_update(account_id=self.acc_id,
**self.account_update_args) sdk_param_send_access_emails = None
aparam_send_access_emails: bool | None = self.aparams[
'send_access_emails'
]
if (
aparam_send_access_emails is not None
and self.acc_info.send_access_emails != aparam_send_access_emails
):
sdk_param_send_access_emails = aparam_send_access_emails
need_account_update_api_call = True
sdk_param_name = None
aparam_name: str | None = self.aparams['name']
if (
self.aparams['id'] and aparam_name
and self.acc_info.name != aparam_name
):
sdk_param_name = aparam_name
need_account_update_api_call = True
sdk_param_cpu_count_quota = None
sdk_param_storage_size_quota_gb = None
sdk_param_gpu_count_quota = None
sdk_param_ext_ip_count_quota = None
sdk_param_ram_size_quota_mb = None
aparam_quotas: dict[str, Any] = self.aparams['quotas']
if aparam_quotas:
aparam_quotas_cpu_count: int | None = aparam_quotas['cpu_count']
if (
aparam_quotas_cpu_count is not None
and self.acc_info.quotas.cpu_count != aparam_quotas_cpu_count
):
sdk_param_cpu_count_quota = aparam_quotas_cpu_count
need_account_update_api_call = True
aparam_quotas_storage_size_gb: int | None = aparam_quotas[
'storage_size_gb'
]
if (
aparam_quotas_storage_size_gb is not None
and (
self.acc_info.quotas.storage_size_gb
!= aparam_quotas_storage_size_gb
)
):
sdk_param_storage_size_quota_gb = aparam_quotas_storage_size_gb
need_account_update_api_call = True
aparam_quotas_gpu_count: int | None = aparam_quotas['gpu_count']
if (
aparam_quotas_gpu_count is not None
and self.acc_info.quotas.gpu_count != aparam_quotas_gpu_count
):
sdk_param_gpu_count_quota = aparam_quotas_gpu_count
need_account_update_api_call = True
aparam_quotas_ext_ip_count: int | None = aparam_quotas[
'ext_ip_count'
]
if (
aparam_quotas_ext_ip_count is not None
and (
self.acc_info.quotas.ext_ip_count
!= aparam_quotas_ext_ip_count
)
):
sdk_param_ext_ip_count_quota = aparam_quotas_ext_ip_count
need_account_update_api_call = True
aparam_quotas_ram_size_mb: int | None = aparam_quotas[
'ram_size_mb'
]
if (
aparam_quotas_ram_size_mb is not None
and (
self.acc_info.quotas.ram_size_mb
!= aparam_quotas_ram_size_mb
)
):
sdk_param_ram_size_quota_mb = aparam_quotas_ram_size_mb
need_account_update_api_call = True
sdk_param_sep_pools = None
aparam_sep_pools: list[dict[str, Any]] = self.aparams['sep_pools']
if aparam_sep_pools is not None:
sep_pools: set[str] = set()
for sep in aparam_sep_pools:
sep_pool_names: list[str] = sep['pool_names']
for pool_name in sep_pool_names:
sep_pools.add(f'{sep["sep_id"]}_{pool_name}')
if set(self.acc_info.sep_pools) != sep_pools:
sdk_param_sep_pools = list(sep_pools)
need_account_update_api_call = True
sdk_param_description = None
aparam_desc: str | None = self.aparams['description']
if (
aparam_desc is not None
and self.acc_info.description != aparam_desc
):
sdk_param_description = aparam_desc
need_account_update_api_call = True
sdk_param_default_zone_id = None
aparam_default_zone_id: int | None = self.aparams['default_zone_id']
if (
aparam_default_zone_id is not None
and self.acc_info.default_zone_id != aparam_default_zone_id
):
sdk_param_default_zone_id = aparam_default_zone_id
need_account_update_api_call = True
if need_account_update_api_call:
OBJ = 'account'
self.sdk_checkmode(self.api.ca.account.update)(
account_id=self.acc_info.id,
cpu_count_quota=sdk_param_cpu_count_quota,
gpu_count_quota=sdk_param_gpu_count_quota,
name=sdk_param_name,
ext_ip_count_quota=sdk_param_ext_ip_count_quota,
ram_size_quota_mb=sdk_param_ram_size_quota_mb,
send_access_emails=sdk_param_send_access_emails,
storage_size_quota_gb=sdk_param_storage_size_quota_gb,
sep_pools=sdk_param_sep_pools,
description=sdk_param_description,
default_zone_id=sdk_param_default_zone_id,
)
if sdk_param_send_access_emails is not None:
smth = 'sending access emails'
if sdk_param_send_access_emails:
self.message(
self.MESSAGES.obj_smth_enabled(
obj=OBJ,
id=self.acc_info.id,
smth=smth,
)
)
else:
self.message(
self.MESSAGES.obj_smth_disabled(
obj=OBJ,
id=self.acc_info.id,
smth=smth,
)
)
if sdk_param_name is not None:
self.message(
self.MESSAGES.obj_renamed(
obj=OBJ,
id=self.acc_info.id,
new_name=sdk_param_name,
)
)
quotas = {
'CPU count quota': sdk_param_cpu_count_quota,
'storage size quota GB': sdk_param_storage_size_quota_gb,
'GPU count quota': sdk_param_gpu_count_quota,
'ext IP count quota': sdk_param_ext_ip_count_quota,
'RAM size quota MB': sdk_param_ram_size_quota_mb,
}
for q_name, q_value in quotas.items():
if q_value is not None:
self.message(
self.MESSAGES.obj_smth_changed(
obj=OBJ,
id=self.acc_info.id,
smth=q_name,
new_value=q_value
)
)
if sdk_param_default_zone_id is not None:
self.message(
self.MESSAGES.obj_smth_changed(
obj=OBJ,
id=self.acc_info.id,
smth='default_zone_id',
new_value=sdk_param_default_zone_id,
)
)
self.get_info() self.get_info()
def change_state(self): def change_state(self):
match self._acc_info: if self._acc_info is None:
case None: self.message(self.MESSAGES.obj_not_found(obj=self.OBJ))
self.message(self.MESSAGES.obj_not_found(obj=self.OBJ)) match self.aparams:
match self.aparams: case {'state': 'absent' | 'absent_permanently'}:
case {'state': 'absent' | 'absent_permanently'}: pass
pass case {'state': 'confirmed' | 'disabled' | 'present'}:
case {'state': 'confirmed' | 'disabled' | 'present'}: self.exit(fail=True)
self.exit(fail=True) elif self._acc_info.status == sdk_types.AccountStatus.DESTROYED:
case {'status': 'DESTROYED'}: match self.aparams:
match self.aparams: case {'state': 'absent' | 'absent_permanently'}:
case {'state': 'absent' | 'absent_permanently'}: self.message(
self.message( self.MESSAGES.obj_deleted(
self.MESSAGES.obj_deleted( obj=self.OBJ,
obj=self.OBJ, id=self.acc_info.id,
id=self.acc_id, permanently=True,
permanently=True, already=True,
already=True,
)
) )
case {'state': 'confirmed' | 'disabled' | 'present'}: )
self.message( case {'state': 'confirmed' | 'disabled' | 'present'}:
self.MESSAGES.obj_not_restored(obj=self.OBJ, self.message(
id=self.acc_id) self.MESSAGES.obj_not_restored(obj=self.OBJ,
) id=self.acc_info.id)
self.exit(fail=True)
case {'status': 'DELETED'}:
match self.aparams:
case {'state': 'absent'}:
self.message(
self.MESSAGES.obj_deleted(
obj=self.OBJ,
id=self.acc_id,
permanently=False,
already=True,
)
) )
case {'state': 'absent_permanently'}: self.exit(fail=True)
self.delete(permanently=True) elif self._acc_info.status == sdk_types.AccountStatus.DELETED:
case {'state': 'confirmed' | 'present'}: match self.aparams:
self.restore() case {'state': 'absent'}:
case {'state': 'disabled'}: self.message(
self.restore() self.MESSAGES.obj_deleted(
self.disable() obj=self.OBJ,
case {'status': 'CONFIRMED'}: id=self.acc_info.id,
match self.aparams: permanently=False,
case {'state': 'absent'}: already=True,
self.delete() )
case {'state': 'absent_permanently'}: )
self.delete(permanently=True) case {'state': 'absent_permanently'}:
case {'state': 'confirmed' | 'present'}: self.delete(permanently=True)
pass case {'state': 'confirmed' | 'present'}:
case {'state': 'disabled'}: self.restore()
self.disable() case {'state': 'disabled'}:
case {'status': 'DISABLED'}: self.restore()
match self.aparams: self.disable()
case {'state': 'absent'}: elif self._acc_info.status == sdk_types.AccountStatus.CONFIRMED:
self.delete() match self.aparams:
case {'state': 'absent_permanently'}: case {'state': 'absent'}:
self.delete(permanently=True) self.delete()
case {'state': 'confirmed'}: case {'state': 'absent_permanently'}:
self.enable() self.delete(permanently=True)
case {'state': 'present' | 'disabled'}: case {'state': 'confirmed' | 'present'}:
pass pass
case {'state': 'disabled'}:
self.disable()
elif self._acc_info.status == sdk_types.AccountStatus.DISABLED:
match self.aparams:
case {'state': 'absent'}:
self.delete()
case {'state': 'absent_permanently'}:
self.delete(permanently=True)
case {'state': 'confirmed'}:
self.enable()
case {'state': 'present' | 'disabled'}:
pass
def delete(self, permanently=False): def delete(self, permanently=False):
self.account_delete(account_id=self.acc_id, permanently=permanently) self.account_delete(
account_id=self.acc_info.id,
permanently=permanently
)
self.get_info() self.get_info()
def disable(self): def disable(self):
self.account_disable(account_id=self.acc_id) self.sdk_checkmode(self.api.ca.account.disable)(
account_id=self.acc_info.id
)
self.get_info() self.get_info()
def enable(self): def enable(self):
self.account_enable(account_id=self.acc_id) self.sdk_checkmode(self.api.ca.account.enable)(
account_id=self.acc_info.id
)
self.get_info() self.get_info()
def restore(self): def restore(self):
self.account_restore(account_id=self.acc_id) self.account_restore(account_id=self.acc_info.id)
self.get_info() self.get_info()
def change_acl(self): def change_acl(self):
@@ -299,7 +502,7 @@ class DecortAccount(DecortController):
return return
actual_users = { actual_users = {
u['userGroupId']: u['right'] for u in self.acc_info['acl'] u.user_name: u.access_type for u in self.acc_info.acl
} }
actual_users_ids = set(actual_users.keys()) actual_users_ids = set(actual_users.keys())
@@ -328,8 +531,8 @@ class DecortAccount(DecortController):
aparams_users_ids.intersection(actual_users_ids) aparams_users_ids.intersection(actual_users_ids)
upd_users = dict() upd_users = dict()
for id in upd_users_ids: for id in upd_users_ids:
if actual_users[id] == 'CXDRAU': if actual_users[id] == sdk_types.AccessType.CXDRAU:
actual_user_rights = 'ARCXDU' actual_user_rights = sdk_types.AccessTypeForSet.ARCXDU
else: else:
actual_user_rights = actual_users[id] actual_user_rights = actual_users[id]
@@ -341,68 +544,12 @@ class DecortAccount(DecortController):
actual_users_ids.difference(aparams_users_ids) actual_users_ids.difference(aparams_users_ids)
if del_users_ids or new_users or upd_users: if del_users_ids or new_users or upd_users:
self.account_change_acl(account_id=self.acc_id, self.account_change_acl(account_id=self.acc_info.id,
del_users=del_users_ids, del_users=del_users_ids,
add_users=new_users, add_users=new_users,
upd_users=upd_users) upd_users=upd_users)
self.get_info() self.get_info()
@property
def account_update_args(self) -> dict:
result_args = dict()
aparam_access_emails = self.aparams['access_emails']
if (aparam_access_emails is not None
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.acc_info['name'] != aparam_name):
result_args['name'] = aparam_name
aparam_quotas = self.aparams['quotas']
if aparam_quotas:
quotas_naming = [
['cpu', 'CU_C', 'cpu_quota'],
['disks_size', 'CU_DM', 'disks_size_quota'],
['gpu', 'gpu_units', 'gpu_quota'],
['public_ip', 'CU_I', 'public_ip_quota'],
['ram', 'CU_M', 'ram_quota'],
]
for aparam, info_key, result_arg in quotas_naming:
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]
aparam_sep_pools = self.aparams['sep_pools']
if aparam_sep_pools is not None:
sep_pools = set()
for sep in aparam_sep_pools:
for pool_name in sep['pool_names']:
sep_pools.add(
f'{sep["sep_id"]}_{pool_name}'
)
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.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: def check_aparam_default_zone_id(self) -> bool | None:
aparam_default_zone_id = self.aparams['default_zone_id'] aparam_default_zone_id = self.aparams['default_zone_id']
if aparam_default_zone_id is not None: if aparam_default_zone_id is not None:

View File

@@ -39,21 +39,21 @@ class decort_bservice(DecortController):
self.fail_json(**self.result) self.fail_json(**self.result)
# fail the module -> exit # fail the module -> exit
# now validate RG # now validate RG
validated_rg_id, validated_rg_facts = self.rg_find( validated_rg_id, validated_rg_model = self.rg_find(
arg_account_id=validated_acc_id, arg_account_id=validated_acc_id,
arg_rg_id=arg_amodule.params['rg_id'], arg_rg_id=arg_amodule.params['rg_id'],
arg_rg_name=arg_amodule.params['rg_name'] arg_rg_name=arg_amodule.params['rg_name']
) )
if not validated_rg_id: if not validated_rg_id or not validated_rg_model:
self.result['failed'] = True self.result['failed'] = True
self.result['changed'] = False self.result['changed'] = False
self.result['msg'] = "Cannot find RG ID {} / name '{}'.".format(arg_amodule.params['rg_id'], self.result['msg'] = "Cannot find RG ID {} / name '{}'.".format(arg_amodule.params['rg_id'],
arg_amodule.params['rg_name']) arg_amodule.params['rg_name'])
self.fail_json(**self.result) self.amodule.fail_json(**self.result)
arg_amodule.params['rg_id'] = validated_rg_id arg_amodule.params['rg_id'] = validated_rg_id
arg_amodule.params['rg_name'] = validated_rg_facts['name'] arg_amodule.params['rg_name'] = validated_rg_model.name
validated_acc_id = validated_rg_facts['accountId'] validated_acc_id = validated_rg_model.account_id
self.bservice_id, self.bservice_info = self.bservice_find( self.bservice_id, self.bservice_info = self.bservice_find(
validated_acc_id, validated_acc_id,
@@ -104,11 +104,11 @@ class decort_bservice(DecortController):
return return
def create(self): def create(self):
self.bservice_id = self.bservice_id = self.bservice_provision( self.bservice_id = self.sdk_checkmode(self.api.ca.bservice.create)(
self.amodule.params['name'], name=self.amodule.params['name'],
self.amodule.params['rg_id'], rg_id=self.amodule.params['rg_id'],
self.amodule.params['sshuser'], ssh_user_name=self.amodule.params['sshuser'],
self.amodule.params['sshkey'], ssh_public_key=self.amodule.params['sshkey'],
zone_id=self.aparams['zone_id'], zone_id=self.aparams['zone_id'],
) )
if self.bservice_id: if self.bservice_id:
@@ -136,7 +136,10 @@ class decort_bservice(DecortController):
pass pass
def destroy(self): def destroy(self):
self.bservice_delete(self.bservice_id) self.sdk_checkmode(self.api.ca.bservice.delete)(
bservice_id=self.bservice_id,
permanently=True,
)
self.bservice_info['status'] = 'DELETED' self.bservice_info['status'] = 'DELETED'
self.bservice_should_exist = False self.bservice_should_exist = False
return return

View File

@@ -58,6 +58,7 @@ class decort_disk(DecortController):
name=arg_amodule.params['name'] if "name" in arg_amodule.params else "", name=arg_amodule.params['name'] if "name" in arg_amodule.params else "",
account_id=self.acc_id, account_id=self.acc_id,
check_state=False, check_state=False,
fail_if_not_found=False,
) )
if arg_amodule.params['place_with']: if arg_amodule.params['place_with']:
@@ -67,18 +68,30 @@ class decort_disk(DecortController):
self.disk_id = validated_disk_id self.disk_id = validated_disk_id
self.disk_info = validated_disk_facts self.disk_info = validated_disk_facts
if self.disk_id: if self.disk_id and self.disk_info.status not in [
self.acc_id = validated_disk_facts['accountId'] sdk_types.DiskStatus.DESTROYED,
sdk_types.DiskStatus.PURGED,
]:
self.acc_id = validated_disk_facts.account_id
self.check_amodule_args_for_change() self.check_amodule_args_for_change()
else: elif (
self.amodule.params['state'] == 'present'
and not arg_amodule.params['id']
):
self.check_amodule_args_for_create() self.check_amodule_args_for_create()
def compare_iotune_params(self, new_iotune: dict, current_iotune: dict): def compare_iotune_params(
self,
new_iotune: dict,
current_iotune: sdk_types.IOTuneAPIResultNM,
):
io_fields = sdk_types.IOTuneAPIResultNM.model_fields.keys() io_fields = sdk_types.IOTuneAPIResultNM.model_fields.keys()
for field in io_fields: for field in io_fields:
new_value = new_iotune.get(field) new_value = new_iotune.get(field)
current_value = current_iotune.get(field) if new_value is None:
continue
current_value = getattr(current_iotune, field, None)
if new_value != current_value: if new_value != current_value:
return False return False
@@ -115,7 +128,11 @@ class decort_disk(DecortController):
) )
#IO tune #IO tune
aparam_limit_io: dict[str, int | None] = self.amodule.params['limitIO'] aparam_limit_io: dict[str, int | None] = self.amodule.params['limitIO']
self.limit_io(aparam_limit_io=aparam_limit_io) if (
aparam_limit_io
and any(value is not None for value in aparam_limit_io.values())
):
self.limit_io(aparam_limit_io=aparam_limit_io)
#set share status #set share status
if self.amodule.params['shareable']: if self.amodule.params['shareable']:
self.sdk_checkmode(self.api.cloudapi.disks.share)( self.sdk_checkmode(self.api.cloudapi.disks.share)(
@@ -133,13 +150,13 @@ class decort_disk(DecortController):
#rename if id present #rename if id present
if ( if (
self.amodule.params['name'] is not None self.amodule.params['name'] is not None
and self.amodule.params['name'] != self.disk_info['name'] and self.amodule.params['name'] != self.disk_info.name
): ):
self.rename() self.rename()
#resize #resize
if ( if (
self.amodule.params['size'] is not None self.amodule.params['size'] is not None
and self.amodule.params['size'] != self.disk_info['sizeMax'] and self.amodule.params['size'] != self.disk_info.size_max_gb
): ):
self.sdk_checkmode(self.api.cloudapi.disks.resize2)( self.sdk_checkmode(self.api.cloudapi.disks.resize2)(
disk_id=self.disk_id, disk_id=self.disk_id,
@@ -147,16 +164,19 @@ class decort_disk(DecortController):
) )
#IO TUNE #IO TUNE
aparam_limit_io: dict[str, int | None] = self.amodule.params['limitIO'] aparam_limit_io: dict[str, int | None] = self.amodule.params['limitIO']
if aparam_limit_io: if (
aparam_limit_io
and any(value is not None for value in aparam_limit_io.values())
):
if not self.compare_iotune_params( if not self.compare_iotune_params(
new_iotune=aparam_limit_io, new_iotune=aparam_limit_io,
current_iotune=self.disk_info['iotune'], current_iotune=self.disk_info.io_tune,
): ):
self.limit_io(aparam_limit_io=aparam_limit_io) self.limit_io(aparam_limit_io=aparam_limit_io)
#share check/update #share check/update
#raise Exception(self.amodule.params['shareable']) #raise Exception(self.amodule.params['shareable'])
if self.amodule.params['shareable'] != self.disk_info['shareable']: if self.amodule.params['shareable'] != self.disk_info.shared:
if self.amodule.params['shareable']: if self.amodule.params['shareable']:
self.sdk_checkmode(self.api.cloudapi.disks.share)( self.sdk_checkmode(self.api.cloudapi.disks.share)(
disk_id=self.disk_id, disk_id=self.disk_id,
@@ -169,7 +189,7 @@ class decort_disk(DecortController):
aparam_storage_policy_id = self.aparams['storage_policy_id'] aparam_storage_policy_id = self.aparams['storage_policy_id']
if ( if (
aparam_storage_policy_id is not None aparam_storage_policy_id is not None
and aparam_storage_policy_id != self.disk_info['storage_policy_id'] and aparam_storage_policy_id != self.disk_info.storage_policy_id
): ):
self.sdk_checkmode(self.api.ca.disks.change_disk_storage_policy)( self.sdk_checkmode(self.api.ca.disks.change_disk_storage_policy)(
disk_id=self.disk_id, disk_id=self.disk_id,
@@ -183,7 +203,7 @@ class decort_disk(DecortController):
detach=self.amodule.params['force_detach'], detach=self.amodule.params['force_detach'],
permanently=self.amodule.params['permanently'], permanently=self.amodule.params['permanently'],
) )
self.disk_id, self.disk_info = self._disk_get_by_id(self.disk_id) self.disk_info = self._disk_get_by_id(disk_id=self.disk_id)
return return
def rename(self): def rename(self):
@@ -199,7 +219,7 @@ class decort_disk(DecortController):
self.result['changed'] = False self.result['changed'] = False
if self.disk_id: if self.disk_id:
self.result['msg'] = ("No state change required for Disk ID {} because of its " self.result['msg'] = ("No state change required for Disk ID {} because of its "
"current status '{}'.").format(self.disk_id, self.disk_info['status']) "current status '{}'.").format(self.disk_id, self.disk_info.status)
else: else:
self.result['msg'] = ("No state change to '{}' can be done for " self.result['msg'] = ("No state change to '{}' can be done for "
"non-existent Disk.").format(self.amodule.params['state']) "non-existent Disk.").format(self.amodule.params['state'])
@@ -219,28 +239,7 @@ class decort_disk(DecortController):
if check_mode or self.disk_info is None: if check_mode or self.disk_info is None:
return ret_dict return ret_dict
# remove io param with zero value return self.disk_info.model_dump()
clean_io = [param for param in self.disk_info['iotune'] if self.disk_info['iotune'][param] == 0]
for key in clean_io: del self.disk_info['iotune'][key]
ret_dict['id'] = self.disk_info['id']
ret_dict['name'] = self.disk_info['name']
ret_dict['size'] = self.disk_info['sizeMax']
ret_dict['state'] = self.disk_info['status']
ret_dict['account_id'] = self.disk_info['accountId']
ret_dict['sep_id'] = self.disk_info['sepId']
ret_dict['pool'] = self.disk_info['pool']
ret_dict['computes'] = self.disk_info['computes']
ret_dict['gid'] = self.disk_info['gid']
ret_dict['iotune'] = self.disk_info['iotune']
ret_dict['size_available'] = self.disk_info['sizeAvailable']
ret_dict['size_used'] = self.disk_info['sizeUsed']
ret_dict['storage_policy_id'] = self.disk_info['storage_policy_id']
ret_dict['to_clean'] = self.disk_info['to_clean']
ret_dict['cache_mode'] = self.disk_info['cache']
ret_dict['blkdiscard'] = self.disk_info['blkdiscard']
return ret_dict
@property @property
def amodule_init_args(self) -> dict: def amodule_init_args(self) -> dict:
@@ -391,8 +390,8 @@ class decort_disk(DecortController):
aparam_storage_policy_id = self.aparams['storage_policy_id'] aparam_storage_policy_id = self.aparams['storage_policy_id']
if ( if (
aparam_storage_policy_id is not None aparam_storage_policy_id is not None
and aparam_storage_policy_id and aparam_storage_policy_id
not in self.acc_info['storage_policy_ids'] not in self.acc_info.storage_policy_ids
): ):
check_errors = True check_errors = True
self.message( self.message(
@@ -431,24 +430,24 @@ class decort_disk(DecortController):
amodule = self.amodule amodule = self.amodule
if self.disk_id: if self.disk_id:
#disk exist #disk exist
if self.disk_info['status'] in ["MODELED", "CREATING"]: if self.disk_info.status in [sdk_types.DiskStatus.MODELED, sdk_types.DiskStatus.CREATING]:
self.result['failed'] = True self.result['failed'] = True
self.result['changed'] = False self.result['changed'] = False
self.result['msg'] = ("No change can be done for existing Disk ID {} because of its current " self.result['msg'] = ("No change can be done for existing Disk ID {} because of its current "
"status '{}'").format(self.disk_id, self.disk_info['status']) "status '{}'").format(self.disk_id, self.disk_info.status)
# "ASSIGNED","CREATED","DELETED","PURGED", "DESTROYED" # "ASSIGNED","CREATED","DELETED","PURGED", "DESTROYED"
elif self.disk_info['status'] in ["ASSIGNED","CREATED"]: elif self.disk_info.status in [sdk_types.DiskStatus.CREATED, sdk_types.DiskStatus.ASSIGNED]:
if amodule.params['state'] == 'absent': if amodule.params['state'] == 'absent':
self.delete() self.delete()
elif amodule.params['state'] == 'present': elif amodule.params['state'] == 'present':
self.action() self.action()
elif self.disk_info['status'] in ["PURGED", "DESTROYED"]: elif self.disk_info.status in [sdk_types.DiskStatus.PURGED, sdk_types.DiskStatus.DESTROYED]:
#re-provision disk #re-provision disk
if amodule.params['state'] in ('present'): if amodule.params['state'] in ('present'):
self.create() self.create()
else: else:
self.nop() self.nop()
elif self.disk_info['status'] == "DELETED": elif self.disk_info.status == sdk_types.DiskStatus.DELETED:
if amodule.params['state'] in ('present'): if amodule.params['state'] in ('present'):
self.action(restore=True) self.action(restore=True)
elif (amodule.params['state'] == 'absent' and elif (amodule.params['state'] == 'absent' and
@@ -459,8 +458,24 @@ class decort_disk(DecortController):
else: else:
# preexisting Disk was not found # preexisting Disk was not found
if amodule.params['state'] == 'absent': if amodule.params['state'] == 'absent':
self.nop() self.exit()
else:
if (
(
amodule.params['state'] == 'present'
or amodule.params['state'] is None
)
and amodule.params['id']
):
self.message(
f'Disk with ID {amodule.params['id']} not found.'
)
self.exit(fail=True)
if (
amodule.params['state'] == 'present'
and not amodule.params['id']
):
self.create() self.create()
if self.result['failed']: if self.result['failed']:

View File

@@ -59,9 +59,11 @@ class DecortDiskList(DecortController):
storage_policy_id=dict( storage_policy_id=dict(
type='int', type='int',
), ),
type=dict( rg_id=dict(
type='str', type='int',
choices=sdk_types.DiskType._member_names_, ),
vm_id=dict(
type='int',
), ),
), ),
), ),
@@ -108,7 +110,6 @@ class DecortDiskList(DecortController):
def get_info(self): def get_info(self):
aparam_filter: dict[str, Any] = self.aparams['filter'] aparam_filter: dict[str, Any] = self.aparams['filter']
aparam_status: str | None = aparam_filter['status'] aparam_status: str | None = aparam_filter['status']
aparam_type: str | None = aparam_filter['type']
aparam_pagination: dict[str, Any] = self.aparams['pagination'] aparam_pagination: dict[str, Any] = self.aparams['pagination']
@@ -137,10 +138,8 @@ class DecortDiskList(DecortController):
if aparam_status else None if aparam_status else None
), ),
storage_policy_id=aparam_filter['storage_policy_id'], storage_policy_id=aparam_filter['storage_policy_id'],
type=( rg_id=aparam_filter['rg_id'],
sdk_types.DiskType[aparam_type] vm_id=aparam_filter['vm_id'],
if aparam_type else None
),
page_number=aparam_pagination['number'], page_number=aparam_pagination['number'],
page_size=aparam_pagination['size'], page_size=aparam_pagination['size'],
sort_by=sort_by, sort_by=sort_by,

View File

@@ -356,7 +356,7 @@ class decort_group(DecortController):
else: else:
if ( if (
aparam_storage_policy_id aparam_storage_policy_id
not in self.rg_info['storage_policy_ids'] not in self.rg_info.storage_policy_ids
): ):
check_errors = True check_errors = True
self.message( self.message(
@@ -366,7 +366,7 @@ class decort_group(DecortController):
) )
if ( if (
aparam_storage_policy_id aparam_storage_policy_id
not in self.acc_info['storage_policy_ids'] not in self.acc_info.storage_policy_ids
): ):
check_errors = True check_errors = True
self.message( self.message(

View File

@@ -18,8 +18,8 @@ class decort_image(DecortController):
super(decort_image, self).__init__(AnsibleModule(**self.amodule_init_args)) super(decort_image, self).__init__(AnsibleModule(**self.amodule_init_args))
amodule = self.amodule amodule = self.amodule
self.validated_image_id = 0 self.validated_image_id: int = 0
self.validated_virt_image_id = 0 self.validated_virt_image_id: int = 0
self.validated_image_name = amodule.params['image_name'] self.validated_image_name = amodule.params['image_name']
self.validated_virt_image_name = None self.validated_virt_image_name = None
self.image_info: dict self.image_info: dict
@@ -180,7 +180,9 @@ class decort_image(DecortController):
def decort_image_delete(self,amodule): def decort_image_delete(self,amodule):
# function that removes an image # function that removes an image
self.image_delete(imageId=amodule.image_id_delete) self.sdk_checkmode(self.api.ca.image.delete)(
image_id=amodule.image_id_delete
)
_, image_facts = decort_image._image_get_by_id(self, amodule.image_id_delete) _, image_facts = decort_image._image_get_by_id(self, amodule.image_id_delete)
self.result['facts'] = decort_image.decort_image_package_facts(image_facts, amodule.check_mode) self.result['facts'] = decort_image.decort_image_package_facts(image_facts, amodule.check_mode)
return return
@@ -195,17 +197,24 @@ class decort_image(DecortController):
image_id, image_facts = decort_image.decort_virt_image_find(self, amodule) image_id, image_facts = decort_image.decort_virt_image_find(self, amodule)
self.result['facts'] = decort_image.decort_image_package_facts(image_facts, amodule.check_mode) self.result['facts'] = decort_image.decort_image_package_facts(image_facts, amodule.check_mode)
return image_id, image_facts return image_id, image_facts
@DecortController.handle_sdk_exceptions
def decort_image_rename(self,amodule): def decort_image_rename(self,amodule):
# image renaming function # image renaming function
image_facts = self.image_rename(imageId=self.validated_image_id, name=amodule.params['image_name']) self.sdk_checkmode(self.api.ca.image.rename)(
image_id=self.validated_image_id,
name=amodule.params['image_name'],
)
self.result['msg'] = ("Image renamed successfully") self.result['msg'] = ("Image renamed successfully")
image_id, image_facts = decort_image.decort_image_find(self, amodule) image_id, image_facts = decort_image.decort_image_find(self, amodule)
return image_id, image_facts return image_id, image_facts
@DecortController.handle_sdk_exceptions
def decort_virt_image_rename(self, amodule): def decort_virt_image_rename(self, amodule):
image_facts = self.image_rename(imageId=self.validated_virt_image_id, self.sdk_checkmode(self.api.ca.image.rename)(
name=amodule.params['virt_name']) image_id=self.validated_virt_image_id,
name=amodule.params['virt_name'],
)
self.result['msg'] = ("Virtual image renamed successfully") self.result['msg'] = ("Virtual image renamed successfully")
image_id, image_facts = self.decort_virt_image_find(amodule) image_id, image_facts = self.decort_virt_image_find(amodule)
return image_id, image_facts return image_id, image_facts
@@ -264,6 +273,8 @@ class decort_image(DecortController):
ret_dict['hot_resize'] = arg_image_facts['hotResize'] ret_dict['hot_resize'] = arg_image_facts['hotResize']
ret_dict['storage_policy_id'] = arg_image_facts['storage_policy_id'] ret_dict['storage_policy_id'] = arg_image_facts['storage_policy_id']
ret_dict['to_clean'] = arg_image_facts['to_clean'] ret_dict['to_clean'] = arg_image_facts['to_clean']
ret_dict['independent'] = arg_image_facts['independent']
ret_dict['links_to'] = arg_image_facts['linksTo']
return ret_dict return ret_dict
@property @property
@@ -372,7 +383,7 @@ class decort_image(DecortController):
if ( if (
aparam_storage_policy_id is not None aparam_storage_policy_id is not None
and aparam_storage_policy_id and aparam_storage_policy_id
not in self.acc_info['storage_policy_ids'] not in self.acc_info.storage_policy_ids
): ):
check_errors = True check_errors = True
self.message( self.message(
@@ -426,7 +437,7 @@ class decort_image(DecortController):
) )
elif ( elif (
aparam_storage_policy_id aparam_storage_policy_id
not in self.acc_info['storage_policy_ids'] not in self.acc_info.storage_policy_ids
): ):
check_errors = True check_errors = True
self.message( self.message(

View File

@@ -20,7 +20,7 @@ class decort_k8s(DecortController):
validated_acc_id = 0 validated_acc_id = 0
validated_rg_id = 0 validated_rg_id = 0
validated_rg_facts = None validated_rg_model = None
validated_k8ci_id = 0 validated_k8ci_id = 0
self.k8s_should_exist = False self.k8s_should_exist = False
self.is_k8s_stopped_or_will_be_stopped: None | bool = None self.is_k8s_stopped_or_will_be_stopped: None | bool = None
@@ -55,12 +55,12 @@ class decort_k8s(DecortController):
self.amodule.fail_json(**self.result) self.amodule.fail_json(**self.result)
# fail the module -> exit # fail the module -> exit
# now validate RG # now validate RG
validated_rg_id, validated_rg_facts = self.rg_find( validated_rg_id, validated_rg_model = self.rg_find(
arg_account_id=validated_acc_id, arg_account_id=validated_acc_id,
arg_rg_id=arg_amodule.params['rg_id'], arg_rg_id=arg_amodule.params['rg_id'],
arg_rg_name=arg_amodule.params['rg_name'] arg_rg_name=arg_amodule.params['rg_name']
) )
if not validated_rg_id: if not validated_rg_id or not validated_rg_model:
self.result['failed'] = True self.result['failed'] = True
self.result['changed'] = False self.result['changed'] = False
self.result['msg'] = "Cannot find RG ID {} / name '{}'.".format(arg_amodule.params['rg_id'], self.result['msg'] = "Cannot find RG ID {} / name '{}'.".format(arg_amodule.params['rg_id'],
@@ -70,19 +70,21 @@ class decort_k8s(DecortController):
self.rg_id = validated_rg_id self.rg_id = validated_rg_id
arg_amodule.params['rg_id'] = validated_rg_id arg_amodule.params['rg_id'] = validated_rg_id
arg_amodule.params['rg_name'] = validated_rg_facts['name'] arg_amodule.params['rg_name'] = validated_rg_model.name
self.acc_id = validated_rg_facts['accountId'] self.acc_id = validated_rg_model.account_id
self.k8s_id,self.k8s_info = self.k8s_find(k8s_id=arg_amodule.params['id'], self.k8s_id, self._k8s_info = self.k8s_find(
k8s_name=arg_amodule.params['name'], k8s_id=arg_amodule.params['id'],
rg_id=validated_rg_id, k8s_name=arg_amodule.params['name'],
check_state=False) rg_id=validated_rg_id,
check_state=False,
)
if self.k8s_id and self.k8s_info['status'] != 'DESTROYED': if self.k8s_id and self.k8s_info['status'] != 'DESTROYED':
self.k8s_should_exist = True self.k8s_should_exist = True
self.acc_id = self.k8s_info['accountId'] self.acc_id = self.k8s_info['account_id']
self.check_amodule_args_for_change() self.check_amodule_args_for_change()
else: elif arg_amodule.params['state'] != 'absent':
self.check_amodule_args_for_create() self.check_amodule_args_for_create()
return return
@@ -96,32 +98,14 @@ class decort_k8s(DecortController):
config=None, config=None,
) )
if self.amodule.params['getConfig'] and self.k8s_info['techStatus'] == "STARTED":
ret_dict['config'] = self.k8s_getConfig()
if check_mode: if check_mode:
# in check mode return immediately with the default values
return ret_dict return ret_dict
#if self.k8s_facts is None: self.k8s_info['vins_id'] = self.k8s_vins_id
# #if void facts provided - change state value to ABSENT and return self.k8s_info['config'] = None
# ret_dict['state'] = "ABSENT" if self.amodule.params['getConfig'] and self.k8s_info['tech_status'] == "STARTED":
# return ret_dict self.k8s_info['config'] = self.k8s_getConfig()
return self.k8s_info
ret_dict['id'] = self.k8s_info['id']
ret_dict['name'] = self.k8s_info['name']
ret_dict['techStatus'] = self.k8s_info['techStatus']
ret_dict['state'] = self.k8s_info['status']
ret_dict['rg_id'] = self.k8s_info['rgId']
ret_dict['vins_id'] = self.k8s_vins_id
ret_dict['account_id'] = self.acc_id
ret_dict['k8s_Masters'] = self.k8s_info['k8sGroups']['masters']
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
def nop(self): def nop(self):
"""No operation (NOP) handler for k8s cluster management by decort_k8s module. """No operation (NOP) handler for k8s cluster management by decort_k8s module.
@@ -204,10 +188,12 @@ class decort_k8s(DecortController):
self.result['failed'] = True self.result['failed'] = True
self.amodule.fail_json(**self.result) self.amodule.fail_json(**self.result)
self.k8s_id,self.k8s_info = self.k8s_find(k8s_id=k8s_id, self.k8s_id, self._k8s_info = self.k8s_find(
k8s_name=self.amodule.params['name'], k8s_id=k8s_id,
rg_id=self.rg_id, k8s_name=self.amodule.params['name'],
check_state=False) rg_id=self.rg_id,
check_state=False,
)
if self.k8s_id: if self.k8s_id:
self.k8s_should_exist = True self.k8s_should_exist = True
@@ -219,19 +205,22 @@ class decort_k8s(DecortController):
self.aparams['storage_policy_id'] self.aparams['storage_policy_id']
), ),
) )
self.k8s_info = self.k8s_get_by_id(k8s_id=self.k8s_id) self._k8s_info = self.k8s_get_by_id(k8s_id=self.k8s_id)
return return
def destroy(self): def destroy(self):
self.k8s_delete(self.k8s_id,self.amodule.params['permanent']) self.sdk_checkmode(self.api.ca.k8s.delete)(
k8s_id=self.k8s_id,
permanently=self.amodule.params['permanent'],
)
self.k8s_info['status'] = 'DELETED' self.k8s_info['status'] = 'DELETED'
self.k8s_should_exist = False self.k8s_should_exist = False
return return
def action(self, disared_state, preupdate: bool = False): def action(self, disared_state, preupdate: bool = False):
if self.amodule.params['master_chipset'] is not None: if self.amodule.params['master_chipset'] is not None:
for master_node in self.k8s_info['k8sGroups']['masters'][ for master_node in self.k8s_info['node_groups']['master'][
'detailedInfo' 'vms'
]: ]:
_, master_node_info, _ = self._compute_get_by_id( _, master_node_info, _ = self._compute_get_by_id(
comp_id=master_node['id'] comp_id=master_node['id']
@@ -247,17 +236,27 @@ class decort_k8s(DecortController):
self.exit(fail=True) self.exit(fail=True)
if ( if (
self.aparams['name'] is not None (
and self.aparams['name'] != self.k8s_info['name'] self.aparams['name'] is not None
and self.aparams['name'] != self.k8s_info['name']
)
or (
self.aparams['description'] is not None
and self.aparams['description'] != self.k8s_info['description']
)
): ):
self.k8s_update(id=self.k8s_id, name=self.aparams['name']) self.sdk_checkmode(self.api.ca.k8s.update)(
k8s_id=self.k8s_id,
description=self.aparams['description'],
name=self.aparams['name'],
)
if preupdate: if preupdate:
# K8s info updating # K8s info updating
self.k8s_info = self.k8s_get_by_id(k8s_id=self.k8s_id) self._k8s_info = self.k8s_get_by_id(k8s_id=self.k8s_id)
#k8s state #k8s state
self.k8s_state(self.k8s_info, disared_state) self.k8s_state(self.k8s_info, disared_state)
self.k8s_info = self.k8s_get_by_id(k8s_id=self.k8s_id) self._k8s_info = self.k8s_get_by_id(k8s_id=self.k8s_id)
#check groups and modify if needed #check groups and modify if needed
if self.aparams['workers'] is not None: if self.aparams['workers'] is not None:
self.k8s_workers_modify( self.k8s_workers_modify(
@@ -266,14 +265,17 @@ class decort_k8s(DecortController):
) )
aparam_zone_id = self.aparams['zone_id'] aparam_zone_id = self.aparams['zone_id']
if aparam_zone_id is not None and aparam_zone_id != self.k8s_info['zoneId']: if (
aparam_zone_id is not None
and aparam_zone_id != self.k8s_info['zone_id']
):
self.k8s_migrate_to_zone( self.k8s_migrate_to_zone(
k8s_id=self.k8s_id, k8s_id=self.k8s_id,
zone_id=aparam_zone_id, zone_id=aparam_zone_id,
) )
if self.result['changed'] == True: if self.result['changed'] == True:
self.k8s_info = self.k8s_get_by_id(k8s_id=self.k8s_id) self._k8s_info = self.k8s_get_by_id(k8s_id=self.k8s_id)
#TODO check workers metadata and modify if needed #TODO check workers metadata and modify if needed
return return
@@ -413,7 +415,6 @@ class decort_k8s(DecortController):
), ),
description=dict( description=dict(
type='str', type='str',
default='Created by decort ansible module',
), ),
with_lb=dict( with_lb=dict(
type='bool', type='bool',
@@ -473,25 +474,25 @@ class decort_k8s(DecortController):
self.is_k8s_stopped_or_will_be_stopped = ( self.is_k8s_stopped_or_will_be_stopped = (
( (
self.k8s_info['techStatus'] == 'STOPPED' self.k8s_info['tech_status'] == 'STOPPED'
and ( and (
self.aparams['state'] is None self.aparams['state'] is None
or self.aparams['state'] in ('present', 'stopped') or self.aparams['state'] in ('present', 'stopped')
) )
) )
or ( or (
self.k8s_info['techStatus'] != 'STOPPED' self.k8s_info['tech_status'] != 'STOPPED'
and self.aparams['state'] == 'stopped' and self.aparams['state'] == 'stopped'
) )
) )
aparam_sysctl = self.aparams['lb_sysctl'] aparam_sysctl = self.aparams['lb_sysctl']
if aparam_sysctl is not None: if aparam_sysctl is not None:
_, lb_info = self._lb_get_by_id(lb_id=self.k8s_info['lbId']) lb_model = self._lb_get_by_id(lb_id=self.k8s_info['lb_id'])
sysctl_with_str_values = { sysctl_with_str_values = {
k: str(v) for k, v in aparam_sysctl.items() k: str(v) for k, v in aparam_sysctl.items()
} }
if sysctl_with_str_values != lb_info['sysctlParams']: if sysctl_with_str_values != lb_model.sysctl_params:
self.message( self.message(
'Check for parameter "lb_sysctl" failed: ' 'Check for parameter "lb_sysctl" failed: '
'cannot change lb_sysctl for an existing cluster ' 'cannot change lb_sysctl for an existing cluster '
@@ -503,7 +504,7 @@ class decort_k8s(DecortController):
check_errors = True check_errors = True
if ( if (
self.aparams['zone_id'] is not None self.aparams['zone_id'] is not None
and self.aparams['zone_id'] != self.k8s_info['zoneId'] and self.aparams['zone_id'] != self.k8s_info['zone_id']
and not self.is_k8s_stopped_or_will_be_stopped and not self.is_k8s_stopped_or_will_be_stopped
): ):
check_errors = True check_errors = True
@@ -515,13 +516,11 @@ class decort_k8s(DecortController):
aparam_storage_policy_id = self.aparams['storage_policy_id'] aparam_storage_policy_id = self.aparams['storage_policy_id']
if aparam_storage_policy_id is not None: if aparam_storage_policy_id is not None:
computes_ids = [] computes_ids = []
for master_node in self.k8s_info['k8sGroups']['masters'][ for master_node in self.k8s_info['node_groups']['master']['vms']:
'detailedInfo'
]:
computes_ids.append(master_node['id']) computes_ids.append(master_node['id'])
for wg in self.k8s_info['k8sGroups']['workers']: for wg in self.k8s_info['node_groups']['worker']:
workers_ids = [ workers_ids = [
worker['id'] for worker in wg['detailedInfo'] worker['id'] for worker in wg['vms']
] ]
computes_ids.extend(workers_ids) computes_ids.extend(workers_ids)
for compute_id in computes_ids: for compute_id in computes_ids:
@@ -589,7 +588,7 @@ class decort_k8s(DecortController):
) )
elif ( elif (
aparam_storage_policy_id aparam_storage_policy_id
not in self.rg_info['storage_policy_ids'] not in self.rg_info.storage_policy_ids
): ):
check_errors = True check_errors = True
self.message( self.message(
@@ -630,7 +629,9 @@ class decort_k8s(DecortController):
if amodule.params['state'] in ( if amodule.params['state'] in (
'disabled', 'enabled', 'present', 'started', 'stopped' 'disabled', 'enabled', 'present', 'started', 'stopped'
): ):
self.k8s_restore(self.k8s_id) self.sdk_checkmode(self.api.ca.k8s.restore)(
k8s_id=self.k8s_id,
)
self.action(disared_state=amodule.params['state'], self.action(disared_state=amodule.params['state'],
preupdate=True) preupdate=True)
if amodule.params['state'] == 'absent': if amodule.params['state'] == 'absent':

View File

@@ -18,11 +18,9 @@ class decort_lb(DecortController):
arg_amodule = self.amodule arg_amodule = self.amodule
self.lb_id = 0 self.lb_id = 0
self.lb_facts = None
self.vins_id = 0 self.vins_id = 0
self.vins_facts = None
self.rg_id = 0 self.rg_id = 0
self.rg_facts = None self.rg_model = None
self.default_server_check = "enabled" self.default_server_check = "enabled"
self.default_alg = "roundrobin" self.default_alg = "roundrobin"
self.default_settings = { self.default_settings = {
@@ -38,22 +36,34 @@ class decort_lb(DecortController):
self.is_lb_stopped_or_will_be_stopped: None | bool = None self.is_lb_stopped_or_will_be_stopped: None | bool = None
if arg_amodule.params['lb_id']: if arg_amodule.params['lb_id']:
self.lb_id, self.lb_facts = self.lb_find(arg_amodule.params['lb_id']) _, self._lb_info = self.lb_find(lb_id=arg_amodule.params['lb_id'])
if not self.lb_id: if self._lb_info is None:
self.result['failed'] = True if arg_amodule.params['state'] == 'absent':
self.result['msg'] = "Specified LB ID {} not found."\ self.nop()
.format(arg_amodule.params['lb_id']) self.exit()
self.amodule.fail_json(**self.result) else:
self.rg_id = self.lb_facts['rgId'] self.message(
self.vins_id = self.lb_facts['vinsId'] self.MESSAGES.obj_not_found(
obj='lb',
id=arg_amodule.params['lb_id'],
)
)
self.exit(fail=True)
self.lb_id = self._lb_info.id
self.rg_id = self._lb_info.rg_id
self.vins_id = self._lb_info.vins_id
elif 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="") self.rg_id, self.rg_model = self.rg_find(
if not self.rg_id: 0,
arg_amodule.params['rg_id'],
arg_rg_name="",
)
if not self.rg_id or not self.rg_model:
self.result['failed'] = True self.result['failed'] = True
self.result['msg'] = "Specified RG ID {} not found.".format(arg_amodule.params['rg_id']) self.result['msg'] = "Specified RG ID {} not found.".format(arg_amodule.params['rg_id'])
self.amodule.fail_json(**self.result) self.amodule.fail_json(**self.result)
self.acc_id = self.rg_facts['accountId'] self.acc_id = self.rg_model.account_id
elif arg_amodule.params['account_id'] or arg_amodule.params['account_name'] != "": elif arg_amodule.params['account_id'] or arg_amodule.params['account_name'] != "":
if not arg_amodule.params['rg_name']: if not arg_amodule.params['rg_name']:
@@ -67,24 +77,22 @@ class decort_lb(DecortController):
self.result['msg'] = ("Current user does not have access to the requested account " self.result['msg'] = ("Current user does not have access to the requested account "
"or non-existent account specified.") "or non-existent account specified.")
self.amodule.fail_json(**self.result) self.amodule.fail_json(**self.result)
self.rg_id, self.rg_facts = self.rg_find(self.acc_id,0, arg_rg_name=arg_amodule.params['rg_name']) self.rg_id, self.rg_model = self.rg_find(
self.acc_id,
0,
arg_rg_name=arg_amodule.params['rg_name'],
)
if arg_amodule.params['vins_id']: if arg_amodule.params['vins_id']:
self.vins_id, self.vins_facts = self.vins_find( self.vins_id = self._vins_get_by_id(
vins_id=arg_amodule.params['vins_id'] vins_id=arg_amodule.params['vins_id']
) ).id
if not self.vins_id:
self.result['failed'] = True
self.result['msg'] = (
f'Specified ViNS ID {arg_amodule.params["vins_id"]}'
f' not found'
)
self.amodule.fail_json(**self.result)
elif arg_amodule.params['vins_name']: elif arg_amodule.params['vins_name']:
self.vins_id, self.vins_facts = self.vins_find( self.vins_id, _ = self.vins_find(
vins_id=arg_amodule.params['vins_id'], vins_id=arg_amodule.params['vins_id'],
vins_name=arg_amodule.params['vins_name'], vins_name=arg_amodule.params['vins_name'],
rg_id=self.rg_id) rg_id=self.rg_id,
)
if not self.vins_id: if not self.vins_id:
self.result['failed'] = True self.result['failed'] = True
self.result['msg'] = ( self.result['msg'] = (
@@ -94,10 +102,17 @@ class decort_lb(DecortController):
self.amodule.fail_json(**self.result) self.amodule.fail_json(**self.result)
if self.rg_id and arg_amodule.params['lb_name']: 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) self.lb_id, self._lb_info = self.lb_find(
0,
arg_amodule.params['lb_name'],
self.rg_id,
)
if self.lb_id and self.lb_facts['status'] != 'DESTROYED': if (
self.acc_id = self.lb_facts['accountId'] self._lb_info
and self._lb_info.status != sdk_types.LBStatus.DESTROYED
):
self.acc_id = self._lb_info.account_id
self.check_amodule_args_for_change() self.check_amodule_args_for_change()
else: else:
self.check_amodule_args_for_create() self.check_amodule_args_for_create()
@@ -115,54 +130,73 @@ class decort_lb(DecortController):
zone_id=self.aparams['zone_id'], zone_id=self.aparams['zone_id'],
start=start_after_create, start=start_after_create,
) )
if self.lb_id and (self.amodule.params['backends'] or if (
self.amodule.params['frontends']): self.lb_id and (
self.lb_id, self.lb_facts = self.lb_find(0,self.amodule.params['lb_name'],self.rg_id) self.amodule.params['backends']
or self.amodule.params['frontends']
)
):
self._lb_info = self._lb_get_by_id(lb_id=self.lb_id)
self.lb_update( self.lb_update(
lb_facts=self.lb_facts, lb_model=self.lb_info,
aparam_backends=self.amodule.params['backends'], aparam_backends=self.amodule.params['backends'],
aparam_frontends=self.amodule.params['frontends'], aparam_frontends=self.amodule.params['frontends'],
aparam_servers=self.amodule.params['servers'], aparam_servers=self.amodule.params['servers'],
) )
return return
def action(self,d_state='',restore=False): def action(
if restore == True: self,
self.lb_restore(lb_id=self.lb_id) d_state='',
_, self.lb_facts = self._lb_get_by_id(lb_id=self.lb_id) restore=False,
self.lb_state(self.lb_facts, 'enabled') ):
_, self.lb_facts = self._lb_get_by_id(lb_id=self.lb_id) if restore:
self.sdk_checkmode(self.api.ca.lb.restore)(
lb_id=self.lb_id,
)
self._lb_info = self._lb_get_by_id(lb_id=self.lb_id)
self.lb_state(self.lb_info, 'enabled')
self._lb_info = self._lb_get_by_id(lb_id=self.lb_id)
self.lb_update( self.lb_update(
lb_facts=self.lb_facts, lb_model=self.lb_info,
aparam_backends=self.amodule.params['backends'], aparam_backends=self.amodule.params['backends'],
aparam_frontends=self.amodule.params['frontends'], aparam_frontends=self.amodule.params['frontends'],
aparam_servers=self.amodule.params['servers'], aparam_servers=self.amodule.params['servers'],
aparam_sysctl=self.aparams['sysctl'], aparam_sysctl=self.aparams['sysctl'],
) )
self._lb_info = self._lb_get_by_id(lb_id=self.lb_id)
if d_state != '': if d_state != '':
self.lb_state(self.lb_facts, d_state) self.lb_state(self.lb_info, d_state)
_, self.lb_facts = self._lb_get_by_id(lb_id=self.lb_id) self._lb_info = self._lb_get_by_id(lb_id=self.lb_id)
if (d_state == 'enabled' and if (
self.lb_facts.get('status') == 'ENABLED' and d_state == 'enabled'
self.lb_facts.get('techStatus') == 'STOPPED'): and self.lb_info.status == sdk_types.LBStatus.ENABLED
self.lb_state(self.lb_facts, 'started') and self.lb_info.tech_status == sdk_types.LBTechStatus.STOPPED
_, self.lb_facts = self._lb_get_by_id(lb_id=self.lb_id) ):
self.lb_state(self.lb_info, 'started')
self._lb_info = self._lb_get_by_id(lb_id=self.lb_id)
aparam_zone_id = self.aparams['zone_id'] aparam_zone_id = self.aparams['zone_id']
if aparam_zone_id is not None and aparam_zone_id != self.lb_facts['zoneId']: if (
aparam_zone_id is not None
and aparam_zone_id != self.lb_info.zone_id
):
self.lb_migrate_to_zone( self.lb_migrate_to_zone(
lb_id=self.lb_id, lb_id=self.lb_info.id,
zone_id=aparam_zone_id, zone_id=aparam_zone_id,
) )
return return
def delete(self): def delete(self):
self.lb_delete(self.lb_id, self.amodule.params['permanently']) self.sdk_checkmode(self.api.ca.lb.delete)(
self.lb_id, self.lb_facts = self._lb_get_by_id(self.lb_id) lb_id=self.lb_id,
permanently=self.amodule.params['permanently'],
)
self._lb_info = self._lb_get_by_id(lb_id=self.lb_id)
return return
def nop(self): def nop(self):
@@ -173,30 +207,39 @@ class decort_lb(DecortController):
""" """
self.result['failed'] = False self.result['failed'] = False
self.result['changed'] = False self.result['changed'] = False
if self.lb_id: if self._lb_info:
self.result['msg'] = ("No state change required for LB ID {} because of its " self.result['msg'] = (
"current status '{}'.").format(self.lb_id, self.lb_facts['status']) f'No state change required for LB ID {self._lb_info.id} '
f'because of its current status "{self._lb_info.status}".'
)
else: else:
self.result['msg'] = ("No state change to '{}' can be done for " self.result['msg'] = (
"non-existent LB instance.").format(self.amodule.params['state']) f'No state change to "{self.amodule.params['state']}" '
f'can be done for non-existent LB instance.'
)
return return
def error(self): def error(self):
self.result['failed'] = True self.result['failed'] = True
self.result['changed'] = False self.result['changed'] = False
if self.vins_id: if self._lb_info:
self.result['failed'] = True self.result['failed'] = True
self.result['changed'] = False self.result['changed'] = False
self.result['msg'] = ("Invalid target state '{}' requested for LB ID {} in the " self.result['msg'] = (
"current status '{}'").format(self.lb_id, f'Invalid target state "{self.amodule.params['state']}" '
self.amodule.params['state'], f'requested for LB ID {self._lb_info.id} in the current status '
self.lb_facts['status']) f'"{self._lb_info.status}".'
)
else: else:
self.result['failed'] = True self.result['failed'] = True
self.result['changed'] = False self.result['changed'] = False
self.result['msg'] = ("Invalid target state '{}' requested for non-existent " self.result['msg'] = (
"LB name '{}'").format(self.amodule.params['state'], f'Invalid target state "{self.amodule.params['state']}" '
self.amodule.params['lb_name']) f'requested for non-existent LB name '
f'"{self.amodule.params['lb_name']}".'
)
return return
def package_facts(self, arg_check_mode=False): def package_facts(self, arg_check_mode=False):
"""Package a dictionary of LB facts according to the decort_lb module specification. """Package a dictionary of LB facts according to the decort_lb module specification.
This dictionary will be returned to the upstream Ansible engine at the completion of This dictionary will be returned to the upstream Ansible engine at the completion of
@@ -205,34 +248,16 @@ class decort_lb(DecortController):
@param arg_check_mode: boolean that tells if this Ansible module is run in check mode @param arg_check_mode: boolean that tells if this Ansible module is run in check mode
""" """
ret_dict = dict(id=0, ret_dict = {}
name="none",
state="CHECK_MODE",
sysctl={},
)
if arg_check_mode: if arg_check_mode:
# in check mode return immediately with the default values # in check mode return immediately with the default values
return ret_dict return ret_dict
if self.lb_facts is None: if self._lb_info is None:
# if void facts provided - change state value to ABSENT and return
ret_dict['state'] = "ABSENT"
return ret_dict return ret_dict
ret_dict['id'] = self.lb_facts['id'] return self._lb_info.model_dump()
ret_dict['name'] = self.lb_facts['name']
ret_dict['state'] = self.lb_facts['status']
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 @property
def amodule_init_args(self) -> dict: def amodule_init_args(self) -> dict:
@@ -321,18 +346,16 @@ class decort_lb(DecortController):
def check_amodule_args_for_change(self): def check_amodule_args_for_change(self):
check_errors = False check_errors = False
self.is_lb_stopped_or_will_be_stopped = (
lb_info: dict = self.lb_facts
self.is_lb_stopped_or_will_be_stopped = (
( (
lb_info['techStatus'] == 'STOPPED' self.lb_info.tech_status == sdk_types.LBTechStatus.STOPPED
and ( and (
self.aparams['state'] is None self.aparams['state'] is None
or self.aparams['state'] in ('present', 'stopped') or self.aparams['state'] in ('present', 'stopped')
) )
) )
or ( or (
lb_info['techStatus'] != 'STOPPED' self.lb_info.tech_status != sdk_types.LBTechStatus.STOPPED
and self.aparams['state'] == 'stopped' and self.aparams['state'] == 'stopped'
) )
) )
@@ -341,7 +364,7 @@ class decort_lb(DecortController):
check_errors = True check_errors = True
if ( if (
self.aparams['zone_id'] is not None self.aparams['zone_id'] is not None
and self.aparams['zone_id'] != lb_info['zoneId'] and self.aparams['zone_id'] != self.lb_info.zone_id
and not self.is_lb_stopped_or_will_be_stopped and not self.is_lb_stopped_or_will_be_stopped
): ):
check_errors = True check_errors = True
@@ -364,18 +387,32 @@ class decort_lb(DecortController):
@DecortController.handle_sdk_exceptions @DecortController.handle_sdk_exceptions
def run(self): def run(self):
amodule = self.amodule amodule = self.amodule
if self.lb_id: if self._lb_info:
if self.lb_facts['status'] in ["MODELED", "DISABLING", "ENABLING", "DELETING","DESTROYING","RESTORING"]: if self._lb_info.status in [
sdk_types.LBStatus.MODELED,
sdk_types.LBStatus.DISABLING,
sdk_types.LBStatus.ENABLING,
sdk_types.LBStatus.DELETING,
sdk_types.LBStatus.DESTROYING,
sdk_types.LBStatus.RESTORING,
]:
self.result['failed'] = True self.result['failed'] = True
self.result['changed'] = False self.result['changed'] = False
self.result['msg'] = ("No change can be done for existing LB ID {} because of its current " self.result['msg'] = (
"status '{}'").format(self.lb_id, self.lb_facts['status']) f'No change can be done for existing LB ID '
elif self.lb_facts['status'] in ('DISABLED', 'ENABLED', 'CREATED'): f'{self._lb_info.id} because of its current status '
f'"{self._lb_info.status.name}".'
)
elif self._lb_info.status in [
sdk_types.LBStatus.DISABLED,
sdk_types.LBStatus.ENABLED,
sdk_types.LBStatus.CREATED,
]:
if amodule.params['state'] == 'absent': if amodule.params['state'] == 'absent':
self.delete() self.delete()
else: else:
self.action(d_state=amodule.params['state']) self.action(d_state=amodule.params['state'])
elif self.lb_facts['status'] == "DELETED": elif self._lb_info.status == sdk_types.LBStatus.DELETED:
if amodule.params['state'] == 'present': if amodule.params['state'] == 'present':
self.action(restore=True) self.action(restore=True)
elif amodule.params['state'] == 'enabled': elif amodule.params['state'] == 'enabled':
@@ -385,7 +422,7 @@ class decort_lb(DecortController):
self.delete() self.delete()
elif amodule.params['state'] == 'disabled': elif amodule.params['state'] == 'disabled':
self.error() self.error()
elif self.lb_facts['status'] == "DESTROYED": elif self._lb_info.status == sdk_types.LBStatus.DESTROYED:
if amodule.params['state'] in ('present', 'enabled'): if amodule.params['state'] in ('present', 'enabled'):
self.create() self.create()
elif amodule.params['state'] == 'absent': elif amodule.params['state'] == 'absent':
@@ -407,7 +444,7 @@ class decort_lb(DecortController):
amodule.fail_json(**self.result) amodule.fail_json(**self.result)
else: else:
if self.result['changed']: if self.result['changed']:
_, self.lb_facts = self.lb_find(lb_id=self.lb_id) self._lb_info = self._lb_get_by_id(lb_id=self.lb_id)
self.result['facts'] = self.package_facts(amodule.check_mode) self.result['facts'] = self.package_facts(amodule.check_mode)
amodule.exit_json(**self.result) amodule.exit_json(**self.result)

View File

@@ -43,7 +43,13 @@ class decort_pfw(DecortController):
supports_check_mode=True, supports_check_mode=True,
) )
def decort_pfw_package_facts(self, comp_facts, vins_facts, pfw_facts, check_mode=False): def decort_pfw_package_facts(
self,
comp_facts,
vins_model: sdk_types.CloudapiVinsGetResultModel,
pfw_facts,
check_mode=False,
):
"""Package a dictionary of PFW rules facts according to the decort_pfw module specification. """Package a dictionary of PFW rules facts according to the decort_pfw module specification.
This dictionary will be returned to the upstream Ansible engine at the completion of This dictionary will be returned to the upstream Ansible engine at the completion of
the module run. the module run.
@@ -68,9 +74,13 @@ class decort_pfw(DecortController):
ret_dict['state'] = "ABSENT" ret_dict['state'] = "ABSENT"
return ret_dict return ret_dict
gw_vnf = vins_model.vnfs.gw
ret_dict['compute_id'] = comp_facts['id'] ret_dict['compute_id'] = comp_facts['id']
ret_dict['vins_id'] = vins_facts['id'] ret_dict['vins_id'] = vins_model.id
ret_dict['public_ip'] = vins_facts['vnfs']['GW']['config']['ext_net_ip'] if gw_vnf:
ret_dict['public_ip'] = gw_vnf.config.ext_net_ip
else:
raise RuntimeError('VINS GW VNF must exist.')
if len(pfw_facts) != 0: if len(pfw_facts) != 0:
ret_dict['state'] = 'PRESENT' ret_dict['state'] = 'PRESENT'
@@ -106,16 +116,15 @@ class decort_pfw(DecortController):
self.result['msg'] = "Cannot find specified Compute ID {}.".format(amodule.params['compute_id']) self.result['msg'] = "Cannot find specified Compute ID {}.".format(amodule.params['compute_id'])
amodule.fail_json(**self.result) amodule.fail_json(**self.result)
validated_vins_id, vins_facts = self.vins_find(amodule.params['vins_id']) vins_model = self._vins_get_by_id(vins_id=amodule.params['vins_id'])
if not validated_vins_id:
self.result['failed'] = True
self.result['msg'] = "Cannot find specified ViNS ID {}.".format(amodule.params['vins_id'])
amodule.fail_json(**self.result)
gw_vnf_facts = vins_facts['vnfs'].get('GW') gw_vnf = vins_model.vnfs.gw
if not gw_vnf_facts or gw_vnf_facts['status'] == "DESTROYED": if not gw_vnf or gw_vnf.status == sdk_types.VNFStatus.DESTROYED:
self.result['failed'] = True self.result['failed'] = True
self.result['msg'] = "ViNS ID {} does not have a configured external connection.".format(validated_vins_id) self.result['msg'] = (
f'ViNS ID {vins_model.id} does not '
f'have a configured external connection.'
)
amodule.fail_json(**self.result) amodule.fail_json(**self.result)
# #
@@ -124,12 +133,20 @@ class decort_pfw(DecortController):
if amodule.params['state'] == 'absent': if amodule.params['state'] == 'absent':
# ignore amodule.params['rules'] and remove all rules associated with this Compute # ignore amodule.params['rules'] and remove all rules associated with this Compute
pfw_facts = self.pfw_configure(comp_facts, vins_facts, None) pfw_facts = self.pfw_configure(
comp_facts,
vins_model,
None,
)
elif amodule.params['rules'] is not None: elif amodule.params['rules'] is not None:
# manage PFW rules accodring to the module arguments # manage PFW rules accodring to the module arguments
pfw_facts = self.pfw_configure(comp_facts, vins_facts, amodule.params['rules']) pfw_facts = self.pfw_configure(
comp_facts,
vins_model,
amodule.params['rules'],
)
else: else:
pfw_facts = self._pfw_get(comp_facts['id'], vins_facts['id']) pfw_facts = self._pfw_get(comp_facts['id'], vins_model.id)
# #
# complete module run # complete module run
@@ -138,7 +155,12 @@ class decort_pfw(DecortController):
amodule.fail_json(**self.result) amodule.fail_json(**self.result)
else: else:
# prepare PFW facts to be returned as part of self.result and then call exit_json(...) # prepare PFW facts to be returned as part of self.result and then call exit_json(...)
self.result['facts'] = self.decort_pfw_package_facts(comp_facts, vins_facts, pfw_facts, amodule.check_mode) self.result['facts'] = self.decort_pfw_package_facts(
comp_facts,
vins_model,
pfw_facts,
amodule.check_mode,
)
amodule.exit_json(**self.result) amodule.exit_json(**self.result)

View File

@@ -19,8 +19,7 @@ class decort_rg(DecortController):
amodule = self.amodule amodule = self.amodule
self.validated_acc_id = 0 self.validated_acc_id = 0
self.validated_rg_id = 0 self.rg_id: int = 0
self.validated_rg_facts = None
if amodule.params['rg_id'] is None: if amodule.params['rg_id'] is None:
if self.amodule.params['account_id']: if self.amodule.params['account_id']:
@@ -42,13 +41,15 @@ class decort_rg(DecortController):
else: else:
self.rg_should_exist = False self.rg_should_exist = False
if self.validated_rg_id and self.rg_facts['status'] != 'DESTROYED': if self.rg_id and (
self.rg_info.status != sdk_types.ResourceGroupStatus.DESTROYED
):
self.check_amodule_args_for_change() self.check_amodule_args_for_change()
def get_info(self): def get_info(self):
# If this is the first getting info # If this is the first getting info
if not self.validated_rg_id: if self._rg_info is None:
self.validated_rg_id, self.rg_facts = self.rg_find( self.rg_id, self._rg_info = self.rg_find(
arg_account_id=self.validated_acc_id, arg_account_id=self.validated_acc_id,
arg_rg_id=self.aparams['rg_id'], arg_rg_id=self.aparams['rg_id'],
arg_rg_name=self.aparams['rg_name'], arg_rg_name=self.aparams['rg_name'],
@@ -61,16 +62,16 @@ class decort_rg(DecortController):
if self.amodule.check_mode: if self.amodule.check_mode:
return return
_, self.rg_facts = self.rg_find(arg_rg_id=self.validated_rg_id) _, self._rg_info = self.rg_find(arg_rg_id=self.rg_id)
def access(self): def access(self):
should_change_access = False should_change_access = False
acc_granted = False acc_granted = False
for rg_item in self.rg_facts['acl']: for rg_item in self.rg_info.acl:
if rg_item['userGroupId'] == self.amodule.params['access']['user']: if rg_item.user_name == self.amodule.params['access']['user']:
acc_granted = True acc_granted = True
if self.amodule.params['access']['action'] == 'grant': if self.amodule.params['access']['action'] == 'grant':
if rg_item['right'] != self.amodule.params['access']['right']: if rg_item.access_type.value != self.amodule.params['access']['right']:
should_change_access = True should_change_access = True
if self.amodule.params['access']['action'] == 'revoke': if self.amodule.params['access']['action'] == 'revoke':
should_change_access = True should_change_access = True
@@ -78,19 +79,30 @@ class decort_rg(DecortController):
should_change_access = True should_change_access = True
if should_change_access == True: if should_change_access == True:
self.rg_access(self.validated_rg_id, self.amodule.params['access']) if self.amodule.params['access']['action'] == "grant":
self.rg_facts['access'] = self.amodule.params['access'] 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 self.rg_should_exist = True
return return
def error(self): def error(self):
self.result['failed'] = True self.result['failed'] = True
self.result['changed'] = False self.result['changed'] = False
if self.validated_rg_id > 0: if self.rg_id:
self.result['msg'] = ("Invalid target state '{}' requested for rg ID {} in the " self.result['msg'] = ("Invalid target state '{}' requested for rg ID {} in the "
"current status '{}'.").format(self.validated_rg_id, "current status '{}'.").format(self.rg_id,
self.amodule.params['state'], self.amodule.params['state'],
self.rg_facts['status']) self.rg_info.status.value)
else: else:
self.result['msg'] = ("Invalid target state '{}' requested for non-existent rg name '{}' " self.result['msg'] = ("Invalid target state '{}' requested for non-existent rg name '{}' "
"in account ID {} ").format(self.amodule.params['state'], "in account ID {} ").format(self.amodule.params['state'],
@@ -99,20 +111,31 @@ class decort_rg(DecortController):
return return
def update(self): def update(self):
resources = self.rg_facts['Resources']['Reserved'] 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(), incorrect_quota = dict(Requested=dict(),
Reserved=dict(),) Reserved=dict(),)
query_key_map = dict( query_key_map = dict(
cpu='cpu', cpu='cpu_count',
ram='ram', ram='ram_size_mb',
disk='disksize', disk='storage_size_gb_by_real_usage',
ext_ips='extips', ext_ips='ext_ip_count',
storage_policies='policies', storage_policies='storage_policies',
) )
if self.amodule.params['quotas']: if self.amodule.params['quotas']:
for quota_item in self.amodule.params['quotas']: for quota_item in self.amodule.params['quotas']:
if quota_item == 'storage_policies': if quota_item == 'storage_policies':
rg_storage_policies = resources[query_key_map[quota_item]] rg_storage_policies = reserved_resources.storage_policies
aparam_storage_policies = self.amodule.params['quotas'][ aparam_storage_policies = self.amodule.params['quotas'][
quota_item quota_item
] ]
@@ -124,32 +147,35 @@ class decort_rg(DecortController):
if ( if (
rg_storage_policy rg_storage_policy
and aparam_storage_policy['storage_size_gb'] and aparam_storage_policy['storage_size_gb']
< rg_storage_policy['disksize'] < rg_storage_policy.storage_size_gb_by_real_usage
): ):
incorrect_quota['Requested'][quota_item] = ( incorrect_quota['Requested'][quota_item] = (
self.amodule.params['quotas'][quota_item] self.amodule.params['quotas'][quota_item]
) )
incorrect_quota['Reserved'][quota_item] = ( incorrect_quota['Reserved'][quota_item] = (
resources[query_key_map[quota_item]] getattr(
reserved_resources,
query_key_map[quota_item]
)
) )
elif ( elif (
self.amodule.params['quotas'][quota_item] self.amodule.params['quotas'][quota_item]
< resources[query_key_map[quota_item]] < getattr(reserved_resources, query_key_map[quota_item])
): ):
incorrect_quota['Requested'][quota_item] = ( incorrect_quota['Requested'][quota_item] = (
self.amodule.params['quotas'][quota_item] self.amodule.params['quotas'][quota_item]
) )
incorrect_quota['Reserved'][quota_item] = ( incorrect_quota['Reserved'][quota_item] = (
resources[query_key_map[quota_item]] getattr(reserved_resources, query_key_map[quota_item])
) )
if incorrect_quota['Requested']: if reserved_resources and incorrect_quota['Requested']:
self.result['msg'] = ("Cannot limit less than already reserved'{}'").format(incorrect_quota) self.result['msg'] = ("Cannot limit less than already reserved'{}'").format(incorrect_quota)
self.result['failed'] = True self.result['failed'] = True
if not self.result['failed']: if not self.result['failed']:
self.rg_update( self.rg_update(
arg_rg_dict=self.rg_facts, rg_model=self.rg_info,
arg_quotas=self.amodule.params['quotas'], arg_quotas=self.amodule.params['quotas'],
arg_res_types=self.amodule.params['resType'], arg_res_types=self.amodule.params['resType'],
arg_newname=self.amodule.params['rename'], arg_newname=self.amodule.params['rename'],
@@ -160,13 +186,15 @@ class decort_rg(DecortController):
return return
def setDefNet(self): def setDefNet(self):
rg_def_net_type = self.rg_facts['def_net_type'] rg_def_net_type = self.rg_info.default_net_type.value
rg_def_net_id = self.rg_facts['def_net_id'] rg_def_net_id = self.rg_info.default_net_id
aparam_def_net_type = self.aparams['def_netType'] aparam_def_net_type = self.aparams['def_netType']
aparam_def_net_id = self.aparams['def_netId'] aparam_def_net_id = self.aparams['def_netId']
need_to_reset = (aparam_def_net_type == 'NONE' need_to_reset = (
and rg_def_net_type != aparam_def_net_type) aparam_def_net_type == sdk_types.RGDefaultNetType.NONE
and rg_def_net_type != aparam_def_net_type
)
need_to_change = False need_to_change = False
if aparam_def_net_id is not None: if aparam_def_net_id is not None:
@@ -174,16 +202,26 @@ class decort_rg(DecortController):
or aparam_def_net_type != rg_def_net_type) or aparam_def_net_type != rg_def_net_type)
if need_to_reset or need_to_change: if need_to_reset or need_to_change:
self.rg_setDefNet( if aparam_def_net_type == sdk_types.RGDefaultNetType.NONE:
arg_rg_id=self.validated_rg_id, self.sdk_checkmode(self.api.ca.rg.remove_def_net)(
arg_net_type=aparam_def_net_type, rg_id=self.rg_id,
arg_net_id=aparam_def_net_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 self.rg_should_exist = True
return return
def create(self): def create(self):
self.validated_rg_id = self.rg_provision( self.rg_id = self.rg_provision(
self.validated_acc_id, self.validated_acc_id,
self.amodule.params['rg_name'], self.amodule.params['rg_name'],
self.amodule.params['owner'], self.amodule.params['owner'],
@@ -198,10 +236,10 @@ class decort_rg(DecortController):
sdn_access_group_id=self.aparams['sdn_access_group_id'], sdn_access_group_id=self.aparams['sdn_access_group_id'],
) )
if self.validated_rg_id: if self.rg_id:
self.validated_rg_id, self.rg_facts = self.rg_find( self.rg_id, self._rg_info = self.rg_find(
arg_account_id=self.validated_acc_id, arg_account_id=self.validated_acc_id,
arg_rg_id=self.validated_rg_id, arg_rg_id=self.rg_id,
arg_rg_name="", arg_rg_name="",
arg_check_state=False arg_check_state=False
) )
@@ -209,31 +247,30 @@ class decort_rg(DecortController):
return return
def enable(self): def enable(self):
self.rg_enable(self.validated_rg_id,
self.amodule.params['state'])
if self.amodule.params['state'] == "enabled": if self.amodule.params['state'] == "enabled":
self.rg_facts['status'] = 'CREATED' self.sdk_checkmode(self.api.ca.rg.enable)(
else: rg_id=self.rg_id,
self.rg_facts['status'] = 'DISABLED' )
elif self.amodule.params['state'] == "disabled":
self.sdk_checkmode(self.api.ca.rg.disable)(
rg_id=self.rg_id,
)
self.rg_should_exist = True self.rg_should_exist = True
return return
def restore(self): def restore(self):
self.rg_restore(self.validated_rg_id) self.sdk_checkmode(self.api.ca.rg.restore)(
self.rg_facts['status'] = 'DISABLED' rg_id=self.rg_id,
)
self.rg_should_exist = True self.rg_should_exist = True
return return
def destroy(self): def destroy(self):
self.rg_delete( self.sdk_checkmode(self.api.ca.rg.delete)(
rg_id=self.validated_rg_id, rg_id=self.rg_id,
permanently=self.amodule.params['permanently'], permanently=self.amodule.params['permanently'],
recursively=self.aparams['recursive_deletion'], recursively=self.aparams['recursive_deletion'],
) )
if self.amodule.params['permanently'] == True:
self.rg_facts['status'] = 'DESTROYED'
else:
self.rg_facts['status'] = 'DELETED'
self.rg_should_exist = False self.rg_should_exist = False
return return
@@ -259,23 +296,7 @@ class decort_rg(DecortController):
# ret_dict['state'] = "ABSENT" # ret_dict['state'] = "ABSENT"
# return ret_dict # return ret_dict
ret_dict['id'] = self.rg_facts['id'] return self.rg_info.model_dump()
ret_dict['name'] = self.rg_facts['name']
ret_dict['state'] = self.rg_facts['status']
ret_dict['account_id'] = self.rg_facts['accountId']
ret_dict['gid'] = self.rg_facts['gid']
ret_dict['quota'] = self.rg_facts['resourceLimits']
ret_dict['resTypes'] = self.rg_facts['resourceTypes']
ret_dict['defNetId'] = self.rg_facts['def_net_id']
ret_dict['defNetType'] = self.rg_facts['def_net_type']
ret_dict['ViNS'] = self.rg_facts['vins']
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']
ret_dict['storage_policy_ids'] = self.rg_facts['storage_policy_ids']
return ret_dict
@property @property
def amodule_init_args(self) -> dict: def amodule_init_args(self) -> dict:
@@ -290,6 +311,24 @@ class decort_rg(DecortController):
), ),
access=dict( access=dict(
type='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( description=dict(
type='str', type='str',
@@ -297,12 +336,8 @@ class decort_rg(DecortController):
), ),
def_netType=dict( def_netType=dict(
type='str', type='str',
choices=[ choices=sdk_types.RGDefaultNetType._member_names_,
'PRIVATE', default=sdk_types.RGDefaultNetType.PRIVATE.name,
'PUBLIC',
'NONE',
],
default='PRIVATE',
), ),
def_netId=dict( def_netId=dict(
type='int', type='int',
@@ -386,13 +421,13 @@ class decort_rg(DecortController):
self.aparams['sdn_access_group_id'] is not None self.aparams['sdn_access_group_id'] is not None
and ( and (
self.aparams['sdn_access_group_id'] self.aparams['sdn_access_group_id']
!= self.rg_facts['sdn_access_group_id'] != self.rg_info.sdn_access_group_id
) )
): ):
self.message( self.message(
'Check for parameter "sdn_access_group_id" failed: ' 'Check for parameter "sdn_access_group_id" failed: '
'cannot change sdn_access_group_id for an existing resource ' 'cannot change sdn_access_group_id for an existing resource '
f'group ID {self.validated_rg_id}.' f'group ID {self.rg_id}.'
) )
check_errors = True check_errors = True
@@ -410,10 +445,15 @@ class decort_rg(DecortController):
def run(self): def run(self):
amodule = self.amodule amodule = self.amodule
#amodule.check_mode=True #amodule.check_mode=True
if self.validated_rg_id > 0: if self.rg_id:
if self.rg_facts['status'] in ["MODELED", "DISABLING", "ENABLING", "DELETING", "DESTROYING", "CONFIRMED"]: if self.rg_info.status in [
sdk_types.ResourceGroupStatus.MODELED,
sdk_types.ResourceGroupStatus.DISABLING,
sdk_types.ResourceGroupStatus.ENABLING,
sdk_types.ResourceGroupStatus.DESTROYING,
]:
self.error() self.error()
elif self.rg_facts['status'] in ("CREATED"): elif self.rg_info.status == sdk_types.ResourceGroupStatus.CREATED:
if amodule.params['state'] == 'absent': if amodule.params['state'] == 'absent':
self.destroy() self.destroy()
elif amodule.params['state'] == "disabled": elif amodule.params['state'] == "disabled":
@@ -432,7 +472,7 @@ class decort_rg(DecortController):
if amodule.params['def_netType'] is not None: if amodule.params['def_netType'] is not None:
self.setDefNet() self.setDefNet()
elif self.rg_facts['status'] == "DELETED": elif self.rg_info.status == sdk_types.ResourceGroupStatus.DELETED:
if amodule.params['state'] == 'absent' and amodule.params['permanently'] == True: if amodule.params['state'] == 'absent' and amodule.params['permanently'] == True:
self.destroy() self.destroy()
elif (amodule.params['state'] == 'present' elif (amodule.params['state'] == 'present'
@@ -441,7 +481,7 @@ class decort_rg(DecortController):
elif amodule.params['state'] == 'enabled': elif amodule.params['state'] == 'enabled':
self.restore() self.restore()
self.enable() self.enable()
elif self.rg_facts['status'] in ("DISABLED"): elif self.rg_info.status == sdk_types.ResourceGroupStatus.DISABLED:
if amodule.params['state'] == 'absent': if amodule.params['state'] == 'absent':
self.destroy() self.destroy()
elif amodule.params['state'] == ("enabled"): elif amodule.params['state'] == ("enabled"):

View File

@@ -0,0 +1,213 @@
#!/usr/bin/python
DOCUMENTATION = r'''
---
module: decort_sdn_access_group
description: See L(Module Documentation,https://repository.basistech.ru/BASIS/decort-ansible/wiki/Home). # noqa: E501
'''
from typing import Any
from ansible.module_utils.basic import AnsibleModule
from ansible.module_utils.decort_utils import DecortController
import requests
class DecortSDNAccessGroups(DecortController):
access_group_id: str | None = None
_access_group_info: dict[str, Any] | None = None
need_final_get: bool = True
def __init__(self):
super().__init__(AnsibleModule(**self.amodule_init_args))
@property
def amodule_init_args(self) -> dict:
return self.pack_amodule_init_args(
argument_spec=dict(
access_group_id=dict(
type='str',
),
comment=dict(
type='str',
),
display_name=dict(
type='str',
),
state=dict(
type='str',
choices=['present', 'absent'],
),
),
supports_check_mode=True,
)
@property
def access_group_info(self) -> dict[str, Any]:
if self._access_group_info is None:
if not isinstance(self.access_group_id, str):
raise TypeError
access_group_info = self.get()
if access_group_info is None:
raise TypeError
self._access_group_info = access_group_info
return self._access_group_info
def check_amodule_args(self):
check_errors = False
if (
self.aparams['state'] == 'absent'
and self.aparams['access_group_id'] is None
):
check_errors = True
self.message(
'Check for parameter "access_group_id" failed: '
'access_group_id must be specified when state is "absent".'
)
if check_errors:
self.exit(fail=True)
def check_amodule_args_for_create(self):
check_errors = False
if self.aparams['comment'] is None:
check_errors = True
self.message(
'Check for parameter "comment" failed: parameter must '
'be specified for creating an access_group.'
)
if self.aparams['display_name'] is None:
check_errors = True
self.message(
'Check for parameter "display_name" failed: parameter must '
'be specified for creating an access_group.'
)
if check_errors:
self.exit(fail=True)
@DecortController.waypoint
@DecortController.checkmode
def get(self) -> dict[str, Any] | None:
params = {'access_group_id': self.access_group_id}
response = self.decort_api_call(
arg_req_function=requests.get,
arg_api_name='/restmachine/sdn/access_group/get',
arg_params=params,
not_fail_codes=[404],
accept_json_response=True,
)
if response.status_code == 404:
self.message(
self.MESSAGES.obj_not_found(
obj='access_group',
id=self.access_group_id,
)
)
self.exit(fail=True)
return response.json()
@DecortController.waypoint
@DecortController.checkmode
def access_group_find(self, display_name: str) -> dict[str, Any] | None:
params = {'display_name': display_name}
response = self.decort_api_call(
arg_req_function=requests.get,
arg_api_name='/restmachine/sdn/access_group/list',
arg_params=params,
accept_json_response=True,
)
for access_group in response.json() or []:
if access_group['display_name'] == display_name:
return access_group
return None
@DecortController.waypoint
@DecortController.checkmode
def create(self):
params = {
'comment': self.aparams['comment'],
'display_name': self.aparams['display_name'],
}
response = self.decort_api_call(
arg_req_function=requests.post,
arg_api_name='/restmachine/sdn/access_group/create',
arg_params=params,
accept_json_response=True,
)
self.access_group_id = response.json()['id']
self.set_changed()
@DecortController.waypoint
@DecortController.checkmode
def delete(self):
params = {'access_group_id': self.access_group_id}
response = self.decort_api_call(
arg_req_function=requests.delete,
arg_api_name='/restmachine/sdn/access_group/delete',
arg_params=params,
not_fail_codes=[204, 404]
)
self.need_final_get = False
if response.status_code == 204:
self.set_changed()
self.message(
self.MESSAGES.obj_deleted(
obj='access_group',
id=self.access_group_id,
)
)
else:
self.message(
self.MESSAGES.obj_not_found(
obj='access_group',
id=self.access_group_id,
)
)
self.facts = {}
def run(self):
self.check_amodule_args()
if self.aparams['access_group_id']:
self.access_group_id = self.aparams['access_group_id']
elif self.aparams['state'] != 'absent':
self.check_amodule_args_for_create()
access_group_info = self.access_group_find(
display_name=self.aparams['display_name'],
)
if access_group_info:
self.access_group_id = access_group_info['id']
self._access_group_info = access_group_info
if self.access_group_id:
if self.aparams['state'] == 'absent':
self.delete()
else:
state = self.aparams['state']
if state is None:
state = 'present'
self.message(
msg=self.MESSAGES.default_value_used(
param_name='state',
default_value=state,
),
warning=True,
)
if state != 'absent':
self.create()
if self.need_final_get:
self.facts = self.get()
self.exit()
def main():
DecortSDNAccessGroups().run()
if __name__ == '__main__':
main()

View File

@@ -0,0 +1,120 @@
#!/usr/bin/python
DOCUMENTATION = r'''
---
module: decort_sdn_access_group_list
description: See L(Module Documentation,https://repository.basistech.ru/BASIS/decort-ansible/wiki/Home). # noqa: E501
'''
from typing import Any
from ansible.module_utils.basic import AnsibleModule
from ansible.module_utils.decort_utils import DecortController
import requests
class DecortSDNAccessGroupList(DecortController):
def __init__(self):
super().__init__(AnsibleModule(**self.amodule_init_args))
@property
def amodule_init_args(self) -> dict:
return self.pack_amodule_init_args(
argument_spec=dict(
filter=dict(
type='dict',
apply_defaults=True,
options=dict(
enabled=dict(
type='bool',
),
deleted=dict(
type='bool',
),
display_name=dict(
type='str',
),
owner_display_name=dict(
type='str',
),
created_from=dict(
type='str',
),
created_to=dict(
type='str',
),
),
),
pagination=dict(
type='dict',
apply_defaults=True,
options=dict(
number=dict(
type='int',
default=1,
),
size=dict(
type='int',
),
),
),
sorting=dict(
type='dict',
options=dict(
asc=dict(
type='bool',
default=True,
),
field=dict(
type='str',
choices=[
'display_name',
'created_at',
'updated_at',
'deleted_at',
'owner_login',
],
required=True,
),
),
),
),
supports_check_mode=True,
)
def run(self):
self.get_info()
self.exit()
def get_info(self):
params: dict[str, Any] = dict()
aparam_filter: dict[str, Any] = self.aparams['filter']
for field, value in aparam_filter.items():
if value is not None:
params[field] = value
aparam_pagination: dict[str, Any] = self.aparams['pagination']
params['page'] = aparam_pagination['number']
params['per_page'] = aparam_pagination['size']
aparam_sorting: dict[str, Any] | None = self.aparams['sorting']
if aparam_sorting:
params['sort_by'] = aparam_sorting['field']
params['sort_order'] = 'asc' if aparam_sorting['asc'] else 'desc'
response = self.decort_api_call(
arg_req_function=requests.get,
arg_api_name='/restmachine/sdn/access_group/list',
arg_params=params,
accept_json_response=True,
)
self.facts = response.json()
def main():
DecortSDNAccessGroupList().run()
if __name__ == '__main__':
main()

View File

@@ -0,0 +1,160 @@
#!/usr/bin/python
DOCUMENTATION = r'''
---
module: decort_sdn_hypervisor
description: See L(Module Documentation,https://repository.basistech.ru/BASIS/decort-ansible/wiki/Home). # noqa: E501
'''
from typing import Any
from ansible.module_utils.basic import AnsibleModule
from ansible.module_utils.decort_utils import DecortController
import requests
class DecortSDNHypervisor(DecortController):
name: str | None = None
_hypervisor_info: dict[str, Any] | None = None
need_final_get: bool = True
def __init__(self):
super().__init__(AnsibleModule(**self.amodule_init_args))
@property
def amodule_init_args(self) -> dict:
return self.pack_amodule_init_args(
argument_spec=dict(
display_name=dict(
type='str',
),
name=dict(
type='str',
),
port_info=dict(
type='str',
choices=['detailed', 'general'],
),
state=dict(
type='str',
choices=['absent'],
),
),
supports_check_mode=True,
)
@property
def hypervisor_info(self) -> dict[str, Any]:
if self._hypervisor_info is None:
if not isinstance(self.name, str):
raise TypeError
hypervisor_info = self.get()
if hypervisor_info is None:
raise TypeError
self._hypervisor_info = hypervisor_info
return self._hypervisor_info
def check_amodule_args(self):
check_errors = False
if self.aparams['name'] is None:
check_errors = True
self.message(
'Check for parameter "name" failed: '
'name must be specified.'
)
if check_errors:
self.exit(fail=True)
@DecortController.waypoint
def get(self) -> dict[str, Any] | None:
params = {'name': self.name}
if self.aparams['port_info'] is not None:
params['port_info'] = self.aparams['port_info']
response = self.decort_api_call(
arg_req_function=requests.get,
arg_api_name='/restmachine/sdn/hypervisor/get',
arg_params=params,
not_fail_codes=[400],
accept_json_response=True,
)
if response.status_code == 400:
self.message(
self.MESSAGES.obj_not_found(
obj='hypervisor',
id=self.name,
)
)
self.exit(fail=True)
return response.json()
@DecortController.waypoint
@DecortController.checkmode
def update_display_name(self):
params = {
'name': self.aparams['name'],
'display_name': self.aparams['display_name'],
}
self.decort_api_call(
arg_req_function=requests.put,
arg_api_name='/restmachine/sdn/hypervisor/update_display_name',
arg_params=params,
accept_json_response=True,
)
self.set_changed()
@DecortController.waypoint
@DecortController.checkmode
def delete(self):
params = {'name': self.name}
response = self.decort_api_call(
arg_req_function=requests.delete,
arg_api_name='/restmachine/sdn/hypervisor/delete',
arg_params=params,
not_fail_codes=[400]
)
self.need_final_get = False
if response.status_code == 200:
self.set_changed()
self.message(
self.MESSAGES.obj_deleted(
obj='hypervisor',
id=self.name,
)
)
else:
self.message(
self.MESSAGES.obj_not_found(
obj='hypervisor',
id=self.name,
)
)
self.facts = {}
def run(self):
self.check_amodule_args()
self.name = self.aparams['name']
if self.aparams['state'] == 'absent':
self.delete()
else:
if (
self.aparams['display_name'] is not None
and self.aparams['display_name']
!= self.hypervisor_info.get('display_name')
):
self.update_display_name()
if self.need_final_get:
self.facts = self.get()
self.exit()
def main():
DecortSDNHypervisor().run()
if __name__ == '__main__':
main()

View File

@@ -0,0 +1,135 @@
#!/usr/bin/python
DOCUMENTATION = r'''
---
module: decort_sdn_hypervisor_list
description: See L(Module Documentation,https://repository.basistech.ru/BASIS/decort-ansible/wiki/Home). # noqa: E501
'''
from typing import Any
from ansible.module_utils.basic import AnsibleModule
from ansible.module_utils.decort_utils import DecortController
import requests
class DecortSDNHypervisorList(DecortController):
def __init__(self):
super().__init__(AnsibleModule(**self.amodule_init_args))
@property
def amodule_init_args(self) -> dict:
return self.pack_amodule_init_args(
argument_spec=dict(
port_info=dict(
type='str',
choices=['detailed', 'general']
),
filter=dict(
type='dict',
apply_defaults=True,
options=dict(
hostname=dict(
type='str',
),
display_name=dict(
type='str',
),
ip=dict(
type='str',
),
created_from=dict(
type='str',
),
created_to=dict(
type='str',
),
updated_from=dict(
type='str',
),
updated_to=dict(
type='str',
),
),
),
pagination=dict(
type='dict',
apply_defaults=True,
options=dict(
number=dict(
type='int',
default=1,
),
size=dict(
type='int',
),
),
),
sorting=dict(
type='dict',
options=dict(
asc=dict(
type='bool',
default=True,
),
field=dict(
type='str',
choices=[
'name',
'hostname',
'last_sync',
'display_name',
'ip',
'created_at',
'updated_at',
],
required=True,
),
),
),
),
supports_check_mode=True,
)
def run(self):
self.get_info()
self.exit()
def get_info(self):
api_params: dict[str, Any] = dict()
aparam_port_info = self.aparams['port_info']
if aparam_port_info is not None:
api_params['port_info'] = aparam_port_info
aparam_filter: dict[str, Any] = self.aparams['filter']
for field, value in aparam_filter.items():
if value is not None:
api_params[field] = value
aparam_pagination: dict[str, Any] = self.aparams['pagination']
api_params['page'] = aparam_pagination['number']
api_params['per_page'] = aparam_pagination['size']
aparam_sorting: dict[str, Any] | None = self.aparams['sorting']
if aparam_sorting:
api_params['sort_by'] = aparam_sorting['field']
api_params['sort_order'] = (
'asc' if aparam_sorting['asc'] else 'desc'
)
response = self.decort_api_call(
arg_req_function=requests.get,
arg_api_name='/restmachine/sdn/hypervisor/list',
arg_params=api_params,
accept_json_response=True,
)
self.facts = response.json()
def main():
DecortSDNHypervisorList().run()
if __name__ == '__main__':
main()

View File

@@ -0,0 +1,588 @@
#!/usr/bin/python
DOCUMENTATION = r'''
---
module: decort_sdn_logical_port
description: See L(Module Documentation,https://repository.basistech.ru/BASIS/decort-ansible/wiki/Home). # noqa: E501
'''
from typing import Any
from ansible.module_utils.basic import AnsibleModule
from ansible.module_utils.decort_utils import DecortController
import requests
import time
class DecortSDNLogicalPort(DecortController):
FIELDS_FOR_CREATE = (
'access_group_id',
'adapter_mac',
'description',
'display_name',
'hypervisor',
'labels',
'port_security',
'segment_id',
'unique_identifier',
)
FIELDS_FOR_UPDATE = (
'adapter_mac',
'description',
'display_name',
'hypervisor',
'labels',
'logical_port_id',
'port_security',
'segment_id',
'version_id',
)
UPDATE_FIELDS = (
'adapter_mac',
'description',
'display_name',
'hypervisor',
'labels',
'port_security',
'segment_id',
)
logical_port_id: str | None = None
_logical_port_info: dict[str, Any] | None = None
need_final_get: bool = True
def __init__(self):
super().__init__(AnsibleModule(**self.amodule_init_args))
@property
def amodule_init_args(self) -> dict:
return self.pack_amodule_init_args(
argument_spec=dict(
no_migration=dict(
type='bool',
),
hypervisor_change_via_migration=dict(
type='bool',
default=False,
),
logical_port_id=dict(
type='str',
),
state=dict(
type='str',
choices=[
'present',
'enabled',
'disabled',
'absent',
'absent_force',
],
),
access_group_id=dict(
type='str',
),
adapter_mac=dict(
type='str',
),
description=dict(
type='str',
),
display_name=dict(
type='str',
),
hypervisor=dict(
type='str',
),
labels=dict(
type='dict',
),
no_addresses=dict(
type='bool',
),
port_security=dict(
type='bool',
),
segment_id=dict(
type='str',
),
unique_identifier=dict(
type='str',
),
version_id=dict(
type='int',
),
),
supports_check_mode=True,
mutually_exclusive=[
('unique_identifier', 'logical_port_id'),
('no_migration', 'hypervisor_change_via_migration'),
],
)
def run(self):
self.check_amodule_args()
state = self.aparams['state']
if self.aparams['unique_identifier'] is not None:
self._logical_port_info = (
self.logical_port_get_by_unique_identifier(
fail_if_not_found=False,
)
)
self.logical_port_id = self._logical_port_info['id']
elif self.aparams['logical_port_id']:
self.logical_port_id = self.aparams['logical_port_id']
self._logical_port_info = self.logical_port_get(
fail_if_not_found=False,
)
elif self.aparams['display_name']:
self._logical_port_info = self.logical_port_find(
display_name=self.aparams['display_name']
)
if self._logical_port_info:
self.logical_port_id = self._logical_port_info['id']
if self._logical_port_info is not None:
if state in ('absent', 'absent_force'):
self.logical_port_delete(
force=state == 'absent_force',
)
elif self.aparams['no_migration']:
self.logical_port_migration_cancel()
elif self.aparams['hypervisor_change_via_migration']:
self.logical_port_migration_start()
else:
self.logical_port_update(
desired_enabled=(
True if state == 'enabled'
else False if state == 'disabled'
else None
)
)
else:
if state == 'present':
self.check_amodule_args_for_create()
self.logical_port_create()
elif state in ('enabled', 'disabled'):
self.message(
'Check for parameter "state" failed: state values '
'"enabled"/"disabled" can only be applied to an existing '
'logical port.'
)
self.exit(fail=True)
else:
self.need_final_get = False
self.facts = {}
if self.need_final_get and not self.amodule.check_mode:
if self._logical_port_info is not None:
self.facts = self.logical_port_info
else:
self.facts = self.logical_port_get()
self.exit()
def check_amodule_args(self):
check_errors = False
if (
self.aparams['no_migration']
and self.aparams['state'] == 'absent'
):
check_errors = True
self.message(
'Check for parameter "no_migration" failed: '
'no_migration cannot be used when state is "absent".'
)
if (
self.aparams['hypervisor_change_via_migration']
and self.aparams['hypervisor'] is None
):
check_errors = True
self.message(
'Check for parameters '
'"hypervisor_change_via_migration/hypervisor" failed: '
'"hypervisor" must be specified when '
'"hypervisor_change_via_migration" is true.'
)
if (
self.aparams['no_migration']
and self.aparams['hypervisor'] is not None
):
check_errors = True
self.message(
'Check for parameters "no_migration/hypervisor" failed: '
'"hypervisor" cannot be used when "no_migration" is true.'
)
if check_errors:
self.exit(fail=True)
def check_amodule_args_for_create(self):
check_errors = False
for field in (
'access_group_id',
'description',
'display_name',
'hypervisor',
'port_security',
'segment_id',
):
if self.aparams[field] is None:
check_errors = True
self.message(
f'Check for parameter "{field}" failed: parameter '
f'"{field}" is required when creating a logical port.'
)
if check_errors:
self.exit(fail=True)
@property
def logical_port_info(self) -> dict[str, Any]:
if self._logical_port_info is None:
if (
self.aparams['unique_identifier'] is None
and self.aparams['logical_port_id'] is None
):
raise TypeError
logical_port_info = self.logical_port_get()
if logical_port_info is None:
raise TypeError
self._logical_port_info = logical_port_info
return self._logical_port_info
@DecortController.waypoint
def logical_port_get_by_unique_identifier(
self,
fail_if_not_found: bool = True,
) -> dict[str, Any] | None:
response = self.decort_api_call(
arg_req_function=requests.get,
arg_api_name=(
'/restmachine/sdn/logical_port/get_by_unique_identifier'
),
arg_params={
'unique_identifier': self.aparams['unique_identifier']
},
not_fail_codes=[404],
accept_json_response=True,
)
if response.status_code == 404:
if fail_if_not_found:
self.message(
self.MESSAGES.obj_not_found(
obj='logical port',
id=self.aparams['unique_identifier'],
)
)
self.exit(fail=True)
return None
return response.json()
@DecortController.waypoint
def logical_port_get(
self,
fail_if_not_found: bool = True,
) -> dict[str, Any] | None:
response = self.decort_api_call(
arg_req_function=requests.get,
arg_api_name='/restmachine/sdn/logical_port/get',
arg_params={'logical_port_id': self.logical_port_id},
accept_json_response=True,
not_fail_codes=[404],
)
if response.status_code == 404:
if fail_if_not_found:
self.message(
self.MESSAGES.obj_not_found(
obj='logical port',
id=self.logical_port_id,
)
)
self.exit(fail=True)
return None
return response.json()
@DecortController.waypoint
def logical_port_find(self, display_name: str) -> dict[str, Any] | None:
response = self.decort_api_call(
arg_req_function=requests.get,
arg_api_name='/restmachine/sdn/logical_port/list',
arg_params={'display_name': display_name},
accept_json_response=True,
)
return response.json()[0] if response.json() else None
@DecortController.waypoint
@DecortController.checkmode
def logical_port_create(self):
payload = {}
for field in self.FIELDS_FOR_CREATE:
value = self.aparams[field]
if value is not None:
payload[field] = value
payload['enabled'] = True
response = self.decort_api_call(
arg_req_function=requests.post,
arg_api_name='/restmachine/sdn/logical_port/create',
arg_json_body=payload,
accept_json_response=True,
)
self.logical_port_id = response.json()['id']
self.set_changed()
@DecortController.waypoint
@DecortController.checkmode
def logical_port_delete(
self,
force: bool = False,
):
version_id = self.aparams['version_id']
if version_id is None and self.logical_port_info is not None:
version_id = self.logical_port_info['version_id']
payload = {
'logical_port_id': self.logical_port_id,
'version_id': version_id,
'force': force,
}
self.decort_api_call(
arg_req_function=requests.delete,
arg_api_name='/restmachine/sdn/logical_port/delete',
arg_json_body=payload,
)
self.need_final_get = False
self.facts = {}
self.set_changed()
self.message(
msg=self.MESSAGES.obj_deleted(
obj='logical port',
id=self.logical_port_id,
permanently=True,
)
)
@DecortController.waypoint
@DecortController.checkmode
def logical_port_update(
self,
desired_enabled: bool | None = None,
):
need_update = False
addresses_to_remove = []
for field in self.UPDATE_FIELDS:
value = self.aparams[field]
if value is None:
continue
if self.aparams['no_addresses']:
bindings = self.logical_port_info['bindings']
if bindings and bindings.get('logical_port_addresses'):
for address in bindings['logical_port_addresses']:
addresses_to_remove.append(address['id'])
if addresses_to_remove:
need_update = True
if field == 'port_security':
current_value = self.logical_port_info['bindings'].get(
'port_security'
)
elif field == 'segment_id':
current_value = self.logical_port_info['bindings'].get(
'segment_id'
)
else:
current_value = self.logical_port_info.get(field)
if isinstance(value, dict) and isinstance(current_value, dict):
if any(
current_value.get(key) != expected_value
for key, expected_value in value.items()
):
need_update = True
break
continue
if value != current_value:
need_update = True
break
if (
not need_update
and desired_enabled is not None
and desired_enabled != self.logical_port_info['enabled']
):
need_update = True
if need_update:
payload = {
'logical_port_id': self.logical_port_id,
'version_id': (
self.aparams['version_id']
or self.logical_port_info['version_id']
),
'adapter_mac': (
self.aparams['adapter_mac']
or self.logical_port_info['adapter_mac']
),
'description': (
self.aparams['description']
or self.logical_port_info['description']
),
'display_name': (
self.aparams['display_name']
or self.logical_port_info['display_name']
),
'enabled': (
desired_enabled
if desired_enabled is not None
else self.logical_port_info['enabled']
),
'hypervisor': (
self.aparams['hypervisor']
or self.logical_port_info['hypervisor']
),
'labels': (
self.aparams['labels']
or self.logical_port_info.get('labels')
),
'port_security': (
self.aparams['port_security']
if self.aparams['port_security'] is not None
else self.logical_port_info['bindings']['port_security']
),
'segment_id': (
self.aparams['segment_id']
or self.logical_port_info['bindings']['segment_id']
),
}
if addresses_to_remove:
payload['remove_addresses'] = addresses_to_remove
self.decort_api_call(
arg_req_function=requests.put,
arg_api_name='/restmachine/sdn/logical_port/update',
arg_json_body=payload,
accept_json_response=True,
)
self._logical_port_info = None
self.set_changed()
@DecortController.waypoint
@DecortController.checkmode
def logical_port_migration_cancel(self):
version_id = self.aparams['version_id']
if version_id is None and self._logical_port_info is not None:
version_id = self._logical_port_info['version_id']
self.decort_api_call(
arg_req_function=requests.delete,
arg_api_name='/restmachine/sdn/logical_port/migration_cancel',
arg_json_body={
'logical_port_id': self.logical_port_id,
'version_id': version_id,
},
)
self.set_changed()
@DecortController.waypoint
@DecortController.checkmode
def logical_port_migration_start(self):
self.decort_api_call(
arg_req_function=requests.post,
arg_api_name='/restmachine/sdn/logical_port/migration_start',
arg_json_body={
'logical_port_id': self.logical_port_id,
'target_hypervisor': self.aparams['hypervisor'],
'version_id': (
self.aparams['version_id']
or self.logical_port_info['version_id']
if self._logical_port_info is not None
else self.aparams['version_id']
),
},
accept_json_response=True,
)
waiting_statuses = {
'Idle',
'SynchronizingAtCore',
'SynchronizingAtOVN',
}
failed_statuses = {
'NoHypervisorAtOVN': (
'Logical port migration failed: no hypervisor at OVN.'
),
'FailedAtCore': 'Logical port migration failed at core.',
'TemporaryFailedAtCore': (
'Logical port migration temporary failed at core.'
),
}
target_hypervisor = self.aparams['hypervisor']
for _ in range(300):
logical_port_info = self.logical_port_get()
if logical_port_info is None:
self.message(
'Logical port migration failed: '
'can\'t get logical port info.'
)
self.exit(fail=True)
status_info = logical_port_info.get('status')
if not isinstance(status_info, dict):
status_info = {}
hypervisors = status_info.get('hypervisors', [])
target_hypervisor_status = None
for hypervisor in hypervisors:
if hypervisor.get('name') == target_hypervisor:
target_hypervisor_status = str(
hypervisor.get('operation_status')
)
break
if target_hypervisor_status == 'Synchronized':
self._logical_port_info = logical_port_info
self.set_changed()
return
if target_hypervisor_status == 'NoHypervisorAtOVN':
self.message(failed_statuses['NoHypervisorAtOVN'])
self.exit(fail=True)
if target_hypervisor_status == 'FailedAtCore':
self.message(failed_statuses['FailedAtCore'])
self.exit(fail=True)
if target_hypervisor_status == 'TemporaryFailedAtCore':
self.message(failed_statuses['TemporaryFailedAtCore'])
self.exit(fail=True)
if target_hypervisor_status in waiting_statuses:
time.sleep(5)
continue
if target_hypervisor_status is None:
self.message(
'Logical port migration failed: target hypervisor '
f'"{target_hypervisor}" not found in status.'
)
else:
self.message(
'Logical port migration failed with unexpected status '
f'for hypervisor "{target_hypervisor}": '
f'{target_hypervisor_status}.'
)
self.exit(fail=True)
self.message('Logical port migration timed out.')
self.exit(fail=True)
def main():
DecortSDNLogicalPort().run()
if __name__ == '__main__':
main()

View File

@@ -0,0 +1,206 @@
#!/usr/bin/python
DOCUMENTATION = r'''
---
module: decort_sdn_logical_port_address
description: See L(Module Documentation,https://repository.basistech.ru/BASIS/decort-ansible/wiki/Home). # noqa: E501
'''
from typing import Any
from ansible.module_utils.basic import AnsibleModule
from ansible.module_utils.decort_utils import DecortController
import requests
class DecortSDNLogicalPortAddress(DecortController):
logical_port_id: str | None = None
_logical_port_info: dict[str, Any] | None = None
def __init__(self):
super().__init__(AnsibleModule(**self.amodule_init_args))
@property
def amodule_init_args(self) -> dict:
return self.pack_amodule_init_args(
argument_spec=dict(
discovered=dict(
type='bool',
),
ip_addr=dict(
type='str',
required=True,
),
ip_type=dict(
type='str',
choices=['IPv4', 'IPv6'],
),
logical_port_id=dict(
type='str',
required=True,
),
mac=dict(
type='str',
),
primary=dict(
type='bool',
),
state=dict(
type='str',
choices=['present', 'absent'],
),
),
supports_check_mode=True,
)
def check_amodule_args_for_create(self):
check_errors = False
if self.aparams['ip_type'] is None:
check_errors = True
self.message(
'Check for parameter "ip_type" failed: '
'ip_type must be specified when creating a new address.'
)
if self.aparams['mac'] is None:
check_errors = True
self.message(
'Check for parameter "mac" failed: '
'mac must be specified when creating a new address.'
)
if check_errors:
self.exit(fail=True)
@property
def logical_port_info(self) -> dict[str, Any]:
if self._logical_port_info is None:
logical_port_info = self.logical_port_get()
if logical_port_info is None:
raise TypeError
self._logical_port_info = logical_port_info
return self._logical_port_info
def find_address(self) -> dict[str, Any] | None:
addresses = self.logical_port_info.get('bindings', {}).get(
'logical_port_addresses', []
)
for addr in addresses:
if addr.get('ip') == self.aparams['ip_addr']:
return addr
return None
def port_payload(self) -> dict:
info = self.logical_port_info
return {
'logical_port_id': self.logical_port_id,
'version_id': info['version_id'],
'adapter_mac': info['adapter_mac'],
'description': info['description'],
'display_name': info['display_name'],
'enabled': info['enabled'],
'hypervisor': info['hypervisor'],
'labels': info.get('labels'),
'port_security': info['bindings']['port_security'],
'segment_id': info['bindings']['segment_id'],
}
def logical_port_update(self, payload: dict):
response = self.decort_api_call(
arg_req_function=requests.put,
arg_api_name='/restmachine/sdn/logical_port/update',
arg_json_body=payload,
accept_json_response=True,
)
self._logical_port_info = response.json()
self.set_changed()
@DecortController.waypoint
def logical_port_get(self) -> dict[str, Any]:
response = self.decort_api_call(
arg_req_function=requests.get,
arg_api_name='/restmachine/sdn/logical_port/get',
arg_params={'logical_port_id': self.logical_port_id},
accept_json_response=True,
not_fail_codes=[404],
)
if response.status_code == 404:
self.message(
self.MESSAGES.obj_not_found(
obj='logical port',
id=self.logical_port_id,
)
)
self.exit(fail=True)
return response.json()
@DecortController.waypoint
@DecortController.checkmode
def address_add(self):
payload = self.port_payload()
address_data: dict[str, Any] = {'ip': self.aparams['ip_addr']}
for param_name, api_param_name in (
('ip_type', 'ip_type'),
('discovered', 'is_discovered'),
('primary', 'is_primary'),
('mac', 'mac'),
):
if self.aparams[param_name] is not None:
address_data[api_param_name] = self.aparams[param_name]
payload['add_addresses'] = [address_data]
self.logical_port_update(payload)
@DecortController.waypoint
@DecortController.checkmode
def address_remove(self, address_id: str):
payload = self.port_payload()
payload['remove_addresses'] = [address_id]
self.logical_port_update(payload)
self.message(
self.MESSAGES.obj_deleted(
obj='logical port address',
id=self.aparams['ip_addr'],
)
)
def run(self):
self.logical_port_id = self.aparams['logical_port_id']
existing_addr = self.find_address()
if existing_addr:
if self.aparams['state'] == 'absent':
self.address_remove(existing_addr['id'])
else:
state = self.aparams['state']
if state is None:
state = 'present'
self.message(
msg=self.MESSAGES.default_value_used(
param_name='state',
default_value=state,
),
warning=True,
)
if state == 'absent':
self.message(
self.MESSAGES.obj_not_found(
obj='logical port address',
id=self.aparams['ip_addr'],
)
)
else:
self.check_amodule_args_for_create()
self.address_add()
self.facts = self.find_address() or {}
self.exit()
def main():
DecortSDNLogicalPortAddress().run()
if __name__ == '__main__':
main()

View File

@@ -0,0 +1,171 @@
#!/usr/bin/python
DOCUMENTATION = r'''
---
module: decort_sdn_logical_port_list
description: See L(Module Documentation,https://repository.basistech.ru/BASIS/decort-ansible/wiki/Home). # noqa: E501
'''
from typing import Any
from ansible.module_utils.basic import AnsibleModule
from ansible.module_utils.decort_utils import DecortController
import requests
class DecortSDNLogicalPortList(DecortController):
def __init__(self):
super().__init__(AnsibleModule(**self.amodule_init_args))
@property
def amodule_init_args(self) -> dict:
return self.pack_amodule_init_args(
argument_spec=dict(
filter=dict(
type='dict',
apply_defaults=True,
options=dict(
access_group_id=dict(
type='str',
),
adapter_mac=dict(
type='str',
),
address_detection=dict(
type='bool',
),
created_from=dict(
type='str',
),
created_to=dict(
type='str',
),
display_name=dict(
type='str',
),
enabled=dict(
type='bool',
),
external_network_id=dict(
type='str',
),
hypervisor=dict(
type='str',
),
hypervisor_display_name=dict(
type='str',
),
hypervisor_status=dict(
type='str',
choices=['Up', 'Warning', 'Error'],
),
live_migration_target_hv=dict(
type='str',
),
operation_status=dict(
type='str',
choices=[
'Idle',
'SynchronizingAtCore',
'SynchronizingAtOVN',
'Synchronized',
'NoHypervisorAtOVN',
'FailedAtCore',
'TemporaryFailedAtCore',
],
),
port_security=dict(
type='bool',
),
segment_display_name=dict(
type='str',
),
segment_id=dict(
type='str',
),
unique_identifier=dict(
type='str',
),
),
),
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=[
'created_at',
'deleted_at',
'display_name',
'hypervisor',
'hypervisor_display_name',
'port_security',
'primary_address',
'segment_display_name',
'segment_id',
'updated_at',
],
required=True,
),
),
),
),
supports_check_mode=True,
)
def run(self):
self.get_info()
self.exit()
def get_info(self):
api_params: dict[str, Any] = dict()
aparam_filter: dict[str, Any] = self.aparams['filter']
for field, value in aparam_filter.items():
if value is not None:
api_params[field] = value
aparam_pagination: dict[str, Any] = self.aparams['pagination']
api_params['page'] = aparam_pagination['number']
api_params['per_page'] = aparam_pagination['size']
aparam_sorting: dict[str, Any] | None = self.aparams['sorting']
if aparam_sorting:
api_params['sort_by'] = aparam_sorting['field']
api_params['sort_order'] = (
'asc' if aparam_sorting['asc'] else 'desc'
)
response = self.decort_api_call(
arg_req_function=requests.get,
arg_api_name='/restmachine/sdn/logical_port/list',
arg_params=api_params,
accept_json_response=True,
)
self.facts = response.json()
def main():
DecortSDNLogicalPortList().run()
if __name__ == '__main__':
main()

View File

@@ -0,0 +1,299 @@
#!/usr/bin/python
DOCUMENTATION = r'''
---
module: decort_sdn_network_object_group
description: See L(Module Documentation,https://repository.basistech.ru/BASIS/decort-ansible/wiki/Home). # noqa: E501
'''
from typing import Any
from ansible.module_utils.basic import AnsibleModule
from ansible.module_utils.decort_utils import DecortController
import requests
class DecortSDNNetworkObjectGroup(DecortController):
REQUIRED_FIELDS = (
'access_group_id',
'description',
'name',
)
object_group_id: str | None = None
_object_group_info: dict[str, Any] | None = None
def __init__(self):
super().__init__(AnsibleModule(**self.amodule_init_args))
@property
def amodule_init_args(self) -> dict:
return self.pack_amodule_init_args(
argument_spec=dict(
access_group_id=dict(
type='str',
),
description=dict(
type='str',
),
no_addresses=dict(
type='bool',
),
no_logical_ports=dict(
type='bool',
),
name=dict(
type='str',
),
object_group_id=dict(
type='str',
),
version_id=dict(
type='int',
),
),
supports_check_mode=True,
)
@property
def object_group_info(self) -> dict[str, Any]:
if self._object_group_info is None:
if not isinstance(self.object_group_id, str):
raise TypeError
object_group_info = self.network_object_group_get()
if object_group_info is None:
raise TypeError
self._object_group_info = object_group_info
return self._object_group_info
def run(self):
if self.aparams['object_group_id']:
self.object_group_id = self.aparams['object_group_id']
elif self.aparams['name']:
object_group_info = self.network_object_group_find(
name=self.aparams['name']
)
if object_group_info:
self.object_group_id = object_group_info['id']
if self.object_group_id is None:
self.check_amodule_args_for_create()
self.network_object_group_create()
else:
self.check_amodule_args_for_update()
self.network_object_group_update()
if self.aparams['no_logical_ports']:
self.network_object_group_detach_logical_ports()
self.facts = self.network_object_group_get()
self.exit()
def check_amodule_args_for_create(self):
check_errors = False
for field in self.REQUIRED_FIELDS:
if self.aparams[field] is None:
check_errors = True
self.message(
f'Check for parameter "{field}" failed: '
f'"{field}" is required when creating an object group.'
)
if check_errors:
self.exit(fail=True)
def check_amodule_args_for_update(self):
check_errors = False
version_id = self.aparams['version_id']
if (
version_id is not None
and version_id != self.object_group_info['version_id']
):
check_errors = True
self.message(
'Check for parameters "version_id" failed: '
'object group version mismatch: '
f'given version: {version_id}, '
f'current version: {self.object_group_info["version_id"]}.'
)
if check_errors:
self.exit(fail=True)
@DecortController.waypoint
def network_object_group_get(self) -> dict[str, Any] | None:
response = self.decort_api_call(
arg_req_function=requests.get,
arg_api_name='/restmachine/sdn/network_object_group/get',
arg_params={'object_group_id': self.object_group_id},
not_fail_codes=[404],
accept_json_response=True,
)
if response.status_code == 404:
self.message(
f'Network object group with id "{self.object_group_id}" '
'not found.'
)
self.exit(fail=True)
return response.json()
@DecortController.waypoint
@DecortController.checkmode
def network_object_group_find(self, name: str) -> dict[str, Any] | None:
response = self.decort_api_call(
arg_req_function=requests.get,
arg_api_name='/restmachine/sdn/network_object_group/list',
arg_params={'name': name},
accept_json_response=True,
)
return response.json()[0] if response.json() else None
@DecortController.waypoint
@DecortController.checkmode
def network_object_group_create(self):
payload = dict()
for field in self.REQUIRED_FIELDS:
value = self.aparams[field]
if value is not None:
payload[field] = value
response = self.decort_api_call(
arg_req_function=requests.post,
arg_api_name='/restmachine/sdn/network_object_group/create',
arg_json_body=payload,
accept_json_response=True,
)
self._object_group_info = response.json()
self.object_group_id = response.json()['id']
self.set_changed()
@DecortController.waypoint
@DecortController.checkmode
def network_object_group_update(self):
need_update = False
remove_addresses = False
if self.aparams['no_addresses']:
current_addresses = self.object_group_info.get('addresses')
if isinstance(current_addresses, list) and current_addresses:
remove_addresses = True
need_update = True
for field in self.REQUIRED_FIELDS:
value = self.aparams[field]
if value is None:
continue
current_value = self.object_group_info.get(field)
if isinstance(value, list) and isinstance(current_value, list):
if len(value) != len(current_value):
need_update = True
break
for index, expected_item in enumerate(value):
current_item = current_value[index]
if (
isinstance(expected_item, dict)
and isinstance(current_item, dict)
):
if any(
current_item.get(key) != expected_value
for key, expected_value in expected_item.items()
):
need_update = True
break
continue
if expected_item != current_item:
need_update = True
break
if need_update:
break
continue
if value != current_value:
need_update = True
break
if not need_update:
return
payload = {
'object_group_id': self.object_group_id,
'version_id': (
self.aparams['version_id']
or self.object_group_info['version_id']
),
'access_group_id': (
self.aparams['access_group_id']
or self.object_group_info['access_group_id']
),
'description': (
self.aparams['description']
or self.object_group_info['description']
),
'name': (
self.aparams['name']
or self.object_group_info['name']
),
}
if remove_addresses:
payload['addresses'] = []
response = self.decort_api_call(
arg_req_function=requests.put,
arg_api_name='/restmachine/sdn/network_object_group/update',
arg_json_body=payload,
accept_json_response=True,
)
self._object_group_info = response.json()
self.set_changed()
@DecortController.waypoint
@DecortController.checkmode
def network_object_group_detach_logical_ports(self):
logical_ports = self.object_group_info.get('logical_ports')
if not isinstance(logical_ports, list) or not logical_ports:
return
port_bindings = []
for logical_port in logical_ports:
if (
isinstance(logical_port, dict)
and logical_port.get('id') is not None
and logical_port.get('version_id') is not None
):
port_bindings.append(
{
'port_id': logical_port['id'],
'port_version': logical_port['version_id'],
}
)
if not port_bindings:
return
self.decort_api_call(
arg_req_function=requests.post,
arg_api_name='/restmachine/sdn/network_object_group/detach_logical_ports', # noqa: E501
arg_json_body={
'access_group_id': (
self.aparams['access_group_id']
or self.object_group_info['access_group_id']
),
'object_group_id': self.object_group_id,
'version_id': (
self.aparams['version_id']
or self.object_group_info['version_id']
),
'port_bindings': port_bindings,
},
accept_json_response=True,
)
self._object_group_info = None
self.set_changed()
def main():
DecortSDNNetworkObjectGroup().run()
if __name__ == '__main__':
main()

View File

@@ -0,0 +1,209 @@
#!/usr/bin/python
DOCUMENTATION = r'''
---
module: decort_sdn_network_object_group_ip_range
description: See L(Module Documentation,https://repository.basistech.ru/BASIS/decort-ansible/wiki/Home). # noqa: E501
'''
from typing import Any
from ansible.module_utils.basic import AnsibleModule
from ansible.module_utils.decort_utils import DecortController
import requests
class DecortSDNNetworkObjectGroupIPRange(DecortController):
object_group_id: str | None = None
_object_group_info: dict[str, Any] | None = None
def __init__(self):
super().__init__(AnsibleModule(**self.amodule_init_args))
@property
def amodule_init_args(self) -> dict:
return self.pack_amodule_init_args(
argument_spec=dict(
object_group_id=dict(
type='str',
required=True,
),
ip_addr_range=dict(
type='dict',
required=True,
options=dict(
start=dict(
type='str',
required=True,
),
end=dict(
type='str',
),
),
),
ip_proto=dict(
type='str',
choices=['IPv4', 'IPv6'],
),
net_prefix=dict(
type='str',
),
state=dict(
type='str',
required=True,
choices=['present', 'absent'],
),
),
supports_check_mode=True,
required_if=[('state', 'present', ['ip_proto'])],
)
@property
def object_group_info(self) -> dict[str, Any]:
if self._object_group_info is None:
object_group_info = self.network_object_group_get()
if object_group_info is None:
raise TypeError
self._object_group_info = object_group_info
return self._object_group_info
def find_ip_range(self) -> dict | None:
ip_addr = self.aparams['ip_addr_range']['start']
ip_proto = self.aparams['ip_proto']
for addr in self.object_group_info.get('addresses') or []:
if addr.get('ip_addr') != ip_addr:
continue
if (
ip_proto is not None
and addr.get('net_address_type') != ip_proto
):
continue
return addr
return None
def group_payload(self) -> dict:
return {
'object_group_id': self.object_group_id,
'version_id': self.object_group_info['version_id'],
'access_group_id': self.object_group_info['access_group_id'],
'description': self.object_group_info['description'],
'name': self.object_group_info['name'],
}
def ip_range_add(self):
current_addresses = self.object_group_info.get('addresses') or []
ip_addr_range = self.aparams['ip_addr_range']
ip_proto = self.aparams['ip_proto']
ip_range_data = {'ip_addr': ip_addr_range['start']}
if ip_proto is not None:
ip_range_data['net_address_type'] = ip_proto
if ip_addr_range['end'] is not None:
ip_range_data['ip_addr_range_end'] = ip_addr_range['end']
if self.aparams['net_prefix'] is not None:
ip_range_data['ip_prefix'] = self.aparams['net_prefix']
for index, addr in enumerate(current_addresses):
if addr.get('ip_addr') != ip_range_data['ip_addr']:
continue
if (
ip_proto is not None
and addr.get('net_address_type') != ip_proto
):
continue
if not any(
addr.get(field) != value
for field, value in ip_range_data.items()
if field in addr
):
return
updated_addresses = list(current_addresses)
updated_addresses[index] = {**addr, **ip_range_data}
self.network_object_group_update_addresses(updated_addresses)
return
self.network_object_group_update_addresses(
list(current_addresses) + [ip_range_data]
)
def ip_range_remove(self):
current_addresses = self.object_group_info.get('addresses') or []
ip_addr = self.aparams['ip_addr_range']['start']
ip_proto = self.aparams['ip_proto']
addresses_to_keep = [
addr for addr in current_addresses
if addr.get('ip_addr') != ip_addr
or (
ip_proto is not None
and addr.get('net_address_type') != ip_proto
)
]
if len(addresses_to_keep) == len(current_addresses):
self.message(
self.MESSAGES.obj_not_found(
obj='ip_range',
id=ip_addr,
)
)
return
self.network_object_group_update_addresses(addresses_to_keep)
self.message(
self.MESSAGES.obj_deleted(
obj='ip_range',
id=ip_addr,
)
)
@DecortController.waypoint
def network_object_group_get(self) -> dict[str, Any]:
response = self.decort_api_call(
arg_req_function=requests.get,
arg_api_name='/restmachine/sdn/network_object_group/get',
arg_params={'object_group_id': self.object_group_id},
not_fail_codes=[404],
accept_json_response=True,
)
if response.status_code == 404:
self.message(
self.MESSAGES.obj_not_found(
obj='network object group',
id=self.object_group_id,
)
)
self.exit(fail=True)
return response.json()
@DecortController.waypoint
@DecortController.checkmode
def network_object_group_update_addresses(self, addresses: list):
payload = self.group_payload()
payload['addresses'] = addresses
response = self.decort_api_call(
arg_req_function=requests.put,
arg_api_name='/restmachine/sdn/network_object_group/update',
arg_json_body=payload,
accept_json_response=True,
)
self._object_group_info = response.json()
self.set_changed()
def run(self):
self.object_group_id = self.aparams['object_group_id']
if self.aparams['state'] == 'present':
self.ip_range_add()
else:
self.ip_range_remove()
self.facts = self.find_ip_range() or {}
self.exit()
def main():
DecortSDNNetworkObjectGroupIPRange().run()
if __name__ == '__main__':
main()

View File

@@ -0,0 +1,245 @@
#!/usr/bin/python
DOCUMENTATION = r'''
---
module: decort_sdn_network_object_group_logical_port
description: See L(Module Documentation,https://repository.basistech.ru/BASIS/decort-ansible/wiki/Home). # noqa: E501
'''
from typing import Any
from ansible.module_utils.basic import AnsibleModule
from ansible.module_utils.decort_utils import DecortController
import requests
class DecortSDNNetworkObjectGroupLogicalPort(DecortController):
object_group_id: str
_object_group_info: dict[str, Any] | None = None
object_group_version: int | None = None
logical_port_id: str
logical_port_version: int | None = None
def __init__(self):
super().__init__(AnsibleModule(**self.amodule_init_args))
@property
def amodule_init_args(self) -> dict:
return self.pack_amodule_init_args(
argument_spec=dict(
access_group_id=dict(
type='str',
required=True,
),
object_group_id=dict(
type='str',
required=True,
),
logical_port_id=dict(
type='str',
required=True,
),
logical_port_version=dict(
type='int',
),
version_id=dict(
type='int',
),
state=dict(
type='str',
choices=['present', 'absent'],
),
),
supports_check_mode=True,
)
@property
def object_group_info(self) -> dict[str, Any]:
if self._object_group_info is None:
if not isinstance(self.object_group_id, str):
raise TypeError
info = self.network_object_group_get()
if info is None:
raise TypeError
self._object_group_info = info
return self._object_group_info
def run(self):
self.object_group_id = self.aparams['object_group_id']
self.logical_port_id = self.aparams['logical_port_id']
is_attached = self.is_logical_port_attached()
state = self.aparams['state']
self.object_group_version = self.get_object_group_version()
self.logical_port_version = self.get_port_version(
is_attached=is_attached
)
if state == 'present':
if not is_attached:
self.network_object_group_logical_port_attach()
elif state == 'absent':
if is_attached:
self.network_object_group_logical_port_detach()
self.facts = {}
self.exit()
def get_object_group_version(self) -> int:
provided_version: int | None = self.aparams['version_id']
current_version: int = self.object_group_info['version_id']
if (
provided_version is not None
and provided_version != current_version
):
self.message(
'Check for parameters "version_id" failed: '
'object group version mismatch: '
f'given version: {provided_version}, '
f'current version: {current_version}.'
)
self.exit(fail=True)
return provided_version or current_version
@DecortController.waypoint
def network_object_group_get(self) -> dict[str, Any] | None:
response = self.decort_api_call(
arg_req_function=requests.get,
arg_api_name='/restmachine/sdn/network_object_group/get',
arg_params={'object_group_id': self.object_group_id},
not_fail_codes=[404],
accept_json_response=True,
)
if response.status_code == 404:
self.message(
self.MESSAGES.obj_not_found(
obj='network object group',
id=self.object_group_id,
)
)
self.exit(fail=True)
return response.json()
@DecortController.waypoint
def logical_port_get(self, logical_port_id: str) -> dict[str, Any]:
response = self.decort_api_call(
arg_req_function=requests.get,
arg_api_name='/restmachine/sdn/logical_port/get',
arg_params={'logical_port_id': logical_port_id},
not_fail_codes=[404],
accept_json_response=True,
)
if response.status_code == 404:
self.message(
self.MESSAGES.obj_not_found(
obj='logical port',
id=logical_port_id,
)
)
self.exit(fail=True)
return response.json()
def is_logical_port_attached(self) -> bool:
logical_ports = self.object_group_info.get('logical_ports') or []
for lp in logical_ports:
if lp['id'] == self.logical_port_id:
return True
return False
def get_port_version_from_object_group(self) -> int | None:
logical_ports = self.object_group_info.get('logical_ports') or []
for lp in logical_ports:
if lp['id'] != self.logical_port_id:
continue
return lp['version_id']
return None
def get_port_version(self, is_attached: bool) -> int:
provided_version = self.aparams['logical_port_version']
current_version = None
if is_attached:
current_version = self.get_port_version_from_object_group()
if current_version is None:
info = self.logical_port_get(logical_port_id=self.logical_port_id)
current_version = info['version_id']
if (
provided_version is not None
and provided_version != current_version
):
self.message(
'Check for parameter "logical_port_version" failed: '
'logical port version mismatch: '
f'given version: {provided_version}, '
f'current version: {current_version}.'
)
self.exit(fail=True)
return provided_version or current_version
@DecortController.waypoint
@DecortController.checkmode
def network_object_group_logical_port_attach(self):
self.decort_api_call(
arg_req_function=requests.post,
arg_api_name='/restmachine/sdn/network_object_group/attach_logical_ports', # noqa: E501
arg_json_body={
'object_group_id': self.object_group_id,
'access_group_id': self.aparams['access_group_id'],
'version_id': self.object_group_version,
'port_bindings': [
{
'port_id': self.logical_port_id,
'port_version': self.logical_port_version,
}
],
},
accept_json_response=True,
)
self.message(
msg=(
f'Logical port ID {self.logical_port_id} attached to object '
f'group ID {self.object_group_id}.'
)
)
self.set_changed()
@DecortController.waypoint
@DecortController.checkmode
def network_object_group_logical_port_detach(self):
self.decort_api_call(
arg_req_function=requests.post,
arg_api_name='/restmachine/sdn/network_object_group/detach_logical_ports', # noqa: E501
arg_json_body={
'object_group_id': self.object_group_id,
'access_group_id': self.aparams['access_group_id'],
'version_id': self.object_group_version,
'port_bindings': [
{
'port_id': self.logical_port_id,
'port_version': self.logical_port_version,
}
],
},
accept_json_response=True,
)
self.message(
msg=(
f'Logical port ID {self.logical_port_id} detached from object '
f'group ID {self.object_group_id}.'
)
)
self.set_changed()
def main():
DecortSDNNetworkObjectGroupLogicalPort().run()
if __name__ == '__main__':
main()

View File

@@ -0,0 +1,166 @@
#!/usr/bin/python
DOCUMENTATION = r'''
---
module: decort_sdn_network_object_group_mac
description: See L(Module Documentation,https://repository.basistech.ru/BASIS/decort-ansible/wiki/Home). # noqa: E501
'''
from typing import Any
from ansible.module_utils.basic import AnsibleModule
from ansible.module_utils.decort_utils import DecortController
import requests
class DecortSDNNetworkObjectGroupMAC(DecortController):
network_object_group_id: str | None = None
_network_object_group_info: dict[str, Any] | None = None
def __init__(self):
super().__init__(AnsibleModule(**self.amodule_init_args))
@property
def amodule_init_args(self) -> dict:
return self.pack_amodule_init_args(
argument_spec=dict(
network_object_group_id=dict(
type='str',
required=True,
),
mac=dict(
type='str',
required=True,
),
state=dict(
type='str',
required=True,
choices=['present', 'absent'],
),
),
supports_check_mode=True,
)
@property
def network_network_object_group_info(self) -> dict[str, Any]:
if self._network_object_group_info is None:
network_network_object_group_info = self.network_object_group_get()
if network_network_object_group_info is None:
raise TypeError
self._network_object_group_info = network_network_object_group_info
return self._network_object_group_info
def find_mac(self) -> dict | None:
mac = self.aparams['mac']
for addr in self.network_network_object_group_info.get('addresses') or []: # noqa: E501
if addr.get('mac_addr') == mac:
return addr
return None
def group_payload(self) -> dict:
return {
'object_group_id': self.network_object_group_id,
'version_id': self.network_network_object_group_info['version_id'],
'access_group_id': self.network_network_object_group_info['access_group_id'], # noqa: E501
'description': self.network_network_object_group_info['description'], # noqa: E501
'name': self.network_network_object_group_info['name'],
}
def mac_add(self):
current_addresses = self.network_network_object_group_info.get('addresses') or [] # noqa: E501
mac = self.aparams['mac']
for addr in current_addresses:
if addr.get('mac_addr') == mac:
return
mac_data = {
'mac_addr': mac,
'net_address_type': 'MAC',
}
self.network_object_group_update_addresses(
list(current_addresses) + [mac_data]
)
def mac_remove(self):
current_addresses = self.network_network_object_group_info.get('addresses') or [] # noqa: E501
mac = self.aparams['mac']
addresses_to_keep = [
addr for addr in current_addresses
if addr.get('mac_addr') != mac
]
if len(addresses_to_keep) == len(current_addresses):
self.message(
self.MESSAGES.obj_not_found(
obj='mac',
id=mac,
)
)
return
self.network_object_group_update_addresses(addresses_to_keep)
self.message(
self.MESSAGES.obj_deleted(
obj='mac',
id=mac,
)
)
@DecortController.waypoint
def network_object_group_get(self) -> dict[str, Any]:
response = self.decort_api_call(
arg_req_function=requests.get,
arg_api_name='/restmachine/sdn/network_object_group/get',
arg_params={'object_group_id': self.network_object_group_id},
not_fail_codes=[404],
accept_json_response=True,
)
if response.status_code == 404:
self.message(
self.MESSAGES.obj_not_found(
obj='network object group',
id=self.network_object_group_id,
)
)
self.exit(fail=True)
return response.json()
@DecortController.waypoint
@DecortController.checkmode
def network_object_group_update_addresses(self, addresses: list):
payload = self.group_payload()
payload['addresses'] = addresses
response = self.decort_api_call(
arg_req_function=requests.put,
arg_api_name='/restmachine/sdn/network_object_group/update',
arg_json_body=payload,
accept_json_response=True,
)
self._network_object_group_info = response.json()
self.set_changed()
def package_facts(self) -> dict:
facts = self.find_mac() or {}
if 'mac_addr' in facts:
facts['mac'] = facts.pop('mac_addr')
return facts
def run(self):
self.network_object_group_id = self.aparams['network_object_group_id']
if self.aparams['state'] == 'present':
self.mac_add()
else:
self.mac_remove()
self.facts = self.package_facts()
self.exit()
def main():
DecortSDNNetworkObjectGroupMAC().run()
if __name__ == '__main__':
main()

View File

@@ -0,0 +1,357 @@
#!/usr/bin/python
DOCUMENTATION = r'''
---
module: decort_sdn_segment
description: See L(Module Documentation,https://repository.basistech.ru/BASIS/decort-ansible/wiki/Home). # noqa: E501
'''
from typing import Any
from ansible.module_utils.basic import AnsibleModule
from ansible.module_utils.decort_utils import DecortController
import requests
class DecortSDNSegment(DecortController):
REQUIRED_FIELDS = (
'access_group_id',
'description',
'dhcp_v4',
'dhcp_v6',
'display_name',
'subnet_v4',
'subnet_v6',
'type',
)
segment_id: str | None = None
_segment_info: dict[str, Any] | None = None
need_final_get: bool = True
def __init__(self):
super().__init__(AnsibleModule(**self.amodule_init_args))
@property
def amodule_init_args(self) -> dict:
return self.pack_amodule_init_args(
argument_spec=dict(
access_group_id=dict(
type='str',
),
description=dict(
type='str',
),
dhcp_v4=dict(
type='dict',
),
dhcp_v6=dict(
type='dict',
),
display_name=dict(
type='str',
),
segment_id=dict(
type='str',
),
state=dict(
type='str',
choices=[
'present',
'enabled',
'disabled',
'absent',
'absent_force',
],
),
subnet_v4=dict(
type='str',
),
subnet_v6=dict(
type='str',
),
type=dict(
type='str',
choices=['User', 'ExtNet'],
),
version_id=dict(
type='int',
),
),
supports_check_mode=True,
)
@property
def segment_info(self) -> dict[str, Any]:
if self._segment_info is None:
if not isinstance(self.segment_id, str):
raise TypeError
segment_info = self.segment_get()
if segment_info is None:
raise TypeError
self._segment_info = segment_info
return self._segment_info
def run(self):
state = self.aparams['state']
if self.aparams['segment_id']:
self.segment_id = self.aparams['segment_id']
self._segment_info = self.segment_get(fail_if_not_found=False)
elif self.aparams['display_name']:
segment_info = self.segment_find(
display_name=self.aparams['display_name']
)
self._segment_info = segment_info
if segment_info:
self.segment_id = segment_info['id']
if state in ('absent', 'absent_force'):
if self._segment_info is None:
self.exit()
if self.segment_id:
self.segment_delete(
force=state == 'absent_force',
)
else:
self.need_final_get = False
elif state == 'present':
if self.segment_id:
self.check_amodule_args_for_update()
self.segment_update()
else:
self.check_amodule_args_for_create()
self.segment_create()
elif state in ('enabled', 'disabled'):
if not self.segment_id:
self.message(
'Check for parameter "state" failed: state values '
'"enabled"/"disabled" can only be applied to an existing '
'segment.'
)
self.check_amodule_args_for_update()
self.segment_update(
desired_enabled=(state == 'enabled'),
)
if self.need_final_get:
self.facts = self.segment_get()
self.exit()
def check_amodule_args_for_create(self):
check_errors = False
if (
self.aparams['subnet_v4'] is None
and self.aparams['subnet_v6'] is None
):
check_errors = True
self.message(
'Check for parameters "subnet_v4/subnet_v6" failed: at '
'least one of these parameters must be specified when '
'creating a segment.'
)
if check_errors:
self.exit(fail=True)
def check_amodule_args_for_update(self):
check_errors = False
if (
self.aparams['version_id'] is not None
and self.aparams['version_id'] != self.segment_info['version_id']
):
check_errors = True
self.message(
'Check for parameters "version_id" failed: '
'segment version mismatch: '
f'given version: {self.aparams['version_id']}, '
f'current version: {self.segment_info['version_id']}.'
)
if check_errors:
self.exit(fail=True)
@DecortController.waypoint
@DecortController.checkmode
def segment_get(
self,
access_group_id: str | None = None,
fail_if_not_found=True,
) -> dict[str, Any] | None:
params = {'segment_id': self.segment_id}
if access_group_id is not None:
params['access_group_id'] = access_group_id
response = self.decort_api_call(
arg_req_function=requests.get,
arg_api_name='/restmachine/sdn/segment/get',
arg_params=params,
not_fail_codes=[404],
accept_json_response=True,
)
if response.status_code == 404:
if fail_if_not_found:
self.message(
self.MESSAGES.obj_not_found(
obj='segment',
id=self.segment_id,
)
)
self.exit(fail=True)
else:
return None
return response.json()
@DecortController.waypoint
@DecortController.checkmode
def segment_find(self, display_name: str) -> dict[str, Any] | None:
response = self.decort_api_call(
arg_req_function=requests.get,
arg_api_name='/restmachine/sdn/segment/list',
arg_params={
'display_name': display_name,
},
accept_json_response=True,
)
return response.json()[0] if response.json() else None
@DecortController.waypoint
@DecortController.checkmode
def segment_create(self):
payload = dict()
for field in self.REQUIRED_FIELDS:
value = self.aparams[field]
if value is not None:
payload[field] = value
payload['enabled'] = True
response = self.decort_api_call(
arg_req_function=requests.post,
arg_api_name='/restmachine/sdn/segment/create',
arg_json_body=payload,
accept_json_response=True,
)
self._segment_info = response.json()
self.segment_id = response.json()['id']
self.set_changed()
@DecortController.waypoint
@DecortController.checkmode
def segment_update(
self,
desired_enabled: bool | None = None,
):
need_update = False
for field in self.REQUIRED_FIELDS:
value = self.aparams[field]
if value is None:
continue
current_value = self.segment_info.get(field)
if isinstance(value, dict) and isinstance(current_value, dict):
if any(
current_value.get(key) != expected_value
for key, expected_value in value.items()
):
need_update = True
break
continue
if value != current_value:
need_update = True
break
if (
not need_update
and desired_enabled is not None
and desired_enabled != self.segment_info['enabled']
):
need_update = True
if need_update:
payload = {
'segment_id': self.segment_id,
'version_id': (
self.aparams['version_id']
or self.segment_info['version_id']
),
'access_group_id': (
self.aparams['access_group_id']
or self.segment_info['access_group_id']
),
'description': (
self.aparams['description']
or self.segment_info['description']
),
'dhcp_v4': (
self.aparams['dhcp_v4'] or self.segment_info.get('dhcp_v4')
),
'dhcp_v6': (
self.aparams['dhcp_v6'] or self.segment_info.get('dhcp_v6')
),
'display_name': (
self.aparams['display_name']
or self.segment_info['display_name']
),
'enabled': (
desired_enabled
if desired_enabled is not None
else self.segment_info['enabled']
),
'subnet_v4': (
self.aparams['subnet_v4']
or self.segment_info.get('subnet_v4')
),
'subnet_v6': (
self.aparams['subnet_v6']
or self.segment_info.get('subnet_v6')
),
'type': (
self.aparams['type'] or self.segment_info['type']
),
}
self.decort_api_call(
arg_req_function=requests.put,
arg_api_name='/restmachine/sdn/segment/update',
arg_json_body=payload,
accept_json_response=True,
)
self.set_changed()
@DecortController.waypoint
@DecortController.checkmode
def segment_delete(
self,
force: bool = False,
):
version_id = self.aparams['version_id']
if version_id is None:
version_id = self.segment_info['version_id']
payload = {
'segment_id': self.segment_id,
'version_id': version_id,
}
self.decort_api_call(
arg_req_function=requests.delete,
arg_api_name='/restmachine/sdn/segment/delete',
arg_params=payload,
)
self.need_final_get = False
self.set_changed()
self.message(
self.MESSAGES.obj_deleted(
obj='segment',
id=self.segment_id,
permanently=force,
)
)
self.facts = {}
def main():
DecortSDNSegment().run()
if __name__ == '__main__':
main()

View File

@@ -0,0 +1,142 @@
#!/usr/bin/python
DOCUMENTATION = r'''
---
module: decort_sdn_segment_list
description: See L(Module Documentation,https://repository.basistech.ru/BASIS/decort-ansible/wiki/Home). # noqa: E501
'''
from typing import Any
from ansible.module_utils.basic import AnsibleModule
from ansible.module_utils.decort_utils import DecortController
import requests
class DecortSDNSegmentList(DecortController):
def __init__(self):
super().__init__(AnsibleModule(**self.amodule_init_args))
@property
def amodule_init_args(self) -> dict:
return self.pack_amodule_init_args(
argument_spec=dict(
filter=dict(
type='dict',
apply_defaults=True,
options=dict(
enabled=dict(
type='bool',
),
is_synced=dict(
type='bool',
),
display_name=dict(
type='str',
),
subnet=dict(
type='str',
),
access_group_id=dict(
type='str',
),
created_from=dict(
type='str',
),
created_to=dict(
type='str',
),
updated_from=dict(
type='str',
),
updated_to=dict(
type='str',
),
operation_status=dict(
type='str',
choices=[
'Idle',
'SynchronizingAtCore',
'SynchronizingAtOVN',
'Synchronized',
'NoHypervisorAtOVN',
'FailedAtCore',
'TemporaryFailedAtCore',
],
),
),
),
pagination=dict(
type='dict',
apply_defaults=True,
options=dict(
number=dict(
type='int',
default=1,
),
size=dict(
type='int',
),
),
),
sorting=dict(
type='dict',
options=dict(
asc=dict(
type='bool',
default=True,
),
field=dict(
type='str',
choices=[
'display_name',
'subnet',
'created_at',
'updated_at',
],
required=True,
),
),
),
),
supports_check_mode=True,
)
def run(self):
self.get_info()
self.exit()
def get_info(self):
api_params: dict[str, Any] = dict()
aparam_filter: dict[str, Any] = self.aparams['filter']
for field, value in aparam_filter.items():
if value is not None:
api_params[field] = value
aparam_pagination: dict[str, Any] = self.aparams['pagination']
api_params['page'] = aparam_pagination['number']
api_params['per_page'] = aparam_pagination['size']
aparam_sorting: dict[str, Any] | None = self.aparams['sorting']
if aparam_sorting:
api_params['sort_by'] = aparam_sorting['field']
api_params['sort_order'] = (
'asc' if aparam_sorting['asc'] else 'desc'
)
response = self.decort_api_call(
arg_req_function=requests.get,
arg_api_name='/restmachine/sdn/segment/list',
arg_params=api_params,
accept_json_response=True,
)
self.facts = response.json()
def main():
DecortSDNSegmentList().run()
if __name__ == '__main__':
main()

View File

@@ -136,7 +136,7 @@ class DecortSecurityGroup(DecortController):
) )
except sdk_exceptions.RequestException as e: except sdk_exceptions.RequestException as e:
if ( if (
e.orig_exception.response e.orig_exception.response is not None
and e.orig_exception.response.status_code == 404 and e.orig_exception.response.status_code == 404
): ):
self.message( self.message(

View File

@@ -42,7 +42,7 @@ class DecortStoragePolicy(DecortController):
) )
except sdk_exceptions.RequestException as e: except sdk_exceptions.RequestException as e:
if ( if (
e.orig_exception.response e.orig_exception.response is not None
and e.orig_exception.response.status_code == 404 and e.orig_exception.response.status_code == 404
): ):
self.message( self.message(

View File

@@ -42,7 +42,7 @@ class DecortTrunk(DecortController):
) )
except sdk_exceptions.RequestException as e: except sdk_exceptions.RequestException as e:
if ( if (
e.orig_exception.response e.orig_exception.response is not None
and e.orig_exception.response.status_code == 404 and e.orig_exception.response.status_code == 404
): ):
self.message( self.message(

View File

@@ -10,6 +10,8 @@ description: See L(Module Documentation,https://repository.basistech.ru/BASIS/de
from ansible.module_utils.basic import AnsibleModule from ansible.module_utils.basic import AnsibleModule
from ansible.module_utils.decort_utils import DecortController from ansible.module_utils.decort_utils import DecortController
from dynamix_sdk import exceptions as sdk_exceptions
class DecortUser(DecortController): class DecortUser(DecortController):
def __init__(self): def __init__(self):
@@ -43,16 +45,35 @@ class DecortUser(DecortController):
self.facts = self.usermanager_whoami_result self.facts = self.usermanager_whoami_result
self.id = self.facts['name'] self.id = self.facts['name']
user_get = self.user_get(id=self.id) try:
for key in ['emailaddresses', 'data']: user_model = self.api.cloudapi.user.get(user_name=self.id)
self.facts[key] = user_get[key] except sdk_exceptions.RequestException as e:
if (
e.orig_exception.response is not None
and e.orig_exception.response.status_code == 404
):
self.message(
self.MESSAGES.obj_not_found(
obj='user',
)
)
self.exit(fail=True)
raise e
self.facts.update(user_model.model_dump())
if self.aparams['resource_consumption']: if self.aparams['resource_consumption']:
self.facts.update(self.user_resource_consumption()) self.facts.update(
self.api.ca.user.get_resource_consumption().model_dump()
)
# Delete duplicate self.facts['name']
del self.facts['user_name']
if self.aparams['api_methods']: if self.aparams['api_methods']:
self.facts['api_methods'] = self.user_api_methods(id=self.id) self.facts['api_methods'] = (
self.api.cloudapi.user.api_list(user_name=self.id).model_dump()
)
search_string = self.aparams['objects_search'] search_string = self.aparams['objects_search']
if search_string: if search_string:
self.facts['objects_search'] = self.user_objects_search( self.facts['objects_search'] = self.user_objects_search(

View File

@@ -20,7 +20,6 @@ class decort_vins(DecortController):
self.vins_id = 0 self.vins_id = 0
self.vins_level = "" # "ID" if specified by ID, "RG" - at resource group, "ACC" - at account level 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 validated_rg_id = 0
rg_facts = None # will hold RG facts rg_facts = None # will hold RG facts
validated_acc_id = 0 validated_acc_id = 0
@@ -28,15 +27,28 @@ class decort_vins(DecortController):
if arg_amodule.params['vins_id']: if arg_amodule.params['vins_id']:
# expect existing ViNS with the specified 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 # 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 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['failed'] = True
self.result['msg'] = "Specified ViNS ID {} not found.".format(arg_amodule.params['vins_id']) self.result['msg'] = "Specified ViNS ID {} not found.".format(arg_amodule.params['vins_id'])
self.amodule.fail_json(**self.result) self.amodule.fail_json(**self.result)
self.vins_level = "ID" self.vins_level = "ID"
#raise Exception(self.vins_facts) validated_acc_id = self._vins_info.account_id
validated_acc_id = self.vins_facts['accountId'] validated_rg_id = self._vins_info.rg_id
validated_rg_id = self.vins_facts['rgId']
elif arg_amodule.params['rg_id']: elif arg_amodule.params['rg_id']:
# expect ViNS @ RG level in the RG with specified 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 # 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 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="") 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 # 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'], self.vins_id, self._vins_info = self.vins_find(
account_id=0, vins_id=0,
rg_id=arg_amodule.params['rg_id'], vins_name=arg_amodule.params['vins_name'],
rg_facts=rg_facts, account_id=0,
check_state=False) rg_id=arg_amodule.params['rg_id'],
check_state=False,
)
# TODO: add checks and setup ViNS presence flags accordingly # TODO: add checks and setup ViNS presence flags accordingly
pass pass
elif arg_amodule.params['account_id'] or arg_amodule.params['account_name'] != "": 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 # 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 # 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']) validated_rg_id, rg_facts = self.rg_find(validated_acc_id, 0, arg_amodule.params['rg_name'])
if (not validated_rg_id or if (not validated_rg_id or rg_facts is None or
rg_facts['status'] in ["DESTROYING", "DESTROYED", "DELETING", "DELETED", "DISABLING", "ENABLING"]): 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['failed'] = True
self.result['msg'] = "RG name '{}' not found or has invalid state.".format(arg_amodule.params['rg_name']) self.result['msg'] = "RG name '{}' not found or has invalid state.".format(arg_amodule.params['rg_name'])
self.amodule.fail_json(**self.result) 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 # 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) set to 0, as we are looking for ViNS under RG
account_id=0, # set to 0, as we are looking for ViNS under RG self.vins_id, self._vins_info = self.vins_find(
rg_id=validated_rg_id, vins_id=0,
rg_facts=rg_facts, vins_name=arg_amodule.params['vins_name'],
check_state=False) account_id=0,
rg_id=validated_rg_id,
check_state=False,
)
self.vins_level = "RG" self.vins_level = "RG"
# TODO: add checks and setup ViNS presence flags accordingly # TODO: add checks and setup ViNS presence flags accordingly
else: # At this point we know for sure that rg_name="" and rg_id=0 else: # At this point we know for sure that rg_name="" and rg_id=0
# So we expect ViNS @ account level # So we expect ViNS @ account level
# This call to vins_find may return vins_id=0 if no ViNS found # 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'], self.vins_id, self._vins_info = self.vins_find(
account_id=validated_acc_id, vins_id=0,
rg_id=0, vins_name=arg_amodule.params['vins_name'],
rg_facts=rg_facts, account_id=validated_acc_id,
check_state=False) rg_id=0,
check_state=False,
)
self.vins_level = "ACC" self.vins_level = "ACC"
# TODO: add checks and setup ViNS presence flags accordingly # TODO: add checks and setup ViNS presence flags accordingly
else: else:
@@ -106,73 +132,121 @@ class decort_vins(DecortController):
self.rg_id = validated_rg_id self.rg_id = validated_rg_id
self.acc_id = validated_acc_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() self.check_amodule_args_for_change()
else: else:
self.check_amodule_args_for_create() self.check_amodule_args_for_create()
return return
def create(self): def create(self):
self.vins_id = self.vins_provision(self.amodule.params['vins_name'], security_group_mode = self.amodule.params['security_group_mode']
self.acc_id, self.rg_id, if security_group_mode is None:
self.amodule.params['ipcidr'], security_group_mode = False
self.amodule.params['ext_net_id'], self.amodule.params['ext_ip_addr'], self.message(
self.amodule.params['description'], msg=self.MESSAGES.default_value_used(
zone_id=self.amodule.params['zone_id'], 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.vins_id:
if self.amodule.params['mgmtaddr'] or self.amodule.params['connect_to']: self._vins_info = self._vins_get_by_id(vins_id=self.vins_id)
_, self.vins_facts = self.vins_find(self.vins_id)
if self.amodule.params['connect_to']: 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']: 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 return
def action(self,d_state='',restore=False):
if restore == True: def action(self, d_state='', restore=False):
self.vins_restore(arg_vins_id=self.vins_id) if restore:
self.vins_state(self.vins_facts, 'enabled') self.sdk_checkmode(self.api.cloudapi.vins.restore)(
self.vins_facts['status'] = "ENABLED" vins_id=self.vins_info.id,
self.vins_facts['VNFDev']['techStatus'] = "STARTED" )
self._vins_info = self._vins_get_by_id(vins_id=self.vins_id)
self.vins_update_extnet(self.vins_facts, self.vins_state(self.vins_info, 'enabled')
self.amodule.params['ext_net_id'],
self.amodule.params['ext_ip_addr'], self._vins_info = self._vins_get_by_id(vins_id=self.vins_id)
)
if (
if d_state == 'enabled' and self.vins_facts['status'] == "DISABLED": self.amodule.params['ext_net_id'] is not None
self.vins_state(self.vins_facts, d_state) or self.amodule.params['ext_ip_addr'] is not None
self.vins_facts['status'] = "ENABLED" ):
self.vins_facts['VNFDev']['techStatus'] = "STARTED" 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 = '' d_state = ''
if self.vins_facts['status'] == "ENABLED" and self.vins_facts['VNFDev']['techStatus'] == "STARTED": if (
self.vins_update_ifaces(self.vins_facts, self.vins_info.status == sdk_types.VINSStatus.ENABLED
self.amodule.params['connect_to'], 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']: 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.vins_update_mgmt(self.vins_facts,
self.amodule.params['mgmtaddr'], self.vins_update_mgmt(
) self.vins_info,
self.amodule.params['mgmtaddr'],
)
if d_state != '': 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'] 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( self.vins_migrate_to_zone(
net_id=self.vins_id, net_id=self.vins_info.id,
zone_id=aparam_zone_id, zone_id=aparam_zone_id,
) )
return return
def delete(self): def delete(self):
self.vins_delete(self.vins_id, self.amodule.params['permanently']) self.sdk_checkmode(self.api.cloudapi.vins.delete)(
self.vins_facts['status'] = 'DESTROYED' vins_id=self.vins_info.id,
permanently=self.amodule.params['permanently'],
)
return return
def nop(self): def nop(self):
"""No operation (NOP) handler for ViNS management by decort_vins module. """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 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['failed'] = False
self.result['changed'] = False self.result['changed'] = False
if self.vins_id: if self._vins_info:
self.result['msg'] = ("No state change required for ViNS ID {} because of its " self.result['msg'] = (
"current status '{}'.").format(self.vins_id, self.vins_facts['status']) f'No state change required for ViNS ID {self._vins_info.id} '
f'because of its "current status "{self._vins_info.status}".'
)
else: else:
self.result['msg'] = ("No state change to '{}' can be done for " self.result['msg'] = ("No state change to '{}' can be done for "
"non-existent ViNS instance.").format(self.amodule.params['state']) "non-existent ViNS instance.").format(self.amodule.params['state'])
return return
def error(self): def error(self):
self.result['failed'] = True self.result['failed'] = True
self.result['changed'] = False self.result['changed'] = False
if self.vins_id: if self._vins_info:
self.result['failed'] = True self.result['failed'] = True
self.result['changed'] = False self.result['changed'] = False
self.result['msg'] = ("Invalid target state '{}' requested for ViNS ID {} in the " self.result['msg'] = (
"current status '{}'").format(self.vins_id, f'Invalid target state "{self.amodule.params['state']}" '
self.amodule.params['state'], f'requested for ViNS ID {self._vins_info.id} in the '
self.vins_facts['status']) f'current status "{self._vins_info.status}"'
)
else: else:
self.result['failed'] = True self.result['failed'] = True
self.result['changed'] = False self.result['changed'] = False
@@ -205,6 +283,7 @@ class decort_vins(DecortController):
"ViNS name '{}'").format(self.amodule.params['state'], "ViNS name '{}'").format(self.amodule.params['state'],
self.amodule.params['vins_name']) self.amodule.params['vins_name'])
return return
def package_facts(self, arg_check_mode=False): def package_facts(self, arg_check_mode=False):
"""Package a dictionary of ViNS facts according to the decort_vins module specification. """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 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 # in check mode return immediately with the default values
return ret_dict 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 # if void facts provided - change state value to ABSENT and return
ret_dict['state'] = "ABSENT" ret_dict['state'] = "ABSENT"
return ret_dict return ret_dict
ret_dict['id'] = self.vins_facts['id'] return self._vins_info.model_dump()
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
@property @property
@@ -276,11 +327,9 @@ class decort_vins(DecortController):
), ),
ext_net_id=dict( ext_net_id=dict(
type='int', type='int',
default=-1,
), ),
ext_ip_addr=dict( ext_ip_addr=dict(
type='str', type='str',
default='',
), ),
ipcidr=dict( ipcidr=dict(
type='str', type='str',
@@ -304,7 +353,6 @@ class decort_vins(DecortController):
), ),
state=dict( state=dict(
type='str', type='str',
default='present',
choices=[ choices=[
'absent', 'absent',
'disabled', 'disabled',
@@ -335,6 +383,9 @@ class decort_vins(DecortController):
zone_id=dict( zone_id=dict(
type=int, type=int,
), ),
security_group_mode=dict(
type='bool',
),
), ),
supports_check_mode=True, supports_check_mode=True,
required_one_of=[ required_one_of=[
@@ -347,6 +398,34 @@ class decort_vins(DecortController):
if self.check_aparam_zone_id() is False: if self.check_aparam_zone_id() is False:
check_errors = True 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: if check_errors:
self.exit(fail=True) self.exit(fail=True)
@@ -384,36 +463,54 @@ class decort_vins(DecortController):
# if cconfig_save is true, only config save without other updates # if cconfig_save is true, only config save without other updates
vins_should_exist = False vins_should_exist = False
if self.vins_id: if self._vins_info:
vins_should_exist = True 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 # error: nothing can be done to existing ViNS in the listed statii regardless of
# the requested state # the requested state
self.result['failed'] = True self.result['failed'] = True
self.result['changed'] = False self.result['changed'] = False
self.result['msg'] = ("No change can be done for existing ViNS ID {} because of its current " self.result['msg'] = (
"status '{}'").format(self.vins_id, self.vins_facts['status']) f'No change can be done for existing '
elif self.vins_facts['status'] == "DISABLED": 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': if amodule.params['state'] == 'absent':
self.delete() self.delete()
vins_should_exist = False 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 # update ViNS, leave in disabled state
self.action() self.action()
elif amodule.params['state'] == 'enabled': elif amodule.params['state'] == 'enabled':
# update ViNS and enable # update ViNS and enable
self.action('enabled') 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': if amodule.params['state'] == 'absent':
self.delete() self.delete()
vins_should_exist = False 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 # update ViNS
self.action() self.action()
elif amodule.params['state'] == 'disabled': elif amodule.params['state'] == 'disabled':
# disable and update ViNS # disable and update ViNS
self.action('disabled') 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']: if amodule.params['state'] in ['present', 'enabled']:
# restore and enable # restore and enable
self.action(restore=True) self.action(restore=True)
@@ -426,28 +523,48 @@ class decort_vins(DecortController):
elif amodule.params['state'] == 'disabled': elif amodule.params['state'] == 'disabled':
self.error() self.error()
vins_should_exist = False vins_should_exist = False
elif self.vins_facts['status'] == "DESTROYED": elif self._vins_info.status == sdk_types.VINSStatus.DESTROYED:
if amodule.params['state'] in ('present', 'enabled'): 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; # need to re-provision ViNS;
self.create() self.create()
vins_should_exist = True vins_should_exist = True
elif amodule.params['state'] == 'absent': elif state == 'absent':
self.nop() self.nop()
vins_should_exist = False vins_should_exist = False
elif amodule.params['state'] == 'disabled': elif state == 'disabled':
self.error() self.error()
else: 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. # Preexisting ViNS was not found.
vins_should_exist = False # we will change it back to True if ViNS is created or restored 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 requested state is 'absent' - nothing to do
if amodule.params['state'] == 'absent': if state == 'absent':
self.nop() self.nop()
elif amodule.params['state'] in ('present', 'enabled'): elif state in ('present', 'enabled'):
self.check_amodule_argument('vins_name') 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 # as we already have account ID and RG ID we can create ViNS and get vins_id on success
self.create() self.create()
vins_should_exist = True vins_should_exist = True
elif amodule.params['state'] == 'disabled': elif state == 'disabled':
self.error() self.error()
# #
# conditional switch end - complete module run # conditional switch end - complete module run
@@ -457,7 +574,7 @@ class decort_vins(DecortController):
else: else:
# prepare ViNS facts to be returned as part of self.result and then call exit_json(...) # prepare ViNS facts to be returned as part of self.result and then call exit_json(...)
if self.result['changed']: 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) self.result['facts'] = self.package_facts(amodule.check_mode)
amodule.exit_json(**self.result) amodule.exit_json(**self.result)

View File

@@ -42,7 +42,7 @@ class decort_vm(DecortController):
validated_acc_id = 0 validated_acc_id = 0
validated_rg_id = 0 validated_rg_id = 0
validated_rg_facts = None validated_rg_model = None
self.vm_to_clone_id = 0 self.vm_to_clone_id = 0
self.vm_to_clone_info = None self.vm_to_clone_info = None
@@ -118,21 +118,21 @@ class decort_vm(DecortController):
self.fail_json(**self.result) self.fail_json(**self.result)
# fail the module -> exit # fail the module -> exit
# now validate RG # now validate RG
validated_rg_id, validated_rg_facts = self.rg_find(validated_acc_id, validated_rg_id, validated_rg_model = self.rg_find(validated_acc_id,
arg_amodule.params['rg_id'], arg_amodule.params['rg_id'],
arg_amodule.params['rg_name']) arg_amodule.params['rg_name'])
if not validated_rg_id: if not validated_rg_id or not validated_rg_model:
self.result['failed'] = True self.result['failed'] = True
self.result['changed'] = False self.result['changed'] = False
self.result['msg'] = "Cannot find RG ID {} / name '{}'.".format(arg_amodule.params['rg_id'], self.result['msg'] = "Cannot find RG ID {} / name '{}'.".format(arg_amodule.params['rg_id'],
arg_amodule.params['rg_name']) arg_amodule.params['rg_name'])
self.fail_json(**self.result) self.amodule.fail_json(**self.result)
# fail the module - exit # fail the module - exit
self.rg_id = validated_rg_id self.rg_id = validated_rg_id
arg_amodule.params['rg_id'] = validated_rg_id arg_amodule.params['rg_id'] = validated_rg_id
arg_amodule.params['rg_name'] = validated_rg_facts['name'] arg_amodule.params['rg_name'] = validated_rg_model.name
self.acc_id = validated_rg_facts['accountId'] self.acc_id = validated_rg_model.account_id
# at this point we are ready to locate Compute, and if anything fails now, then it must be # at this point we are ready to locate Compute, and if anything fails now, then it must be
# because this Compute does not exist or something goes wrong in the upstream API # because this Compute does not exist or something goes wrong in the upstream API
@@ -635,7 +635,11 @@ class decort_vm(DecortController):
"""Compute destroy handler for VM management by decort_vm module. """Compute destroy handler for VM management by decort_vm module.
Note that this handler deletes the VM permanently together with all assigned disk resources. Note that this handler deletes the VM permanently together with all assigned disk resources.
""" """
self.compute_delete(comp_id=self.comp_id, permanently=True) self.sdk_checkmode(self.api.ca.compute.delete)(
vm_id=self.comp_id,
detach_disks=True,
permanently=True,
)
self.comp_id, self.comp_info, _ = self._compute_get_by_id(self.comp_id) self.comp_id, self.comp_info, _ = self._compute_get_by_id(self.comp_id)
return return
@@ -693,7 +697,13 @@ class decort_vm(DecortController):
aparam_disk_id = aparam_boot['disk_id'] aparam_disk_id = aparam_boot['disk_id']
if aparam_disk_id is not None: if aparam_disk_id is not None:
for disk in self.comp_info['disks']: for disk in self.comp_info['disks']:
if disk['id'] == aparam_disk_id and disk['type'] != 'B': if (
disk['id'] == aparam_disk_id
and not self.is_vm_boot_disk(
vm_chipset=self.comp_info['chipset'],
vm_disk=disk,
)
):
self.compute_boot_disk( self.compute_boot_disk(
comp_id=self.comp_info['id'], comp_id=self.comp_info['id'],
boot_disk=aparam_disk_id, boot_disk=aparam_disk_id,
@@ -1000,7 +1010,10 @@ class decort_vm(DecortController):
ret_dict['disks'] = self.comp_info['disks'] ret_dict['disks'] = self.comp_info['disks']
for disk in ret_dict['disks']: for disk in ret_dict['disks']:
if disk['type'] == 'B': if self.is_vm_boot_disk(
vm_chipset=self.comp_info['chipset'],
vm_disk=disk,
):
# if it is a boot disk - store its size # if it is a boot disk - store its size
ret_dict['disk_size'] = disk['sizeMax'] ret_dict['disk_size'] = disk['sizeMax']
@@ -1073,6 +1086,8 @@ class decort_vm(DecortController):
ret_dict['read_only'] = self.comp_info['read_only'] ret_dict['read_only'] = self.comp_info['read_only']
ret_dict['weight'] = self.comp_info['weight']
return ret_dict return ret_dict
def check_amodule_args_for_create(self): def check_amodule_args_for_create(self):
@@ -1199,7 +1214,7 @@ class decort_vm(DecortController):
) )
elif ( elif (
aparam_storage_policy_id aparam_storage_policy_id
not in self.rg_info['storage_policy_ids'] not in self.rg_info.storage_policy_ids
): ):
check_errors = True check_errors = True
self.message( self.message(
@@ -1681,7 +1696,9 @@ class decort_vm(DecortController):
if new_boot_disk_size is not None: if new_boot_disk_size is not None:
boot_disk_size = 0 boot_disk_size = 0
for disk in self.comp_info['disks']: for disk in self.comp_info['disks']:
if disk['type'] == 'B': if self.is_vm_boot_disk(
vm_chipset=self.comp_info['chipset'], vm_disk=disk,
):
boot_disk_size = disk['sizeMax'] boot_disk_size = disk['sizeMax']
break break
else: else:
@@ -1844,7 +1861,9 @@ class decort_vm(DecortController):
aparam_disks_ids = [disk['id'] for disk in aparam_disks] aparam_disks_ids = [disk['id'] for disk in aparam_disks]
comp_boot_disk_id = None comp_boot_disk_id = None
for comp_disk in self.comp_info['disks']: for comp_disk in self.comp_info['disks']:
if comp_disk['type'] == 'B': if self.is_vm_boot_disk(
vm_chipset=self.comp_info['chipset'], vm_disk=comp_disk,
):
comp_boot_disk_id = comp_disk['id'] comp_boot_disk_id = comp_disk['id']
break break
disks_to_detach = [] disks_to_detach = []
@@ -2137,8 +2156,8 @@ class decort_vm(DecortController):
vm_has_shared_sep_disk = False vm_has_shared_sep_disk = False
vm_disk_ids = [disk['id'] for disk in self.comp_info['disks']] vm_disk_ids = [disk['id'] for disk in self.comp_info['disks']]
for disk_id in vm_disk_ids: for disk_id in vm_disk_ids:
_, disk_info = self._disk_get_by_id(disk_id=disk_id) disk_info = self._disk_get_by_id(disk_id=disk_id)
if disk_info['sepType'] == 'SHARED': if disk_info.sep_type == sdk_types.SEPType.SHARED:
vm_has_shared_sep_disk = True vm_has_shared_sep_disk = True
break break
@@ -2230,7 +2249,9 @@ class decort_vm(DecortController):
if disk_redeploy: if disk_redeploy:
vm_has_boot_disk = False vm_has_boot_disk = False
for disk in self.comp_info['disks']: for disk in self.comp_info['disks']:
if disk['type'] == 'B': if self.is_vm_boot_disk(
vm_chipset=self.comp_info['chipset'], vm_disk=disk,
):
vm_has_boot_disk = True vm_has_boot_disk = True
break break
if not vm_has_boot_disk: if not vm_has_boot_disk:
@@ -2341,7 +2362,9 @@ class decort_vm(DecortController):
check_errors = False check_errors = False
# check if account has vm feature “trunk” # check if account has vm feature “trunk”
if not self.check_account_vm_features(vm_feature=self.VMFeature.trunk): if not self.check_account_vm_features(
vm_feature=sdk_types.VMFeature.TRUNK,
):
check_errors = True check_errors = True
self.message( self.message(
'Check for parameter "networks" failed: ' 'Check for parameter "networks" failed: '
@@ -2349,7 +2372,7 @@ class decort_vm(DecortController):
'trunk type networks ' 'trunk type networks '
) )
# check if rg has vm feature “trunk” # check if rg has vm feature “trunk”
if not self.check_rg_vm_features(vm_feature=self.VMFeature.trunk): if not self.check_rg_vm_features(vm_feature=sdk_types.VMFeature.TRUNK):
check_errors = True check_errors = True
self.message( self.message(
'Check for parameter "networks" failed: ' 'Check for parameter "networks" failed: '

View File

@@ -40,7 +40,7 @@ class DecortZone(DecortController):
zone_model = self.api.cloudapi.zone.get(id=self.id) zone_model = self.api.cloudapi.zone.get(id=self.id)
except sdk_exceptions.RequestException as e: except sdk_exceptions.RequestException as e:
if ( if (
e.orig_exception.response e.orig_exception.response is not None
and e.orig_exception.response.status_code == 404 and e.orig_exception.response.status_code == 404
): ):
self.message( self.message(

File diff suppressed because it is too large Load Diff

View File

@@ -1,3 +1,3 @@
ansible==11.6.0 ansible==11.6.0
requests==2.32.3 requests==2.32.3
git+https://repository.basistech.ru/BASIS/dynamix-python-sdk.git@1.4.latest git+https://repository.basistech.ru/BASIS/dynamix-python-sdk.git@1.5.latest