diff --git a/CHANGELOG.md b/CHANGELOG.md
index aa0852b..035b9cd 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -1,122 +1,174 @@
-# Список изменений в версии 9.0.0
+# Список изменений в версии 10.0.0
## Добавлено
-### Глобально
+### Модуль decort_vm_snapshot
| Идентификатор
задачи | Описание |
| --- | --- |
-| BANS-798 | Обновлены системные требования: версия интерпретатора Python обновлена до 3.12, версия Python-библиотеки ansible обновлена до 11.6.0 |
+| BANS-807 | Модуль переименован из `decort_snapshot` в `decort_vm_snapshot`. |
### Модуль decort_kvmvm
| Идентификатор
задачи | Описание |
| --- | --- |
-| BANS-790 | Добавлен параметр `zone_id` и возвращаемое значение `zone_id`. |
-| BANS-810 | Добавлен параметр `guest_agent` и возвращаемое значение `guest_agent`. |
-| BANS-806 | Добавлен параметр `get_snapshot_merge_status` и возвращаемое значение `snapshot_merge_status`. |
-| BANS-823 | Добавлено значение `TRUNK` для параметра `networks.type`. |
-| BANS-813 | Добавлено значение `SDN` для параметра `networks.type`. |
-| BANS-835 | Добавлена возможность использования параметра `networks.mtu` для внешней сети. |
+| BANS-723 | Добавлен параметр `cdrom` и возвращаемое значение `cd_image_id`. |
+| BANS-727 | Добавлен параметр `boot.from_cdrom`. |
+| BANS-728 | Добавлен параметр `boot.order` и возвращаемое значение `boot_order`. |
+| BANS-871 | Добавлен параметр `storage_policy_id`. |
+| BANS-729 | Добавлен параметр `boot.disk_redeploy`. |
+| BANS-693 | Добавлена проверка при подключении сетей к запущенной ВМ. |
+| BANS-881 | Добавлены параметры `networks.security_group_mode` и `networks.security_group_ids` и возвращаемые значения `interfaces.security_group_mode` и `interfaces.security_group_ids`. |
+| BANS-895 | Добавлен возможный тип данных `null` для возвращаемого значения `image_id`. |
+| BANS-894 | Добавлен параметр `os_version` и возвращаемое значение `os_version`. |
+| BANS-890 | Добавлен параметр `networks.enabled` и возвращаемое значение `interfaces.enabled`. |
+| BANS-902 | Добавлено возвращаемое значение `boot_loader_metaiso`. |
+| BANS-904 | Добавлены параметры `clone_from.sep_pool_name`, `clone_from.sep_id`, `clone_from.storage_policy_id`. |
+| BANS-905 | Добавлены параметры `abort_cloning`, `get_cloning_status` и возвращаемое значение `cloning_status`. |
-### Модуль decort_lb
+### Модуль decort_group
| Идентификатор
задачи | Описание |
| --- | --- |
-| BANS-793 | Добавлен параметр `zone_id` и возвращаемое значение `zone_id`. |
-| BANS-819 | Добавлено возвращаемое значение `account_id`. |
-| BANS-800 | Добавлены значения `stopped` и `started` для параметра `state` и возвращаемое значение `tech_status`. |
+| BANS-865 | Добавлен параметр `storage_policy_id`. |
-### Модуль decort_k8s
+### Модуль decort_disk
| Идентификатор
задачи | Описание |
| --- | --- |
-| BANS-794 | Добавлен параметр `zone_id` и возвращаемое значение `zone_id`. |
-| BANS-804 | Добавлены значения `stopped` и `started` для параметра `state`. |
+| BANS-867 | Добавлен параметр `storage_policy_id` и возвращаемое значение `storage_policy_id`. |
+| BANS-866 | Добавлена возможность изменять `storage_policy_id` диска. |
+| BANS-897 | Добавлено возвращаемое значение `to_clean`. |
+| BANS-898 | Добавлены параметры `disks.objects`, `disks.objects.pci_slot_num_hex`, `disks.objects.bus_num_hex`. |
-### Модуль decort_vins
+### Модуль decort_osimage
| Идентификатор
задачи | Описание |
| --- | --- |
-| BANS-791 | Добавлен параметр `zone_id` и возвращаемое значение `zone_id`. |
+| BANS-868 | Добавлен параметр `storage_policy_id` и возвращаемое значение `storage_policy_id`. |
+| BANS-885 | Добавлена возможность изменять `storage_policy_id` шаблонного образа. |
+| BANS-914 | Добавлено возвращаемое значение `to_clean`. |
-### Модуль decort_bservice
+### Модуль decort_k8s
| Идентификатор
задачи | Описание |
| --- | --- |
-| BANS-792 | Добавлен параметр `zone_id` и возвращаемое значение `zone_id`. |
-| BANS-805 | Добавлены значения `stopped` и `started` для параметра `state`. |
+| BANS-869 | Добавлен параметр `storage_policy_id`. |
+| BANS-234 | Добавлена возможность переименования кластера. |
-### Модуль decort_user_info
+### Модуль decort_rg
| Идентификатор
задачи | Описание |
| --- | --- |
-| BANS-796 | Добавлен параметр `zones` и возвращаемое значение `zones`. |
-| BANS-826 | Добавлен параметр `trunks` и возвращаемое значение `trunks`. |
+| BANS-877 | Добавлено возвращаемое значение `storage_policy_ids`. |
+| BANS-882 | Добавлен параметр `quotas.storage_policies`. |
### Модуль decort_account
| Идентификатор
задачи | Описание |
| --- | --- |
-| BANS-789 | Добавлен параметр `default_zone_id` и возвращаемые значение `zoneIds`, `defaultZoneId`. |
+| BANS-875 | Добавлены возвращаемые значения `storage_policy_ids` и `resourceLimits.storage_policies`. |
### Модуль decort_account_info
| Идентификатор
задачи | Описание |
| --- | --- |
-| BANS-809 | Добавлено значение `MERGE` для параметра `computes.filter.tech_status`. |
-| BANS-855 | Добавлены значения `SNAPCREATE`, `CLONING`, `ROLLBACK` для параметра `computes.filter.tech_status`. |
+| BANS-876 | Добавлены возвращаемые значения `storage_policy_ids` и `resourceLimits.storage_policies`. |
+| BANS-911 | У параметра `computes.filter.tech_status` добавлены значения `MIGRATING_IN` и `MIGRATING_OUT`. |
+| BANS-903 | Добавлены новые возвращаемые значения в `audits`. |
-### Модуль decort_rg
+### Модуль decort_storage_policy
| Идентификатор
задачи | Описание |
| --- | --- |
-| BANS-812 | Добавлен параметр `sdn_access_group_id` и возвращаемое значение `sdn_access_group_id`. |
+| BANS-878 | Добавлен модуль `decort_storage_policy`. |
-### Модуль decort_zone
+### Модуль decort_user_info
| Идентификатор
задачи | Описание |
| --- | --- |
-| BANS-795 | Добавлен модуль `decort_zone` для получения информации о зонах. |
+| BANS-879 | Добавлен параметр `storage_policies` и возвращаемое значение `storage_policies`. |
+| BANS-889 | Добавлен параметр `accounts.filter.zone_id`. |
+| BANS-892 | Добавлено возвращаемое значение `accounts.zone_ids`. |
+| BANS-891 | Добавлен параметр `security_groups` и возвращаемое значение `security_groups`. |
-### Модуль decort_trunk
+### Модуль decort_security_group
| Идентификатор
задачи | Описание |
| --- | --- |
-| BANS-825 | Добавлен модуль `decort_trunk` для получения информации о транковых портах. |
+| BANS-883 | Добавлен модуль `decort_security_group`. |
-### Модуль decort_snapshot
+### Модуль decort_vins
| Идентификатор
задачи | Описание |
| --- | --- |
-| BANS-808 | Добавлено значение `merge_aborted` для параметра `state`. |
+| BANS-147 | Добавлен параметр `permanently` для удаления внутренней сети в корзину или безвозвратно. |
+## Удалено
### Модуль decort_osimage
| Идентификатор
задачи | Описание |
| --- | --- |
-| BANS-849 | Добавлен параметр `account_id`, используемый при создании шаблонных и виртуальных образов. |
+| BANS-862 | Удален параметр `drivers`. |
+| BANS-292 | Удален неиспользуемый параметр `gid`. |
+
+### Модуль decort_kvmvm
+| Идентификатор
задачи | Описание |
+| --- | --- |
+| BANS-730 | Удалён параметр `image_name`. |
+| BANS-893 | Удалена необходимость указывать параметр `networks.mac` при подключении сети `SDN`. |
-## Удалено
### Модуль decort_disk
| Идентификатор
задачи | Описание |
| --- | --- |
-| BANS-815 | Удалено значение по умолчанию для параметра `description`. |
+| BANS-867 | Удалён параметр `iops`. |
+| BANS-898 | Удалён параметр `disks.ids`. |
### Модуль decort_lb
| Идентификатор
задачи | Описание |
| --- | --- |
-| BANS-815 | Удалено значение по умолчанию для параметра `description`. |
+| BANS-320 | Удалён неиспользуемый параметр `ext_ip_addr`. |
### Модуль decort_k8s
| Идентификатор
задачи | Описание |
| --- | --- |
-| BANS-804 | Удален параметр `started` в связи с переносом логики в параметр `state` (значение `started`). |
+| BANS-199 | Удалён неиспользуемый параметр `quotas`. |
+
+### Модуль decort_group
+| Идентификатор
задачи | Описание |
+| --- | --- |
+| BANS-388 | Удалён неиспользуемый параметр `image_name`. |
+## Исправлено
### Модуль decort_bservice
| Идентификатор
задачи | Описание |
| --- | --- |
-| BANS-805 | Удален параметр `started` в связи с переносом логики в параметр `state` (значение `started`). |
+| BANS-851 | Скорректирована логика параметра целевого состояния `present`. Теперь состояние `present` соответствует тому, что базовая служба существует, и не приводит к изменению состояния существующей базовой службы. Также для параметра `state` значение по умолчанию `present` теперь только при создании базовой службы. |
+| BANS-873 | Указание параметров `account_id`/`account_name`, `rg_name`, `name` могло найти базовую службу с таким же именем, но принадлежащей другому аккаунту.|
-### Модуль decort_osimage
+### Модуль decort_kvmvm
| Идентификатор
задачи | Описание |
| --- | --- |
-| BANS-849 | Удален параметр `account_Id` в связи с переименованием в `account_id`. |
+| BANS-685 | Исправлена ошибка, приводящая к отображению неактуальной информации о виртуальной машине после её удаления. |
+| BANS-699 | Исправлена логика удаления ВМ в статусе DISABLED, приводящая к возникновению ошибок. |
+| BANS-748 | Исправлена логика работы с несуществующей ВМ, по которой получение информации о ВМ приводило к возникновению ошибок. |
+| BANS-697 | Исправлена логика, приводящая к ошибке при указании идентификатора несуществующего образа. |
+| BANS-122 | Исправлена логика, приводящая к изменению `affinity rules`, `anti-affinity rules`, `anti-affinity rules` и ` tags` в режиме `check_mode`. |
+
+### Модуль decort_disk
+| Идентификатор
задачи | Описание |
+| --- | --- |
+| BANS-742 | Исправлена ошибка, приводящая к отображению неактуальной информации о статусе диска после его удаления. |
+| BANS-743 | Исправлена логика работы с несуществующим диском, по которой получение информации о диске приводило к возникновению ошибок. |
+| BANS-247 | Модуль завершал свою работу ошибкой запроса API при создании/изменении диска с указанием параметра `size` меньше 1. |
-## Исправлено
### Модуль decort_lb
| Идентификатор
задачи | Описание |
| --- | --- |
-| BANS-803 | Модуль завершал работу ошибкой Python при создании балансировщика с указанием параметра `backends` или `frontends`. |
-| BANS-820 | Выполнение модуля с указанием параметра `vins_id` и без указания параметра `ext_net_id` вызывало создание балансировщика с некорректной сетевой конфигурацией, дальнейшее добавление конфигурации backend к которому завершалось ошибкой платформы. |
-| BANS-799 | Скорректирована логика параметра целевого состояния `present`. Теперь состояние `present` соответствует тому, что балансировщик нагрузки существует, и не приводит к изменению состояния существующего балансировщика нагрузки. Также для параметра `state` значение по умолчанию `present` теперь только при создании балансировщика нагрузки. |
+| BANS-752 | Исправлена ошибка, приводящая к отображению неактуальной информации о статусе балансировщика после его удаления. |
+| BANS-754 | Исправлена логика работы с несуществующими объектами, по которой получение информации об объектах приводило к возникновению ошибок. |
+| BANS-753 | Исправлена логика работы с несуществующим балансировщиком, по которой получение информации о балансировщике приводило к возникновению ошибок. |
-### Модуль decort_account
+### Модуль decort_group
+| Идентификатор
задачи | Описание |
+| --- | --- |
+| BANS-648 | Исправлена логика передачи параметров, приводящая к ошибке при передаче параметра `name` без параметра `id`. |
+| BANS-418 | Исправлена логика обновления сетей у группы базовой службы, из-за которой не происходило обновление внешних сетей. |
+| BANS-646 | Исправлена логика повторного удаления группы базовой службы, которая приводила к возникновению ошибок. |
+| BANS-559 | При создании группы с указанием параметра `timeoutStart` модуль производил попытку запустить группу после её создания. |
+
+### Модуль decort_osimage
+| Идентификатор
задачи | Описание |
+| --- | --- |
+| BANS-653 | Исправлена ошибка, приводящая к отображению неактуальной информации об образе после его удаления. |
+
+### Модуль decort_vins
| Идентификатор
задачи | Описание |
| --- | --- |
-| BANS-817 | Модуль некорректно отслеживал завершение удаления и восстановления аккаунта. |
+| BANS-663 | Исправлена ошибка передачи параметра `description` при создании внутренней сети. |
+| BANS-174 | Исправлена логика, приводящая к невозможности задать внешнюю сеть для внутренней сети без внешней сети. |
diff --git a/README.md b/README.md
index c1edd5b..58f1c1e 100644
--- a/README.md
+++ b/README.md
@@ -5,6 +5,7 @@
| Версия платформы | Версия модулей Ansible |
|:----------------:|:--------------------------:|
+| 4.4.0 | 10.0.x |
| 4.4.0 build 963 | 9.0.x |
| 4.3.0 | 8.0.x |
| 4.2.0 | 7.0.x, 7.1.x, 7.2.x |
diff --git a/library/decort_bservice.py b/library/decort_bservice.py
index 8edeca6..b34e75c 100644
--- a/library/decort_bservice.py
+++ b/library/decort_bservice.py
@@ -28,7 +28,7 @@ class decort_bservice(DecortController):
self.fail_json(**self.result)
if not arg_amodule.params['id']:
if not arg_amodule.params['rg_id']: # RG ID is not set -> locate RG by name -> need account ID
- validated_acc_id, self.acc_info = self.account_find(arg_amodule.params['account_name'],
+ validated_acc_id, self._acc_info = self.account_find(arg_amodule.params['account_name'],
arg_amodule.params['account_id'])
if not validated_acc_id:
self.result['failed'] = True
@@ -113,7 +113,7 @@ class decort_bservice(DecortController):
)
if self.bservice_id:
_, self.bservice_info = self.bservice_get_by_id(self.bservice_id)
- self.bservice_state(self.bservice_info,'enabled')
+ self.bservice_state(self.bservice_info, self.aparams['state'])
return
def action(self,d_state):
@@ -176,7 +176,6 @@ class decort_bservice(DecortController):
),
state=dict(
type='str',
- default='present',
choices=[
'absent',
'disabled',
@@ -298,25 +297,30 @@ def main():
subj.action(amodule.params['state'])
if amodule.params['state'] == 'absent':
subj.nop()
- elif subj.bservice_info['status'] in ('ENABLED', 'DISABLED'):
+ elif subj.bservice_info['status'] in (
+ 'ENABLED', 'DISABLED', 'CREATED',
+ ):
if amodule.params['state'] == 'absent':
subj.destroy()
else:
subj.action(amodule.params['state'])
- elif subj.bservice_info['status'] == "DESTROED":
+ elif subj.bservice_info['status'] == "DESTROYED":
if amodule.params['state'] in ('present','enabled'):
subj.create()
subj.action(amodule.params['state'])
if amodule.params['state'] == 'absent':
subj.nop()
else:
- if amodule.params['state'] == 'absent':
+ state = amodule.params['state']
+ if state is None:
+ state = 'present'
+ if state == 'absent':
subj.nop()
- if amodule.params['state'] in ('present','started'):
+ if state in ('present','started'):
subj.create()
- elif amodule.params['state'] in ('stopped', 'disabled','enabled'):
+ elif state in ('stopped', 'disabled','enabled'):
subj.error()
-
+
if subj.result['failed']:
amodule.fail_json(**subj.result)
else:
diff --git a/library/decort_disk.py b/library/decort_disk.py
index b9eb521..ac1856b 100644
--- a/library/decort_disk.py
+++ b/library/decort_disk.py
@@ -23,7 +23,7 @@ class decort_disk(DecortController):
self.disk_id = 0
self.account_id = 0
# limitIO check for exclusive parameters
-
+
if arg_amodule.params['limitIO']:
self.disk_check_iotune_arg(arg_amodule.params['limitIO'])
@@ -41,6 +41,8 @@ class decort_disk(DecortController):
validated_acc_id, validated_acc_info = self.account_find(
arg_amodule.params['account_name'],
arg_amodule.params['account_id'])
+ self.acc_id = validated_acc_id
+ self._acc_info = validated_acc_info
if not validated_acc_id:
self.result['changed'] = False
self.result['msg'] = (
@@ -51,8 +53,6 @@ class decort_disk(DecortController):
)
self.amodule.fail_json(**self.result)
- self.acc_id = validated_acc_id
- self._acc_info = validated_acc_info
validated_disk_id, validated_disk_facts = self.disk_find(
disk_id=arg_amodule.params['id'],
name=arg_amodule.params['name'] if "name" in arg_amodule.params else "",
@@ -67,15 +67,21 @@ 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']
+ self.check_amodule_args_for_change()
+ else:
+ self.check_amodule_args_for_create()
+
def create(self):
-
- self.disk_id = self.disk_create(accountId=self.acc_id,
- name = self.amodule.params['name'],
- description=self.amodule.params['description'],
- size=self.amodule.params['size'],
- iops=self.amodule.params['iops'],
- sep_id=self.amodule.params['sep_id'],
- pool=self.amodule.params['pool'],
+ self.disk_id = self.disk_create(
+ accountId=self.acc_id,
+ name = self.amodule.params['name'],
+ description=self.amodule.params['description'],
+ size=self.amodule.params['size'],
+ sep_id=self.amodule.params['sep_id'],
+ pool=self.amodule.params['pool'],
+ storage_policy_id=self.aparams['storage_policy_id'],
)
#IO tune
if self.amodule.params['limitIO']:
@@ -115,16 +121,27 @@ class decort_disk(DecortController):
#raise Exception(self.amodule.params['shareable'])
if self.amodule.params['shareable'] != self.disk_info['shareable']:
self.disk_share(self.disk_id,self.amodule.params['shareable'])
+
+ 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']
+ ):
+ self.disk_change_storage_policy(
+ disk_id=self.disk_id,
+ storage_policy_id=aparam_storage_policy_id,
+ )
+
return
def delete(self):
- self.disk_id = self.disk_delete(disk_id=self.disk_id,
+ self.disk_delete(disk_id=self.disk_id,
detach=self.amodule.params['force_detach'],
permanently=self.amodule.params['permanently'],
reason=self.amodule.params['reason'])
- self.disk_info['status'] = "DELETED"
+ self.disk_id, self.disk_info = self._disk_get_by_id(self.disk_id)
return
-
+
def rename(self):
@@ -175,6 +192,8 @@ class decort_disk(DecortController):
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']
return ret_dict
@@ -219,10 +238,6 @@ class decort_disk(DecortController):
size=dict(
type='int',
),
- iops=dict(
- type='int',
- default=2000,
- ),
limitIO=dict(
type='dict',
options=dict(
@@ -287,6 +302,9 @@ class decort_disk(DecortController):
'present',
],
),
+ storage_policy_id=dict(
+ type='int',
+ ),
),
supports_check_mode=True,
required_one_of=[
@@ -294,6 +312,73 @@ class decort_disk(DecortController):
],
)
+ def check_amodule_args_for_change(self):
+ check_errors = False
+
+ if self.check_aparam_storage_policy_id() is False:
+ check_errors = True
+ if self.check_aparam_size() is False:
+ check_errors = True
+
+ if check_errors:
+ self.exit(fail=True)
+
+ def check_amodule_args_for_create(self):
+ check_errors = False
+
+ aparam_storage_policy_id = self.aparams['storage_policy_id']
+ if aparam_storage_policy_id is None:
+ check_errors = True
+ self.message(
+ msg='Check for parameter "storage_policy_id" failed: '
+ 'storage_policy_id must be specified when creating '
+ 'a new disk'
+ )
+
+ if self.check_aparam_storage_policy_id() is False:
+ check_errors = True
+ if self.check_aparam_size() is False:
+ check_errors = True
+
+ if check_errors:
+ self.exit(fail=True)
+
+ def check_aparam_storage_policy_id(self) -> bool:
+ check_errors = False
+
+ 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']
+ ):
+ check_errors = True
+ self.message(
+ msg='Check for parameter "storage_policy_id" failed: '
+ f'Account ID {self.acc_id} does not have access to '
+ f'storage_policy_id {aparam_storage_policy_id}'
+ )
+
+ return not check_errors
+
+ def check_aparam_size(self) -> bool:
+ check_errors = False
+
+ aparam_size = self.aparams['size']
+ if (
+ aparam_size is not None
+ and aparam_size <= 0
+ ):
+ check_errors = True
+ self.message(
+ msg=(
+ 'Check for parameter "size" failed: '
+ f'Disk cannot be size {aparam_size}'
+ ),
+ )
+
+ return not check_errors
+
def main():
decon = decort_disk()
amodule = decon.amodule
@@ -334,12 +419,12 @@ def main():
if amodule.params['state'] == 'absent':
decon.nop()
else:
- decon.create()
-
+ decon.create()
+
if decon.result['failed']:
amodule.fail_json(**decon.result)
else:
- if decon.result['changed'] and amodule.params['state'] in ('present'):
+ if decon.result['changed']:
_, decon.disk_info = decon.disk_find(decon.disk_id)
decon.result['facts'] = decon.package_facts(amodule.check_mode)
amodule.exit_json(**decon.result)
diff --git a/library/decort_group.py b/library/decort_group.py
index 5636b18..95f1994 100644
--- a/library/decort_group.py
+++ b/library/decort_group.py
@@ -36,11 +36,15 @@ class decort_group(DecortController):
group_id=arg_amodule.params['id'],
group_name=arg_amodule.params['name'],
)
+ self.acc_id = self.bservice_info['accountId']
+ self.rg_id = self.bservice_info['rgId']
if self.group_id:
self.group_should_exist = True
self.check_amodule_args_for_change()
-
+ else:
+ self.check_amodule_args_for_create()
+
return
def nop(self):
"""No operation (NOP) handler for B-service.
@@ -84,6 +88,17 @@ class decort_group(DecortController):
warning=True,
)
+ driver = self.aparams['driver']
+ if driver is None:
+ driver = 'KVM_X86'
+ self.message(
+ msg=self.MESSAGES.default_value_used(
+ param_name='driver',
+ default_value=driver,
+ ),
+ warning=True,
+ )
+
self.group_id=self.group_provision(
bs_id=self.bservice_id,
arg_name=self.amodule.params['name'],
@@ -92,14 +107,18 @@ class decort_group(DecortController):
arg_ram=self.amodule.params['ram'],
arg_boot_disk=self.amodule.params['boot_disk'],
arg_image_id=self.amodule.params['image_id'],
- arg_driver=self.amodule.params['driver'],
arg_role=self.amodule.params['role'],
arg_network=self.amodule.params['networks'],
- arg_timeout=self.amodule.params['timeoutStart'],
+ arg_timeout=self.amodule.params['timeoutStart'],
chipset=chipset,
+ storage_policy_id=self.aparams['storage_policy_id'],
+ driver=driver,
)
-
- if self.amodule.params['state'] in ('started','present'):
+
+ if (
+ self.amodule.params['state'] in ('started','present')
+ and self.amodule.params['timeoutStart'] is None
+ ):
self.group_state(self.bservice_id,self.group_id,self.amodule.params['state'])
self.group_should_exist = True
@@ -196,6 +215,7 @@ class decort_group(DecortController):
ret_dict['techStatus'] = self.group_info['techStatus']
ret_dict['state'] = self.group_info['status']
ret_dict['Computes'] = self.group_info['computes']
+ ret_dict['driver'] = self.group_info['driver']
return ret_dict
@property
@@ -229,17 +249,6 @@ class decort_group(DecortController):
image_id=dict(
type='int',
),
- image_name=dict(
- type='str',
- ),
- driver=dict(
- type='str',
- choices=[
- 'KVM_X86',
- 'SVA_KVM_X86',
- ],
- default='KVM_X86',
- ),
boot_disk=dict(
type='int',
),
@@ -287,17 +296,16 @@ class decort_group(DecortController):
'i440fx',
]
),
+ storage_policy_id=dict(
+ type='int',
+ ),
+ driver=dict(
+ type='str',
+ ),
),
supports_check_mode=True,
required_one_of=[
('id', 'name'),
- ('id', 'networks'),
- ('id', 'count'),
- ('id', 'cpu'),
- ('id', 'ram'),
- ('id', 'boot_disk'),
- ('id', 'image_id'),
- ('id', 'driver'),
],
)
@@ -306,7 +314,7 @@ class decort_group(DecortController):
if (
self.aparams['chipset'] is None
- and self.aparams['count'] > len(self.group_info['computes'])
+ and (self.aparams['count'] if self.aparams['count'] is not None else 0) > len(self.group_info['computes'])
):
check_errors = True
self.message(
@@ -335,9 +343,71 @@ class decort_group(DecortController):
)
break
+ aparam_storage_policy_id = self.aparams['storage_policy_id']
+ if aparam_storage_policy_id is not None:
+ for compute in self.group_info['computes']:
+ _, compute_info, _ = self._compute_get_by_id(compute['id'])
+ for disk in compute_info['disks']:
+ if aparam_storage_policy_id != disk['storage_policy_id']:
+ check_errors = True
+ self.message(
+ msg='Check for parameter "storage_policy_id" '
+ 'failed: storage_policy_id can not be changed '
+ f'for compute ID {compute['id']} '
+ f'disk ID {disk['id']}'
+ )
+
+ aparam_driver = self.aparams['driver']
+ if (
+ aparam_driver is not None
+ and aparam_driver != self.group_info['driver']
+ ):
+ check_errors = True
+ self.message(
+ msg='Check for parameter "driver" failed: '
+ 'driver can not be changed'
+ )
+
+ if check_errors:
+ self.exit(fail=True)
+
+ def check_amodule_args_for_create(self):
+ check_errors = False
+
+ aparam_storage_policy_id = self.aparams['storage_policy_id']
+ if aparam_storage_policy_id is None:
+ check_errors = True
+ self.message(
+ msg='Check for parameter "storage_policy_id" failed: '
+ 'storage_policy_id must be specified when creating '
+ 'a new group'
+ )
+ else:
+ if (
+ aparam_storage_policy_id
+ not in self.rg_info['storage_policy_ids']
+ ):
+ check_errors = True
+ self.message(
+ msg='Check for parameter "storage_policy_id" failed: '
+ f'RG ID {self.rg_id} does not have access to '
+ f'storage_policy_id {aparam_storage_policy_id}'
+ )
+ if (
+ aparam_storage_policy_id
+ not in self.acc_info['storage_policy_ids']
+ ):
+ check_errors = True
+ self.message(
+ msg='Check for parameter "storage_policy_id" failed: '
+ f'Account ID {self.acc_id} does not have access to '
+ f'storage_policy_id {aparam_storage_policy_id}'
+ )
+
if check_errors:
self.exit(fail=True)
+
def main():
subj = decort_group()
amodule = subj.amodule
@@ -359,8 +429,7 @@ def main():
if subj.group_id:
if subj.group_info['status'] in ("DELETING","DESTROYNG","CREATING","DESTROYING",
- "ENABLING","DISABLING","RESTORING","MODELED",
- "DISABLED","DESTROYED"):
+ "ENABLING","DISABLING","RESTORING","MODELED","DESTROYED"):
subj.error()
elif subj.group_info['status'] in ("DELETED","DESTROYED"):
if amodule.params['state'] == 'absent':
diff --git a/library/decort_k8s.py b/library/decort_k8s.py
index df24506..1313ab9 100644
--- a/library/decort_k8s.py
+++ b/library/decort_k8s.py
@@ -166,34 +166,36 @@ class decort_k8s(DecortController):
if wg[param] is None:
wg[param] = default_value
- k8s_id = self.k8s_provision(self.amodule.params['name'],
- self.amodule.params['k8ci_id'],
- self.amodule.params['rg_id'],
- self.amodule.params['vins_id'],
- self.amodule.params['network_plugin'],
- self.amodule.params['master_count'],
- self.amodule.params['master_cpu'],
- self.amodule.params['master_ram'],
- self.amodule.params['master_disk'],
- self.amodule.params['master_sepid'],
- self.amodule.params['master_pool'],
- target_wgs[0],
- self.amodule.params['extnet_id'],
- self.amodule.params['with_lb'],
- self.amodule.params['ha_lb'],
- self.amodule.params['additionalSANs'],
- self.amodule.params['init_conf'],
- self.amodule.params['cluster_conf'],
- self.amodule.params['kublet_conf'],
- self.amodule.params['kubeproxy_conf'],
- self.amodule.params['join_conf'],
- self.amodule.params['oidc_cert'],
- self.amodule.params['description'],
- self.amodule.params['extnet_only'],
- master_chipset,
- lb_sysctl=self.amodule.params['lb_sysctl'],
- zone_id=self.aparams['zone_id'],
- )
+ k8s_id = self.k8s_provision(
+ self.amodule.params['name'],
+ self.amodule.params['k8ci_id'],
+ self.amodule.params['rg_id'],
+ self.amodule.params['vins_id'],
+ self.amodule.params['network_plugin'],
+ self.amodule.params['master_count'],
+ self.amodule.params['master_cpu'],
+ self.amodule.params['master_ram'],
+ self.amodule.params['master_disk'],
+ self.amodule.params['master_sepid'],
+ self.amodule.params['master_pool'],
+ target_wgs[0],
+ self.amodule.params['extnet_id'],
+ self.amodule.params['with_lb'],
+ self.amodule.params['ha_lb'],
+ self.amodule.params['additionalSANs'],
+ self.amodule.params['init_conf'],
+ self.amodule.params['cluster_conf'],
+ self.amodule.params['kublet_conf'],
+ self.amodule.params['kubeproxy_conf'],
+ self.amodule.params['join_conf'],
+ self.amodule.params['oidc_cert'],
+ self.amodule.params['description'],
+ self.amodule.params['extnet_only'],
+ master_chipset=master_chipset,
+ lb_sysctl=self.amodule.params['lb_sysctl'],
+ zone_id=self.aparams['zone_id'],
+ storage_policy_id=self.aparams['storage_policy_id'],
+ )
if not k8s_id:
if k8s_id == 0:
@@ -241,6 +243,12 @@ class decort_k8s(DecortController):
)
self.exit(fail=True)
+ if (
+ self.aparams['name'] is not None
+ and self.aparams['name'] != self.k8s_info['name']
+ ):
+ self.k8s_update(id=self.k8s_id, name=self.aparams['name'])
+
if preupdate:
# K8s info updating
self.k8s_info = self.k8s_get_by_id(k8s_id=self.k8s_id)
@@ -275,9 +283,6 @@ class decort_k8s(DecortController):
type='str',
default='',
),
- quotas=dict(
- type='dict',
- ),
state=dict(
type='str',
default='present',
@@ -448,6 +453,9 @@ class decort_k8s(DecortController):
zone_id=dict(
type='int',
),
+ storage_policy_id=dict(
+ type='int',
+ ),
),
supports_check_mode=True,
required_one_of=[
@@ -499,6 +507,32 @@ class decort_k8s(DecortController):
'K8s cluster must be stopped to migrate to a zone.'
)
+ 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'
+ ]:
+ computes_ids.append(master_node['id'])
+ for wg in self.k8s_info['k8sGroups']['workers']:
+ workers_ids = [
+ worker['id'] for worker in wg['detailedInfo']
+ ]
+ computes_ids.extend(workers_ids)
+ for compute_id in computes_ids:
+ _, compute_info, _ = self._compute_get_by_id(
+ comp_id=compute_id
+ )
+ for disk in compute_info['disks']:
+ if aparam_storage_policy_id != disk['storage_policy_id']:
+ check_errors = True
+ self.message(
+ msg='Check for parameter "storage_policy_id" '
+ 'failed: storage_policy_id can not be changed '
+ f'for k8s cluster ID {self.k8s_id} compute ID '
+ f'{compute_id} disk ID {disk['id']}'
+ )
+
if check_errors:
self.exit(fail=True)
@@ -540,6 +574,26 @@ class decort_k8s(DecortController):
if self.check_aparam_zone_id() is False:
check_errors = True
+ aparam_storage_policy_id = self.aparams['storage_policy_id']
+ if aparam_storage_policy_id is None:
+ check_errors = True
+ self.message(
+ msg='Check for parameter "storage_policy_id" failed: '
+ 'storage_policy_id must be specified when creating '
+ 'a new cluster'
+ )
+ elif (
+ aparam_storage_policy_id
+ not in self.rg_info['storage_policy_ids']
+ ):
+ check_errors = True
+ self.message(
+ msg='Check for parameter "storage_policy_id" failed: '
+ f'RG ID {self.rg_id} does not have access to '
+ f'storage_policy_id {aparam_storage_policy_id}'
+ )
+
+
if check_errors:
self.exit(fail=True)
diff --git a/library/decort_kvmvm.py b/library/decort_kvmvm.py
index 6ae84ce..75f8f79 100644
--- a/library/decort_kvmvm.py
+++ b/library/decort_kvmvm.py
@@ -56,6 +56,7 @@ class decort_kvmvm(DecortController):
comp_id=self.aparams['clone_from']['id'],
)
)
+ self.rg_id = self.vm_to_clone_info['rgId']
if not self.vm_to_clone_id:
self.message(
f'Check for parameter "clone_from.id" failed: '
@@ -73,7 +74,7 @@ class decort_kvmvm(DecortController):
clone_id, clone_dict, _ = self.compute_find(
comp_name=self.aparams['name'],
- rg_id=self.vm_to_clone_info['rgId'],
+ rg_id=self.rg_id,
)
self.check_amodule_args_for_clone(
clone_id=clone_id,
@@ -389,7 +390,7 @@ class decort_kvmvm(DecortController):
Compute instance with the specified characteristics into the target Resource Group.
The target RG must exist.
"""
- # the following parameters must be present: cpu, ram, image_id or image_name
+ # the following parameters must be present: cpu, ram, image_id
# each of the following calls will abort if argument is missing
self.check_amodule_argument('cpu')
self.check_amodule_argument('ram')
@@ -429,27 +430,15 @@ class decort_kvmvm(DecortController):
image_id, image_facts = None, None
if self.aparam_image:
- # either image_name or image_id must be present
if (
self.check_amodule_argument('image_id', abort=False)
and self.amodule.params['image_id'] > 0
):
# find image by image ID and account ID
- # image_find(self, image_id, image_name, account_id, rg_id=0, sepid=0, pool=""):
+ # image_find(self, image_id, account_id, rg_id=0, sepid=0, pool=""):
image_id, image_facts = self.image_find(
image_id=self.amodule.params['image_id'],
- image_name="",
account_id=self.acc_id)
- elif (
- self.check_amodule_argument('image_name', abort=False)
- and self.amodule.params['image_name'] != ""
- ):
- # find image by image name and account ID
- image_id, image_facts = self.image_find(
- image_id=0,
- image_name=self.amodule.params['image_name'],
- account_id=self.acc_id,
- )
if validated_bdisk_size <= image_facts['size']:
# adjust disk size to the minimum allowed by OS image, which will be used to spin off this Compute
@@ -523,26 +512,31 @@ class decort_kvmvm(DecortController):
# if we get through here, all parameters required to create new Compute instance should be at hand
# NOTE: KVM VM is created in HALTED state and must be explicitly started
- self.comp_id = self.kvmvm_provision(rg_id=self.rg_id,
- comp_name=self.amodule.params['name'],
- cpu=self.amodule.params['cpu'], ram=self.amodule.params['ram'],
- boot_disk_size=validated_bdisk_size,
- image_id=image_id,
- description=self.amodule.params['description'],
- userdata=cloud_init_params,
- sep_id=self.amodule.params['sep_id' ] if "sep_id" in self.amodule.params else None,
- pool_name=self.amodule.params['pool'] if "pool" in self.amodule.params else None,
- start_on_create=start_compute,
- chipset=chipset,
- cpu_pin=cpu_pin,
- hp_backed=hp_backed,
- numa_affinity=numa_affinity,
- preferred_cpu_cores=self.amodule.params['preferred_cpu_cores'],
- boot_mode=boot_mode,
- boot_loader_type=loader_type,
- network_interface_naming=network_interface_naming,
- hot_resize=hot_resize,
- zone_id=self.aparams['zone_id'],)
+ self.comp_id = self.kvmvm_provision(
+ rg_id=self.rg_id,
+ comp_name=self.amodule.params['name'],
+ cpu=self.amodule.params['cpu'],
+ ram=self.amodule.params['ram'],
+ boot_disk_size=validated_bdisk_size,
+ image_id=image_id,
+ description=self.amodule.params['description'],
+ userdata=cloud_init_params,
+ sep_id=self.amodule.params['sep_id' ] if "sep_id" in self.amodule.params else None,
+ pool_name=self.amodule.params['pool'] if "pool" in self.amodule.params else None,
+ start_on_create=start_compute,
+ chipset=chipset,
+ cpu_pin=cpu_pin,
+ hp_backed=hp_backed,
+ numa_affinity=numa_affinity,
+ preferred_cpu_cores=self.amodule.params['preferred_cpu_cores'],
+ boot_mode=boot_mode,
+ boot_loader_type=loader_type,
+ network_interface_naming=network_interface_naming,
+ hot_resize=hot_resize,
+ zone_id=self.aparams['zone_id'],
+ storage_policy_id=self.aparams['storage_policy_id'],
+ os_version=self.aparams['os_version'],
+ )
self.comp_should_exist = True
# Originally we would have had to re-read comp_info after VM was provisioned
@@ -579,7 +573,7 @@ class decort_kvmvm(DecortController):
if self.amodule.params['disks'] is not None:
self.compute_disks(
comp_dict=self.comp_info,
- aparam_disks=self.amodule.params['disks'],
+ aparam_disks_dict=self.amodule.params['disks'],
)
self.compute_affinity(self.comp_info,
@@ -626,8 +620,7 @@ class decort_kvmvm(DecortController):
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.comp_info['status'] = 'DESTROYED'
- self.comp_should_exist = False
+ self.comp_id, self.comp_info, _ = self._compute_get_by_id(self.comp_id)
return
def restore(self):
@@ -676,7 +669,7 @@ class decort_kvmvm(DecortController):
if self.amodule.params['disks'] is not None:
self.compute_disks(
comp_dict=self.comp_info,
- aparam_disks=self.amodule.params['disks'],
+ aparam_disks_dict=self.amodule.params['disks'],
)
aparam_boot = self.amodule.params['boot']
@@ -695,6 +688,59 @@ class decort_kvmvm(DecortController):
if boot_disk_new_size:
self.compute_bootdisk_size(self.comp_info, boot_disk_new_size)
+ boot_order = aparam_boot['order']
+ if (
+ boot_order is not None
+ and self.comp_info['bootOrder'] != boot_order
+ ):
+ self.compute_set_boot_order(
+ vm_id=self.comp_id,
+ order=boot_order,
+ )
+
+ disk_redeploy = aparam_boot['disk_redeploy']
+ if disk_redeploy:
+ auto_start = False
+ if self.aparams['state'] is None:
+ if self.comp_info['techStatus'] == 'STARTED':
+ auto_start = True
+ else:
+ if self.aparams['state'] == 'started':
+ auto_start = True
+
+ disk_size = None
+ if (
+ aparam_boot is not None
+ and aparam_boot['disk_size'] is not None
+ ):
+ disk_size = aparam_boot['disk_size']
+ elif self.aparams['image_id'] is not None:
+ _, image_facts = self.image_find(
+ image_id=self.aparams['image_id'],
+ )
+ disk_size = image_facts['size']
+
+ os_version = None
+ if (
+ self.aparams['image_id'] is None
+ or self.aparams['image_id'] == self.comp_info['imageId']
+ ):
+ if self.aparams['os_version'] is None:
+ os_version = self.comp_info['os_version']
+ else:
+ os_version = self.aparams['os_version']
+ elif self.aparams['image_id'] != self.comp_info['imageId']:
+ os_version = self.aparams['os_version']
+
+ self.compute_disk_redeploy(
+ vm_id=self.comp_id,
+ storage_policy_id=self.aparams['storage_policy_id'],
+ image_id=self.aparams['image_id'],
+ disk_size=disk_size,
+ auto_start=auto_start,
+ os_version=os_version,
+ )
+
self.compute_resize(self.comp_info,
self.amodule.params['cpu'], self.amodule.params['ram'],
wait_for_state_change=arg_wait_cycles)
@@ -754,6 +800,28 @@ class decort_kvmvm(DecortController):
)
)
+ aparam_cdrom = self.aparams['cdrom']
+ if aparam_cdrom is not None:
+ mode = aparam_cdrom['mode']
+ image_id = aparam_cdrom['image_id']
+ if (
+ mode == 'insert'
+ and self.comp_info['cdImageId'] != image_id
+ ):
+ self.compute_cd_insert(
+ vm_id=self.comp_id,
+ image_id=image_id,
+ )
+ elif mode == 'eject':
+ self.compute_cd_eject(
+ vm_id=self.comp_id,
+ )
+
+ if self.aparams['abort_cloning']:
+ self.compute_clone_abort(
+ vm_id=self.comp_id,
+ )
+
return
@property
@@ -773,6 +841,7 @@ class decort_kvmvm(DecortController):
'boot.loader_type': 'loaderType',
'network_interface_naming': 'networkInterfaceNaming',
'hot_resize': 'hotResize',
+ 'os_version': 'os_version',
}
def get_nested_value(
@@ -814,6 +883,14 @@ class decort_kvmvm(DecortController):
)
if aparam_value is not None and aparam_value != comp_value:
+ # If disk_redeploy = True no need to update os_version.
+ # Updating os_version through compute_disk_redeploy
+ if (
+ aparam_name == 'os_version'
+ and self.aparams['boot'] is not None
+ and self.aparams['boot']['disk_redeploy']
+ ):
+ continue
result_args[aparam_name.replace('.', '_')] = (
aparam_value
)
@@ -837,7 +914,6 @@ class decort_kvmvm(DecortController):
cpu="",
ram="",
disk_size=0,
- data_disks=[], # IDs of attached data disks; this list can be emty
state="CHECK_MODE",
tech_status="",
account_id=0,
@@ -898,18 +974,19 @@ class decort_kvmvm(DecortController):
elif iface['connType'] == "VLAN": # This is direct external network connection
ret_dict['public_ips'].append(iface['ipAddress'])
+ iface['security_group_mode'] = iface.pop('enable_secgroups')
+ iface['security_group_ids'] = iface.pop('security_groups')
+
ret_dict['cpu'] = self.comp_info['cpus']
ret_dict['ram'] = self.comp_info['ram']
ret_dict['image_id'] = self.comp_info['imageId']
- for ddisk in self.comp_info['disks']:
- if ddisk['type'] == 'B':
+ ret_dict['disks'] = self.comp_info['disks']
+ for disk in ret_dict['disks']:
+ if disk['type'] == 'B':
# if it is a boot disk - store its size
- ret_dict['disk_size'] = ddisk['sizeMax']
- elif ddisk['type'] == 'D':
- # if it is a data disk - append its ID to the list of data disks IDs
- ret_dict['data_disks'].append(ddisk['id'])
+ ret_dict['disk_size'] = disk['sizeMax']
ret_dict['chipset'] = self.comp_info['chipset']
@@ -960,15 +1037,30 @@ class decort_kvmvm(DecortController):
self.comp_info['snapshot_merge_status']
)
+ ret_dict['cd_image_id'] = self.comp_info['cdImageId']
+
+ ret_dict['boot_order'] = self.comp_info['bootOrder']
+
+ ret_dict['os_version'] = self.comp_info['os_version']
+
+ ret_dict['boot_loader_metaiso'] = self.comp_info['loaderMetaIso']
+ if self.comp_info['loaderMetaIso'] is not None:
+ ret_dict['boot_loader_metaiso'] = {
+ 'device_name': self.comp_info['loaderMetaIso']['devicename'],
+ 'path': self.comp_info['loaderMetaIso']['path'],
+ }
+
+ if self.amodule.params['get_cloning_status']:
+ ret_dict['cloning_status'] = self.compute_get_clone_status(
+ vm_id=self.comp_id,
+ )
+
return ret_dict
def check_amodule_args_for_create(self):
check_errors = False
# Check for unacceptable parameters for a blank Compute
- if (
- self.aparams['image_id'] is not None
- or self.aparams['image_name'] is not None
- ):
+ if self.aparams['image_id'] is not None:
self.aparam_image = True
for param in (
'network_interface_naming',
@@ -1017,7 +1109,7 @@ class decort_kvmvm(DecortController):
check_errors = True
self.message(
f'Check for parameter "{parameter}" failed: '
- f'"image_id" or "image_name" must be specified '
+ f'"image_id" must be specified '
f'to set {parameter}.'
)
@@ -1029,7 +1121,7 @@ class decort_kvmvm(DecortController):
check_errors = True
self.message(
'Check for parameter "sep_id" failed: '
- '"image_id" or "image_name" or "boot.disk_size" '
+ '"image_id" or "boot.disk_size" '
'must be specified to set sep_id.'
)
@@ -1072,6 +1164,39 @@ class decort_kvmvm(DecortController):
if self.check_aparam_networks_trunk() is False:
check_errors = True
+ if self.aparams['cdrom'] is not None:
+ check_errors = True
+ self.message(
+ 'Check for parameter "cdrom" failed: '
+ 'cdrom can be specified only for existing compute.'
+ )
+
+ aparam_storage_policy_id = self.aparams['storage_policy_id']
+ if aparam_storage_policy_id is None:
+ check_errors = True
+ self.message(
+ msg='Check for parameter "storage_policy_id" failed: '
+ 'storage_policy_id must be specified when creating '
+ 'a new compute'
+ )
+ elif (
+ aparam_storage_policy_id
+ not in self.rg_info['storage_policy_ids']
+ ):
+ check_errors = True
+ self.message(
+ msg='Check for parameter "storage_policy_id" failed: '
+ f'RG ID {self.rg_id} does not have access to '
+ f'storage_policy_id {aparam_storage_policy_id}'
+ )
+
+ if self.aparams['abort_cloning'] is not None:
+ check_errors = True
+ self.message(
+ 'Check for parameter "abort_cloning" failed: '
+ 'abort_cloning can be specified only for existing compute.'
+ )
+
if check_errors:
self.exit(fail=True)
@@ -1114,6 +1239,19 @@ class decort_kvmvm(DecortController):
'unknown',
],
),
+ from_cdrom=dict(
+ type='int',
+ ),
+ order=dict(
+ type='list',
+ elements='str',
+ choices=[
+ e.value for e in self.VMBootDevice
+ ],
+ ),
+ disk_redeploy=dict(
+ type='bool',
+ ),
),
),
sep_id=dict(
@@ -1142,9 +1280,24 @@ class decort_kvmvm(DecortController):
],
default='update',
),
- ids=dict(
+ objects=dict(
type='list',
- elements='int',
+ elements='dict',
+ options=dict(
+ id=dict(
+ type='int',
+ required=True,
+ ),
+ pci_slot_num_hex=dict(
+ type='str',
+ ),
+ bus_num_hex=dict(
+ type='str',
+ ),
+ ),
+ required_together=[
+ ('pci_slot_num_hex', 'bus_num_hex'),
+ ],
),
),
),
@@ -1155,9 +1308,6 @@ class decort_kvmvm(DecortController):
image_id=dict(
type='int',
),
- image_name=dict(
- type='str',
- ),
name=dict(
type='str',
),
@@ -1190,6 +1340,16 @@ class decort_kvmvm(DecortController):
mac=dict(
type='str',
),
+ security_group_ids=dict(
+ type='list',
+ elements='int',
+ ),
+ security_group_mode=dict(
+ type='bool',
+ ),
+ enabled=dict(
+ type='bool',
+ ),
),
required_if=[
('type', 'VINS', ('id',)),
@@ -1197,7 +1357,7 @@ class decort_kvmvm(DecortController):
('type', 'VFNIC', ('id',)),
('type', 'DPDK', ('id',)),
('type', 'TRUNK', ('id',)),
- ('type', 'SDN', ('id', 'mac')),
+ ('type', 'SDN', ('id',)),
],
),
network_order_changing=dict(
@@ -1326,6 +1486,16 @@ class decort_kvmvm(DecortController):
('name', 'timestamp', 'datetime'),
],
),
+ sep_pool_name=dict(
+ type='str',
+ ),
+ sep_id=dict(
+ type='int',
+ ),
+ storage_policy_id=dict(
+ type='int',
+ requiered=True,
+ ),
),
),
network_interface_naming=dict(
@@ -1368,6 +1538,34 @@ class decort_kvmvm(DecortController):
get_snapshot_merge_status=dict(
type='bool',
),
+ cdrom=dict(
+ type='dict',
+ options=dict(
+ mode=dict(
+ type='str',
+ choices=[
+ 'insert',
+ 'eject',
+ ],
+ default='insert',
+ ),
+ image_id=dict(
+ type='int',
+ ),
+ ),
+ ),
+ storage_policy_id=dict(
+ type='int',
+ ),
+ os_version=dict(
+ type='str',
+ ),
+ get_cloning_status=dict(
+ type='bool',
+ ),
+ abort_cloning=dict(
+ type='bool',
+ ),
),
supports_check_mode=True,
required_one_of=[
@@ -1377,7 +1575,7 @@ class decort_kvmvm(DecortController):
'clone_from': 'name',
},
)
-
+
def check_amodule_args_for_change(self):
check_errors = False
@@ -1404,30 +1602,6 @@ class decort_kvmvm(DecortController):
aparam_boot = self.amodule.params['boot']
if aparam_boot is not None:
- new_boot_disk_size = aparam_boot['disk_size']
- if new_boot_disk_size is not None:
- boot_disk_size = 0
- for disk in self.comp_info['disks']:
- if disk['type'] == 'B':
- boot_disk_size = disk['sizeMax']
- break
- else:
- if aparam_boot is None or aparam_boot['disk_id'] is None:
- check_errors = True
- self.message(
- f'Can\'t set boot disk size for Compute '
- f'{comp_id}, because it doesn\'t '
- f'have a boot disk.'
- )
-
- if new_boot_disk_size < boot_disk_size:
- check_errors = True
- self.message(
- f'New boot disk size {new_boot_disk_size} is less'
- f' than current {boot_disk_size} for Compute ID '
- f'{comp_id}'
- )
-
aparam_disks = self.amodule.params['disks']
aparam_boot_disk_id = aparam_boot['disk_id']
comp_disk_ids = [disk['id'] for disk in self.comp_info['disks']]
@@ -1479,6 +1653,75 @@ class decort_kvmvm(DecortController):
f'to Compute ID {self.comp_id}.'
)
+ if self.check_aparam_boot_disk_redeploy() is False:
+ check_errors = True
+
+ new_boot_disk_size = aparam_boot['disk_size']
+ if new_boot_disk_size is not None:
+ boot_disk_size = 0
+ for disk in self.comp_info['disks']:
+ if disk['type'] == 'B':
+ boot_disk_size = disk['sizeMax']
+ break
+ else:
+ if aparam_boot is None or aparam_boot['disk_id'] is None:
+ check_errors = True
+ self.message(
+ f'Can\'t set boot disk size for Compute '
+ f'{comp_id}, because it doesn\'t '
+ f'have a boot disk.'
+ )
+
+ if new_boot_disk_size < boot_disk_size:
+ check_errors = True
+ self.message(
+ f'New boot disk size {new_boot_disk_size} is less'
+ f' than current {boot_disk_size} for Compute ID '
+ f'{comp_id}'
+ )
+
+ cd_rom_image_id = aparam_boot['from_cdrom']
+ if cd_rom_image_id is not None:
+ if not (
+ self.comp_info['techStatus'] == 'STOPPED'
+ and self.aparams['state'] == 'started'
+ ):
+ check_errors = True
+ self.message(
+ f'Check for parameter "boot.from_cdrom" failed: '
+ f'VM ID {self.comp_id} must be stopped and "state" '
+ 'must be "started" to boot from CD-ROM.'
+ )
+ _, image_info = self._image_get_by_id(
+ image_id=cd_rom_image_id,
+ )
+ if image_info is None:
+ check_errors = True
+ self.message(
+ 'Check for parameter "boot.from_cdrom" failed: '
+ f'Image ID {cd_rom_image_id} not found.'
+ )
+ elif image_info['type'] != 'cdrom':
+ check_errors = True
+ self.message(
+ 'Check for parameter "boot.from_cdrom" failed: '
+ f'Image ID {cd_rom_image_id} is not a cd-rom type.'
+ )
+
+ boot_order_list = aparam_boot['order']
+ if boot_order_list is not None:
+ boot_order_duplicates = set([
+ boot_dev for boot_dev in boot_order_list
+ if boot_order_list.count(boot_dev) > 1
+ ])
+ if boot_order_duplicates:
+ check_errors = True
+ self.message(
+ 'Check for parameter "boot.order" failed: '
+ 'List of boot devices has duplicates: '
+ f'{boot_order_duplicates}.'
+ )
+
if (
not comp_info['imageId']
and self.amodule.params['state'] in (
@@ -1574,24 +1817,24 @@ class decort_kvmvm(DecortController):
'VM must be started to get console url.'
)
- aparam_disks = self.aparams['disks']
- if aparam_disks is not None:
- aparam_disks_ids = aparam_disks['ids']
+ aparam_disks_dict = self.aparams['disks']
+ if aparam_disks_dict is not None:
+ aparam_disks = aparam_disks_dict.get('objects', [])
+ 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':
comp_boot_disk_id = comp_disk['id']
break
disks_to_detach = []
- match aparam_disks['mode']:
+ match aparam_disks_dict['mode']:
case 'detach' | 'delete':
disks_to_detach = aparam_disks_ids
case 'match':
comp_disk_ids = {
disk['id'] for disk in self.comp_info['disks']
}
- disks = set(aparam_disks_ids)
- disks_to_detach = comp_disk_ids - disks
+ disks_to_detach = comp_disk_ids - set(aparam_disks_ids)
if (
comp_boot_disk_id is not None
and comp_boot_disk_id in disks_to_detach
@@ -1611,6 +1854,18 @@ class decort_kvmvm(DecortController):
f'Compute ID {self.comp_id} while snapshots exist.'
)
+ if aparam_disks_dict['mode'] in ('delete', 'detach'):
+ for disk in aparam_disks:
+ for param, value in disk.items():
+ if param != 'id' and value is not None:
+ check_errors = True
+ self.message(
+ msg='Check for parameter "disks.objects" '
+ 'failed: only disk id can be specified if '
+ 'disks.mode is "delete" or "detach"'
+ )
+ break
+
if (
(
self.aparams['cpu'] is not None
@@ -1637,11 +1892,72 @@ class decort_kvmvm(DecortController):
aparam_networks = self.aparams['networks']
if aparam_networks is not None:
+ vm_networks = self.comp_info['interfaces']
+ if (
+ not vm_networks
+ and not self.is_vm_stopped_or_will_be_stopped
+ ):
+ check_errors = True
+ self.message(
+ 'Check for parameter "networks" failed: '
+ 'VM must be stopped before attach it\'s first network.'
+ )
+ vm_networks_ids = [
+ network['netId'] for network in vm_networks
+ if network['type'] != self.VMNetType.EMPTY.value
+ ]
+ aparam_networks_ids = [
+ network['id'] for network in aparam_networks
+ if network['type'] != self.VMNetType.EMPTY.value
+ ]
+ new_networks = list(
+ set(aparam_networks_ids) - set(vm_networks_ids)
+ )
net_types = {net['type'] for net in aparam_networks}
+ if new_networks:
+ if not (
+ len(new_networks) == 1
+ and self.VMNetType.DPDK.value in net_types
+ ) and not self.is_vm_stopped_or_will_be_stopped:
+ check_errors = True
+ self.message(
+ 'Check for parameter "networks" failed: '
+ 'VM must be stopped to attach non-DPDK network.'
+ )
+
if self.VMNetType.TRUNK.value in net_types:
if self.check_aparam_networks_trunk() is False:
check_errors = True
+ for network in aparam_networks:
+ if (
+ network['enabled'] is not None
+ and network['type'] not in [
+ self.VMNetType.VINS.value,
+ self.VMNetType.EXTNET.value,
+ self.VMNetType.DPDK.value,
+ self.VMNetType.SDN.value,
+ self.VMNetType.TRUNK.value,
+ ]
+ ):
+ check_errors = True
+ self.message(
+ 'Check for parameter "networks.enabled" failed: '
+ 'Can not enable or disable network '
+ f'ID {network['id']} and type {network['type']}.'
+ 'Only networks of type VINS, EXTNET, DPDK, SDN, TRUNK '
+ 'can be enabled or disabled.'
+ )
+
+ if self.check_aparam_cdrom() is False:
+ check_errors = True
+
+ if self.check_aparam_storage_policy_id() is False:
+ check_errors = True
+
+ if self.check_aparam_image_id() is False:
+ check_errors = True
+
if check_errors:
self.exit(fail=True)
@@ -1731,6 +2047,9 @@ class decort_kvmvm(DecortController):
snapshot_timestamp=snapshot_timestamp,
snapshot_name=snapshot_name,
snapshot_datetime=snapshot_datetime,
+ sep_pool_name=self.aparams['clone_from']['sep_pool_name'],
+ sep_id=self.aparams['clone_from']['sep_id'],
+ storage_policy_id=self.aparams['clone_from']['storage_policy_id'],
)
return clone_id
@@ -1812,6 +2131,130 @@ class decort_kvmvm(DecortController):
return not check_errors
+ def check_aparam_cdrom(self) -> bool | None:
+ check_errors = False
+ aparam_cdrom = self.aparams['cdrom']
+ if aparam_cdrom is not None:
+ mode = aparam_cdrom['mode']
+ if self.is_vm_stopped_or_will_be_stopped:
+ check_errors = True
+ self.message(
+ 'Check for parameter "cdrom" failed: '
+ f'VM ID {self.comp_id} must be started to {mode} '
+ f'CD-ROM.'
+ )
+ image_id = aparam_cdrom['image_id']
+ match mode:
+ case 'insert':
+ if image_id is None:
+ check_errors = True
+ self.message(
+ 'Check for parameter "cdrom.image_id" failed: '
+ f'cdrom.image_id must be specified '
+ f'if cdrom.mode is "insert".'
+ )
+ _, image_info = self._image_get_by_id(
+ image_id=image_id,
+ )
+ if image_info is None:
+ check_errors = True
+ self.message(
+ 'Check for parameter "cdrom.image_id" failed: '
+ f'Image ID {image_id} not found.'
+ )
+ elif image_info['type'] != 'cdrom':
+ check_errors = True
+ self.message(
+ 'Check for parameter "cdrom.image_id" failed: '
+ f'Image ID {image_id} is not a CD-ROM type.'
+ )
+ case 'eject':
+ if image_id is not None:
+ check_errors = True
+ self.message(
+ 'Check for parameter "cdrom.image_id" failed: '
+ f'cdrom.image_id must not be specified '
+ f'if cdrom.mode is "eject".'
+ )
+ if not self.comp_info['cdImageId']:
+ check_errors = True
+ self.message(
+ 'Check for parameter "cdrom.mode" failed: '
+ f'VM ID {self.comp_id} does not have CD-ROM '
+ 'to eject.'
+ )
+
+ return not check_errors
+
+ def check_aparam_storage_policy_id(self) -> bool:
+ check_errors = False
+
+ aparam_storage_policy_id = self.aparams['storage_policy_id']
+ if aparam_storage_policy_id is not None:
+ for disk in self.comp_info['disks']:
+ if aparam_storage_policy_id != disk['storage_policy_id']:
+ check_errors = True
+ self.message(
+ msg='Check for parameter "storage_policy_id" failed: '
+ 'storage_policy_id can not be changed for compute '
+ f'ID {self.comp_id} disk ID {disk['id']}'
+ )
+
+ return not check_errors
+
+ def check_aparam_boot_disk_redeploy(self) -> bool:
+ check_errors = False
+
+ disk_redeploy = self.aparams['boot']['disk_redeploy']
+ if disk_redeploy:
+ if self.aparams['storage_policy_id'] is None:
+ check_errors = True
+ self.message(
+ 'Check for parameter "storage_policy_id" failed:'
+ '"storage_policy_id" must be specified to redeploy.'
+ )
+
+ vm_has_boot_disk = False
+ for disk in self.comp_info['disks']:
+ if disk['type'] == 'B':
+ vm_has_boot_disk = True
+ break
+ if not vm_has_boot_disk:
+ check_errors = True
+ self.message(
+ 'Check for parameter "boot.redeploy" failed: '
+ 'VM does not have boot disk to redeploy.'
+ )
+
+ aparam_disks = self.amodule.params['disks']
+ if aparam_disks is not None and aparam_disks['mode'] == 'match':
+ check_errors = True
+ self.message(
+ 'Check for parameter "disks.mode" failed: '
+ '"disks.mode" must not be "match" to redeploy.'
+ )
+
+ return not check_errors
+
+ def check_aparam_image_id(self) -> bool:
+ check_errors = False
+
+ aparam_image_id = self.aparams['image_id']
+ if aparam_image_id is not None:
+ if aparam_image_id != self.comp_info['imageId']:
+ if (
+ self.aparams['boot'] is None
+ or self.aparams['boot']['disk_redeploy'] is None
+ ):
+ check_errors = True
+ self.message(
+ 'Check for parameter "image_id" failed: '
+ '"boot.disk_redeploy" must be set to True to change '
+ 'VM image.'
+ )
+
+ return not check_errors
+
def find_networks_tags_intersections(
self,
trunk_networks: list,
@@ -1958,7 +2401,7 @@ def main():
amodule = subj.amodule
if subj.comp_id:
- if subj.comp_info['status'] in ("DISABLED", "MIGRATING", "DELETING", "DESTROYING", "ERROR", "REDEPLOYING"):
+ if subj.comp_info['status'] in ("MIGRATING", "DELETING", "DESTROYING", "ERROR", "REDEPLOYING"):
# cannot do anything on the existing Compute in the listed states
subj.error() # was subj.nop()
elif subj.comp_info['status'] in ("ENABLED", "DISABLED"):
diff --git a/library/decort_lb.py b/library/decort_lb.py
index c4a4e87..0603649 100644
--- a/library/decort_lb.py
+++ b/library/decort_lb.py
@@ -42,8 +42,8 @@ class decort_lb(DecortController):
if not self.lb_id:
self.result['failed'] = True
self.result['msg'] = "Specified LB ID {} not found."\
- .format(arg_amodule.params['lb _id'])
- self.fail_json(**self.result)
+ .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']
@@ -51,12 +51,11 @@ class decort_lb(DecortController):
self.rg_id, self.rg_facts = self.rg_find(0,arg_amodule.params['rg_id'], arg_rg_name="")
if not self.rg_id:
self.result['failed'] = True
- self.result['msg'] = "Specified RG ID {} not found.".format(arg_amodule.params['vins_id'])
+ self.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']
elif arg_amodule.params['account_id'] or arg_amodule.params['account_name'] != "":
-
if not arg_amodule.params['rg_name']:
self.result['failed'] = True
self.result['msg'] = ("RG name must be specified with account present")
@@ -163,8 +162,9 @@ class decort_lb(DecortController):
def delete(self):
self.lb_delete(self.lb_id, self.amodule.params['permanently'])
- self.lb_facts['status'] = 'DESTROYED'
+ self.lb_id, self.lb_facts = self._lb_get_by_id(self.lb_id)
return
+
def nop(self):
"""No operation (NOP) handler for LB management by decort_lb module.
This function is intended to be called from the main switch construct of the module
@@ -252,10 +252,6 @@ class decort_lb(DecortController):
type='int',
default=0,
),
- ext_ip_addr=dict(
- type='str',
- default='',
- ),
state=dict(
type='str',
choices=[
@@ -411,10 +407,10 @@ def main():
if decon.result['failed']:
amodule.fail_json(**decon.result)
else:
- if decon.result['changed'] and amodule.params['state'] != 'absent':
- _, decon.lb_facts = decon.lb_find(decon.lb_id)
- if decon.lb_id:
- decon.result['facts'] = decon.package_facts(amodule.check_mode)
+ if decon.result['changed']:
+ _, decon.lb_facts = decon.lb_find(lb_id=decon.lb_id)
+ decon.result['facts'] = decon.package_facts(amodule.check_mode)
amodule.exit_json(**decon.result)
+
if __name__ == "__main__":
main()
diff --git a/library/decort_osimage.py b/library/decort_osimage.py
index be7c04e..5e7f286 100644
--- a/library/decort_osimage.py
+++ b/library/decort_osimage.py
@@ -22,7 +22,8 @@ class decort_osimage(DecortController):
self.validated_virt_image_id = 0
self.validated_image_name = amodule.params['image_name']
self.validated_virt_image_name = None
- self.validated_virt_image_id = amodule.params['virt_id']
+ self.image_info: dict
+ self.virt_image_info: dict
if amodule.params['account_name']:
self.validated_account_id, _ = self.account_find(amodule.params['account_name'])
else:
@@ -34,23 +35,51 @@ class decort_osimage(DecortController):
self.result['changed'] = False
self.result['msg'] = ("Cannot find account '{}'").format(amodule.params['account_name'])
amodule.fail_json(**self.result)
+ self.acc_id = self.validated_account_id
-
- if amodule.params['virt_id'] != 0 and amodule.params['virt_name']:
- self.validated_virt_image_id, image_facts =\
+ if (
+ self.aparams['virt_id'] != 0
+ or self.aparams['virt_name'] is not None
+ ):
+ self.validated_virt_image_id, self.virt_image_info = (
self.decort_virt_image_find(amodule)
- if (self.validated_virt_image_id and
- amodule.params['virt_name'] != image_facts['name']):
- self.decort_virt_image_rename(amodule)
- self.result['msg'] = 'Virtual image renamed successfully'
- elif amodule.params['image_id'] != 0 and amodule.params['image_name']:
- self.validated_image_id, image_facts = self.decort_image_find(amodule)
- if (self.validated_image_id and
- amodule.params['image_name'] != image_facts['name']):
- decort_osimage.decort_image_rename(self,amodule)
- self.result['msg'] = ("Image renamed successfully")
-
-
+ )
+ if self.virt_image_info:
+ _, linked_image_info = self._image_get_by_id(
+ image_id=self.virt_image_info['linkTo']
+ )
+ self.acc_id = linked_image_info['accountId']
+ if (
+ self.aparams['virt_name'] is not None
+ and self.aparams['virt_name']
+ != self.virt_image_info['name']
+ ):
+ self.decort_virt_image_rename(amodule)
+ self.result['msg'] = 'Virtual image renamed successfully'
+ elif (
+ self.aparams['image_id'] != 0
+ or self.aparams['image_name'] is not None
+ ):
+ self.validated_image_id, self.image_info = (
+ self.decort_image_find(amodule)
+ )
+ if self.image_info:
+ self.acc_id = self.image_info['accountId']
+ if (
+ amodule.params['image_name']
+ and amodule.params['image_name'] != self.image_info['name']
+ ):
+ decort_osimage.decort_image_rename(self,amodule)
+ self.result['msg'] = ("Image renamed successfully")
+
+ if self.validated_image_id:
+ self.check_amodule_args_for_change()
+ elif self.validated_virt_image_id:
+ self.check_amodule_args_for_change_virt_image()
+ elif self.aparams['virt_name']:
+ self.check_amodule_args_for_create_virt_image()
+ else:
+ self.check_amodule_args_for_create_image()
def decort_image_find(self, amodule):
# function that finds the OS image
@@ -121,21 +150,22 @@ class decort_osimage(DecortController):
)
# function that creates OS image
- image_facts = self.image_create(img_name=self.validated_image_name,
- url=amodule.params['url'],
- gid=amodule.params['gid'],
- boot_mode=boot_mode,
- boot_loader_type=loader_type,
- hot_resize=hot_resize,
- username=amodule.params['image_username'],
- password=amodule.params['image_password'],
- account_id=self.validated_account_id,
- usernameDL=amodule.params['usernameDL'],
- passwordDL=amodule.params['passwordDL'],
- sepId=amodule.params['sepId'],
- poolName=amodule.params['poolName'],
- drivers=amodule.params['drivers'],
- network_interface_naming=network_interface_naming)
+ image_facts = self.image_create(
+ img_name=self.validated_image_name,
+ url=amodule.params['url'],
+ boot_mode=boot_mode,
+ boot_loader_type=loader_type,
+ hot_resize=hot_resize,
+ username=amodule.params['image_username'],
+ password=amodule.params['image_password'],
+ account_id=self.validated_account_id,
+ usernameDL=amodule.params['usernameDL'],
+ passwordDL=amodule.params['passwordDL'],
+ sepId=amodule.params['sepId'],
+ poolName=amodule.params['poolName'],
+ network_interface_naming=network_interface_naming,
+ storage_policy_id=amodule.params['storage_policy_id'],
+ )
self.result['changed'] = True
return image_facts
@@ -151,8 +181,9 @@ class decort_osimage(DecortController):
def decort_image_delete(self,amodule):
# function that removes an image
self.image_delete(imageId=amodule.image_id_delete)
- self.result['changed'] = True
- self.result['msg'] = ("Image '{}' deleted").format(amodule.image_id_delete)
+ _, image_facts = decort_osimage._image_get_by_id(self, amodule.image_id_delete)
+ self.result['facts'] = decort_osimage.decort_osimage_package_facts(image_facts, amodule.check_mode)
+ return
def decort_virt_image_create(self,amodule):
# function that creates a virtual image
@@ -231,6 +262,8 @@ class decort_osimage(DecortController):
'networkInterfaceNaming'
]
ret_dict['hot_resize'] = arg_osimage_facts['hotResize']
+ ret_dict['storage_policy_id'] = arg_osimage_facts['storage_policy_id']
+ ret_dict['to_clean'] = arg_osimage_facts['to_clean']
return ret_dict
@property
@@ -273,17 +306,9 @@ class decort_osimage(DecortController):
'present',
],
),
- drivers=dict(
- type='str',
- default='KVM_X86',
- ),
url=dict(
type='str',
),
- gid=dict(
- type='int',
- default=0,
- ),
sepId=dict(
type='int',
default=0,
@@ -333,10 +358,102 @@ class decort_osimage(DecortController):
'eth',
],
),
+ storage_policy_id=dict(
+ type='int',
+ ),
),
supports_check_mode=True,
)
+ def check_amodule_args_for_change(self):
+ check_errors = False
+
+ 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']
+ ):
+ check_errors = True
+ self.message(
+ msg='Check for parameter "storage_policy_id" failed: '
+ f'Account ID {self.acc_id} does not have access to '
+ f'storage_policy_id {aparam_storage_policy_id}'
+ )
+
+ if check_errors:
+ self.exit(fail=True)
+
+ def check_amodule_args_for_change_virt_image(self):
+ check_errors = False
+
+ aparam_storage_policy_id = self.aparams['storage_policy_id']
+ if (
+ aparam_storage_policy_id is not None
+ and (
+ aparam_storage_policy_id
+ != self.virt_image_info['storage_policy_id']
+ )
+ ):
+ check_errors = True
+ self.message(
+ msg='Check for parameter "storage_policy_id" failed: '
+ 'storage_policy_id can not be changed in virtual image'
+ )
+
+ if check_errors:
+ self.exit(fail=True)
+
+ def check_amodule_args_for_create_image(self):
+ check_errors = False
+
+ aparam_account_id = self.aparams['account_id']
+ if aparam_account_id is None:
+ check_errors = True
+ self.message(
+ msg='Check for parameter "account_id" failed: '
+ 'account_id must be specified when creating '
+ 'a new image'
+ )
+
+ aparam_storage_policy_id = self.aparams['storage_policy_id']
+ if aparam_storage_policy_id is None:
+ check_errors = True
+ self.message(
+ msg='Check for parameter "storage_policy_id" failed: '
+ 'storage_policy_id must be specified when creating '
+ 'a new image'
+ )
+ elif (
+ aparam_storage_policy_id
+ not in self.acc_info['storage_policy_ids']
+ ):
+ check_errors = True
+ self.message(
+ msg='Check for parameter "storage_policy_id" failed: '
+ f'Account ID {self.acc_id} does not have access to '
+ f'storage_policy_id {aparam_storage_policy_id}'
+ )
+
+ if check_errors:
+ self.exit(fail=True)
+
+ def check_amodule_args_for_create_virt_image(self):
+ check_errors = False
+
+ aparam_storage_policy_id = self.aparams['storage_policy_id']
+ if aparam_storage_policy_id is not None:
+ check_errors = True
+ self.message(
+ msg='Check for parameter "storage_policy_id" failed: '
+ 'storage_policy_id can not be specified when creating '
+ 'virtual image'
+ )
+
+ if check_errors:
+ self.exit(fail=True)
+
+
def main():
decon = decort_osimage()
amodule = decon.amodule
@@ -360,15 +477,31 @@ def main():
decon.result['msg'] = ("Cannot find OS image")
amodule.fail_json(**decon.result)
- if decon.validated_virt_image_id and decon.target_image_id:
- if decort_osimage.decort_osimage_package_facts(image_facts)['linkto'] != decon.target_image_id:
+ if decon.validated_virt_image_id:
+ if (
+ decon.target_image_id
+ and decort_osimage.decort_osimage_package_facts(image_facts)[
+ 'linkto'
+ ] != decon.target_image_id
+ ):
decort_osimage.decort_virt_image_link(decon,amodule)
decon.result['changed'] = True
amodule.exit_json(**decon.result)
+ if (
+ amodule.params['storage_policy_id'] is not None
+ and amodule.params['storage_policy_id']
+ != image_facts['storage_policy_id']
+ ):
+ decon.image_change_storage_policy(
+ image_id=decon.validated_virt_image_id,
+ storage_policy_id=amodule.params['storage_policy_id'],
+ )
- if decon.validated_virt_image_id > 0 and amodule.params['state'] == "absent":
+ if amodule.params['state'] == "absent" and decon.validated_virt_image_id:
amodule.image_id_delete = decon.validated_virt_image_id
- decort_osimage.decort_image_delete(decon, amodule)
+ image_id, image_facts = decort_osimage.decort_virt_image_find(decon, amodule)
+ if image_facts['status'] != 'PURGED':
+ decort_osimage.decort_image_delete(decon,amodule)
elif amodule.params['image_name'] or amodule.params['image_id']:
image_id, image_facts = decort_osimage.decort_image_find(decon, amodule)
@@ -385,16 +518,38 @@ def main():
decon.validated_image_id = decort_osimage.decort_osimage_package_facts(image_facts)['id']
elif amodule.params['state'] == "absent" and decon.validated_image_id:
- amodule.image_id_delete = decon.validated_image_id
+ amodule.image_id_delete = decon.validated_image_id
+ image_id, image_facts = decort_osimage.decort_image_find(decon, amodule)
+ if image_facts['status'] != 'DESTROYED':
decort_osimage.decort_image_delete(decon,amodule)
+ if decon.validated_image_id:
+ if (
+ amodule.params['storage_policy_id'] is not None
+ and amodule.params['storage_policy_id']
+ != image_facts['storage_policy_id']
+ ):
+ decon.image_change_storage_policy(
+ image_id=decon.validated_image_id,
+ storage_policy_id=amodule.params['storage_policy_id'],
+ )
+
if decon.result['failed'] == True:
# we failed to find the specified image - fail the module
decon.result['changed'] = False
amodule.fail_json(**decon.result)
+ else:
+ if decon.validated_image_id:
+ _, image_facts = decon.decort_image_find(amodule=amodule)
+ elif decon.validated_virt_image_id:
+ _, image_facts = decon.decort_virt_image_find(amodule=amodule)
+ decon.result['facts'] = decort_osimage.decort_osimage_package_facts(
+ arg_osimage_facts=image_facts,
+ arg_check_mode=amodule.check_mode,
+ )
amodule.exit_json(**decon.result)
-
+
if __name__ == "__main__":
main()
diff --git a/library/decort_rg.py b/library/decort_rg.py
index baf8a80..2341d70 100644
--- a/library/decort_rg.py
+++ b/library/decort_rg.py
@@ -97,26 +97,57 @@ class decort_rg(DecortController):
self.amodule.params['rg_name'],
self.validated_acc_id)
return
-
+
def update(self):
resources = self.rg_facts['Resources']['Reserved']
incorrect_quota = dict(Requested=dict(),
Reserved=dict(),)
- query_key_map = dict(cpu='cpu',
- ram='ram',
- disk='disksize',
- ext_ips='extips',
- net_transfer='exttraffic',)
+ query_key_map = dict(
+ cpu='cpu',
+ ram='ram',
+ disk='disksize',
+ ext_ips='extips',
+ net_transfer='exttraffic',
+ storage_policies='policies',
+ )
if self.amodule.params['quotas']:
for quota_item in self.amodule.params['quotas']:
- if self.amodule.params['quotas'][quota_item] < 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]]
+ if quota_item == 'storage_policies':
+ rg_storage_policies = resources[query_key_map[quota_item]]
+ aparam_storage_policies = self.amodule.params['quotas'][
+ quota_item
+ ]
+ for aparam_storage_policy in aparam_storage_policies:
+ aparam_storage_policy_id = aparam_storage_policy['id']
+ rg_storage_policy = rg_storage_policies.get(
+ str(aparam_storage_policy_id)
+ )
+ if (
+ rg_storage_policy
+ and aparam_storage_policy['storage_size_gb']
+ < rg_storage_policy['disksize']
+ ):
+ incorrect_quota['Requested'][quota_item] = (
+ self.amodule.params['quotas'][quota_item]
+ )
+ incorrect_quota['Reserved'][quota_item] = (
+ resources[query_key_map[quota_item]]
+ )
+ elif (
+ self.amodule.params['quotas'][quota_item]
+ < 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]]
+ )
if 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,
@@ -243,6 +274,7 @@ class decort_rg(DecortController):
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
diff --git a/library/decort_security_group.py b/library/decort_security_group.py
new file mode 100644
index 0000000..d785824
--- /dev/null
+++ b/library/decort_security_group.py
@@ -0,0 +1,351 @@
+#!/usr/bin/python
+
+DOCUMENTATION = r'''
+---
+module: decort_security_group
+
+description: See L(Module Documentation,https://repository.basistech.ru/BASIS/decort-ansible/wiki/Home). # noqa: E501
+'''
+
+from ansible.module_utils.basic import AnsibleModule
+from ansible.module_utils.decort_utils import DecortController
+
+
+class DecortSecurityGroup(DecortController):
+ id: int = 0
+ facts: dict = dict()
+
+ 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(
+ account_id=dict(
+ type='int',
+ ),
+ description=dict(
+ type='str',
+ ),
+ id=dict(
+ type='int',
+ ),
+ name=dict(
+ type='str',
+ ),
+ rules=dict(
+ type='dict',
+ options=dict(
+ mode=dict(
+ type='str',
+ choices=[
+ e.value
+ for e in self.SecurityGroupRuleMode
+ ],
+ default=self.SecurityGroupRuleMode.update.value,
+ ),
+ objects=dict(
+ type='list',
+ elements='dict',
+ required=True,
+ options=dict(
+ direction=dict(
+ type='str',
+ choices=[
+ e.name for e in
+ self.SecurityGroupRuleDirection
+ ],
+ required=True,
+ ),
+ ethertype=dict(
+ type='str',
+ choices=[
+ e.name for e in
+ self.SecurityGroupRuleEtherType
+ ],
+ ),
+ id=dict(
+ type='int',
+ ),
+ port_range=dict(
+ type='dict',
+ options=dict(
+ min=dict(
+ type='int',
+ ),
+ max=dict(
+ type='int',
+ ),
+ ),
+ ),
+ protocol=dict(
+ type='str',
+ choices=[
+ e.name for e in
+ self.SecurityGroupRuleProtocol
+ ],
+ ),
+ remote_ip_prefix=dict(
+ type='str',
+ ),
+ ),
+ ),
+ ),
+ ),
+ state=dict(
+ type='str',
+ choices=[e.value for e in self.SecurityGroupState],
+ ),
+ ),
+ supports_check_mode=True,
+ )
+
+ def run(self):
+ if self.aparams['id'] is not None:
+ self.id = self.aparams['id']
+ elif self.aparams['name'] is not None:
+ security_group = self.security_group_find(
+ account_id=self.aparams['account_id'],
+ name=self.aparams['name'],
+ )
+ if security_group:
+ self.id = security_group['id']
+
+ if self.id:
+ self.get_info()
+ self.check_amodule_args_for_change()
+ self.change()
+ else:
+ self.check_amodule_args_for_create()
+ self.create()
+ if not self.amodule.check_mode:
+ self.change_rules()
+
+ if self.result['changed']:
+ self.get_info()
+ self.exit()
+
+ def get_info(self):
+ self.facts: dict = self.security_group_get(id=self.id)
+ self.facts['created_timestamp'] = self.facts.pop('created_at')
+ self.facts['updated_timestamp'] = self.facts.pop('updated_at')
+ for rule in self.facts['rules']:
+ rule['port_range'] = {
+ 'min': rule.pop('port_range_min'),
+ 'max': rule.pop('port_range_max'),
+ }
+
+ def check_amodule_args_for_create(self):
+ check_errors = False
+
+ aparam_account_id = self.aparams['account_id']
+ if aparam_account_id is None:
+ check_errors = True
+ self.message(
+ msg='Check for parameter "account_id" failed: '
+ 'account_id must be specified when creating '
+ 'a new security group'
+ )
+
+ aparam_name = self.aparams['name']
+ if aparam_name is None:
+ check_errors = True
+ self.message(
+ msg='Check for parameter "name" failed: '
+ 'name must be specified when creating '
+ 'a new security group'
+ )
+
+ aparam_state = self.aparams['state']
+ if (
+ aparam_state is not None
+ and aparam_state == self.SecurityGroupState.absent.value
+ ):
+ check_errors = True
+ self.message(
+ msg='Check for parameter "state" failed: '
+ 'state can not be "absent" when creating '
+ 'a new security group'
+ )
+
+ if self.check_aparam_rules(for_create=True) is False:
+ check_errors = True
+
+ if check_errors:
+ self.exit(fail=True)
+
+ def check_amodule_args_for_change(self):
+ check_errors = False
+
+ if self.check_aparam_rules() is False:
+ check_errors = True
+
+ if check_errors:
+ self.exit(fail=True)
+
+ def check_aparam_rules(self, for_create: bool = False):
+ check_errors = False
+
+ aparam_rules = self.aparams['rules']
+ if aparam_rules is None:
+ return True
+
+ mode = aparam_rules['mode']
+ rules = aparam_rules['objects']
+
+ if for_create and mode == self.SecurityGroupRuleMode.delete.value:
+ check_errors = True
+ self.message(
+ msg='Check for parameter "rules.mode" failed: '
+ 'mode can not be "delete" when creating '
+ 'new security group'
+ )
+
+ sg_rules_ids = []
+ if self.facts.get('rules'):
+ sg_rules_ids = [rule['id'] for rule in self.facts['rules']]
+ for rule in rules:
+ rule_id = rule.get('id')
+ if rule_id is not None:
+ if for_create:
+ check_errors = True
+ self.message(
+ msg='Check for parameter "rules.objects.id" failed: '
+ 'can not set rule id when creating new '
+ 'security group'
+ )
+ elif rule_id not in sg_rules_ids:
+ check_errors = True
+ self.message(
+ msg='Check for parameter "rules.objects.id" failed: '
+ f'rule ID {rule_id} not found for '
+ f'security group ID {self.id}'
+ )
+ if mode == self.SecurityGroupRuleMode.delete.value:
+ for param, value in rule.items():
+ if param != 'id' and value is not None:
+ check_errors = True
+ self.message(
+ msg='Check for parameter "rules.objects" '
+ 'failed: only rule id can be specified if'
+ 'rules.mode is "delete"'
+ )
+ break
+ elif mode == self.SecurityGroupRuleMode.delete.value:
+ check_errors = True
+ self.message(
+ msg='Check for parameter "rules.objects" '
+ 'failed: rule id must be specified if mode is delete'
+ )
+
+ return not check_errors
+
+ def create(self):
+ security_groups_by_account_id = self.user_security_groups(
+ account_id=self.aparams['account_id']
+ )
+ sg_names = [sg['name'] for sg in security_groups_by_account_id]
+ if self.aparams['name'] not in sg_names:
+ self.id = self.security_group_create(
+ account_id=self.aparams['account_id'],
+ name=self.aparams['name'],
+ description=self.aparams['description'],
+ )
+
+ def change(self):
+ self.change_state()
+ self.change_params()
+ self.change_rules()
+
+ def change_state(self):
+ if self.aparams['state'] == self.SecurityGroupState.absent.value:
+ self.delete()
+
+ def change_params(self):
+ aparam_name = self.aparams['name']
+ aparam_description = self.aparams['description']
+ new_name, new_description = None, None
+ if (
+ aparam_name is not None
+ and aparam_name != self.facts['name']
+ ):
+ new_name = aparam_name
+ if (
+ aparam_description is not None
+ and aparam_description != self.facts['description']
+ ):
+ new_description = aparam_description
+ if new_name or new_description:
+ self.security_group_update(
+ security_group_id=self.id,
+ name=new_name,
+ description=new_description,
+ )
+
+ def change_rules(self):
+ aparam_rules = self.aparams['rules']
+ if aparam_rules is not None:
+ rules = aparam_rules['objects']
+ match aparam_rules['mode']:
+ case self.SecurityGroupRuleMode.delete.value:
+ for rule in rules:
+ self.security_group_detele_rule(
+ security_group_id=self.id,
+ rule_id=rule['id'],
+ )
+ case self.SecurityGroupRuleMode.match.value:
+ for rule in rules:
+ if rule.get('id') is None:
+ self.create_rule(rule=rule)
+
+ sg_rules_ids = set(
+ [rule['id'] for rule in self.facts['rules']]
+ )
+ aparam_rules_ids = set(
+ [rule['id'] for rule in rules if rule.get('id')]
+ )
+ rules_ids_to_delete = sg_rules_ids - aparam_rules_ids
+ for rule_id in rules_ids_to_delete:
+ self.security_group_detele_rule(
+ security_group_id=self.id,
+ rule_id=rule_id,
+ )
+ case self.SecurityGroupRuleMode.update.value:
+ for rule in rules:
+ if rule.get('id') is None:
+ self.create_rule(rule=rule)
+
+ def delete(self):
+ self.security_group_detele(security_group_id=self.id)
+ self.facts = {}
+ self.exit()
+
+ def create_rule(self, rule: dict):
+ port_range_min, port_range_max = None, None
+ if rule.get('port_range'):
+ port_range_min = rule['port_range'].get('min')
+ port_range_max = rule['port_range'].get('max')
+ self.security_group_create_rule(
+ security_group_id=self.id,
+ direction=self.SecurityGroupRuleDirection[rule['direction']],
+ ethertype=(
+ self.SecurityGroupRuleEtherType[rule['ethertype']]
+ if rule.get('ethertype') else None
+ ),
+ protocol=(
+ self.SecurityGroupRuleProtocol[rule['protocol']]
+ if rule.get('protocol') else None
+ ),
+ port_range_min=port_range_min,
+ port_range_max=port_range_max,
+ remote_ip_prefix=rule.get('remote_ip_prefix'),
+ )
+
+
+def main():
+ DecortSecurityGroup().run()
+
+
+if __name__ == '__main__':
+ main()
diff --git a/library/decort_storage_policy.py b/library/decort_storage_policy.py
new file mode 100644
index 0000000..5e08743
--- /dev/null
+++ b/library/decort_storage_policy.py
@@ -0,0 +1,50 @@
+#!/usr/bin/python
+
+DOCUMENTATION = r'''
+---
+module: decort_storage_policy
+
+description: See L(Module Documentation,https://repository.basistech.ru/BASIS/decort-ansible/wiki/Home). # noqa: E501
+'''
+
+from ansible.module_utils.basic import AnsibleModule
+from ansible.module_utils.decort_utils import DecortController
+
+
+class DecortStoragePolicy(DecortController):
+ def __init__(self):
+ super().__init__(AnsibleModule(**self.amodule_init_args))
+ self.id: int = self.aparams['id']
+
+ @property
+ def amodule_init_args(self) -> dict:
+ return self.pack_amodule_init_args(
+ argument_spec=dict(
+ id=dict(
+ type='int',
+ required=True,
+ ),
+ ),
+ supports_check_mode=True,
+ )
+
+ def run(self):
+ self.get_info()
+ self.exit()
+
+ def get_info(self):
+ self.facts = self.storage_policy_get(id=self.id)
+ self.facts['sep_pools'] = self.facts.pop('access_seps_pools')
+ self.facts['iops_limit'] = self.facts.pop('limit_iops')
+ self.facts['usage']['account_ids'] = self.facts['usage'].pop(
+ 'accounts'
+ )
+ self.facts['usage']['rg_ids'] = self.facts['usage'].pop('resgroups')
+
+
+def main():
+ DecortStoragePolicy().run()
+
+
+if __name__ == '__main__':
+ main()
diff --git a/library/decort_user_info.py b/library/decort_user_info.py
index ce502fd..d3cb666 100644
--- a/library/decort_user_info.py
+++ b/library/decort_user_info.py
@@ -48,6 +48,9 @@ class DecortUserInfo(DecortController):
e.value for e in self.AccountStatus
],
),
+ zone_id=dict(
+ type='int',
+ ),
),
),
pagination=dict(
@@ -303,6 +306,143 @@ class DecortUserInfo(DecortController):
),
),
),
+ storage_policies=dict(
+ type='dict',
+ options=dict(
+ filter=dict(
+ type='dict',
+ options=dict(
+ account_id=dict(
+ type='int',
+ ),
+ description=dict(
+ type='str',
+ ),
+ id=dict(
+ type='int',
+ ),
+ iops_limit=dict(
+ type='int',
+ ),
+ name=dict(
+ type='str',
+ ),
+ pool_name=dict(
+ type='str',
+ ),
+ rg_id=dict(
+ type='int',
+ ),
+ sep_id=dict(
+ type='int',
+ ),
+ status=dict(
+ type='str',
+ choices=[
+ e.value for e
+ in self.StoragePolicyStatus
+ ],
+ ),
+ ),
+ ),
+ pagination=dict(
+ type='dict',
+ apply_defaults=True,
+ options=dict(
+ number=dict(
+ type='int',
+ default=1,
+ ),
+ size=dict(
+ type='int',
+ default=50,
+ ),
+ ),
+ ),
+ sorting=dict(
+ type='dict',
+ options=dict(
+ asc=dict(
+ type='bool',
+ default=True,
+ ),
+ field=dict(
+ type='str',
+ choices=[
+ e.value for e
+ in self.StoragePoliciesSortableField
+ ],
+ required=True,
+ ),
+ ),
+ ),
+ ),
+ ),
+ security_groups=dict(
+ type='dict',
+ options=dict(
+ filter=dict(
+ type='dict',
+ options=dict(
+ account_id=dict(
+ type='int',
+ ),
+ created_timestamp_max=dict(
+ type='int',
+ ),
+ created_timestamp_min=dict(
+ type='int',
+ ),
+ description=dict(
+ type='str',
+ ),
+ id=dict(
+ type='int',
+ ),
+ name=dict(
+ type='str',
+ ),
+ updated_timestamp_max=dict(
+ type='int',
+ ),
+ updated_timestamp_min=dict(
+ type='int',
+ ),
+ ),
+ ),
+ pagination=dict(
+ type='dict',
+ apply_defaults=True,
+ options=dict(
+ number=dict(
+ type='int',
+ default=1,
+ ),
+ size=dict(
+ type='int',
+ default=50,
+ ),
+ ),
+ ),
+ sorting=dict(
+ type='dict',
+ options=dict(
+ asc=dict(
+ type='bool',
+ default=True,
+ ),
+ field=dict(
+ type='str',
+ choices=[
+ e.name for e
+ in self.SecurityGroupSortableField
+ ],
+ required=True,
+ ),
+ ),
+ ),
+ ),
+ ),
),
supports_check_mode=True,
)
@@ -390,6 +530,8 @@ class DecortUserInfo(DecortController):
self.AccountStatus(input_args_filter_status)
)
+ mapped_args['zone_id'] = input_args_filter['zone_id']
+
input_args_pagination = input_args['pagination']
if input_args_pagination:
mapped_args['page_number'] = input_args_pagination['number']
@@ -552,6 +694,82 @@ class DecortUserInfo(DecortController):
return mapped_args
+ @property
+ def mapped_storage_policies_args(self):
+ """
+ Map the module argument `storage_policies` to
+ arguments dictionary for the method
+ `DecortController.user_storage_policies`.
+ """
+
+ input_args = self.aparams['storage_policies']
+ if not input_args:
+ return input_args
+
+ mapped_args = {}
+
+ input_args_filter = input_args['filter']
+ if input_args_filter:
+ mapped_args.update(input_args_filter)
+
+ input_args_filter_status = input_args_filter['status']
+ if input_args_filter_status:
+ mapped_args['status'] = (
+ self.StoragePolicyStatus(input_args_filter_status)
+ )
+
+ input_args_pagination = input_args['pagination']
+ if input_args_pagination:
+ mapped_args['page_number'] = input_args_pagination['number']
+ mapped_args['page_size'] = input_args_pagination['size']
+
+ input_args_sorting = input_args['sorting']
+ if input_args_sorting:
+ mapped_args['sort_by_asc'] = input_args_sorting['asc']
+
+ input_args_sorting_field = input_args_sorting['field']
+ if input_args_sorting_field:
+ mapped_args['sort_by_field'] = (
+ self.StoragePoliciesSortableField(input_args_sorting_field)
+ )
+
+ return mapped_args
+
+ @property
+ def mapped_security_groups_args(self):
+ """
+ Map the module argument `security_groups` to
+ arguments dictionary for the method
+ `DecortController.user_security_groups`.
+ """
+
+ input_args = self.aparams['security_groups']
+ if not input_args:
+ return input_args
+
+ mapped_args = {}
+
+ input_args_filter = input_args['filter']
+ if input_args_filter:
+ mapped_args.update(input_args_filter)
+
+ input_args_pagination = input_args['pagination']
+ if input_args_pagination:
+ mapped_args['page_number'] = input_args_pagination['number']
+ mapped_args['page_size'] = input_args_pagination['size']
+
+ input_args_sorting = input_args['sorting']
+ if input_args_sorting:
+ mapped_args['sort_by_asc'] = input_args_sorting['asc']
+
+ input_args_sorting_field = input_args_sorting['field']
+ if input_args_sorting_field:
+ mapped_args['sort_by_field'] = (
+ self.SecurityGroupSortableField[input_args_sorting_field]
+ )
+
+ return mapped_args
+
def run(self):
self.get_info()
self.exit()
@@ -606,6 +824,52 @@ class DecortUserInfo(DecortController):
trunk_facts['ovs_bridge'] = trunk_facts.pop('ovsBridge')
trunk_facts['vlan_ids'] = trunk_facts.pop('trunkTags')
+ if self.aparams['storage_policies']:
+ self.facts['storage_policies'] = self.user_storage_policies(
+ **self.mapped_storage_policies_args
+ )
+ for storage_policy_facts in self.facts['storage_policies']:
+ storage_policy_facts['sep_pools'] = storage_policy_facts.pop(
+ 'access_seps_pools'
+ )
+ storage_policy_facts['iops_limit'] = storage_policy_facts.pop(
+ 'limit_iops'
+ )
+ storage_policy_facts['usage']['account_ids'] = (
+ storage_policy_facts['usage'].pop('accounts')
+ )
+ storage_policy_facts['usage']['rg_ids'] = (
+ storage_policy_facts['usage'].pop('resgroups')
+ )
+
+ if self.aparams['security_groups']:
+ self.facts['security_groups'] = self.user_security_groups(
+ **self.mapped_security_groups_args
+ )
+ for security_groups_facts in self.facts['security_groups']:
+ for rule in security_groups_facts.get('rules', []):
+ rule['port_range'] = {
+ 'min': rule.pop('port_range_min'),
+ 'max': rule.pop('port_range_max'),
+ }
+
+ security_groups_facts['created_timestamp'] = (
+ security_groups_facts.pop('created_at')
+ )
+ security_groups_facts['created_timestamp_readable'] = (
+ self.sec_to_dt_str(security_groups_facts[
+ 'created_timestamp'
+ ])
+ )
+ security_groups_facts['updated_timestamp'] = (
+ security_groups_facts.pop('updated_at')
+ )
+ security_groups_facts['updated_timestamp_readable'] = (
+ self.sec_to_dt_str(security_groups_facts[
+ 'updated_timestamp'
+ ])
+ )
+
def main():
DecortUserInfo().run()
diff --git a/library/decort_vins.py b/library/decort_vins.py
index a06a4e9..1271617 100644
--- a/library/decort_vins.py
+++ b/library/decort_vins.py
@@ -170,7 +170,7 @@ class decort_vins(DecortController):
return
def delete(self):
- self.vins_delete(self.vins_id, permanently=True)
+ self.vins_delete(self.vins_id, self.amodule.params['permanently'])
self.vins_facts['status'] = 'DESTROYED'
return
def nop(self):
@@ -320,6 +320,10 @@ class decort_vins(DecortController):
type='str',
default='',
),
+ permanently=dict(
+ type='bool',
+ default=False,
+ ),
vins_id=dict(
type='int',
default=0,
@@ -416,8 +420,9 @@ def main():
vins_should_exist = True
elif amodule.params['state'] == 'absent':
# destroy permanently
- decon.delete()
- vins_should_exist = False
+ if decon.amodule.params['permanently']:
+ decon.delete()
+ vins_should_exist = False
elif amodule.params['state'] == 'disabled':
decon.error()
vins_should_exist = False
diff --git a/library/decort_snapshot.py b/library/decort_vm_snapshot.py
similarity index 95%
rename from library/decort_snapshot.py
rename to library/decort_vm_snapshot.py
index 52a292d..ea07497 100644
--- a/library/decort_snapshot.py
+++ b/library/decort_vm_snapshot.py
@@ -2,7 +2,7 @@
DOCUMENTATION = r'''
---
-module: decort_snapshot
+module: decort_vm_snapshot
description: See L(Module Documentation,https://repository.basistech.ru/BASIS/decort-ansible/wiki/Home). # noqa: E501
'''
@@ -12,7 +12,7 @@ from ansible.module_utils.basic import AnsibleModule
from ansible.module_utils.decort_utils import DecortController
-class DecortSnapshot(DecortController):
+class DecortVMSnapshot(DecortController):
def __init__(self):
super().__init__(AnsibleModule(**self.amodule_init_args))
self.check_amodule_args()
@@ -181,7 +181,7 @@ class DecortSnapshot(DecortController):
def main():
- DecortSnapshot().run()
+ DecortVMSnapshot().run()
if __name__ == '__main__':
diff --git a/module_utils/decort_utils.py b/module_utils/decort_utils.py
index 74118a1..276df8e 100644
--- a/module_utils/decort_utils.py
+++ b/module_utils/decort_utils.py
@@ -118,6 +118,8 @@ class DecortController(object):
'DOWN',
'MERGE',
'MIGRATING',
+ 'MIGRATING_IN',
+ 'MIGRATING_OUT',
'PAUSED',
'PAUSING',
'ROLLBACK',
@@ -258,6 +260,66 @@ class DecortController(object):
trunk = 'trunk'
+ class VMBootDevice(Enum):
+ hd = 'hd'
+ network = 'network'
+ cdrom = 'cdrom'
+
+
+ class StoragePolicyStatus(Enum):
+ DISABLED = 'DISABLED'
+ ENABLED = 'ENABLED'
+
+
+ class StoragePoliciesSortableField(Enum):
+ description = 'description'
+ guid = 'guid'
+ id = 'id'
+ limit_iops = 'iops_limit'
+ name = 'name'
+ access_seps_pools = 'sep_pools'
+ status = 'status'
+ usage = 'usage'
+
+
+ class SecurityGroupState(Enum):
+ absent = 'absent'
+ present = 'present'
+
+
+ class SecurityGroupRuleMode(Enum):
+ delete = 'delete'
+ match = 'match'
+ update = 'update'
+
+
+ class SecurityGroupRuleDirection(Enum):
+ INBOUND = 'inbound'
+ OUTBOUND = 'outbound'
+
+
+ class SecurityGroupRuleEtherType(Enum):
+ IPV4 = 'IPv4'
+ IPV6 = 'IPv6'
+
+
+ class SecurityGroupRuleProtocol(Enum):
+ ICMP = 'icmp'
+ TCP = 'tcp'
+ UDP = 'udp'
+
+ class SecurityGroupSortableField(Enum):
+ account_id = 'account_id'
+ description = 'description'
+ id = 'id'
+ name = 'name'
+ rules = 'rules'
+ created_timestamp = 'created_at'
+ created_by = 'created_by'
+ updated_timestamp = 'updated_at'
+ updated_by = 'updated_by'
+
+
class MESSAGES:
@staticmethod
def ssl_error(url: None | str = None):
@@ -1050,6 +1112,7 @@ class DecortController(object):
resource_consumption: bool = False,
sort_by_asc=True,
sort_by_field: None | AccountSortableField = None,
+ zone_id: None | int = None,
) -> list[dict]:
"""
Implementation of the functionality of API methods
@@ -1071,6 +1134,7 @@ class DecortController(object):
'size': page_size,
'sortBy': sort_by,
'status': account_status.value if account_status else None,
+ 'zone_id': zone_id,
}
api_resp = self.decort_api_call(
arg_req_function=requests.post,
@@ -1103,6 +1167,7 @@ class DecortController(object):
a['deletedTime_readable'] = self.sec_to_dt_str(a['deletedTime'])
a['updatedTime_readable'] = self.sec_to_dt_str(a['updatedTime'])
a['description'] = a.pop('desc')
+ a['zone_ids'] = a.pop('zoneIds')
return accounts
@@ -1166,7 +1231,7 @@ class DecortController(object):
audits = api_resp.json()['data']
for a in audits:
- a['Time_readable'] = self.sec_to_dt_str(a['Time'])
+ a['timestamp_readable'] = self.sec_to_dt_str(a['timestamp'])
return audits
@@ -1322,54 +1387,105 @@ class DecortController(object):
@waypoint
@checkmode
- def compute_disks(self, comp_dict: dict, aparam_disks: dict):
- disks = set(aparam_disks['ids'])
- mode = aparam_disks['mode']
-
- comp_disks = {disk['id'] for disk in comp_dict['disks']}
- disks_to_attach = set()
- disks_to_detach = set()
- disks_to_delete = set()
+ def compute_disks(self, comp_dict: dict, aparam_disks_dict: dict):
+ aparam_disks = aparam_disks_dict.get('objects', [])
+ aparam_disks_ids = set([disk['id'] for disk in aparam_disks])
+ mode = aparam_disks_dict['mode']
+
+ comp_disks_ids = {disk['id'] for disk in comp_dict['disks']}
+ disks_ids_to_attach = set()
+ disks_ids_to_detach = set()
+ disks_ids_to_delete = set()
match mode:
case 'update':
- disks_to_attach = disks - comp_disks
+ disks_ids_to_attach = aparam_disks_ids - comp_disks_ids
case 'detach':
- disks_to_detach = disks & comp_disks
+ disks_ids_to_detach = aparam_disks_ids & comp_disks_ids
case 'delete':
- disks_to_delete = disks & comp_disks
+ disks_ids_to_delete = aparam_disks_ids & comp_disks_ids
case 'match':
- disks_to_attach = disks - comp_disks
- disks_to_detach = comp_disks - disks
+ disks_ids_to_attach = aparam_disks_ids - comp_disks_ids
+ disks_ids_to_detach = comp_disks_ids - aparam_disks_ids
+
+ disks_to_attach = []
+ if disks_ids_to_attach:
+ disks_to_attach = [
+ d for d in aparam_disks if d['id'] in disks_ids_to_attach
+ ]
+ disks_to_detach = []
+ if disks_ids_to_detach:
+ disks_to_detach = [
+ d for d in comp_dict['disks'] if d['id'] in disks_ids_to_detach
+ ]
+ disks_to_delete = []
+ if disks_ids_to_delete:
+ disks_to_delete = [
+ d for d in comp_dict['disks'] if d['id'] in disks_ids_to_delete
+ ]
+
+ unchanged_disks_ids = (
+ aparam_disks_ids - disks_ids_to_attach -
+ disks_ids_to_detach - disks_ids_to_delete
+ )
+ unchanged_disks = [
+ d for d in aparam_disks if d['id'] in unchanged_disks_ids
+ ]
+ for aparam_disk in unchanged_disks:
+ for comp_disk in comp_dict['disks']:
+ if comp_disk['id'] == aparam_disk['id']:
+ if (
+ (
+ aparam_disk['pci_slot_num_hex'] is not None
+ and str(comp_disk['pci_slot'])
+ != aparam_disk['pci_slot_num_hex']
+ ) or (
+ aparam_disk['bus_num_hex'] is not None
+ and str(comp_disk['bus_number'])
+ != aparam_disk['bus_num_hex']
+ )
+ ):
+ disks_to_detach.append(aparam_disk)
+ disks_to_attach.append(aparam_disk)
- for disk_id in disks_to_attach:
+ for disk in disks_to_detach:
api_params = {
'computeId': comp_dict['id'],
- 'diskId': disk_id,
- 'diskType': 'D',
+ 'diskId': disk['id'],
}
self.decort_api_call(
arg_req_function=requests.post,
- arg_api_name='/restmachine/cloudapi/compute/diskAttach',
+ arg_api_name='/restmachine/cloudapi/compute/diskDetach',
arg_params=api_params,
)
self.set_changed()
- for disk_id in disks_to_detach:
+ for disk in disks_to_attach:
+ pci_slot_num = disk['pci_slot_num_hex']
+ if pci_slot_num is not None:
+ pci_slot_num = hex(int(pci_slot_num))
+
+ bus_num = disk['bus_num_hex']
+ if bus_num is not None:
+ bus_num = hex(int(bus_num))
+
api_params = {
'computeId': comp_dict['id'],
- 'diskId': disk_id,
+ 'diskId': disk['id'],
+ 'diskType': 'D',
+ 'pci_slot': pci_slot_num,
+ 'bus_number': bus_num,
}
self.decort_api_call(
arg_req_function=requests.post,
- arg_api_name='/restmachine/cloudapi/compute/diskDetach',
+ arg_api_name='/restmachine/cloudapi/compute/diskAttach',
arg_params=api_params,
)
self.set_changed()
- for disk_id in disks_to_delete:
+ for disk in disks_to_delete:
api_params = {
'computeId': comp_dict['id'],
- 'diskId': disk_id,
+ 'diskId': disk['id'],
}
self.decort_api_call(
arg_req_function=requests.post,
@@ -1417,7 +1533,7 @@ class DecortController(object):
self.result['failed'] = False
self.result['changed'] = True
- return
+ return comp_id
def _compute_get_by_id(
self,
@@ -1439,10 +1555,14 @@ class DecortController(object):
ret_rg_id = 0
api_params = dict(computeId=comp_id, )
- api_resp = self.decort_api_call(requests.post, "/restmachine/cloudapi/compute/get", api_params)
+ api_resp = self.decort_api_call(
+ requests.post,
+ '/restmachine/cloudapi/compute/get',
+ api_params,
+ not_fail_codes=[404],
+ )
if api_resp.status_code == 200:
ret_comp_id = comp_id
-
ret_comp_dict = json.loads(api_resp.content.decode('utf8'))
# Sorting network interface list by PCI slot number
ret_comp_dict['interfaces'].sort(key=lambda k: k['pciSlot'])
@@ -1471,7 +1591,9 @@ class DecortController(object):
)
)
ret_comp_dict['snapshot_merge_status'] = snapshot_merge_status
-
+ elif api_resp.status_code == 404:
+ self.message(f'Compute with ID {comp_id} not found.')
+ self.exit(fail=True)
else:
self.result['warning'] = ("compute_get_by_id(): failed to get Compute by ID {}. HTTP code {}, "
"response {}.").format(comp_id, api_resp.status_code, api_resp.reason)
@@ -1633,6 +1755,12 @@ class DecortController(object):
expected_techState = "STARTED"
elif comp_facts['techStatus'] == "STOPPED" and target_state in ('poweredon', 'restarted', 'started'):
powerstate_api = "/restmachine/cloudapi/compute/start"
+ if (
+ target_state == 'started'
+ and self.aparams['boot'] is not None
+ and self.aparams['boot']['from_cdrom'] is not None
+ ):
+ api_params['altBootId'] = self.aparams['boot']['from_cdrom']
expected_techState = "STARTED"
if powerstate_api != "":
@@ -1649,26 +1777,31 @@ class DecortController(object):
target_state)
return
- def kvmvm_provision(self, rg_id,
- comp_name,
- cpu, ram,
- boot_disk_size,
- image_id,
- chipset: Literal['Q35', 'i440fx'] = 'i440fx',
- description="",
- userdata=None,
- sep_id=None,
- pool_name=None,
- start_on_create=True,
- cpu_pin: bool = False,
- hp_backed: bool = False,
- numa_affinity: Literal['none', 'loose', 'strict'] = 'none',
- preferred_cpu_cores: list[int] | None = None,
- boot_mode: Literal['bios', 'uefi'] = 'bios',
- boot_loader_type: Literal['linux', 'windows', 'unknown'] = 'unknown',
- network_interface_naming: Literal['eth', 'ens'] = 'ens',
- hot_resize: bool = False,
- zone_id: None | int = None,
+ def kvmvm_provision(
+ self,
+ rg_id,
+ comp_name,
+ cpu,
+ ram,
+ boot_disk_size,
+ image_id,
+ chipset: Literal['Q35', 'i440fx'] = 'i440fx',
+ description="",
+ userdata=None,
+ sep_id=None,
+ pool_name=None,
+ start_on_create=True,
+ cpu_pin: bool = False,
+ hp_backed: bool = False,
+ numa_affinity: Literal['none', 'loose', 'strict'] = 'none',
+ preferred_cpu_cores: list[int] | None = None,
+ boot_mode: Literal['bios', 'uefi'] = 'bios',
+ boot_loader_type: Literal['linux', 'windows', 'unknown'] = 'unknown',
+ network_interface_naming: Literal['eth', 'ens'] = 'ens',
+ hot_resize: bool = False,
+ zone_id: None | int = None,
+ storage_policy_id: None | int = None,
+ os_version: None | str = None,
):
"""Manage KVM VM provisioning. To remove existing KVM VM compute instance use compute_remove method,
to resize use compute_resize, to manage power state use compute_powerstate method.
@@ -1708,6 +1841,8 @@ class DecortController(object):
'withoutBootDisk': not boot_disk_size,
'preferredCpu': preferred_cpu_cores,
'zoneId': zone_id,
+ 'storage_policy_id': storage_policy_id,
+ 'os_version': os_version,
}
if description:
api_params['desc'] = description
@@ -1757,6 +1892,8 @@ class DecortController(object):
nets_for_change_ip = []
nets_for_change_mac_dict = {}
nets_for_change_mtu_dict = {}
+ nets_for_change_sec_groups_dict = {}
+ nets_for_change_state_dict = {}
# Either only attaching or only detaching networks
if not ifaces or not new_networks:
@@ -1853,6 +1990,33 @@ class DecortController(object):
if new_mtu and current_mtu != new_mtu:
nets_for_change_mtu_dict[net_key] = net
+ # Adding networks for change security groups
+ current_secgroups_mode = ifaces_dict[net_key][
+ 'enable_secgroups'
+ ]
+ new_secgroups_mode = net['security_group_mode']
+ current_secgroup_ids = ifaces_dict[net_key][
+ 'security_groups'
+ ]
+ new_secgroup_ids = net['security_group_ids']
+ if (
+ (
+ new_secgroups_mode is not None
+ and current_secgroups_mode != new_secgroups_mode
+ ) or (
+ new_secgroup_ids
+ and set(current_secgroup_ids) != set(new_secgroup_ids)
+ )
+ ):
+ nets_for_change_sec_groups_dict[net_key] = net
+
+ # Adding networks for change state
+ if (
+ net['enabled'] is not None
+ and ifaces_dict[net_key]['enabled'] != net['enabled']
+ ):
+ nets_for_change_state_dict[net_key] = net
+
# Detaching networks
for iface in ifaces_for_delete:
self.decort_api_call(
@@ -1868,6 +2032,28 @@ class DecortController(object):
# Attaching networks
for net in nets_for_attach:
+ security_group_mode = net.get('security_group_mode')
+ if security_group_mode is None:
+ security_group_mode = False
+ self.message(
+ msg=self.MESSAGES.default_value_used(
+ param_name='networks.security_group_mode',
+ default_value=False
+ ),
+ warning=True,
+ )
+
+ enabled = net.get('enabled')
+ if enabled is None:
+ enabled = True
+ self.message(
+ msg=self.MESSAGES.default_value_used(
+ param_name='networks.enabled',
+ default_value=True
+ ),
+ warning=True,
+ )
+
api_params = {
'computeId': vm_id,
'netType': net['type'],
@@ -1875,6 +2061,9 @@ class DecortController(object):
'ipAddr': net.get('ip_addr'),
'mtu': net.get('mtu'),
'mac_addr': net.get('mac'),
+ 'security_groups': net.get('security_group_ids'),
+ 'enable_secgroups': security_group_mode,
+ 'enabled': enabled,
}
if net['type'] == self.VMNetType.SDN.value:
api_params['sdn_interface_id'] = net['id']
@@ -1926,6 +2115,35 @@ class DecortController(object):
)
self.set_changed()
+ # Changing security groups
+ if nets_for_change_sec_groups_dict:
+ for net_key, net in nets_for_change_sec_groups_dict.items():
+ self.decort_api_call(
+ arg_req_function=requests.post,
+ arg_api_name='/restmachine/cloudapi/compute/change_security_groups', # noqa: E501
+ arg_params={
+ 'compute_id': vm_id,
+ 'interface': ifaces_dict[net_key]['mac'],
+ 'security_groups': net['security_group_ids'],
+ 'enable_secgroups': net['security_group_mode'],
+ },
+ )
+ self.set_changed()
+
+ # Changing state
+ if nets_for_change_state_dict:
+ for net_key, net in nets_for_change_state_dict.items():
+ self.decort_api_call(
+ arg_req_function=requests.post,
+ arg_api_name='/restmachine/cloudapi/compute/changeLinkState', # noqa: E501
+ arg_params={
+ 'computeId': vm_id,
+ 'interface': ifaces_dict[net_key]['mac'],
+ 'state': 'on' if net['enabled'] else 'off',
+ },
+ )
+ self.set_changed()
+
def compute_resize_vector(self, comp_dict, new_cpu, new_ram):
"""Check if the Compute new size parameters passed to this function are different from the
current Compute configuration.
@@ -2117,6 +2335,7 @@ class DecortController(object):
return False
+ @waypoint
def compute_affinity(self,comp_dict,tags,aff,aaff,label):
"""
Manage Compute Tags,Affinitylabel and rules
@@ -2126,8 +2345,22 @@ class DecortController(object):
@param (list) aaff: antiaffinity rules
@param (str) label: affinity group label
"""
- self.result['waypoints'] = "{} -> {}".format(self.result['waypoints'], "compute_affinity")
-
+
+ if self.amodule.check_mode:
+ method_kwargs = {}
+ if aff:
+ method_kwargs['affinity'] = aff
+ if aaff:
+ method_kwargs['anti-affinity'] = aaff
+ if tags:
+ method_kwargs['tags'] = tags
+ if label:
+ method_kwargs['label'] = label
+ if method_kwargs:
+ self.message(self.MESSAGES.method_in_check_mode('compute_affinity()', (), method_kwargs))
+
+ return
+
if tags is not None:
if tags:
for tag in tags.items():
@@ -2269,6 +2502,7 @@ class DecortController(object):
boot_loader_type: None | Literal['linux', 'windows', 'unknown'] = None,
network_interface_naming: None | Literal['eth', 'ens'] = None,
hot_resize: None | bool = None,
+ os_version: None | str = None,
):
OBJ = 'compute'
@@ -2291,6 +2525,7 @@ class DecortController(object):
'loaderType': boot_loader_type,
'networkInterfaceNaming': network_interface_naming,
'hotResize': hot_resize,
+ 'os_version': os_version,
},
)
@@ -2309,6 +2544,7 @@ class DecortController(object):
'loader_type': boot_loader_type,
'network_interface_naming': network_interface_naming,
'hot_resize': hot_resize,
+ 'os_version': os_version,
}
for param, value in params_to_check.items():
if value is not None:
@@ -2392,10 +2628,13 @@ class DecortController(object):
self,
compute_id: int,
name: str,
- snapshot_timestamp: int | None = None,
- snapshot_name: str | None = None,
- snapshot_datetime: str | None = None,
+ storage_policy_id: int,
force: bool = False,
+ sep_pool_name: str | None = None,
+ sep_id: int | None = None,
+ snapshot_datetime: str | None = None,
+ snapshot_name: str | None = None,
+ snapshot_timestamp: int | None = None,
):
_snapshot_timestamp = snapshot_timestamp
if snapshot_datetime:
@@ -2403,10 +2642,13 @@ class DecortController(object):
api_params = {
'computeId': compute_id,
- 'name': name,
'force': force,
+ 'name': name,
+ 'pool_name': sep_pool_name,
+ 'sep_id': sep_id,
'snapshotName': snapshot_name,
'snapshotTimestamp': _snapshot_timestamp,
+ 'storage_policy_id': storage_policy_id,
}
api_resp = self.decort_api_call(
@@ -2414,8 +2656,60 @@ class DecortController(object):
arg_api_name='/restmachine/cloudapi/compute/clone',
arg_params=api_params,
)
- self.set_changed()
- return int(api_resp.content)
+ audit_id = api_resp.content
+ task_link = (
+ f'{self.controller_url}/portal/#/system/tasks/{audit_id}'
+ )
+ clone_id = None
+ task_schedule_timeout = 600
+ clone_timeout = 1200
+ sleep_interval = 5
+ while True:
+ task_response = self.decort_api_call(
+ arg_req_function=requests.post,
+ arg_api_name='/restmachine/cloudapi/tasks/get',
+ arg_params={'auditId': audit_id},
+ )
+ task_resp_data = task_response.json()
+ match task_resp_data['status']:
+ case 'SCHEDULED':
+ if task_schedule_timeout <= 0:
+ self.message(
+ 'Time to schedule task to clone '
+ f'VM ID {compute_id} has been exceeded.'
+ f'\nTask details: {task_link}'
+ )
+ self.exit(fail=True)
+ task_schedule_timeout -= sleep_interval
+ case 'PROCESSING':
+ if clone_timeout <= 0:
+ self.message(
+ f'Time to clone VM ID {compute_id} '
+ f'has been exceeded.'
+ f'\nTask details: {task_link}'
+ )
+ self.exit(fail=True)
+ clone_timeout -= sleep_interval
+ case 'ERROR':
+ self.result['msg'] = (
+ f'Cloning VM ID {compute_id} failed: '
+ f'{task_resp_data.get("error")}.'
+ f'\nTask details: {task_link}'
+ )
+ self.exit(fail=True)
+ case 'OK':
+ clone_id, _, _ = self.compute_find(
+ comp_name=name,
+ rg_id=self.rg_id,
+ )
+ self.message(
+ f'Clone ID {clone_id} from VM ID {compute_id} '
+ 'created successfully'
+ )
+ self.set_changed()
+ break
+ time.sleep(sleep_interval)
+ return clone_id
@waypoint
def compute_get_console_url(self, compute_id: int):
@@ -2544,6 +2838,147 @@ class DecortController(object):
self.set_changed()
return response.json()['return']
+ @waypoint
+ @checkmode
+ def compute_cd_eject(
+ self,
+ vm_id: int,
+ ):
+ """
+ Implementation of functionality of the API method
+ `/cloudapi/compute/cdEject`.
+ """
+ api_params = {
+ 'computeId': vm_id,
+ }
+ response = self.decort_api_call(
+ arg_req_function=requests.post,
+ arg_api_name='/restmachine/cloudapi/compute/cdEject',
+ arg_params=api_params,
+ )
+ self.set_changed()
+ return response.json()
+
+ @waypoint
+ @checkmode
+ def compute_cd_insert(
+ self,
+ vm_id: int,
+ image_id: int,
+ ):
+ """
+ Implementation of functionality of the API method
+ `/cloudapi/compute/cdInsert`.
+ """
+ api_params = {
+ 'computeId': vm_id,
+ 'cdromId': image_id,
+ }
+ response = self.decort_api_call(
+ arg_req_function=requests.post,
+ arg_api_name='/restmachine/cloudapi/compute/cdInsert',
+ arg_params=api_params,
+ )
+ self.set_changed()
+ return response.json()
+
+ @waypoint
+ @checkmode
+ def compute_set_boot_order(
+ self,
+ vm_id: int,
+ order: list[VMBootDevice],
+ ):
+ """
+ Implementation of functionality of the API method
+ `/cloudapi/compute/cdInsert`.
+ """
+ api_params = {
+ 'computeId': vm_id,
+ 'order': order,
+ }
+ response = self.decort_api_call(
+ arg_req_function=requests.post,
+ arg_api_name='/restmachine/cloudapi/compute/bootOrderSet',
+ arg_params=api_params,
+ )
+ self.set_changed()
+ return response.json()
+
+ @waypoint
+ @checkmode
+ def compute_disk_redeploy(
+ self,
+ vm_id: int,
+ storage_policy_id: int,
+ image_id: None | int,
+ disk_size: None | int,
+ os_version: None | str,
+ auto_start: bool = False,
+ ):
+ """
+ Implementation of functionality of the API method
+ `/cloudapi/compute/redeploy`.
+ """
+ api_params = {
+ 'computeId': vm_id,
+ 'storage_policy_id': storage_policy_id,
+ 'imageId': image_id,
+ 'diskSize': disk_size,
+ 'dataDisks': 'KEEP',
+ 'autoStart': auto_start,
+ 'forceStop': True,
+ 'os_version': os_version,
+ }
+ response = self.decort_api_call(
+ arg_req_function=requests.post,
+ arg_api_name='/restmachine/cloudapi/compute/redeploy',
+ arg_params=api_params,
+ )
+ self.set_changed()
+ return response.json()
+
+ @waypoint
+ @checkmode
+ def compute_clone_abort(
+ self,
+ vm_id: int,
+ ):
+ """
+ Implementation of functionality of the API method
+ `/cloudapi/compute/clone_abort`.
+ """
+ api_params = {
+ 'compute_id': vm_id,
+ }
+ response = self.decort_api_call(
+ arg_req_function=requests.post,
+ arg_api_name='/restmachine/cloudapi/compute/clone_abort',
+ arg_params=api_params,
+ )
+ self.set_changed()
+ return response.json()
+
+ @waypoint
+ @checkmode
+ def compute_get_clone_status(
+ self,
+ vm_id: int,
+ ):
+ """
+ Implementation of functionality of the API method
+ `/cloudapi/compute/clone_status`.
+ """
+ api_params = {
+ 'compute_id': vm_id,
+ }
+ response = self.decort_api_call(
+ arg_req_function=requests.get,
+ arg_api_name='/restmachine/cloudapi/compute/clone_status',
+ arg_params=api_params,
+ )
+ return response.json()
+
###################################
# OS image manipulation methods
###################################
@@ -2551,7 +2986,7 @@ class DecortController(object):
# TODO: update once cloudapi/image/get is implemented, see ticket #2963
api_params = dict(imageId=image_id,
- showAll=False)
+ showAll=True)
api_resp = self.decort_api_call(
arg_req_function=requests.post,
arg_api_name='/restmachine/cloudapi/image/get',
@@ -2559,11 +2994,12 @@ class DecortController(object):
not_fail_codes=[404],
)
if api_resp.status_code == 404:
- return 0, None
+ self.message(f'Image with ID {image_id} not found.')
+ self.exit(fail=True)
return image_id, api_resp.json()
- def image_find(self, image_id, image_name, account_id, rg_id=0, sepid=0, pool=""):
+ def image_find(self, image_id, image_name='', account_id=0, rg_id=0, sepid=0, pool=""):
"""Locates image specified by name and returns its facts as dictionary.
Primary use of this function is to obtain the ID of the image identified by its name and,
optionally SEP ID and/or pool name. Also note that only images in status CREATED are
@@ -2607,7 +3043,7 @@ class DecortController(object):
if image_record['name'] == image_name and image_record['status'] == "CREATED":
if sepid == 0 and pool == "":
# if no filtering by SEP ID or pool name is requested, return the first match
- return image_record['id'], image_record
+ return self._image_get_by_id(image_record['id'])
# if positive SEP ID and/or non-emtpy pool name are passed, match by them
full_match = True
if sepid > 0 and sepid != image_record['sepId']:
@@ -2615,7 +3051,7 @@ class DecortController(object):
if pool != "" and pool != image_record['pool']:
full_match = False
if full_match:
- return image_record['id'], image_record
+ return self._image_get_by_id(image_record['id'])
self.result['failed'] = False
self.result['msg'] = ("Failed to find OS image by name '{}', SEP ID {}, pool '{}' for "
@@ -2672,12 +3108,15 @@ class DecortController(object):
images_list = json.loads(api_resp.content.decode('utf8'))
for image_record in images_list['data']:
if image_record['name'] == virt_name and image_record['status'] == "CREATED" and image_record['type'] == "virtual":
+ image_id, image_info = self._image_get_by_id(
+ image_id=image_record['id'],
+ )
if sepid == 0 and pool == "":
# if no filtering by SEP ID or pool name is requested, return the first match
- return image_record['id'], image_record
+ return image_id, image_info
full_match = True
if full_match:
- return image_record['id'], image_record
+ return image_id, image_info
self.result['msg'] = ("Failed to find virtual OS image by name '{}', SEP ID {}, pool '{}' for "
"account ID '{}'.").format(virt_name,
@@ -2717,15 +3156,13 @@ class DecortController(object):
image_dict = json.loads(api_resp.content.decode('utf8'))
self.result['changed'] = True
- return 0, None
+ return imageId
def image_create(
self,
img_name,
url,
- gid,
- drivers,
username,
password,
account_id,
@@ -2733,6 +3170,7 @@ class DecortController(object):
passwordDL,
sepId,
poolName,
+ storage_policy_id: int,
boot_mode: Literal['bios', 'uefi'] = 'bios',
boot_loader_type: Literal['linux', 'windows', 'unknown'] = 'unknown',
network_interface_naming: Literal['eth', 'ens'] = 'ens',
@@ -2743,10 +3181,8 @@ class DecortController(object):
api_params = {
'name': img_name,
'url': url,
- 'gid': gid,
'boottype': boot_mode,
'imagetype': boot_loader_type,
- 'drivers': drivers,
'accountId': account_id,
'hotresize': hot_resize,
'username': username,
@@ -2756,6 +3192,7 @@ class DecortController(object):
'sepId': sepId,
'poolName': poolName,
'networkInterfaceNaming': network_interface_naming,
+ 'storage_policy_id': storage_policy_id,
}
api_resp = self.decort_api_call(requests.post, "/restmachine/cloudapi/image/create", api_params)
# On success the above call will return here. On error it will abort execution by calling fail_json.
@@ -2787,14 +3224,37 @@ class DecortController(object):
self.result['failed'] = False
self.result['changed'] = True
- ###################################
- # Resource Group (RG) manipulation methods
- ###################################
- def rg_delete(self, rg_id, permanently, recursively: bool):
- """Deletes specified VDC.
-
- @param (int) rg_id: integer value that identifies the RG to be deleted.
- @param (bool) permanently: a bool that tells if deletion should be permanent. If False, the RG will be
+ @waypoint
+ @checkmode
+ def image_change_storage_policy(
+ self,
+ image_id: int,
+ storage_policy_id: int,
+ ):
+ """
+ Implementation of functionality of the API method
+ `/cloudapi/image/change_storage_policy`.
+ """
+ api_params = {
+ 'image_id': image_id,
+ 'storage_policy_id': storage_policy_id,
+ }
+ response = self.decort_api_call(
+ arg_req_function=requests.post,
+ arg_api_name='/restmachine/cloudapi/image/change_storage_policy', # noqa: E501
+ arg_params=api_params,
+ )
+ self.set_changed()
+ return response.json()
+
+ ###################################
+ # Resource Group (RG) manipulation methods
+ ###################################
+ def rg_delete(self, rg_id, permanently, recursively: bool):
+ """Deletes specified VDC.
+
+ @param (int) rg_id: integer value that identifies the RG to be deleted.
+ @param (bool) permanently: a bool that tells if deletion should be permanent. If False, the RG will be
marked as deleted and placed into a trash bin for predefined period of time (usually, a few days). Until
this period passes the RG can be restored by calling the corresponding 'restore' method.
"""
@@ -2926,11 +3386,26 @@ class DecortController(object):
ret_rg_dict = None
if arg_rg_id is not None and arg_rg_id > 0:
- ret_rg_id, ret_rg_dict = self._rg_get_by_id(arg_rg_id)
+ api_params['includedeleted'] = True
+ api_resp = self.decort_api_call(
+ requests.post,
+ '/restmachine/cloudapi/rg/list',
+ api_params,
+ )
+ rg_list = json.loads(api_resp.content.decode('utf8'))
+ for rg_item in rg_list['data']:
+ if rg_item['id'] == arg_rg_id:
+ got_id, got_specs = self._rg_get_by_id(rg_item['id'])
+ if not arg_check_state or got_specs['status'] not in RG_INVALID_STATES:
+ ret_rg_id = got_id
+ ret_rg_dict = got_specs
+ break
+
if not ret_rg_id:
self.result['failed'] = True
self.result['msg'] = "rg_find(): cannot find RG by ID {}.".format(arg_rg_id)
self.amodule.fail_json(**self.result)
+
elif arg_rg_name != "":
# TODO: cloudapi/rg/list will be implemented per Ticket #2848.
# Until then use a three # step approach:
@@ -2943,7 +3418,7 @@ class DecortController(object):
self.result['msg'] = "rg_find(): cannot find RG by name if account ID is zero or less."
self.amodule.fail_json(**self.result)
# try to locate RG by name - start with getting all RGs IDs within the specified account
- #api_params['accountId'] = arg_account_id
+ api_params['accountId'] = arg_account_id
api_params['includedeleted'] = True
#api_resp = self.decort_api_call(requests.post, "/restmachine/cloudapi/account/listRG", api_params)
api_resp = self.decort_api_call(requests.post, "/restmachine/cloudapi/rg/list",api_params)
@@ -3149,32 +3624,71 @@ class DecortController(object):
# One more inconsistency in API keys:
# - when setting resource limits, the keys are in the form 'max{{ RESOURCE_NAME }}Capacity'
# - when quering resource limits, the keys are in the form of cloud units (CU_*)
- query_key_map = dict(cpu='CU_C',
- ram='CU_M',
- disk='CU_DM',
- ext_ips='CU_I',
- net_transfer='CU_NP',)
- set_key_map = dict(cpu='maxCPUCapacity',
- ram='maxMemoryCapacity',
- disk='maxVDiskCapacity',
- ext_ips='maxNumPublicIP',
- net_transfer='maxNetworkPeerTransfer',)
-
- for new_limit in ('cpu', 'ram', 'disk', 'ext_ips', 'net_transfer'):
+ query_key_map = dict(
+ cpu='CU_C',
+ ram='CU_M',
+ disk='CU_DM',
+ ext_ips='CU_I',
+ net_transfer='CU_NP',
+ storage_policies='storage_policy',
+ )
+ set_key_map = dict(
+ cpu='maxCPUCapacity',
+ ram='maxMemoryCapacity',
+ disk='maxVDiskCapacity',
+ ext_ips='maxNumPublicIP',
+ net_transfer='maxNetworkPeerTransfer',
+ storage_policies='storage_policies',
+ )
+
+ rg_resource_limits_dict = arg_rg_dict['resourceLimits']
+ for quota_type in (
+ 'cpu', 'ram', 'disk', 'ext_ips',
+ 'net_transfer', 'storage_policies',
+ ):
if arg_quotas:
- if new_limit in arg_quotas:
+ if quota_type in arg_quotas:
# If this resource type limit is found in the desired quotas, check if the desired setting is
# different from the current settings of VDC. If it is different, set the new one.
- if arg_quotas[new_limit] != arg_rg_dict['resourceLimits'][query_key_map[new_limit]]:
- api_params[set_key_map[new_limit]] = arg_quotas[new_limit]
- update_required = True
- elif arg_quotas[new_limit] == arg_rg_dict['resourceLimits'][query_key_map[new_limit]]:
- api_params[set_key_map[new_limit]] = arg_quotas[new_limit]
+ if quota_type == 'storage_policies':
+ quotas = []
+ for aparam_storage_policy in arg_quotas[
+ 'storage_policies'
+ ]:
+ for rg_storage_policy in rg_resource_limits_dict[
+ 'storage_policy'
+ ]:
+ if (
+ aparam_storage_policy['id']
+ == rg_storage_policy['id']
+ and aparam_storage_policy[
+ 'storage_size_gb'
+ ]
+ != rg_storage_policy['limit']
+ ):
+ update_required = True
+ quotas.append({
+ 'id': aparam_storage_policy['id'],
+ 'limit': aparam_storage_policy[
+ 'storage_size_gb'
+ ],
+ })
+ break
+ if quotas:
+ api_params[set_key_map[quota_type]] = (
+ json.dumps(quotas)
+ )
+ else:
+ if arg_quotas[quota_type] != rg_resource_limits_dict[query_key_map[quota_type]]:
+ api_params[set_key_map[quota_type]] = arg_quotas[quota_type]
+ update_required = True
+ elif arg_quotas[quota_type] == rg_resource_limits_dict[query_key_map[quota_type]]:
+ api_params[set_key_map[quota_type]] = arg_quotas[quota_type]
else:
- api_params[set_key_map[new_limit]] = arg_rg_dict['resourceLimits'][query_key_map[new_limit]]
+ api_params[set_key_map[quota_type]] = rg_resource_limits_dict[query_key_map[quota_type]]
else:
# if quotas dictionary is None, it means that no quotas should be set - reset the limits
- api_params[set_key_map[new_limit]] = arg_rg_dict['resourceLimits'][query_key_map[new_limit]]
+ api_params[set_key_map[quota_type]] = rg_resource_limits_dict[query_key_map[quota_type]]
if arg_sep_pools is not None:
if arg_sep_pools:
@@ -3374,6 +3888,14 @@ class DecortController(object):
account_details['updatedTime_readable'] = self.sec_to_dt_str(
account_details['updatedTime']
)
+ account_details['resourceLimits']['storage_policies'] = (
+ account_details['resourceLimits'].pop('storage_policy')
+ )
+ account_storage_policies = (
+ account_details['resourceLimits']['storage_policies']
+ )
+ for storage_policy in account_storage_policies:
+ storage_policy['storage_size_gb'] = storage_policy.pop('limit')
if resource_consumption:
resource_consumption = self.account_resource_consumption(
@@ -3835,8 +4357,8 @@ class DecortController(object):
api_resp = self.decort_api_call(
arg_req_function=requests.post,
- arg_api_name='/restmachine/cloudapi/account/audits',
- arg_params={'accountId': account_id},
+ arg_api_name='/restmachine/cloudapi/audit/list',
+ arg_params={'account_id': account_id},
not_fail_codes=[404]
)
@@ -3848,10 +4370,29 @@ class DecortController(object):
self.amodule.fail_json(**self.result)
return
- audits = api_resp.json()
+ audits = api_resp.json()['data']
for a in audits:
- a['timestamp_readable'] = self.sec_to_dt_str(int(a['timestamp']))
+ a['api_url_path'] = a.pop('call')
+ if 'apitask' in a:
+ a['async_request_task_id'] = a.pop('apitask')
+ if 'service_id' in a:
+ a['bservice_id'] = a.pop('service_id')
+ if 'flipgroup_id' in a:
+ a['flip_group_id'] = a.pop('flipgroup_id')
+ if 'resgroup_id' in a:
+ a['rg_id'] = a.pop('resgroup_id')
+ if 'compute_id' in a:
+ a['vm_id'] = a.pop('compute_id')
+ if 'timestampEnd' in a:
+ a['response_timestamp'] = a.pop('timestampEnd')
+ a['response_timestamp_readable'] = self.sec_to_dt_str(a['response_timestamp'])
+ a['request_timestamp'] = a.pop('timestamp')
+ a['user_name'] = a.pop('user')
+ a['client_ip_addr'] = a.pop('remote_addr')
+ a['request_datetime_iso8601'] = a.pop('_ttl')
+ a['status_code'] = a.pop('statuscode')
+ a['execution_time_sec'] = a.pop('responsetime')
return audits
@@ -4473,16 +5014,22 @@ class DecortController(object):
"""
# transient and deleted/destroyed states are deemed invalid
- VINS_INVALID_STATES = ["ENABLING", "DISABLING", "DELETING", "DELETED", "DESTROYING", "DESTROYED"]
+ VINS_INVALID_STATES = ["ENABLING", "DISABLING", "DELETING", "DESTROYING"]
ret_vins_id = 0
- # api_params = dict()
+ api_params: dict = {
+ 'includeDeleted': True,
+ }
ret_vins_facts = None
self.result['waypoints'] = "{} -> {}".format(self.result['waypoints'], "vins_find")
-
+
if vins_id > 0:
- ret_vins_id, ret_vins_facts = self._vins_get_by_id(vins_id)
+ got_id, got_specs = self._vins_get_by_id(vins_id)
+ if got_specs['status'] not in VINS_INVALID_STATES:
+ ret_vins_id = got_id
+ ret_vins_facts = got_specs
+
if not ret_vins_id:
self.result['failed'] = True
self.result['msg'] = "vins_find(): cannot find ViNS by ID {}.".format(vins_id)
@@ -4613,7 +5160,8 @@ class DecortController(object):
if ipcidr != "":
api_params['ipcidr'] = ipcidr
- api_params['desc'] = desc
+
+ api_params['desc'] = desc
api_params['zoneId'] = zone_id
@@ -4770,6 +5318,12 @@ class DecortController(object):
self.result['warning'] = ("vins_update(): ViNS ID {} is already connected to ext net ID {}, "
"ignore ext IP address change if any.").format(vins_dict['id'],
ext_net_id)
+ elif gw_config is None:
+ # connect to the new
+ api_params['netId'] = ext_net_id
+ api_params['ip'] = ext_ip_addr
+ self.decort_api_call(requests.post, "/restmachine/cloudapi/vins/extNetConnect", api_params)
+ self.set_changed()
else: # ext_net_id = 0, i.e. connect ViNS to default network
# we will connect ViNS to default network only if it is NOT connected to any ext network yet
if not gw_config:
@@ -5046,7 +5600,7 @@ class DecortController(object):
# On success the above call will return here. On error it will abort execution by calling fail_json.
self.result['failed'] = False
self.result['changed'] = True
- return
+ return disk_id
def _disk_get_by_id(self, disk_id):
"""Helper function that locates Disk by ID and returns Disk facts. This function
@@ -5070,10 +5624,18 @@ class DecortController(object):
self.amodule.fail_json(**self.result)
api_params = dict(diskId=disk_id, )
- api_resp = self.decort_api_call(requests.post, "/restmachine/cloudapi/disks/get", api_params)
+ api_resp = self.decort_api_call(
+ requests.post,
+ '/restmachine/cloudapi/disks/get',
+ api_params,
+ not_fail_codes=[404],
+ )
if api_resp.status_code == 200:
ret_disk_id = disk_id
ret_disk_dict = json.loads(api_resp.content.decode('utf8'))
+ elif api_resp.status_code == 404:
+ self.message(f'Disk with ID {disk_id} not found.')
+ self.exit(fail=True)
else:
self.result['warning'] = ("disk_get_by_id(): failed to get Disk by ID {}. HTTP code {}, "
"response {}.").format(disk_id, api_resp.status_code, api_resp.reason)
@@ -5106,6 +5668,7 @@ class DecortController(object):
if disk_id:
ret_disk_id, ret_disk_facts = self._disk_get_by_id(disk_id)
+
if not ret_disk_id:
self.result['failed'] = True
self.result['msg'] = "disk_find(): cannot find Disk by ID {}.".format(disk_id)
@@ -5148,7 +5711,16 @@ class DecortController(object):
return 0, None
- def disk_create(self, accountId, name, description, size, iops, sep_id, pool):
+ def disk_create(
+ self,
+ accountId,
+ name,
+ description,
+ size,
+ sep_id,
+ pool,
+ storage_policy_id: int,
+ ):
"""Provision Disk according to the specified arguments.
Note that disks created by this method will be of type 'D' (data disks).
If critical error occurs the embedded call to API function will abort further execution
@@ -5167,13 +5739,15 @@ class DecortController(object):
"""
self.result['waypoints'] = "{} -> {}".format(self.result['waypoints'], "disk_creation")
- api_params = dict(accountId=accountId,
- name=name,
- description=description,
- size=size,
- iops=iops,
- sep_id=sep_id,
- pool=pool )
+ api_params = dict(
+ accountId=accountId,
+ name=name,
+ description=description,
+ size=size,
+ sep_id=sep_id,
+ pool=pool,
+ storage_policy_id=storage_policy_id,
+ )
api_resp = self.decort_api_call(requests.post, "/restmachine/cloudapi/disks/create", api_params)
if api_resp.status_code == 200:
ret_disk_id = json.loads(api_resp.content.decode('utf8'))
@@ -5313,6 +5887,29 @@ class DecortController(object):
self.result['changed'] = True
return
+ @waypoint
+ @checkmode
+ def disk_change_storage_policy(
+ self,
+ disk_id: int,
+ storage_policy_id: int,
+ ):
+ """
+ Implementation of functionality of the API method
+ `/cloudapi/disks/change_disk_storage_policy`.
+ """
+ api_params = {
+ 'disk_id': disk_id,
+ 'storage_policy_id': storage_policy_id,
+ }
+ response = self.decort_api_call(
+ arg_req_function=requests.post,
+ arg_api_name='/restmachine/cloudapi/disks/change_disk_storage_policy', # noqa: E501
+ arg_params=api_params,
+ )
+ self.set_changed()
+ return response.json()
+
##############################
#
# Port Forward rules management
@@ -5750,34 +6347,37 @@ class DecortController(object):
):
target_wg['need_to_recreate'] = True
- def k8s_provision(self, k8s_name,
- k8ci_id,
- rg_id,
- vins_id,
- plugin,
- master_count,
- master_cpu,
- master_ram,
- master_disk,
- master_sepid,
- master_pool,
- default_worker,
- extnet_id,
- with_lb,
- ha_lb,
- sans,
- init_conf,
- cluster_conf,
- kublet_conf,
- kubeproxy_conf,
- join_conf,
- oidc_cert,
- description,
- extnet_only,
- master_chipset: Literal['Q35', 'i440fx'] = 'i440fx',
- lb_sysctl: dict | None = None,
- zone_id: None | int = None,
- ):
+ def k8s_provision(
+ self,
+ k8s_name,
+ k8ci_id,
+ rg_id,
+ vins_id,
+ plugin,
+ master_count,
+ master_cpu,
+ master_ram,
+ master_disk,
+ master_sepid,
+ master_pool,
+ default_worker,
+ extnet_id,
+ with_lb,
+ ha_lb,
+ sans,
+ init_conf,
+ cluster_conf,
+ kublet_conf,
+ kubeproxy_conf,
+ join_conf,
+ oidc_cert,
+ description,
+ extnet_only,
+ storage_policy_id: int,
+ master_chipset: Literal['Q35', 'i440fx'] = 'i440fx',
+ lb_sysctl: dict | None = None,
+ zone_id: None | int = None,
+ ):
self.result['waypoints'] = "{} -> {}".format(self.result['waypoints'], "k8s_provision")
@@ -5826,6 +6426,7 @@ class DecortController(object):
{k: str(v) for k, v in lb_sysctl.items()}
),
zoneId=zone_id,
+ storage_policy_id=storage_policy_id,
)
upload_files = None
@@ -6076,6 +6677,23 @@ class DecortController(object):
self.result['failed'] = False
return
+ @waypoint
+ def k8s_update(self, *, id: int, name: str):
+
+ api_params = {
+ 'k8sId': id,
+ 'name': name,
+ }
+
+ self.decort_api_call(
+ arg_req_function=requests.post,
+ arg_api_name='/restmachine/cloudapi/k8s/update',
+ arg_params=api_params,
+ )
+
+ self.set_changed()
+ return
+
def k8s_k8ci_find(self,arg_k8ci_id):
self.result['waypoints'] = "{} -> {}".format(self.result['waypoints'], "k8s_k8ci_find")
@@ -6207,13 +6825,10 @@ class DecortController(object):
def bservice_state(self,bs_dict,desired_state):
self.result['waypoints'] = "{} -> {}".format(self.result['waypoints'], "bservice_state")
-
- if desired_state == "present":
- desired_state = "enabled"
-
+
NOP_STATES_FOR_BS_CHANGE = ["MODELED", "DISABLING", "ENABLING", "DELETING", "DELETED", "DESTROYING",
"DESTROYED","RESTORYNG","RECONFIGURING"]
- VALID_TARGET_STATES = ["enabled", "disabled", 'started', 'stopped']
+ VALID_TARGET_STATES = ["enabled", "disabled", 'started', 'stopped', 'present']
if bs_dict['status'] in NOP_STATES_FOR_BS_CHANGE:
self.result['failed'] = False
@@ -6221,7 +6836,10 @@ class DecortController(object):
"in its current state '{}'.").format(bs_dict['id'], bs_dict['status'])
return
- if desired_state not in VALID_TARGET_STATES:
+ if (
+ desired_state is not None
+ and desired_state not in VALID_TARGET_STATES
+ ):
self.result['failed'] = False
self.result['warning'] = ("bservice_state(): unrecognized desired state '{}' requested "
"for B-service ID {}. No B-service state change will be done.").format(desired_state,
@@ -6239,7 +6857,12 @@ class DecortController(object):
api_params = dict(serviceId=bs_dict['id'])
expected_state = ""
- if bs_dict['status'] in ['CREATED', 'ENABLED']:
+ if bs_dict['status'] == 'CREATED':
+ if desired_state == 'disabled':
+ bsstate_api = '/restmachine/cloudapi/bservice/disable'
+ elif desired_state == 'enabled':
+ bsstate_api = '/restmachine/cloudapi/bservice/enable'
+ if bs_dict['status'] == 'ENABLED':
if desired_state == 'disabled':
bsstate_api = '/restmachine/cloudapi/bservice/disable'
expected_state = 'DISABLED'
@@ -6249,10 +6872,7 @@ class DecortController(object):
):
bsstate_api = '/restmachine/cloudapi/bservice/stop'
elif (
- (
- bs_dict['techStatus'] == 'STOPPED'
- or bs_dict['status'] == 'CREATED'
- )
+ bs_dict['techStatus'] == 'STOPPED'
and desired_state == 'started'
):
bsstate_api = '/restmachine/cloudapi/bservice/start'
@@ -6314,16 +6934,28 @@ class DecortController(object):
# GROUP MANAGE
#
def _group_get_by_id(self,bs_id,g_id):
-
+ ret_gr_dict = {}
api_params = dict(serviceId=bs_id,compgroupId=g_id)
- api_resp = self.decort_api_call(requests.post, "/restmachine/cloudapi/bservice/groupGet", api_params)
+ api_resp = self.decort_api_call(
+ requests.post,
+ '/restmachine/cloudapi/bservice/groupGet',
+ api_params,
+ not_fail_codes=[400],
+ )
if api_resp.status_code == 200:
- ret_gr_id = g_id
ret_gr_dict = json.loads(api_resp.content.decode('utf8'))
+ elif api_resp.status_code == 400:
+ self.message(
+ f'Group with ID {g_id} for BService with ID {bs_id} not found '
+ f'or Group with ID {g_id} wast deleted.'
+ )
+ self.exit()
else:
- self.result['warning'] = ("group_get_by_id(): failed to get Group by ID {}. HTTP code {}, "
- "response {}.").format(g_id, api_resp.status_code, api_resp.reason)
- return ret_gr_id,ret_gr_dict
+ self.result['warning'] = (
+ f'group_get_by_id(): failed to get Group by ID {g_id}. '
+ f'HTTP code {api_resp.status_code}, response {api_resp.reason}.'
+ )
+ return g_id, ret_gr_dict
def group_find(self,bs_id,bs_info,group_id=None,group_name=""):
@@ -6423,25 +7055,43 @@ class DecortController(object):
list_vins.append(net['id'])
else:
list_extnet.append(net['id'])
+ if len(list_vins) > 0:
+ if sorted(gr_dict['vinses']) != sorted(list_vins):
+ api_url = "/restmachine/cloudapi/bservice/groupUpdateVins"
+ api_params = dict(
+ serviceId=bs_id,
+ compgroupId=gr_dict['id'],
+ vinses=list_vins
+ )
+ self.decort_api_call(requests.post, api_url, api_params)
+ self.set_changed()
- if sorted(gr_dict['vinses']) != sorted(list_vins):
- api_url = "/restmachine/cloudapi/bservice/groupUpdateVins"
- api_params = dict(
- serviceId=bs_id,
- compgroupId=gr_dict['id'],
- vinses=list_vins
- )
- self.decort_api_call(requests.post, api_url, api_params)
- self.result['failed'] = False
- self.result['changed'] = True
-
- #extnet connection need stoped status of group
- #rly need connect group to extnet ?
+ if len(list_extnet) > 0:
+ if sorted(gr_dict['extnets']) != sorted(list_extnet):
+ api_url = '/restmachine/cloudapi/bservice/groupUpdateExtnet'
+ api_params = dict(
+ serviceId=bs_id,
+ compgroupId=gr_dict['id'],
+ extnets=list_extnet
+ )
+ self.decort_api_call(requests.post, api_url, api_params)
+ self.set_changed()
return
def group_provision(
- self,bs_id,arg_name,chipset: Literal['Q35', 'i440fx'],arg_count=1,arg_cpu=1,arg_ram=1024,
- arg_boot_disk=10,arg_image_id=0,arg_driver="KVM_X86",arg_role="",
- arg_network=None,arg_timeout=0,
+ self,
+ bs_id,
+ arg_name,
+ chipset: Literal['Q35', 'i440fx'],
+ storage_policy_id: int,
+ driver: str,
+ arg_count=1,
+ arg_cpu=1,
+ arg_ram=1024,
+ arg_boot_disk=10,
+ arg_image_id=0,
+ arg_role="",
+ arg_network=None,
+ arg_timeout=0,
):
self.result['waypoints'] = "{} -> {}".format(self.result['waypoints'], "group_provision")
@@ -6457,12 +7107,13 @@ class DecortController(object):
ram = arg_ram,
disk = arg_boot_disk,
imageId = arg_image_id,
- driver = arg_driver,
role = arg_role,
vinses = [n['id'] for n in arg_network if n['type'] == 'VINS'],
extnets = [n['id'] for n in arg_network if n['type'] == 'EXTNET'],
timeoutStart = arg_timeout,
chipset=chipset,
+ storage_policy_id=storage_policy_id,
+ driver=driver,
)
api_resp = self.decort_api_call(requests.post, api_url, api_params)
new_bsgroup_id = int(api_resp.text)
@@ -6505,15 +7156,26 @@ class DecortController(object):
self.amodule.fail_json(**self.result)
api_params = dict(lbId=lb_id)
- api_resp = self.decort_api_call(requests.post, "/restmachine/cloudapi/lb/get", api_params)
+ api_resp = self.decort_api_call(
+ requests.post,
+ '/restmachine/cloudapi/lb/get',
+ api_params,
+ not_fail_codes=[404]
+ )
if api_resp.status_code == 200:
ret_lb_id = lb_id
ret_lb_dict = json.loads(api_resp.content.decode('utf8'))
+ elif api_resp.status_code == 404:
+ self.message(f'LB with ID {lb_id} not found.')
+ self.exit(fail=True)
else:
- self.result['warning'] = ("lb_get_by_id(): failed to get LB by ID {}. HTTP code {}, "
- "response {}.").format(lb_id, api_resp.status_code, api_resp.reason)
-
+ self.message(
+ f'lb_get_by_id(): failed to get LB by ID {lb_id}. '
+ f'HTTP code {api_resp.status_code}, response {api_resp.reason}.'
+ )
+ self.exit(fail=True)
return ret_lb_id, ret_lb_dict
+
def _rg_listlb(self,rg_id):
"""List all LB in the resource group
@param (int) rg_id: id onr resource group
@@ -6523,7 +7185,7 @@ class DecortController(object):
self.result['msg'] = "_rg_listlb(): zero RG ID specified."
self.amodule.fail_json(**self.result)
- api_params = dict(rgId=rg_id, includedeleted=True)
+ api_params = dict(includedeleted=True)
api_resp = self.decort_api_call(requests.post, "/restmachine/cloudapi/lb/list", api_params)
if api_resp.status_code == 200:
ret_rg_vins_list = json.loads(api_resp.content.decode('utf8'))
@@ -6540,6 +7202,7 @@ class DecortController(object):
"""
LB_INVALID_STATES = ["ENABLING", "DISABLING", "DELETING", "DESTROYING", "DESTROYED"]
+ api_params = dict()
ret_lb_id = 0
ret_lb_facts = None
@@ -6547,10 +7210,7 @@ class DecortController(object):
if lb_id > 0:
ret_lb_id, ret_lb_facts = self._lb_get_by_id(lb_id)
- if not ret_lb_id:
- self.result['failed'] = True
- self.result['msg'] = "lb_find(): cannot find LB by ID {}.".format(lb_id)
- self.amodule.fail_json(**self.result)
+
if not self.amodule.check_mode or ret_lb_facts['status'] not in LB_INVALID_STATES:
return ret_lb_id, ret_lb_facts
else:
@@ -6650,7 +7310,8 @@ class DecortController(object):
# On success the above call will return here. On error it will abort execution by calling fail_json.
self.result['failed'] = False
self.result['changed'] = True
- return
+ return lb_id
+
def lb_state(self, lb_dict, desired_state):
"""Change state for LB.
"""
@@ -7588,3 +8249,294 @@ class DecortController(object):
self.exit(fail=True)
return extnet_info
+
+ @waypoint
+ def storage_policy_get(self, id: int) -> dict:
+ """
+ Implementation of functionality of the API method
+ `/cloudapi/storage_policy/get`.
+ """
+
+ api_resp = self.decort_api_call(
+ arg_req_function=requests.get,
+ arg_api_name='/restmachine/cloudapi/storage_policy/get',
+ arg_params={
+ 'storage_policy_id': id,
+ },
+ not_fail_codes=[404],
+ )
+
+ storage_policy_info = None
+ if api_resp.status_code == 200:
+ storage_policy_info = api_resp.json()
+
+ if not storage_policy_info:
+ self.message(
+ self.MESSAGES.obj_not_found(obj='storage_policy', id=id)
+ )
+ self.exit(fail=True)
+
+ return storage_policy_info
+
+ @waypoint
+ def user_storage_policies(
+ self,
+ account_id: None | int = None,
+ description: None | str = None,
+ id: None | int = None,
+ iops_limit: None | int = None,
+ name: None | str = None,
+ pool_name: None | str = None,
+ rg_id: None | int = None,
+ sep_id: None | int = None,
+ status: None | StoragePolicyStatus = None,
+ page_number: int = 1,
+ page_size: None | int = None,
+ sort_by_asc: bool = True,
+ sort_by_field: None | StoragePoliciesSortableField = None,
+ ) -> list[dict]:
+ """
+ Implementation of the functionality of API method
+ `/cloudapi/storage_policy/list`.
+ """
+ sort_by = None
+ if sort_by_field:
+ sort_by_prefix = '+' if sort_by_asc else '-'
+ sort_by = f'{sort_by_prefix}{sort_by_field.name}'
+
+ api_params = {
+ 'account_id': account_id,
+ 'by_id': id,
+ 'name': name,
+ 'status': status.value if status else None,
+ 'desc': description,
+ 'limit_iops': iops_limit,
+ 'resgroup_id': rg_id,
+ 'sep_id': sep_id,
+ 'pool_name': pool_name,
+ 'page': page_number if page_size else None,
+ 'size': page_size,
+ 'sort_by': sort_by,
+ }
+ api_resp = self.decort_api_call(
+ arg_req_function=requests.get,
+ arg_api_name='/restmachine/cloudapi/storage_policy/list',
+ arg_params=api_params,
+ )
+
+ storage_policies = api_resp.json()['data']
+
+ return storage_policies
+
+ @waypoint
+ def user_security_groups(
+ self,
+ account_id: None | int = None,
+ description: None | str = None,
+ id: None | int = None,
+ name: None | str = None,
+ created_timestamp_min: None | int = None,
+ created_timestamp_max: None | int = None,
+ updated_timestamp_min: None | int = None,
+ updated_timestamp_max: None | int = None,
+ page_number: int = 1,
+ page_size: None | int = None,
+ sort_by_asc: bool = True,
+ sort_by_field: None | SecurityGroupSortableField = None,
+ ) -> list[dict]:
+ """
+ Implementation of the functionality of API method
+ `/cloudapi/security_group/list`.
+ """
+ sort_by = None
+ if sort_by_field:
+ sort_by_prefix = '+' if sort_by_asc else '-'
+ sort_by = f'{sort_by_prefix}{sort_by_field.value}'
+
+ api_params = {
+ 'account_id': account_id,
+ 'by_id': id,
+ 'name': name,
+ 'description': description,
+ 'created_min': created_timestamp_min,
+ 'created_max': created_timestamp_max,
+ 'updated_min': updated_timestamp_min,
+ 'updated_max': updated_timestamp_max,
+ 'page': page_number if page_size else None,
+ 'size': page_size,
+ 'sort_by': sort_by,
+ }
+ api_resp = self.decort_api_call(
+ arg_req_function=requests.get,
+ arg_api_name='/restmachine/cloudapi/security_group/list',
+ arg_params=api_params,
+ )
+
+ storage_policies = api_resp.json()['data']
+
+ return storage_policies
+
+ def security_group_find(self, account_id: int, name: str) -> None | dict:
+ security_groups_by_account_id = self.user_security_groups(
+ account_id=account_id,
+ )
+ for sg in security_groups_by_account_id:
+ if sg['name'] == name:
+ return sg
+
+ @waypoint
+ def security_group_get(self, id: int) -> dict:
+ """
+ Implementation of functionality of the API method
+ `/cloudapi/security_group/get`.
+ """
+
+ api_resp = self.decort_api_call(
+ arg_req_function=requests.get,
+ arg_api_name='/restmachine/cloudapi/security_group/get',
+ arg_params={
+ 'security_group_id': id,
+ },
+ not_fail_codes=[404],
+ )
+
+ storage_policy_info = None
+ if api_resp.status_code == 200:
+ storage_policy_info = api_resp.json()
+
+ if not storage_policy_info:
+ self.message(
+ self.MESSAGES.obj_not_found(obj='security_group', id=id)
+ )
+ self.exit(fail=True)
+
+ return storage_policy_info
+
+ @waypoint
+ @checkmode
+ def security_group_create(
+ self,
+ account_id: int,
+ name: str,
+ description: None | str,
+ ) -> int:
+ """
+ Implementation of functionality of the API method
+ `/cloudapi/security_group/create`.
+ """
+
+ api_resp = self.decort_api_call(
+ arg_req_function=requests.post,
+ arg_api_name='/restmachine/cloudapi/security_group/create',
+ arg_params={
+ 'account_id': account_id,
+ 'name': name,
+ 'description': description,
+ },
+ )
+ self.set_changed()
+
+ return api_resp.json()
+
+ @waypoint
+ @checkmode
+ def security_group_detele(self, security_group_id: int) -> bool:
+ """
+ Implementation of functionality of the API method
+ `/cloudapi/security_group/delete`.
+ """
+
+ api_resp = self.decort_api_call(
+ arg_req_function=requests.post,
+ arg_api_name='/restmachine/cloudapi/security_group/delete',
+ arg_params={
+ 'security_group_id': security_group_id,
+ },
+ )
+ self.set_changed()
+
+ return api_resp.json()
+
+ @waypoint
+ @checkmode
+ def security_group_update(
+ self,
+ security_group_id: int,
+ name: None | str,
+ description: None | str,
+ ) -> dict:
+ """
+ Implementation of functionality of the API method
+ `/cloudapi/security_group/update`.
+ """
+
+ api_resp = self.decort_api_call(
+ arg_req_function=requests.post,
+ arg_api_name='/restmachine/cloudapi/security_group/update',
+ arg_params={
+ 'security_group_id': security_group_id,
+ 'name': name,
+ 'description': description,
+ },
+ )
+ self.set_changed()
+
+ return api_resp.json()
+
+ @waypoint
+ @checkmode
+ def security_group_create_rule(
+ self,
+ security_group_id: int,
+ direction: SecurityGroupRuleDirection,
+ ethertype: None | SecurityGroupRuleEtherType,
+ protocol: None | SecurityGroupRuleProtocol,
+ port_range_min: None | int,
+ port_range_max: None | int,
+ remote_ip_prefix: None | str,
+ ) -> int:
+ """
+ Implementation of functionality of the API method
+ `/cloudapi/security_group/create_rule`.
+ """
+
+ api_resp = self.decort_api_call(
+ arg_req_function=requests.post,
+ arg_api_name='/restmachine/cloudapi/security_group/create_rule',
+ arg_params={
+ 'security_group_id': security_group_id,
+ 'direction': direction.value,
+ 'ethertype': ethertype.value if ethertype else None,
+ 'protocol': protocol.value if protocol else None,
+ 'port_range_min': port_range_min,
+ 'port_range_max': port_range_max,
+ 'remote_ip_prefix': remote_ip_prefix,
+ },
+ )
+ self.set_changed()
+
+ return api_resp.json()
+
+ @waypoint
+ @checkmode
+ def security_group_detele_rule(
+ self,
+ security_group_id: int,
+ rule_id: int,
+ ) -> bool:
+ """
+ Implementation of functionality of the API method
+ `/cloudapi/security_group/delete_rule`.
+ """
+
+ api_resp = self.decort_api_call(
+ arg_req_function=requests.post,
+ arg_api_name='/restmachine/cloudapi/security_group/delete_rule',
+ arg_params={
+ 'security_group_id': security_group_id,
+ 'rule_id': rule_id,
+ },
+ )
+ self.set_changed()
+
+ return api_resp.json()