This commit is contained in:
2026-06-01 18:27:15 +03:00
parent ae986fa9e6
commit f9e0109c7b
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
| Идентификатор<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 |
|:----------------:|:--------------------------:|
| 4.6.0 | 12.0.x |
| 4.5.0 | 11.0.x |
| 4.4.0 | 10.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
'''
from typing import Iterable
from typing import Any, Iterable
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):
@@ -23,7 +23,7 @@ class DecortAccount(DecortController):
def amodule_init_args(self) -> dict:
return self.pack_amodule_init_args(
argument_spec=dict(
access_emails=dict(
send_access_emails=dict(
type='bool',
),
acl=dict(
@@ -45,8 +45,13 @@ class DecortAccount(DecortController):
options=dict(
rights=dict(
type='str',
choices=['R', 'RCX', 'ARCXDU'],
default='R',
choices=(
sdk_types.AccessTypeForSet
._member_names_
),
default=(
sdk_types.AccessTypeForSet.R.name
),
),
id=dict(
type='str',
@@ -69,19 +74,19 @@ class DecortAccount(DecortController):
quotas=dict(
type='dict',
options=dict(
cpu=dict(
cpu_count=dict(
type='int',
),
disks_size=dict(
storage_size_gb=dict(
type='int',
),
gpu=dict(
gpu_count=dict(
type='int',
),
public_ip=dict(
ext_ip_count=dict(
type='int',
),
ram=dict(
ram_size_mb=dict(
type='int',
),
),
@@ -135,7 +140,7 @@ class DecortAccount(DecortController):
# Parameters or combinations of parameters that can
# cause changing the object.
changing_params = [
'access_emails',
'send_access_emails',
'acl',
['id', 'name'],
'quotas',
@@ -188,110 +193,308 @@ class DecortAccount(DecortController):
self.acc_id, self._acc_info = self.account_find(
account_name=self.aparams['name'],
account_id=self.aparams['id'],
resource_consumption=self.aparams['get_resource_consumption'],
)
)
# If this is a repeated getting info
else:
# If check mode is enabled, there is no needed to
# request info again
if not self.amodule.check_mode:
self.acc_id, self._acc_info = self.account_find(
account_id=self.acc_id,
resource_consumption=(
self.aparams['get_resource_consumption']
),
account_id=self._acc_info.id,
)
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):
self.change_state()
self.change_acl()
if self.account_update_args:
self.account_update(account_id=self.acc_id,
**self.account_update_args)
need_account_update_api_call: bool = False
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()
def change_state(self):
match self._acc_info:
case None:
self.message(self.MESSAGES.obj_not_found(obj=self.OBJ))
match self.aparams:
case {'state': 'absent' | 'absent_permanently'}:
pass
case {'state': 'confirmed' | 'disabled' | 'present'}:
self.exit(fail=True)
case {'status': 'DESTROYED'}:
match self.aparams:
case {'state': 'absent' | 'absent_permanently'}:
self.message(
self.MESSAGES.obj_deleted(
obj=self.OBJ,
id=self.acc_id,
permanently=True,
already=True,
)
if self._acc_info is None:
self.message(self.MESSAGES.obj_not_found(obj=self.OBJ))
match self.aparams:
case {'state': 'absent' | 'absent_permanently'}:
pass
case {'state': 'confirmed' | 'disabled' | 'present'}:
self.exit(fail=True)
elif self._acc_info.status == sdk_types.AccountStatus.DESTROYED:
match self.aparams:
case {'state': 'absent' | 'absent_permanently'}:
self.message(
self.MESSAGES.obj_deleted(
obj=self.OBJ,
id=self.acc_info.id,
permanently=True,
already=True,
)
case {'state': 'confirmed' | 'disabled' | 'present'}:
self.message(
self.MESSAGES.obj_not_restored(obj=self.OBJ,
id=self.acc_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': 'confirmed' | 'disabled' | 'present'}:
self.message(
self.MESSAGES.obj_not_restored(obj=self.OBJ,
id=self.acc_info.id)
)
case {'state': 'absent_permanently'}:
self.delete(permanently=True)
case {'state': 'confirmed' | 'present'}:
self.restore()
case {'state': 'disabled'}:
self.restore()
self.disable()
case {'status': 'CONFIRMED'}:
match self.aparams:
case {'state': 'absent'}:
self.delete()
case {'state': 'absent_permanently'}:
self.delete(permanently=True)
case {'state': 'confirmed' | 'present'}:
pass
case {'state': 'disabled'}:
self.disable()
case {'status': '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
self.exit(fail=True)
elif self._acc_info.status == sdk_types.AccountStatus.DELETED:
match self.aparams:
case {'state': 'absent'}:
self.message(
self.MESSAGES.obj_deleted(
obj=self.OBJ,
id=self.acc_info.id,
permanently=False,
already=True,
)
)
case {'state': 'absent_permanently'}:
self.delete(permanently=True)
case {'state': 'confirmed' | 'present'}:
self.restore()
case {'state': 'disabled'}:
self.restore()
self.disable()
elif self._acc_info.status == sdk_types.AccountStatus.CONFIRMED:
match self.aparams:
case {'state': 'absent'}:
self.delete()
case {'state': 'absent_permanently'}:
self.delete(permanently=True)
case {'state': 'confirmed' | 'present'}:
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):
self.account_delete(account_id=self.acc_id, permanently=permanently)
self.account_delete(
account_id=self.acc_info.id,
permanently=permanently
)
self.get_info()
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()
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()
def restore(self):
self.account_restore(account_id=self.acc_id)
self.account_restore(account_id=self.acc_info.id)
self.get_info()
def change_acl(self):
@@ -299,7 +502,7 @@ class DecortAccount(DecortController):
return
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())
@@ -328,8 +531,8 @@ class DecortAccount(DecortController):
aparams_users_ids.intersection(actual_users_ids)
upd_users = dict()
for id in upd_users_ids:
if actual_users[id] == 'CXDRAU':
actual_user_rights = 'ARCXDU'
if actual_users[id] == sdk_types.AccessType.CXDRAU:
actual_user_rights = sdk_types.AccessTypeForSet.ARCXDU
else:
actual_user_rights = actual_users[id]
@@ -341,68 +544,12 @@ class DecortAccount(DecortController):
actual_users_ids.difference(aparams_users_ids)
if del_users_ids or new_users or upd_users:
self.account_change_acl(account_id=self.acc_id,
self.account_change_acl(account_id=self.acc_info.id,
del_users=del_users_ids,
add_users=new_users,
upd_users=upd_users)
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:
aparam_default_zone_id = self.aparams['default_zone_id']
if aparam_default_zone_id is not None:

View File

@@ -39,21 +39,21 @@ class decort_bservice(DecortController):
self.fail_json(**self.result)
# fail the module -> exit
# 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_rg_id=arg_amodule.params['rg_id'],
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['changed'] = False
self.result['msg'] = "Cannot find RG ID {} / name '{}'.".format(arg_amodule.params['rg_id'],
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_name'] = validated_rg_facts['name']
validated_acc_id = validated_rg_facts['accountId']
arg_amodule.params['rg_name'] = validated_rg_model.name
validated_acc_id = validated_rg_model.account_id
self.bservice_id, self.bservice_info = self.bservice_find(
validated_acc_id,
@@ -104,11 +104,11 @@ class decort_bservice(DecortController):
return
def create(self):
self.bservice_id = self.bservice_id = self.bservice_provision(
self.amodule.params['name'],
self.amodule.params['rg_id'],
self.amodule.params['sshuser'],
self.amodule.params['sshkey'],
self.bservice_id = self.sdk_checkmode(self.api.ca.bservice.create)(
name=self.amodule.params['name'],
rg_id=self.amodule.params['rg_id'],
ssh_user_name=self.amodule.params['sshuser'],
ssh_public_key=self.amodule.params['sshkey'],
zone_id=self.aparams['zone_id'],
)
if self.bservice_id:
@@ -136,7 +136,10 @@ class decort_bservice(DecortController):
pass
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_should_exist = False
return

View File

@@ -58,6 +58,7 @@ class decort_disk(DecortController):
name=arg_amodule.params['name'] if "name" in arg_amodule.params else "",
account_id=self.acc_id,
check_state=False,
fail_if_not_found=False,
)
if arg_amodule.params['place_with']:
@@ -67,18 +68,30 @@ class decort_disk(DecortController):
self.disk_id = validated_disk_id
self.disk_info = validated_disk_facts
if self.disk_id:
self.acc_id = validated_disk_facts['accountId']
if self.disk_id and self.disk_info.status not in [
sdk_types.DiskStatus.DESTROYED,
sdk_types.DiskStatus.PURGED,
]:
self.acc_id = validated_disk_facts.account_id
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()
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()
for field in io_fields:
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:
return False
@@ -115,7 +128,11 @@ class decort_disk(DecortController):
)
#IO tune
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
if self.amodule.params['shareable']:
self.sdk_checkmode(self.api.cloudapi.disks.share)(
@@ -133,13 +150,13 @@ class decort_disk(DecortController):
#rename if id present
if (
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()
#resize
if (
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)(
disk_id=self.disk_id,
@@ -147,16 +164,19 @@ class decort_disk(DecortController):
)
#IO TUNE
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(
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)
#share check/update
#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']:
self.sdk_checkmode(self.api.cloudapi.disks.share)(
disk_id=self.disk_id,
@@ -169,7 +189,7 @@ class decort_disk(DecortController):
aparam_storage_policy_id = self.aparams['storage_policy_id']
if (
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)(
disk_id=self.disk_id,
@@ -183,7 +203,7 @@ class decort_disk(DecortController):
detach=self.amodule.params['force_detach'],
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
def rename(self):
@@ -199,7 +219,7 @@ class decort_disk(DecortController):
self.result['changed'] = False
if self.disk_id:
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:
self.result['msg'] = ("No state change to '{}' can be done for "
"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:
return ret_dict
# remove io param with zero value
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
return self.disk_info.model_dump()
@property
def amodule_init_args(self) -> dict:
@@ -391,8 +390,8 @@ class decort_disk(DecortController):
aparam_storage_policy_id = self.aparams['storage_policy_id']
if (
aparam_storage_policy_id is not None
and aparam_storage_policy_id
not in self.acc_info['storage_policy_ids']
and aparam_storage_policy_id
not in self.acc_info.storage_policy_ids
):
check_errors = True
self.message(
@@ -431,24 +430,24 @@ class decort_disk(DecortController):
amodule = self.amodule
if self.disk_id:
#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['changed'] = False
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"
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':
self.delete()
elif amodule.params['state'] == 'present':
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
if amodule.params['state'] in ('present'):
self.create()
else:
self.nop()
elif self.disk_info['status'] == "DELETED":
elif self.disk_info.status == sdk_types.DiskStatus.DELETED:
if amodule.params['state'] in ('present'):
self.action(restore=True)
elif (amodule.params['state'] == 'absent' and
@@ -459,8 +458,24 @@ class decort_disk(DecortController):
else:
# preexisting Disk was not found
if amodule.params['state'] == 'absent':
self.nop()
else:
self.exit()
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()
if self.result['failed']:

View File

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

View File

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

View File

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

View File

@@ -20,7 +20,7 @@ class decort_k8s(DecortController):
validated_acc_id = 0
validated_rg_id = 0
validated_rg_facts = None
validated_rg_model = None
validated_k8ci_id = 0
self.k8s_should_exist = False
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)
# fail the module -> exit
# 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_rg_id=arg_amodule.params['rg_id'],
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['changed'] = False
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
arg_amodule.params['rg_id'] = validated_rg_id
arg_amodule.params['rg_name'] = validated_rg_facts['name']
self.acc_id = validated_rg_facts['accountId']
arg_amodule.params['rg_name'] = validated_rg_model.name
self.acc_id = validated_rg_model.account_id
self.k8s_id,self.k8s_info = self.k8s_find(k8s_id=arg_amodule.params['id'],
k8s_name=arg_amodule.params['name'],
rg_id=validated_rg_id,
check_state=False)
self.k8s_id, self._k8s_info = self.k8s_find(
k8s_id=arg_amodule.params['id'],
k8s_name=arg_amodule.params['name'],
rg_id=validated_rg_id,
check_state=False,
)
if self.k8s_id and self.k8s_info['status'] != 'DESTROYED':
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()
else:
elif arg_amodule.params['state'] != 'absent':
self.check_amodule_args_for_create()
return
@@ -96,32 +98,14 @@ class decort_k8s(DecortController):
config=None,
)
if self.amodule.params['getConfig'] and self.k8s_info['techStatus'] == "STARTED":
ret_dict['config'] = self.k8s_getConfig()
if check_mode:
# in check mode return immediately with the default values
return ret_dict
#if self.k8s_facts is None:
# #if void facts provided - change state value to ABSENT and return
# ret_dict['state'] = "ABSENT"
# return ret_dict
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
self.k8s_info['vins_id'] = self.k8s_vins_id
self.k8s_info['config'] = None
if self.amodule.params['getConfig'] and self.k8s_info['tech_status'] == "STARTED":
self.k8s_info['config'] = self.k8s_getConfig()
return self.k8s_info
def nop(self):
"""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.amodule.fail_json(**self.result)
self.k8s_id,self.k8s_info = self.k8s_find(k8s_id=k8s_id,
k8s_name=self.amodule.params['name'],
rg_id=self.rg_id,
check_state=False)
self.k8s_id, self._k8s_info = self.k8s_find(
k8s_id=k8s_id,
k8s_name=self.amodule.params['name'],
rg_id=self.rg_id,
check_state=False,
)
if self.k8s_id:
self.k8s_should_exist = True
@@ -219,19 +205,22 @@ class decort_k8s(DecortController):
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
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_should_exist = False
return
def action(self, disared_state, preupdate: bool = False):
if self.amodule.params['master_chipset'] is not None:
for master_node in self.k8s_info['k8sGroups']['masters'][
'detailedInfo'
for master_node in self.k8s_info['node_groups']['master'][
'vms'
]:
_, master_node_info, _ = self._compute_get_by_id(
comp_id=master_node['id']
@@ -247,17 +236,27 @@ class decort_k8s(DecortController):
self.exit(fail=True)
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:
# 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
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
if self.aparams['workers'] is not None:
self.k8s_workers_modify(
@@ -266,14 +265,17 @@ class decort_k8s(DecortController):
)
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(
k8s_id=self.k8s_id,
zone_id=aparam_zone_id,
)
if self.result['changed'] == True:
self.k8s_info = self.k8s_get_by_id(k8s_id=self.k8s_id)
self._k8s_info = self.k8s_get_by_id(k8s_id=self.k8s_id)
#TODO check workers metadata and modify if needed
return
@@ -413,7 +415,6 @@ class decort_k8s(DecortController):
),
description=dict(
type='str',
default='Created by decort ansible module',
),
with_lb=dict(
type='bool',
@@ -473,25 +474,25 @@ class decort_k8s(DecortController):
self.is_k8s_stopped_or_will_be_stopped = (
(
self.k8s_info['techStatus'] == 'STOPPED'
self.k8s_info['tech_status'] == 'STOPPED'
and (
self.aparams['state'] is None
or self.aparams['state'] in ('present', 'stopped')
)
)
or (
self.k8s_info['techStatus'] != 'STOPPED'
self.k8s_info['tech_status'] != 'STOPPED'
and self.aparams['state'] == 'stopped'
)
)
aparam_sysctl = self.aparams['lb_sysctl']
if aparam_sysctl is not None:
_, lb_info = self._lb_get_by_id(lb_id=self.k8s_info['lbId'])
lb_model = self._lb_get_by_id(lb_id=self.k8s_info['lb_id'])
sysctl_with_str_values = {
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(
'Check for parameter "lb_sysctl" failed: '
'cannot change lb_sysctl for an existing cluster '
@@ -503,7 +504,7 @@ class decort_k8s(DecortController):
check_errors = True
if (
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
):
check_errors = True
@@ -515,13 +516,11 @@ class decort_k8s(DecortController):
aparam_storage_policy_id = self.aparams['storage_policy_id']
if aparam_storage_policy_id is not None:
computes_ids = []
for master_node in self.k8s_info['k8sGroups']['masters'][
'detailedInfo'
]:
for master_node in self.k8s_info['node_groups']['master']['vms']:
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 = [
worker['id'] for worker in wg['detailedInfo']
worker['id'] for worker in wg['vms']
]
computes_ids.extend(workers_ids)
for compute_id in computes_ids:
@@ -589,7 +588,7 @@ class decort_k8s(DecortController):
)
elif (
aparam_storage_policy_id
not in self.rg_info['storage_policy_ids']
not in self.rg_info.storage_policy_ids
):
check_errors = True
self.message(
@@ -630,7 +629,9 @@ class decort_k8s(DecortController):
if amodule.params['state'] in (
'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'],
preupdate=True)
if amodule.params['state'] == 'absent':

View File

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

View File

@@ -43,7 +43,13 @@ class decort_pfw(DecortController):
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.
This dictionary will be returned to the upstream Ansible engine at the completion of
the module run.
@@ -68,9 +74,13 @@ class decort_pfw(DecortController):
ret_dict['state'] = "ABSENT"
return ret_dict
gw_vnf = vins_model.vnfs.gw
ret_dict['compute_id'] = comp_facts['id']
ret_dict['vins_id'] = vins_facts['id']
ret_dict['public_ip'] = vins_facts['vnfs']['GW']['config']['ext_net_ip']
ret_dict['vins_id'] = vins_model.id
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:
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'])
amodule.fail_json(**self.result)
validated_vins_id, vins_facts = self.vins_find(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)
vins_model = self._vins_get_by_id(vins_id=amodule.params['vins_id'])
gw_vnf_facts = vins_facts['vnfs'].get('GW')
if not gw_vnf_facts or gw_vnf_facts['status'] == "DESTROYED":
gw_vnf = vins_model.vnfs.gw
if not gw_vnf or gw_vnf.status == sdk_types.VNFStatus.DESTROYED:
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)
#
@@ -124,12 +133,20 @@ class decort_pfw(DecortController):
if amodule.params['state'] == 'absent':
# 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:
# 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:
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
@@ -138,7 +155,12 @@ class decort_pfw(DecortController):
amodule.fail_json(**self.result)
else:
# 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)

View File

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

View File

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

View File

@@ -42,7 +42,7 @@ class DecortTrunk(DecortController):
)
except sdk_exceptions.RequestException as e:
if (
e.orig_exception.response
e.orig_exception.response is not None
and e.orig_exception.response.status_code == 404
):
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.decort_utils import DecortController
from dynamix_sdk import exceptions as sdk_exceptions
class DecortUser(DecortController):
def __init__(self):
@@ -43,16 +45,35 @@ class DecortUser(DecortController):
self.facts = self.usermanager_whoami_result
self.id = self.facts['name']
user_get = self.user_get(id=self.id)
for key in ['emailaddresses', 'data']:
self.facts[key] = user_get[key]
try:
user_model = self.api.cloudapi.user.get(user_name=self.id)
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']:
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']:
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']
if search_string:
self.facts['objects_search'] = self.user_objects_search(

View File

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

View File

@@ -42,7 +42,7 @@ class decort_vm(DecortController):
validated_acc_id = 0
validated_rg_id = 0
validated_rg_facts = None
validated_rg_model = None
self.vm_to_clone_id = 0
self.vm_to_clone_info = None
@@ -118,21 +118,21 @@ class decort_vm(DecortController):
self.fail_json(**self.result)
# fail the module -> exit
# 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_name'])
if not validated_rg_id:
if not validated_rg_id or not validated_rg_model:
self.result['failed'] = True
self.result['changed'] = False
self.result['msg'] = "Cannot find RG ID {} / name '{}'.".format(arg_amodule.params['rg_id'],
arg_amodule.params['rg_name'])
self.fail_json(**self.result)
self.amodule.fail_json(**self.result)
# fail the module - exit
self.rg_id = validated_rg_id
arg_amodule.params['rg_id'] = validated_rg_id
arg_amodule.params['rg_name'] = validated_rg_facts['name']
self.acc_id = validated_rg_facts['accountId']
arg_amodule.params['rg_name'] = validated_rg_model.name
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
# 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.
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)
return
@@ -693,7 +697,13 @@ class decort_vm(DecortController):
aparam_disk_id = aparam_boot['disk_id']
if aparam_disk_id is not None:
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(
comp_id=self.comp_info['id'],
boot_disk=aparam_disk_id,
@@ -1000,7 +1010,10 @@ class decort_vm(DecortController):
ret_dict['disks'] = self.comp_info['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
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['weight'] = self.comp_info['weight']
return ret_dict
def check_amodule_args_for_create(self):
@@ -1199,7 +1214,7 @@ class decort_vm(DecortController):
)
elif (
aparam_storage_policy_id
not in self.rg_info['storage_policy_ids']
not in self.rg_info.storage_policy_ids
):
check_errors = True
self.message(
@@ -1681,7 +1696,9 @@ class decort_vm(DecortController):
if new_boot_disk_size is not None:
boot_disk_size = 0
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']
break
else:
@@ -1844,7 +1861,9 @@ class decort_vm(DecortController):
aparam_disks_ids = [disk['id'] for disk in aparam_disks]
comp_boot_disk_id = None
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']
break
disks_to_detach = []
@@ -2137,8 +2156,8 @@ class decort_vm(DecortController):
vm_has_shared_sep_disk = False
vm_disk_ids = [disk['id'] for disk in self.comp_info['disks']]
for disk_id in vm_disk_ids:
_, disk_info = self._disk_get_by_id(disk_id=disk_id)
if disk_info['sepType'] == 'SHARED':
disk_info = self._disk_get_by_id(disk_id=disk_id)
if disk_info.sep_type == sdk_types.SEPType.SHARED:
vm_has_shared_sep_disk = True
break
@@ -2230,7 +2249,9 @@ class decort_vm(DecortController):
if disk_redeploy:
vm_has_boot_disk = False
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
break
if not vm_has_boot_disk:
@@ -2341,7 +2362,9 @@ class decort_vm(DecortController):
check_errors = False
# 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
self.message(
'Check for parameter "networks" failed: '
@@ -2349,7 +2372,7 @@ class decort_vm(DecortController):
'trunk type networks '
)
# 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
self.message(
'Check for parameter "networks" failed: '

View File

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

File diff suppressed because it is too large Load Diff

View File

@@ -1,3 +1,3 @@
ansible==11.6.0
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