6 Commits

42 changed files with 1154 additions and 1274 deletions

View File

@@ -9,4 +9,4 @@ Requirements:
* PyJWT 2.0.0 Python module or higher
* requests Python module
* netaddr Python module
* DECORT cloud platform version 3.8.7 or higher
* DECORT cloud platform version 3.5.0 or higher

View File

View File

@@ -1,38 +0,0 @@
#
# DECORT kvmvm module example
#
- hosts: ansible_master
tasks:
- name: create a VM named cloud-init_example
decort_kvmvm:
annotation: "VM managed by decort_kvmvm module"
authenticator: oauth2
app_id: "" # Application id from SSO Digital Energy
app_secret: "" # API key from SSO Digital Energy
controller_url: "" #"https://mr4.digitalenergy.online"
name: cloud-init_example
cpu: 2
ram: 2048
boot_disk: 10
image_name: "DECS Ubuntu 18.04 v1.2.3" #Name of OS image
networks:
- type: VINS
id: #VINS id
tags: "Ansible cloud init example"
state: present
rg_id: #Resource group id
ci_user_data:
- packages:
- apache2
- write_files:
- content: |
<div>
Hello World!
</div>
owner: user:user
path: /var/www/html/index.html
- hostname: test-apache
- ssh_keys:
- rsa_public: ssh-rsa AAAAOasDmLxnD= user@pc
delegate_to: localhost
register: simple_vm

View File

@@ -1,22 +0,0 @@
---
- hosts: localhost
tasks:
- name: manage data disk 01
decort_disk:
authenticator: oauth2
app_id: #Application id from SSO DigitalEnergy
app_secret: #Application secret from SSO DigitalEnergy
controller_url: "https://cloud.digitalenergy.online"
account_name: "account_name"
name: "example_disk"
sep_id: 1
pool: 0
gid: 0
size: 2
type: "D"
description: "Disk created by decort_disk module"
iops: 2000
state: present
verify_ssl: false
delegate_to: localhost

View File

@@ -1,18 +0,0 @@
---
- hosts: localhost
tasks:
- name: manage data disk 01
decort_disk:
authenticator: oauth2
app_id: #Application id from SSO DigitalEnergy
app_secret: #Application secret from SSO DigitalEnergy
controller_url: "https://cloud.digitalenergy.online"
account_name: "account_name"
name: "example_disk"
permanently: False
force_detach: True
reason: "Just to test module decort_disk"
state: absent
verify_ssl: false
delegate_to: localhost

View File

@@ -1,28 +0,0 @@
---
- hosts: localhost
tasks:
- name: manage data disk 01
decort_disk:
authenticator: oauth2
app_id: #Application id from SSO DigitalEnergy
app_secret: #Application secret from SSO DigitalEnergy
controller_url: "https://cloud.digitalenergy.online"
account_name: "account_name"
id: 111
limitIO:
read_bytes_sec: 100
read_bytes_sec_max: 100
read_iops_sec: 100
read_iops_sec_max: 100
size_iops_sec: 100
write_bytes_sec: 100
write_bytes_sec_max: 100
write_iops_sec: 100
write_iops_sec_max: 100
total_bytes_sec: 0
total_iops_sec: 0
total_bytes_sec_max: 0
total_iops_sec_max: 0
verify_ssl: false
delegate_to: localhost

View File

@@ -1,15 +0,0 @@
---
- hosts: localhost
tasks:
- name: manage data disk 01
decort_disk:
authenticator: oauth2
app_id: #Application id from SSO DigitalEnergy
app_secret: #Application secret from SSO DigitalEnergy
controller_url: "https://cloud.digitalenergy.online"
account_name: "account_name"
id: 111
name: "example_disk2"
verify_ssl: false
delegate_to: localhost

View File

@@ -1,15 +0,0 @@
---
- hosts: localhost
tasks:
- name: manage data disk 01
decort_disk:
authenticator: oauth2
app_id: #Application id from SSO DigitalEnergy
app_secret: #Application secret from SSO DigitalEnergy
controller_url: "https://cloud.digitalenergy.online"
account_name: "account_name"
id: 111
state: present
verify_ssl: false
delegate_to: localhost

View File

@@ -1,20 +0,0 @@
- hosts: localhost
tasks:
- name: create
decort_rg:
authenticator: oauth2
controller_url: "https://cloud.digitalenergy.online"
rg_name: "rg_created_by_module"
# or
#rg_id: 999
account_id: 99
quotas:
cpu: 8
ram: 4096
disk: 20
ext_ips: 10
net_transfer: 200
state: present
verify_ssl: false
register: my_rg
delegate_to: localhost

View File

@@ -1,21 +0,0 @@
- hosts: localhost
tasks:
- name: create
decort_rg:
authenticator: oauth2
controller_url: "https://cloud.digitalenergy.online"
rg_name: "rg_created_by_module"
# or
#rg_id: 999
account_id: 99
resType:
- vins
- compute
- k8s
- openshift
- lb
- flipgroup
state: present
verify_ssl: false
register: my_rg
delegate_to: localhost

View File

@@ -1,30 +0,0 @@
- hosts: localhost
tasks:
- name: create
decort_rg:
authenticator: oauth2
controller_url: "https://cloud.digitalenergy.online"
rg_name: "rg_created_by_module"
account_id: 99
owner: "user_1" #Leave blank to set current user as owner.
quotas:
cpu: 8
ram: 4096
disk: 20
ext_ips: 10
net_transfer: 200
access:
action: "grant"
user: "user_2"
right: "RCX"
def_netType: "PRIVATE"
ipcidr: "" "192.168.1.1"
extNetId: 0
extNetIp: "" "10.100.1.10"
resType:
- vins
- compute
state: present
verify_ssl: false
register: my_rg
delegate_to: localhost

View File

@@ -1,15 +0,0 @@
- hosts: localhost
tasks:
- name: create
decort_rg:
authenticator: oauth2
controller_url: "https://cloud.digitalenergy.online"
rg_name: "test_rg"
# or
#rg_id: 999
account_id: 99
state: present
permanently: True
verify_ssl: false
register: my_rg
delegate_to: localhost

View File

@@ -1,12 +0,0 @@
- hosts: localhost
tasks:
- name: create
decort_rg:
authenticator: oauth2
controller_url: "https://cloud.digitalenergy.online"
rg_id: 999 # rg can be restored only by rg id
account_id: 99
state: present
verify_ssl: false
register: my_rg
delegate_to: localhost

View File

@@ -1,14 +0,0 @@
- hosts: localhost
tasks:
- name: create
decort_rg:
authenticator: oauth2
controller_url: "https://cloud.digitalenergy.online"
rg_name: "rg_created_by_module"
# or
#rg_id: 999
account_id: 99
state: enabled
verify_ssl: false
register: my_rg
delegate_to: localhost

View File

@@ -1,18 +0,0 @@
- hosts: localhost
tasks:
- name: create
decort_rg:
authenticator: oauth2
controller_url: "https://cloud.digitalenergy.online"
rg_name: "rg_created_by_module"
# or
#rg_id: 999
account_id: 99
access:
action: "grant"
user: "new_user"
right: "R"
state: present
verify_ssl: false
register: my_rg
delegate_to: localhost

View File

@@ -1,15 +0,0 @@
- hosts: localhost
tasks:
- name: create
decort_rg:
authenticator: oauth2
controller_url: "https://cloud.digitalenergy.online"
rg_name: "old_rg_name"
# or
#rg_id: 1737
account_id: 99
rename: "new_rg_name"
state: present
verify_ssl: false
register: my_rg
delegate_to: localhost

View File

@@ -1,17 +0,0 @@
- hosts: localhost
tasks:
- name: create
decort_rg:
authenticator: oauth2
controller_url: "https://cloud.digitalenergy.online"
rg_name: "rg_created_by_module"
# or
#rg_id: 999
account_id: 99
access:
action: "revoke"
user: "old_user"
state: present
verify_ssl: false
register: my_rg
delegate_to: localhost

View File

@@ -1,16 +0,0 @@
- hosts: localhost
tasks:
- name: create
decort_rg:
authenticator: oauth2
controller_url: "https://cloud.digitalenergy.online"
rg_name: "rg_created_by_module"
# or
#rg_id: 999
account_id: 99
def_netType: "PRIVATE"
def_netId: 199
state: present
verify_ssl: false
register: my_rg
delegate_to: localhost

2
examples/inventory Normal file
View File

@@ -0,0 +1,2 @@
[all]
ansible_master ansible_host=<ansible host IP address> ansible_port=<SSH port on ansible host> ansible_user=root

18
examples/jwt.yaml Normal file
View File

@@ -0,0 +1,18 @@
#
# More details on how to use DECORT Ansible module can be found at:
# https://github.com/rudecs/decort-ansible/wiki
#
- hosts: ansible_master
tasks:
- name: obtain JWT
decort_jwt:
oauth2_url: "{{ decort_sso }}" # "https://sso.digitalenergy.online"
validity: 1200
register: my_jwt
delegate_to: localhost
- name: print out JWT
debug:
var: my_jwt.jwt
delegate_to: localhost

323
examples/main.yaml Normal file
View File

@@ -0,0 +1,323 @@
#
# More details on how to use DECORT Ansible module can be found at:
# https://github.com/rudecs/decort-ansible/wiki
#
- hosts: ansible_master
vars_files:
- vars.yaml
tasks:
- name: obtain JWT
decort_jwt:
oauth2_url: "{{ decort_sso }}"
validity: 1200
register: token
delegate_to: localhost
- name: obtain OS image
decort_osimage:
authenticator: jwt
jwt: "{{ token.jwt }}"
oauth2_url: "{{ decort_sso }}"
controller_url: "{{ decort_ctrl }}"
image_name: "{{ os_image_name }}"
account_name: "{{ target_account_name }}"
verify_ssl: false
register: my_image
delegate_to: localhost
- name: print out the result
debug:
var: my_image.facts
delegate_to: localhost
- name: manage RG
decort_rg:
authenticator: jwt
jwt: "{{ token.jwt }}"
oauth2_url: "{{ decort_sso }}"
controller_url: "{{ decort_ctrl }}"
account_id: 32
rg_name: "{{ target_rg_name }}"
state: present
verify_ssl: false
register: my_rg
delegate_to: localhost
- name: print out the result
debug:
var: my_rg.facts
delegate_to: localhost
- name: manage ViNS 01
decort_vins:
authenticator: jwt
jwt: "{{ token.jwt }}"
oauth2_url: "{{ decort_sso }}"
controller_url: "{{ decort_ctrl }}"
vins_name: "{{ vins01_name }}"
rg_id: "{{ my_rg.facts.id }}"
ext_net_id: "{{ target_ext_net_id }}"
state: present
verify_ssl: false
register: my_vins01
delegate_to: localhost
- name: print out the result
debug:
var: my_vins01.facts
delegate_to: localhost
- name: manage ViNS 02
decort_vins:
authenticator: jwt
jwt: "{{ token.jwt }}"
oauth2_url: "{{ decort_sso }}"
controller_url: "{{ decort_ctrl }}"
vins_name: "{{ vins02_name }}"
rg_id: "{{ my_rg.facts.id }}"
ext_net_id: -1
state: present
verify_ssl: false
register: my_vins02
delegate_to: localhost
- name: print out the result
debug:
var: my_vins02.facts
delegate_to: localhost
- name: manage data disk 01
decort_disk:
authenticator: jwt
jwt: "{{ token.jwt }}"
oauth2_url: "{{ decort_sso }}"
controller_url: "{{ decort_ctrl }}"
name: "{{ datadisk01_name }}"
size: "{{ datadisk01_size }}"
account_name: "{{ target_account_name }}"
pool: data01
place_with: "{{ my_image.facts.id }}"
state: present
verify_ssl: false
register: my_disk01
delegate_to: localhost
- name: print out the result
debug:
var: my_disk01.facts
delegate_to: localhost
- name: manage data disk 02
decort_disk:
authenticator: jwt
jwt: "{{ token.jwt }}"
oauth2_url: "{{ decort_sso }}"
controller_url: "{{ decort_ctrl }}"
name: "{{ datadisk02_name }}"
size: "{{ datadisk02_size }}"
account_name: "{{ target_account_name }}"
pool: data01
place_with: "{{ my_image.facts.id }}"
state: present
verify_ssl: false
register: my_disk02
delegate_to: localhost
- name: print out the result
debug:
var: my_disk02.facts
delegate_to: localhost
- name: manage KVM X86 VM
decort_kvmvm:
authenticator: jwt
jwt: "{{ token.jwt }}"
oauth2_url: "{{ decort_sso }}"
controller_url: "{{ decort_ctrl }}"
name: "{{ vm01_name }}"
arch: KVM_X86
ram: "{{ vm01_ram }}"
cpu: "{{ vm01_cpu }}"
image_id: "{{ my_image.facts.id }}"
boot_disk: "{{ vm01_boot_disk }}"
data_disks:
- "{{ my_disk01.facts.id }}"
- "{{ my_disk02.facts.id }}"
networks:
- type: VINS
id: "{{ my_vins01.facts.id }}"
- type: VINS
id: "{{ my_vins02.facts.id }}"
- type: EXTNET
id: "{{ target_ext_net_id }}"
rg_id: "{{ my_rg.facts.id }}"
state: present
verify_ssl: false
register: my_kvmvm
delegate_to: localhost
- name: print out the result
debug:
var: my_kvmvm.facts
delegate_to: localhost
- name: manage PFW rules on Compute
decort_pfw:
authenticator: jwt
jwt: "{{ token.jwt }}"
oauth2_url: "{{ decort_sso }}"
controller_url: "{{ decort_ctrl }}"
compute_id: "{{ my_kvmvm.facts.id }}"
vins_id: "{{ my_vins01.facts.id }}"
rules:
- public_port_start: 30022
local_port: 22
proto: tcp
- public_port_start: 30080
public_port_end: 30085
local_port: 30080
proto: tcp
state: present
verify_ssl: false
register: my_pfw
delegate_to: localhost
- name: print out the result
debug:
var: my_pfw.facts
delegate_to: localhost
- name: Create k8s cluster with params
decort_k8s:
authenticator: jwt
jwt: "{{ token.jwt }}"
controller_url: "{{ decort_ctrl }}"
k8s_name: "k8s_cluster_name"
wg_name: "k8s_wg_name"
k8ci_id: "{{ k8ci_id }}"
rg_id: "{{ my_rg.facts.id }}"
master_count: 1
master_cpu: 2
master_ram_mb: 2048
master_disk_gb: 20
worker_count: 3
worker_cpu: 1
worker_ram_mb: 1024
worker_disk_gb: 20
extnet_id: "{{ target_ext_net_id }}"
with_lb: True
state: present
register: k8s
delegate_to: localhost
- name: print out the result
debug:
var: k8s
delegate_to: localhost
- name: Disable k8s cluster
decort_k8s:
authenticator: jwt
jwt: "{{ token.jwt }}"
controller_url: "{{ decort_ctrl }}"
k8s_name: "k8s_cluster_name"
wg_name: "k8s_wg_name"
k8ci_id: "{{ k8ci_id }}"
rg_id: "{{ my_rg.facts.id }}"
state: disabled
register: k8s
delegate_to: localhost
- name: print out the result
debug:
var: k8s
delegate_to: localhost
- name: Delete in trash k8s cluster
decort_k8s:
authenticator: jwt
jwt: "{{ token.jwt }}"
controller_url: "{{ decort_ctrl }}"
k8s_name: "k8s_cluster_name"
wg_name: "k8s_wg_name"
k8ci_id: "{{ k8ci_id }}"
rg_id: "{{ my_rg.facts.id }}"
state: absent
permanent: False
register: k8s
delegate_to: localhost
- name: print out the result
debug:
var: k8s
delegate_to: localhost
- name: Restore from trash deleted k8s cluster
decort_k8s:
authenticator: jwt
jwt: "{{ token.jwt }}"
controller_url: "{{ decort_ctrl }}"
k8s_name: "k8s_cluster_name"
wg_name: "k8s_wg_name"
k8ci_id: "{{ k8ci_id }}"
rg_id: "{{ my_rg.facts.id }}"
state: enabled
register: k8s
delegate_to: localhost
- name: print out the result
debug:
var: k8s
delegate_to: localhost
- name: Enable k8s cluster
decort_k8s:
authenticator: jwt
jwt: "{{ token.jwt }}"
controller_url: "{{ decort_ctrl }}"
k8s_name: "k8s_cluster_name"
wg_name: "k8s_wg_name"
k8ci_id: "{{ k8ci_id }}"
rg_id: "{{ my_rg.facts.id }}"
state: enabled
register: k8s
delegate_to: localhost
- name: Enable k8s cluster
decort_k8s:
authenticator: jwt
jwt: "{{ token.jwt }}"
controller_url: "{{ decort_ctrl }}"
k8s_name: "k8s_cluster_name"
wg_name: "k8s_wg_name"
k8ci_id: "{{ k8ci_id }}"
rg_id: "{{ my_rg.facts.id }}"
state: enabled
started: True
register: k8s
delegate_to: localhost
- name: print out the result
debug:
var: k8s
delegate_to: localhost
- name: Destroy k8s cluster
decort_k8s:
authenticator: jwt
jwt: "{{ token.jwt }}"
controller_url: "{{ decort_ctrl }}"
k8s_name: "k8s_cluster_name"
wg_name: "k8s_wg_name"
k8ci_id: "{{ k8ci_id }}"
rg_id: "{{ my_rg.facts.id }}"
state: absent
permanent: True
register: k8s
delegate_to: localhost
- name: print out the result
debug:
var: k8s
delegate_to: localhost

21
examples/prepenv.sh Normal file
View File

@@ -0,0 +1,21 @@
#!/bin/bash
# 1. As this file will contain sensitive data (application ID & Secret pair) put this file
# in a directory, where only you will have access to it, e.g. your ~/.ssh directory.
# 2. Make sure this file is not readable by anybody else (chmod 400).
# 3. Paste your Application ID (obtained from DECORT SSO application) to DECORT_APP_ID.
# 4. Paste your Application Secret (obtained from DECORT SSO application) to DECORT_APP_SECRET.
# 5. Paste DECORT SSO application URL to DECORT_OAUTH2_URL.
#
# Source this file into shell to prepare environment for running DECORT Ansible module, e.g.
# . ~/.ssh/prepenv.sh
#
# More informaiton on DECORT Ansible module can be found at:
# https://github.com/rudecs/decort-ansible/wiki
#
export DECORT_APP_ID="put your application ID here"
export DECORT_APP_SECRET="put your application secret here"
export DECORT_OAUTH2_URL="put DECORT SSO URL here" # "https://sso.digitalenergy.online"
export ANSIBLE_HOST_KEY_CHEKCING=False

26
examples/vars.yaml Normal file
View File

@@ -0,0 +1,26 @@
#
# More details on how to use DECORT Ansible module can be found at:
# https://github.com/rudecs/decort-ansible/wiki
#
decort_sso: "put DECORT SSO application URL here" # "https://sso.digitalenergy.online"
decort_ctrl: "put DECORT controller URL here" # "https://ds1.digitalenergy.online"
target_account_name: "your account name"
target_rg_name: "target resource group name"
os_image_name: "OS image name"
vins01_name: "Vins01-ansible"
vins02_name: "Vins02-ansible"
target_ext_net_id: 0
datadisk01_name: "Data01-ansible"
datadisk01_size: 5
datadisk02_name: "Data02-ansible"
datadisk02_size: 5
vm01_name: "Vm01-ansible"
vm01_cpu: 1
vm01_ram: 1024
vm01_boot_disk: 10

View File

@@ -252,127 +252,161 @@ from ansible.module_utils.basic import env_fallback
from ansible.module_utils.decort_utils import *
class decort_disk(DecortController):
def __init__(self,arg_amodule):
super(decort_disk, self).__init__(arg_amodule)
def __init__(self,amodule):
super(decort_disk, self).__init__(amodule)
self.validated_account_id = 0
self.validated_disk_id = 0
self.disk_facts = None # will hold Disk facts
self.acc_facts = None # will hold Account facts
validated_acc_id = 0
validated_acc_info = None
validated_disk_id = 0
self.disk_id = 0
self.account_id = 0
validated_disk_facts = None
# limitIO check for exclusive parameters
if arg_amodule.params['limitIO']:
self.disk_check_iotune_arg(arg_amodule.params['limitIO'])
if amodule.params['limitIO']:
limit = amodule.params['limitIO']
if limit['total_bytes_sec'] > 0 and limit['read_bytes_sec'] > 0 or \
limit['total_bytes_sec'] > 0 and limit['write_bytes_sec'] > 0:
self.result['failed'] = True
self.result['msg'] = ("total and read/write of bytes_sec cannot be set at the same time.")
amodule.fail_json(**self.result)
elif limit['total_iops_sec'] > 0 and limit['read_iops_sec'] > 0 or \
limit['total_iops_sec'] > 0 and limit['write_iops_sec'] > 0:
self.result['failed'] = True
self.result['msg'] = ("total and read/write of iops_sec cannot be set at the same time.")
amodule.fail_json(**self.result)
elif limit['total_bytes_sec_max'] > 0 and limit['read_bytes_sec_max'] > 0 or \
limit['total_bytes_sec_max'] > 0 and limit['write_bytes_sec_max'] > 0:
self.result['failed'] = True
self.result['msg'] = ("total and read/write of bytes_sec_max cannot be set at the same time.")
amodule.fail_json(**self.result)
elif limit['total_iops_sec_max'] > 0 and limit['read_iops_sec_max'] > 0 or \
limit['total_iops_sec_max'] > 0 and limit['write_iops_sec_max'] > 0:
self.result['failed'] = True
self.result['msg'] = ("total and read/write of iops_sec_max cannot be set at the same time.")
amodule.fail_json(**self.result)
if arg_amodule.params['id'] or arg_amodule.params['name']:
if arg_amodule.params['account_id'] or arg_amodule.params['account_name'] :
validated_acc_id,validated_acc_info = self.account_find(arg_amodule.params['account_name'],arg_amodule.params['account_id'])
if not validated_acc_id:
self.result['failed'] = True
self.result['changed'] = False
self.result['msg'] = ("Current user does not have access to the account ID {} / "
"name '{}' or non-existent account specified.").format(arg_amodule.params['account_id'],
arg_amodule.params['account_name'])
self.fail_json(**self.result)
else:
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 "",
account_id=self.acc_id,
check_state=False,
)
if amodule.params['account_id']:
self.validated_account_id = amodule.params['account_id']
elif amodule.params['account_name']:
self.validated_account_id, _ = self.account_find(amodule.params['account_name'])
elif not amodule.params['id'] and not amodule.params['account_name']:
self.result['failed'] = True
self.result['msg'] = ("Cannot found disk without account id or name.")
amodule.fail_json(**self.result)
if self.validated_account_id == 0 and not amodule.params['id']:
# we failed either to find or access the specified account - fail the module
self.result['failed'] = True
self.result['msg'] = ("Cannot find account '{}'").format(amodule.params['account_name'])
amodule.fail_json(**self.result)
if amodule.params['id'] or amodule.params['name']:
self.validated_disk_id, self.disk_facts = self.decort_disk_find(amodule)
else:
self.result['failed'] = True
self.result['msg'] = ("Cannot manage Disk when its ID is 0 and name is empty")
self.fail_json(**self.result)
if arg_amodule.params['place_with']:
image_id, image_facts = self.image_find(arg_amodule.params['place_with'], "", 0)
arg_amodule.params['sep_id'] = image_facts['sepId']
self.disk_id = validated_disk_id
self.disk_info = validated_disk_facts
def create(self):
self.result['msg'] = ("Cannot find or create disk without disk name or disk id")
amodule.fail_json(**self.result)
self.disk_id = self.disk_create(accountId=self.acc_id,
name = self.amodule.params['name'],
description=self.amodule.params['annotation'],
size=self.amodule.params['size'],
type=self.amodule.params['type'],
iops=self.amodule.params['iops'],
sep_id=self.amodule.params['sep_id'],
pool=self.amodule.params['pool'],
)
#IO tune
if self.amodule.params['limitIO']:
self.disk_limitIO(self.amodule.params['limitIO'],self.disk_id)
#set share status
if self.amodule.params['shareable'] and self.amodule.params['type'] == "D":
self.dick_share(self.disk_id,self.amodule.params['shareable'])
return
if amodule.params['place_with'] > 0:
image_id, image_facts = self.image_find(amodule.params['place_with'], "", 0)
amodule.params['sep_id']= image_facts['sepId']
def action(self,restore=False):
#restore never be done
if restore:
self.disk_restore(self.disk_id)
#rename if id present
if self.amodule.params['name'] != self.disk_info['name']:
self.disk_rename(diskId=self.disk_id,
name=self.amodule.params['name'])
self.disk_info['name'] = self.amodule.params['name']
#resize
if self.amodule.params['size'] != self.disk_info['sizeMax']:
self.disk_resize(self.disk_info,self.amodule.params['size'])
#IO TUNE
if self.amodule.params['limitIO']:
clean_io = [param for param in self.amodule.params['limitIO'] \
if self.amodule.params['limitIO'][param] == None]
for key in clean_io: del self.amodule.params['limitIO'][key]
if self.amodule.params['limitIO'] != self.disk_info['iotune']:
self.disk_limitIO(self.disk_id,self.amodule.params['limitIO'])
#share check/update
#raise Exception(self.amodule.params['shareable'])
if self.amodule.params['shareable'] != self.disk_info['shareable'] and \
self.amodule.params['type'] == "D":
self.disk_share(self.disk_id,self.amodule.params['shareable'])
return
def delete(self):
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"
return
def rename(self):
self.disk_rename(diskId = self.disk_id,
name = self.amodule.params['name'])
self.disk_info['name'] = self.amodule.params['name']
return
def nop(self):
def decort_disk_create(self, amodule):
if not self.disk_facts:
self.disk_id = self.disk_create(accountId=self.validated_account_id, gid=amodule.params['gid'],
name=amodule.params['name'], description=amodule.params['description'],
size=amodule.params['size'], type=amodule.params['type'],
iops=amodule.params['iops'],
sep_id=amodule.params['sep_id'], pool=amodule.params['pool'])
self.result['msg'] = ("Disk with id '{}' successfully created.").format(self.disk_id)
elif self.disk_facts['status'] in ["DESTROYED", "PURGED"]:
if not amodule.params['limitIO']:
amodule.params['limitIO'] = self.disk_facts['iotune']
if amodule.params['sep_id'] == 0:
validated_sep_id = self.disk_facts['sepId']
else:
validated_sep_id = amodule.params['sep_id']
if amodule.params['pool'] == 0:
validated_pool = self.disk_facts['pool']
else:
validated_pool = amodule.params['pool']
if amodule.params['size'] == 0:
validated_size = self.disk_facts['sizeMax']
else:
validated_size = amodule.params['size']
if amodule.params['gid'] == 0:
validated_gid = self.disk_facts['gid']
else:
validated_gid = amodule.params['gid']
self.disk_id = self.disk_create(accountId=self.validated_account_id, gid=validated_gid,
name=self.disk_facts['name'], description=amodule.params['description'],
size=validated_size, type=self.disk_facts['type'],
iops=self.disk_facts['iotune']['total_iops_sec'],
sep_id=validated_sep_id, pool=validated_pool)
if not amodule.params['limitIO']:
amodule.params['limitIO'] = self.disk_facts['iotune']
self.result['msg'] = ("Disk with id '{}' successfully recreated.").format(self.disk_id)
self.result['failed'] = False
self.result['changed'] = False
if self.disk_id:
self.result['msg'] = ("No state change required for Disk ID {} because of its "
"current status '{}'.").format(self.disk_id, self.disk_info['status'])
else:
self.result['msg'] = ("No state change to '{}' can be done for "
"non-existent Disk.").format(self.amodule.params['state'])
return
self.result['changed'] = True
return self.disk_id
def package_facts(self, check_mode=False):
def decort_disk_delete(self, amodule):
self.disk_id = self.disk_delete(disk_id=self.validated_disk_id,
detach=amodule.params['force_detach'],
permanently=amodule.params['permanently'],
reason=amodule.params['reason'])
return
def decort_disk_find(self, amodule):
if amodule.params['name'] and not amodule.params['id']:
self.disk_id, self.disk_facts = self.disk_find(disk_id=self.validated_disk_id,
name=amodule.params['name'],
account_id=self.validated_account_id)
elif self.validated_disk_id > 0:
self.disk_id, self.disk_facts = self.disk_find(disk_id=self.validated_disk_id,
name=self.disk_facts['name'],
account_id=0)
elif amodule.params['id']:
self.disk_id, self.disk_facts = self.disk_find(disk_id=amodule.params['id'],
name=amodule.params['name'],
account_id=0)
if not self.disk_id and not amodule.params['name']:
self.result['failed'] = True
self.result['msg'] = "Specified Disk ID {} not found.".format(amodule.params['id'])
amodule.fail_json(**self.result)
self.result['facts'] = decort_disk.decort_disk_package_facts(self.disk_facts)
return self.disk_id, self.disk_facts
def decort_disk_limitIO(self, amodule):
self.limits = amodule.params['limitIO']
self.disk_limitIO(limits = self.limits,
diskId = self.validated_disk_id)
self.disk_facts['iotune'] = amodule.params['limitIO']
self.result['facts'] = decort_disk.decort_disk_package_facts(self.disk_facts)
return
def decort_disk_rename(self, amodule):
self.disk_rename(diskId = self.validated_disk_id,
name = amodule.params['name'])
self.disk_facts['name'] = amodule.params['name']
self.result['facts'] = decort_disk.decort_disk_package_facts(self.disk_facts)
self.result['msg'] = ("Disk with id '{}',successfully renamed to '{}'.").format(self.validated_disk_id, amodule.params['name'])
return
def decort_disk_package_facts(disk_facts, check_mode=False):
ret_dict = dict(id=0,
name="none",
state="CHECK_MODE",
@@ -384,27 +418,29 @@ class decort_disk(DecortController):
gid=0
)
if check_mode or self.disk_info is None:
if check_mode:
# in check mode return immediately with the default values
return ret_dict
# remove io param with zero value
clean_io = [param for param in self.disk_info['iotune'] if self.disk_info['iotune'][param] == 0]
for key in clean_io: del self.disk_info['iotune'][key]
ret_dict['id'] = self.disk_info['id']
ret_dict['name'] = self.disk_info['name']
ret_dict['size'] = self.disk_info['sizeMax']
ret_dict['state'] = self.disk_info['status']
ret_dict['account_id'] = self.disk_info['accountId']
ret_dict['sep_id'] = self.disk_info['sepId']
ret_dict['pool'] = self.disk_info['pool']
ret_dict['attached_to'] = self.disk_info['vmid']
ret_dict['gid'] = self.disk_info['gid']
ret_dict['iotune'] = self.disk_info['iotune']
if disk_facts is None:
# if void facts provided - change state value to ABSENT and return
ret_dict['state'] = "ABSENT"
return ret_dict
ret_dict['id'] = disk_facts['id']
ret_dict['name'] = disk_facts['name']
ret_dict['size'] = disk_facts['sizeMax']
ret_dict['state'] = disk_facts['status']
ret_dict['account_id'] = disk_facts['accountId']
ret_dict['sep_id'] = disk_facts['sepId']
ret_dict['pool'] = disk_facts['pool']
ret_dict['attached_to'] = disk_facts['vmid']
ret_dict['gid'] = disk_facts['gid']
ret_dict['iotune'] = disk_facts['iotune']
return ret_dict
@staticmethod
def build_parameters():
def decort_disk_parameters():
"""Build and return a dictionary of parameters expected by decort_disk module in a form accepted
by AnsibleModule utility class."""
@@ -440,30 +476,32 @@ class decort_disk(DecortController):
place_with=dict(type='int', default=0),
pool=dict(type='str', default=''),
sep_id=dict(type='int', default=0),
gid=dict(type='int', default=0),
size=dict(type='int', default=0),
type=dict(type='str',
required=False,
default="D",
choices=['B', 'D', 'T']),
iops=dict(type='int',required=False,default=2000),
iops=dict(type='int', default=2000),
limitIO=dict(type='dict',
options=dict(
total_bytes_sec=dict(required=False,type='int'),
read_bytes_sec=dict(required=False,type='int'),
write_bytes_sec=dict(required=False,type='int'),
total_iops_sec=dict(required=False,type='int'),
read_iops_sec=dict(required=False,type='int'),
write_iops_sec=dict(required=False,type='int'),
total_bytes_sec_max=dict(required=False,type='int'),
read_bytes_sec_max=dict(required=False,type='int'),
write_bytes_sec_max=dict(required=False,type='int'),
total_iops_sec_max=dict(required=False,type='int'),
read_iops_sec_max=dict(required=False,type='int'),
write_iops_sec_max=dict(required=False,type='int'),
size_iops_sec=dict(required=False,type='int'),)),
total_bytes_sec=dict(default=0,type='int'),
read_bytes_sec=dict(default=0,type='int'),
write_bytes_sec=dict(default=0,type='int'),
total_iops_sec=dict(default=0,type='int'),
read_iops_sec=dict(default=0,type='int'),
write_iops_sec=dict(default=0,type='int'),
total_bytes_sec_max=dict(default=0,type='int'),
read_bytes_sec_max=dict(default=0,type='int'),
write_bytes_sec_max=dict(default=0,type='int'),
total_iops_sec_max=dict(default=0,type='int'),
read_iops_sec_max=dict(default=0,type='int'),
write_iops_sec_max=dict(default=0,type='int'),
size_iops_sec=dict(default=0,type='int'),)),
permanently=dict(type='bool', required=False, default=False),
shareable=dict(type='bool', required=False, default=False),
reason=dict(type='str', required=False,default='Managed by Ansible decort_disk'),
reason=dict(type='int', required=False),
description=dict(type='str', required=False,
default="Disk created with Ansible Decort_disk module."),
state=dict(type='str',
default='present',
choices=['absent', 'present']),
@@ -476,7 +514,7 @@ class decort_disk(DecortController):
)
def main():
module_parameters = decort_disk.build_parameters()
module_parameters = decort_disk.decort_disk_parameters()
amodule = AnsibleModule(argument_spec=module_parameters,
supports_check_mode=True,
@@ -492,51 +530,105 @@ def main():
)
decon = decort_disk(amodule)
#
#Full range of Disk status is as follows:
#
# "ASSIGNED","MODELED", "CREATING","CREATED","DELETED", "DESTROYED","PURGED",
#
if decon.disk_id:
#disk exist
if decon.disk_info['status'] in ["MODELED", "CREATING"]:
decon.result['failed'] = True
decon.result['changed'] = False
decon.result['msg'] = ("No change can be done for existing Disk ID {} because of its current "
"status '{}'").format(decon.disk_id, decon.disk_info['status'])
# "ASSIGNED","CREATED","DELETED","PURGED", "DESTROYED"
elif decon.disk_info['status'] in ["ASSIGNED","CREATED"]:
if amodule.params['state'] == 'absent':
decon.delete()
elif amodule.params['state'] == 'present':
decon.action()
elif decon.disk_info['status'] in ["PURGED", "DESTROYED"]:
#re-provision disk
if amodule.params['state'] in ('present'):
decon.create()
else:
decon.nop()
elif decon.disk_info['status'] == "DELETED":
if amodule.params['state'] in ('present'):
decon.action(restore=True)
else:
decon.nop()
else:
# preexisting Disk was not found
if amodule.params['state'] == 'absent':
decon.nop()
else:
decon.create()
if decon.validated_disk_id == 0 and amodule.params['state'] == 'present':
# if sep_id or place_with not specified, then exit with error
if amodule.params['sep_id'] == 0 and amodule.params['place_with'] == 0:
decon.result['msg'] = ("To create a disk, you must specify sep_id or place_with.")\
.format(decon.validated_disk_id)
amodule.fail_json(**decon.result)
# if id cannot cannot be found and have a state 'present', then create a new disk
decon.validated_disk_id = decon.decort_disk_create(amodule)
_, decon.disk_facts = decon.decort_disk_find(amodule)
decon.result['changed'] = True
decon.result['msg'] = ("Disk with id '{}' successfully created.").format(decon.validated_disk_id)
if decon.result['failed']:
amodule.fail_json(**decon.result)
else:
if decon.result['changed'] and amodule.params['state'] in ('present'):
_, decon.disk_info = decon.disk_find(decon.disk_id)
decon.result['facts'] = decon.package_facts(amodule.check_mode)
elif decon.validated_disk_id == 0 and amodule.params['state'] == 'absent' and amodule.params['name']:
# if disk with specified name cannot be found and have a state 'absent', then nothing to do,
# specified disk already deleted
decon.result['msg'] = ("Disk with name '{}' has already been deleted or your account does not have"
" access to it.")\
.format(amodule.params['name'])
amodule.exit_json(**decon.result)
elif decon.validated_disk_id == 0 and amodule.params['state'] == 'absent' and amodule.params['id']:
# if disk with specified id cannot be found and have a state 'absent', then nothing to do,
# specified disk already deleted
decon.result['msg'] = ("Disk with name '{}' has already been deleted or your account does not have"
" access to it.")\
.format(decon.validated_disk_id)
amodule.exit_json(**decon.result)
elif decon.disk_facts['status'] == "CREATED":
if amodule.params['state'] == 'present':
# if disk status in condition "CREATED" and state "present", nothing to do,
# specified disk already created
decon.result['msg'] = "Specified Disk ID {} already created.".format(decon.validated_disk_id)
if amodule.params['state'] == 'absent':
# if disk status in condition "CREATED" and state "absent", delete the disk
decon.validated_disk_id = decon.decort_disk_delete(amodule)
decon.disk_facts['status'] = "DESTROYED"
decon.result['msg'] = ("Disk with id '{}' successfully deleted.").format(decon.disk_facts['id'])
decon.result['facts'] = decon.decort_disk_package_facts(decon.disk_facts)
amodule.exit_json(**decon.result)
elif decon.disk_facts['status'] in ["MODELED", "CREATING" ]:
# if disk in status "MODELED" or "CREATING",
# then we cannot do anything, while disk in this status
decon.result['changed'] = False
decon.result['msg'] = ("Cannot do anything with disk id '{}',please wait until disk will be created.")\
.format(decon.validated_disk_id)
amodule.fail_json(**decon.result)
elif decon.disk_facts['status'] == "DELETED":
if amodule.params['state'] == 'present':
# if disk in "DELETED" status and "present" state, restore
decon.disk_restore(decon.validated_disk_id)
_, decon.disk_facts = decon.decort_disk_find(amodule)
decon.result['changed'] = True
decon.result['msg'] = ("Disk with id '{}',restored successfully.").format(decon.validated_disk_id)
elif amodule.params['state'] == 'absent':
# if disk in "DELETED" status and "absent" state, nothing to do
decon.result['msg'] = "Specified Disk ID {} already destroyed.".format(decon.validated_disk_id)
amodule.exit_json(**decon.result)
elif decon.disk_facts['status'] in ["DESTROYED", "PURGED"]:
if amodule.params['state'] == 'present':
decon.validated_disk_id = decon.decort_disk_create(amodule)
_, decon.disk_facts = decon.decort_disk_find(amodule)
elif amodule.params['state'] == 'absent':
decon.result['msg'] = "Specified Disk ID {} already destroyed.".format(decon.validated_disk_id)
amodule.exit_json(**decon.result)
if amodule.params['state'] == "present":
if decon.disk_facts['sizeMax'] != amodule.params['size']:
if decon.disk_facts['sizeMax'] > amodule.params['size'] and amodule.params['size'] != 0:
decon.result['failed'] = True
decon.result['msg'] = ("Disk id '{}', cannot reduce disk size.").format(decon.validated_disk_id)
amodule.fail_json(**decon.result)
elif decon.disk_facts['sizeMax'] < amodule.params['size']:
decon.disk_resize(disk_facts=decon.disk_facts,
new_size=amodule.params['size'])
decon.result['changed'] = True
decon.disk_facts['size'] = amodule.params['size']
decon.result['msg'] = ("Disk with id '{}',resized successfully.").format(decon.validated_disk_id)
if amodule.params['limitIO'] and amodule.params['limitIO'] != decon.disk_facts['iotune']:
decon.decort_disk_limitIO(amodule)
decon.result['changed'] = True
decon.result['msg'] = ("Disk with id '{}',limited successfully.").format(decon.validated_disk_id)
if amodule.params['name'] and amodule.params['id']:
if amodule.params['name'] != decon.disk_facts['name']:
decon.decort_disk_rename(amodule)
decon.result['changed'] = True
decon.result['msg'] = ("Disk with id '{}',renamed successfully from '{}' to '{}'.")\
.format(decon.validated_disk_id, decon.disk_facts['name'], amodule.params['name'])
amodule.exit_json(**decon.result)
if __name__ == "__main__":
main()
#SHARE

View File

@@ -1,68 +1,19 @@
#!/usr/bin/python
#
# Digital Enegry Cloud Orchestration Technology (DECORT) modules for Ansible
# Copyright: (c) 2018-2023 Digital Energy Cloud Solutions LLC
# Copyright: (c) 2018-2021 Digital Energy Cloud Solutions LLC
#
# Apache License 2.0 (see http://www.apache.org/licenses/LICENSE-2.0.txt)
#
#
# Author: Aleksandr Malyavin (aleksandr.malyavin@digitalenergy.online)
#
ANSIBLE_METADATA = {'metadata_version': '1.1',
'status': ['preview'],
'supported_by': 'community'}
DOCUMENTATION = '''
---
---
'''
EXAMPLES = '''
- name: Create k8s cluster
decort_k8s:
verify_ssl: false
authenticator: jwt
jwt: "{{ run_jwt.jwt }}"
controller_url: "{{CONTROLLER_URL}}"
name: SOME_NAME
rg_id: {{RG_ID}}
k8ci_id: 10
master_count: 3
master_cpu: 2
master_ram: 2048
master_disk: 10
state: present
permanent: True
started: True
getConfig: True
network_plugin: flannel
workers:
- name: wg1
ram: 1024
cpu: 2
disk: 10
num: 1
labels:
- disktype1=ssd1
- disktype2=ssd2
- disktype3=ssd3
taints:
- key1=value1:NoSchedule
- key2=value2:NoSchedule
- key3=value3:NoSchedule
annotations:
- node.deckhouse.io/group1=g1
- node.deckhouse.io/group2=g2
- node.deckhouse.io/group3=g3
- name: wg2
ram: 1024
cpu: 2
disk: 10
num: 1
labels:
- apptype=main
annotations:
- node.mainapp.domen.local/group1=g1
register: some_cluster
'''
from ansible.module_utils.basic import AnsibleModule
from ansible.module_utils.basic import env_fallback
from ansible.module_utils.decort_utils import *
@@ -70,17 +21,11 @@ from ansible.module_utils.decort_utils import *
class decort_k8s(DecortController):
def __init__(self,arg_amodule):
super(decort_k8s, self).__init__(arg_amodule)
validated_acc_id = 0
validated_rg_id = 0
validated_rg_facts = None
validated_k8ci_id = 0
self.k8s_should_exist = False
if not arg_amodule.params['workers']:
self.result['failed'] = True
self.result['changed'] = False
self.result['msg'] = "At least one worker group must be present"
self.fail_json(**self.result)
if arg_amodule.params['name'] == "" and arg_amodule.params['id'] == 0:
self.result['failed'] = True
@@ -163,11 +108,9 @@ class decort_k8s(DecortController):
ret_dict['techStatus'] = self.k8s_info['techStatus']
ret_dict['state'] = self.k8s_info['status']
ret_dict['rg_id'] = self.rg_id
ret_dict['vins_id'] = self.k8s_vins_id
ret_dict['account_id'] = self.acc_id
if self.amodule.params['getConfig'] and self.k8s_info['techStatus'] == "STARTED":
ret_dict['config'] = self.k8s_getConfig()
return ret_dict
def nop(self):
@@ -206,11 +149,10 @@ class decort_k8s(DecortController):
self.k8s_provision(self.amodule.params['name'],
self.amodule.params['k8ci_id'],
self.amodule.params['rg_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_ram_mb'],
self.amodule.params['master_disk_gb'],
self.amodule.params['workers'][0],
self.amodule.params['extnet_id'],
self.amodule.params['with_lb'],
@@ -223,12 +165,12 @@ class decort_k8s(DecortController):
if self.k8s_id:
self.k8s_should_exist = True
if self.k8s_id and len(self.amodule.params['workers'])>1 :
if self.k8s_id and self.amodule.params['workers'][1]:
self.k8s_workers_modify(self.k8s_info,self.amodule.params['workers'])
return
def destroy(self):
self.k8s_delete(self.k8s_id,self.amodule.params['permanent'])
self.k8s_delete(self.k8s_id)
self.k8s_info['status'] = 'DELETED'
self.k8s_should_exist = False
return
@@ -290,17 +232,16 @@ class decort_k8s(DecortController):
rg_id=dict(type='int', default=0),
rg_name=dict(type='str',default=""),
k8ci_id=dict(type='int', required=True),
network_plugin=dict(type='str',required=False,default="flannel"),
wg_name=dict(type='str', required=False),
master_count=dict(type='int', default=1),
master_cpu=dict(type='int', default=2),
master_ram=dict(type='int', default=2048),
master_disk=dict(type='int', default=10),
master_ram_mb=dict(type='int', default=2048),
master_disk_gb=dict(type='int', default=10),
worker_count=dict(type='int', default=1),
worker_cpu=dict(type='int', default=1),
worker_ram_mb=dict(type='int', default=1024),
worker_disk_gb=dict(type='int', default=10),
workers=dict(type='list',required=True),
workers=dict(type='list'),
extnet_id=dict(type='int', default=0),
description=dict(type='str', default="Created by decort ansible module"),
with_lb=dict(type='bool', default=True),

View File

@@ -1,11 +1,15 @@
#!/usr/bin/python
#
# Digital Enegry Cloud Orchestration Technology (DECORT) modules for Ansible
# Copyright: (c) 2018-2023 Digital Energy Cloud Solutions LLC
# Copyright: (c) 2018-2021 Digital Energy Cloud Solutions LLC
#
# Apache License 2.0 (see http://www.apache.org/licenses/LICENSE-2.0.txt)
#
#
# Author: Sergey Shubin (sergey.shubin@digitalenergy.online)
#
ANSIBLE_METADATA = {'metadata_version': '1.1',
'status': ['preview'],
'supported_by': 'community'}
@@ -20,14 +24,15 @@ description: >
network port forwarding rules, restart guest OS and delete a virtual machine thus releasing
corresponding cloud resources.
version_added: "2.2"
author:
- Sergey Shubin <sergey.shubin@digitalenergy.online>
requirements:
- python >= 3.8
- python >= 2.6
- PyJWT Python module
- requests Python module
- netaddr Python module
- decort_utils utility library (module)
- DECORT cloud platform version 3.8.6 or higher
- DECORT cloud platform version 3.6.1 or higher
notes:
- Environment variables can be used to pass selected parameters to the module, see details below.
- Specified Oauth2 provider must be trusted by the DECORT cloud controller on which JWT will be used.
@@ -226,7 +231,7 @@ options:
choices: [ present, absent, poweredon, poweredoff, halted, paused, check ]
tags:
description:
- Dict of custom tags to be assigned to the VM.
- String of custom tags to be assigned to the VM (This feature is not implemented yet!).
- These tags are arbitrary text that can be used for grouping or indexing the VMs by other applications.
required: no
user:
@@ -282,14 +287,24 @@ EXAMPLES = '''
name: SimpleVM
cpu: 2
ram: 4096
boot_disk: 10
boot_disk:
size: 10
model: ovs
pool: boot
image_name: "Ubuntu 16.04 v1.1"
data_disks:
- {{DISK_ID}}
- size: 50
model: ovs
pool: data
port_forwards:
- ext_port: 21022
int_port: 22
proto: tcp
- ext_port: 80
int_port: 80
proto: tcp
state: present
tags:
PROJECT:Ansible
STATUS:Test
tags: "PROJECT:Ansible STATUS:Test"
account_name: "Development"
rg_name: "ANewVDC"
delegate_to: localhost
@@ -322,6 +337,18 @@ EXAMPLES = '''
state: poweredoff
delegate_to: localhost
register: simple_vm
- name: check if VM exists and read in its specs.
decort_kvmvm:
authenticator: oauth2
app_id: "{{ MY_APP_ID }}"
app_secret: "{{ MY_APP_SECRET }}"
controller_url: "https://ds1.digitalenergy.online"
name: "{{ TARGET_VM_NAME }}"
rg_name: "{{ TARGET_VDC_NAME }}"
account_name: "{{ TRAGET_TENANT }}"
state: check
delegate_to: localhost
register: existing_vm
'''
RETURN = '''
@@ -546,8 +573,6 @@ class decort_kvmvm(DecortController):
image_id=image_facts['id'],
annotation=self.amodule.params['annotation'],
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)
self.comp_should_exist = True
@@ -566,10 +591,6 @@ class decort_kvmvm(DecortController):
'techStatus': "STOPPED",
'interfaces': [], # new compute instance is created network-less
'disks': [], # new compute instance is created without any data disks attached
'tags': {},
'affinityLabel': "",
'affinityRules': [],
'antiAffinityRules': [],
}
#
@@ -631,13 +652,11 @@ class decort_kvmvm(DecortController):
self.compute_resize(self.comp_info,
self.amodule.params['cpu'], self.amodule.params['ram'],
wait_for_state_change=arg_wait_cycles)
self.compute_affinity(self.comp_info,
self.amodule.params['tag'],
self.amodule.params['aff_rule'],
self.amodule.params['aaff_rule'],
label=self.amodule.params['affinity_label'])
label=self.amodule.params['affinity_label'],)
return
def package_facts(self, check_mode=False):
@@ -667,7 +686,6 @@ class decort_kvmvm(DecortController):
public_ips=[], # direct IPs; this list can be empty
private_ips=[], # IPs on ViNSes; usually, at least one IP is listed
nat_ip="", # IP of the external ViNS interface; can be empty.
tags={},
)
if check_mode or self.comp_info is None:
@@ -685,8 +703,6 @@ class decort_kvmvm(DecortController):
ret_dict['tech_status'] = self.comp_info['techStatus']
ret_dict['account_id'] = self.comp_info['accountId']
ret_dict['rg_id'] = self.comp_info['rgId']
if self.comp_info['tags']:
ret_dict['tags'] = self.comp_info['tags']
# if the VM is an imported VM, then the 'accounts' list may be empty,
# so check for this case before trying to access login and passowrd values
if len(self.comp_info['osUsers']):
@@ -748,8 +764,6 @@ class decort_kvmvm(DecortController):
required=True,
choices=['legacy', 'oauth2', 'jwt']),
boot_disk=dict(type='int', required=False),
sep_id=dict(type='int', required=False),
pool=dict(type='str', required=False),
controller_url=dict(type='str', required=True),
# count=dict(type='int', required=False, default=1),
cpu=dict(type='int', required=False),
@@ -776,7 +790,7 @@ class decort_kvmvm(DecortController):
rg_name=dict(type='str', default=""),
ssh_key=dict(type='str', required=False),
ssh_key_user=dict(type='str', required=False),
tag=dict(type='dict', required=False),
tag=dict(type='list', required=False),
affinity_label=dict(type='str', required=False),
aff_rule=dict(type='list', required=False),
aaff_rule=dict(type='list', required=False),

View File

@@ -1,7 +1,7 @@
#!/usr/bin/python
#
# Digital Enegry Cloud Orchestration Technology (DECORT) modules for Ansible
# Copyright: (c) 2018-2023 Digital Energy Cloud Solutions LLC
# Copyright: (c) 2018-2022 Digital Energy Cloud Solutions LLC
#
# Apache License 2.0 (see http://www.apache.org/licenses/LICENSE-2.0.txt)
#

View File

@@ -424,7 +424,6 @@ class decort_osimage(DecortController):
ret_dict['pool'] = arg_osimage_facts['pool']
ret_dict['state'] = arg_osimage_facts['status']
ret_dict['linkto'] = arg_osimage_facts['linkTo']
ret_dict['accountId'] = arg_osimage_facts['accountId']
return ret_dict
@@ -522,11 +521,9 @@ def main():
decon.validated_image_id = decort_osimage.decort_osimage_package_facts(image_facts)['id']
elif amodule.params['state'] == "absent":
if amodule.params['image_name'] or amodule.params['image_id'] and\
decort_osimage.decort_osimage_package_facts(image_facts)['accountId'] == amodule.params['account_Id']:
amodule.image_id_delete = decon.validated_image_id
decort_osimage.decort_image_delete(decon,amodule)
elif amodule.params['state'] == "absent" and amodule.params['image_name'] or amodule.params['image_id'] and decort_osimage.decort_osimage_package_facts(image_facts)['accountId'] == amodule.params['account_Id']:
amodule.image_id_delete = decon.validated_image_id
decort_osimage.decort_image_delete(decon,amodule)

View File

@@ -209,255 +209,90 @@ from ansible.module_utils.basic import env_fallback
from ansible.module_utils.decort_utils import *
class decort_rg(DecortController):
def __init__(self,amodule):
super(decort_rg, self).__init__(amodule)
self.validated_acc_id = 0
self.validated_rg_id = 0
self.validated_rg_facts = None
def decort_rg_package_facts(arg_rg_facts, arg_check_mode=False):
"""Package a dictionary of RG facts according to the decort_rg module specification. This dictionary will
be returned to the upstream Ansible engine at the completion of the module run.
if self.amodule.params['account_id']:
self.validated_acc_id, _ = self.account_find("", amodule.params['account_id'])
elif amodule.params['account_name']:
self.validated_acc_id, _ = self.account_find(amodule.params['account_name'])
if not self.validated_acc_id:
# we failed to locate account by either name or ID - abort with an error
self.result['failed'] = True
self.result['msg'] = ("Current user does not have access to the requested account "
"or non-existent account specified.")
self.fail_json(**self.result)
@param arg_rg_facts: dictionary with RG facts as returned by API call to .../rg/get
@param arg_check_mode: boolean that tells if this Ansible module is run in check mode
"""
if amodule.params['rg_id'] > 0:
self.validated_rg_id = amodule.params['rg_id']
# Check if the RG with the specified parameters already exists
self.validated_rg_id, self.rg_facts = self.rg_find(self.validated_acc_id,
arg_rg_id = self.validated_rg_id,
arg_rg_name=amodule.params['rg_name'],
arg_check_state=False)
if amodule.params['state'] != "absent":
self.rg_should_exist = True
else:
self.rg_should_exist = False
def access(self):
should_change_access = False
acc_granted = False
for rg_item in self.rg_facts['acl']:
if rg_item['userGroupId'] == self.amodule.params['access']['user']:
acc_granted = True
if self.amodule.params['access']['action'] == 'grant':
if rg_item['right'] != self.amodule.params['access']['right']:
should_change_access = True
if self.amodule.params['access']['action'] == 'revoke':
should_change_access = True
if acc_granted == False and self.amodule.params['access']['action'] == 'grant':
should_change_access = True
if should_change_access == True:
self.rg_access(self.validated_rg_id, self.amodule.params['access'])
self.rg_facts['access'] = self.amodule.params['access']
self.rg_should_exist = True
return
def error(self):
self.result['failed'] = True
self.result['changed'] = False
if self.validated_rg_id > 0:
self.result['msg'] = ("Invalid target state '{}' requested for rg ID {} in the "
"current status '{}'.").format(self.validated_rg_id,
self.amodule.params['state'],
self.rg_facts['status'])
else:
self.result['msg'] = ("Invalid target state '{}' requested for non-existent rg name '{}' "
"in account ID {} ").format(self.amodule.params['state'],
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',)
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 incorrect_quota['Requested']:
self.result['msg'] = ("Cannot limit less than already reserved'{}'").format(incorrect_quota)
self.result['failed'] = True
if self.result['failed'] != True:
self.rg_update(self.rg_facts, self.amodule.params['quotas'],
self.amodule.params['resType'], self.amodule.params['rename'])
self.rg_should_exist = True
return
def setDefNet(self):
if self.amodule.params['def_netId'] != self.rg_facts['def_net_id']:
self.rg_setDefNet(self.validated_rg_id,
self.amodule.params['def_netType'],
self.amodule.params['def_netId'])
self.rg_should_exist = True
return
def create(self):
self.validated_rg_id = self.rg_provision(self.validated_acc_id,
self.amodule.params['rg_name'],
self.amodule.params['owner'],
self.amodule.params['annotation'],
self.amodule.params['resType'],
self.amodule.params['def_netType'],
self.amodule.params['ipcidr'],
self.amodule.params['extNetId'],
self.amodule.params['extNetIp'],
self.amodule.params['quotas'],
"", # this is location code. TODO: add module argument
)
self.validated_rg_id, self.rg_facts = self.rg_find(self.validated_acc_id,
self.validated_rg_id,
arg_rg_name="",
arg_check_state=False)
self.rg_should_exist = True
return
def enable(self):
self.rg_enable(self.validated_rg_id,
self.amodule.params['state'])
if self.amodule.params['state'] == "enabled":
self.rg_facts['status'] = 'CREATED'
else:
self.rg_facts['status'] = 'DISABLED'
self.rg_should_exist = True
return
def restore(self):
self.rg_restore(self.validated_rg_id)
self.rg_facts['status'] = 'DISABLED'
self.rg_should_exist = True
return
def destroy(self):
self.rg_delete(self.validated_rg_id, self.amodule.params['permanently'])
if self.amodule.params['permanently'] == True:
self.rg_facts['status'] = 'DESTROYED'
else:
self.rg_facts['status'] = 'DELETED'
self.rg_should_exist = False
return
def package_facts(self, check_mode=False):
"""Package a dictionary of RG facts according to the decort_rg module specification. This dictionary will
be returned to the upstream Ansible engine at the completion of the module run.
@param arg_rg_facts: dictionary with RG facts as returned by API call to .../rg/get
@param arg_check_mode: boolean that tells if this Ansible module is run in check mode
"""
ret_dict = dict(id=0,
name="none",
state="CHECK_MODE",
)
if check_mode:
# in check mode return immediately with the default values
return ret_dict
#if arg_rg_facts is None:
# # if void facts provided - change state value to ABSENT and return
# ret_dict['state'] = "ABSENT"
# return ret_dict
ret_dict['id'] = self.rg_facts['id']
ret_dict['name'] = self.rg_facts['name']
ret_dict['state'] = self.rg_facts['status']
ret_dict['account_id'] = self.rg_facts['accountId']
ret_dict['gid'] = self.rg_facts['gid']
ret_dict['quota'] = self.rg_facts['resourceLimits']
ret_dict['resTypes'] = self.rg_facts['resourceTypes']
ret_dict['defNetId'] = self.rg_facts['def_net_id']
ret_dict['defNetType'] = self.rg_facts['def_net_type']
ret_dict['ViNS'] = self.rg_facts['vins']
ret_dict['computes'] = self.rg_facts['vms']
ret_dict = dict(id=0,
name="none",
state="CHECK_MODE",
)
if arg_check_mode:
# in check mode return immediately with the default values
return ret_dict
def parameters():
"""Build and return a dictionary of parameters expected by decort_rg module in a form accepted
by AnsibleModule utility class."""
if arg_rg_facts is None:
# if void facts provided - change state value to ABSENT and return
ret_dict['state'] = "ABSENT"
return ret_dict
return dict(
account_id=dict(type='int', required=False),
account_name=dict(type='str', required=False, default=''),
access=dict(type='dict'),
annotation=dict(type='str', required=False, default=''),
app_id=dict(type='str',
ret_dict['id'] = arg_rg_facts['id']
ret_dict['name'] = arg_rg_facts['name']
ret_dict['state'] = arg_rg_facts['status']
ret_dict['account_id'] = arg_rg_facts['accountId']
ret_dict['gid'] = arg_rg_facts['gid']
return ret_dict
def decort_rg_parameters():
"""Build and return a dictionary of parameters expected by decort_rg module in a form accepted
by AnsibleModule utility class."""
return dict(
account_id=dict(type='int', required=False),
account_name=dict(type='str', required=False, default=''),
annotation=dict(type='str', required=False, default=''),
app_id=dict(type='str',
required=False,
fallback=(env_fallback, ['DECORT_APP_ID'])),
app_secret=dict(type='str',
required=False,
fallback=(env_fallback, ['DECORT_APP_ID'])),
app_secret=dict(type='str',
required=False,
fallback=(env_fallback, ['DECORT_APP_SECRET']),
no_log=True),
authenticator=dict(type='str',
required=True,
choices=['legacy', 'oauth2', 'jwt']),
controller_url=dict(type='str', required=True),
# datacenter=dict(type='str', required=False, default=''),
def_netType=dict(type='str', choices=['PRIVATE','PUBLIC', 'NONE'], default='PRIVATE'),
def_netId=dict(type='int', default=0),
extNetId=dict(type='int', default=0),
extNetIp=dict(type='str', default=""),
owner=dict(type='str', default=""),
ipcidr=dict(type='str', default=""),
jwt=dict(type='str',
required=False,
fallback=(env_fallback, ['DECORT_JWT']),
no_log=True),
oauth2_url=dict(type='str',
required=False,
fallback=(env_fallback, ['DECORT_OAUTH2_URL'])),
rename=dict(type='str', default=""),
password=dict(type='str',
required=False,
fallback=(env_fallback, ['DECORT_PASSWORD']),
no_log=True),
quotas=dict(type='dict', required=False),
resType=dict(type='list'),
state=dict(type='str',
default='present',
choices=['absent', 'disabled', 'enabled', 'present']),
permanently=dict(type='bool',
default='False'),
user=dict(type='str',
fallback=(env_fallback, ['DECORT_APP_SECRET']),
no_log=True),
authenticator=dict(type='str',
required=True,
choices=['legacy', 'oauth2', 'jwt']),
controller_url=dict(type='str', required=True),
# datacenter=dict(type='str', required=False, default=''),
jwt=dict(type='str',
required=False,
fallback=(env_fallback, ['DECORT_JWT']),
no_log=True),
oauth2_url=dict(type='str',
required=False,
fallback=(env_fallback, ['DECORT_OAUTH2_URL'])),
password=dict(type='str',
required=False,
fallback=(env_fallback, ['DECORT_USER'])),
rg_name=dict(type='str', required=False,),
rg_id=dict(type='int', required=False, default=0),
verify_ssl=dict(type='bool', required=False, default=True),
workflow_callback=dict(type='str', required=False),
workflow_context=dict(type='str', required=False),
)
fallback=(env_fallback, ['DECORT_PASSWORD']),
no_log=True),
quotas=dict(type='dict', required=False),
state=dict(type='str',
default='present',
choices=['absent', 'disabled', 'enabled', 'present']),
user=dict(type='str',
required=False,
fallback=(env_fallback, ['DECORT_USER'])),
rg_name=dict(type='str', required=True,),
verify_ssl=dict(type='bool', required=False, default=True),
workflow_callback=dict(type='str', required=False),
workflow_context=dict(type='str', required=False),
)
# Workflow digest:
# 1) authenticate to DECORT controller & validate authentication by issuing API call - done when creating DECORTController
# 2) check if the RG with the specified id or rg_name:name exists
# 3) if RG does not exist -> deploy
# 4) if RG exists: check desired state, desired configuration -> initiate action accordingly
# 5) report result to Ansible
# Workflow digest:
# 1) authenticate to DECORT controller & validate authentication by issuing API call - done when creating DECORTController
# 2) check if the RG with the specified id or rg_name:name exists
# 3) if RG does not exist -> deploy
# 4) if RG exists: check desired state, desired configuration -> initiate action accordingly
# 5) report result to Ansible
def main():
module_parameters = decort_rg.parameters()
module_parameters = decort_rg_parameters()
amodule = AnsibleModule(argument_spec=module_parameters,
supports_check_mode=True,
@@ -472,52 +307,157 @@ def main():
],
)
decon = decort_rg(amodule)
#amodule.check_mode=True
if decon.validated_rg_id > 0:
if decon.rg_facts['status'] in ["MODELED", "DISABLING", "ENABLING", "DELETING", "DESTROYING", "CONFIRMED"]:
decon.error()
elif decon.rg_facts['status'] in ("CREATED"):
if amodule.params['state'] == 'absent':
decon.destroy()
elif amodule.params['state'] == "disabled":
decon.enable()
if amodule.params['state'] in ['present', 'enabled']:
if amodule.params['quotas'] or amodule.params['resType'] or amodule.params['rename'] != "":
decon.update()
if amodule.params['access']:
decon.access()
if amodule.params['def_netId'] > 0:
decon.setDefNet()
elif decon.rg_facts['status'] == "DELETED":
if amodule.params['state'] == 'absent' and amodule.params['permanently'] == True:
decon.destroy()
elif amodule.params['state'] == 'present':
decon.restore()
elif decon.rg_facts['status'] in ("DISABLED"):
if amodule.params['state'] == 'absent':
decon.destroy()
elif amodule.params['state'] == ("enabled"):
decon.enable()
decon = DecortController(amodule)
# We need valid Account ID to manage RG.
# Account may be specified either by account_id or account_name. In both cases we
# have to validate account presence and accesibility by the current user.
validated_acc_id = 0
if decon.check_amodule_argument('account_id', False):
validated_acc_id, _ = decon.account_find("", amodule.params['account_id'])
else:
if amodule.params['state'] in ('present', 'enabled'):
decon.create()
if amodule.params['access']:
decon.access()
elif amodule.params['state'] in ('disabled'):
decon.error()
decon.check_amodule_argument('account_name') # if no account_name, this function will abort module
validated_acc_id, _ = decon.account_find(amodule.params['account_name'])
if not validated_acc_id:
# we failed to locate account by either name or ID - abort with an error
decon.result['failed'] = True
decon.result['msg'] = ("Current user does not have access to the requested account "
"or non-existent account specified.")
decon.fail_json(**decon.result)
# Check if the RG with the specified parameters already exists
rg_id, rg_facts = decon.rg_find(validated_acc_id,
0, arg_rg_name=amodule.params['rg_name'],
arg_check_state=False)
rg_should_exist = True
if rg_id:
if rg_facts['status'] in ["MODELED", "DISABLING", "ENABLING", "DELETING", "DESTROYING"]:
# error: nothing can be done to existing RG in the listed statii regardless of
# the requested state
decon.result['failed'] = True
decon.result['changed'] = False
decon.result['msg'] = ("No change can be done for existing RG ID {} because of its current "
"status '{}'").format(rg_id, rg_facts['status'])
elif rg_facts['status'] == "DISABLED":
if amodule.params['state'] == 'absent':
decon.rg_delete(arg_rg_id=rg_id, arg_permanently=True)
rg_facts['status'] = 'DESTROYED'
rg_should_exist = False
elif amodule.params['state'] in ('present', 'disabled'):
# update quotas
decon.rg_quotas(rg_facts, amodule.params['quotas'])
elif amodule.params['state'] == 'enabled':
# update quotas and enable
decon.rg_quotas(rg_facts, amodule.params['quotas'])
decon.rg_state(rg_facts, 'enabled')
elif rg_facts['status'] == "CREATED":
if amodule.params['state'] == 'absent':
decon.rg_delete(arg_rg_id=rg_id, arg_permanently=True)
rg_facts['status'] = 'DESTROYED'
rg_should_exist = False
elif amodule.params['state'] in ('present', 'enabled'):
# update quotas
decon.rg_quotas(rg_facts, amodule.params['quotas'])
elif amodule.params['state'] == 'disabled':
# disable and update quotas
decon.rg_state(rg_facts, 'disabled')
decon.rg_quotas(rg_facts, amodule.params['quotas'])
elif rg_facts['status'] == "DELETED":
if amodule.params['state'] in ['present', 'enabled']:
# restore and enable
# TODO: check if restore RG API returns the new RG ID of the restored RG instance.
decon.rg_restore(arg_rg_id=rg_id)
decon.rg_state(rg_facts, 'enabled')
# TODO: Not sure what to do with the quotas after RG is restored. May need to update rg_facts.
rg_should_exist = True
elif amodule.params['state'] == 'absent':
# destroy permanently
decon.rg_delete(arg_rg_id=rg_id, arg_permanently=True)
rg_facts['status'] = 'DESTROYED'
rg_should_exist = False
elif amodule.params['state'] == 'disabled':
# error
decon.result['failed'] = True
decon.result['changed'] = False
decon.result['msg'] = ("Invalid target state '{}' requested for RG ID {} in the "
"current status '{}'").format(rg_id,
amodule.params['state'],
rg_facts['status'])
rg_should_exist = False
elif rg_facts['status'] == "DESTROYED":
if amodule.params['state'] in ('present', 'enabled'):
# need to re-provision RG
decon.check_amodule_argument('rg_name')
# As we already have validated account ID we can create RG and get rg_id on success
# pass empty string for location code, rg_provision will select the 1st location
rg_id = decon.rg_provision(validated_acc_id,
amodule.params['rg_name'], decon.decort_username,
amodule.params['quotas'],
"", # this is location code. TODO: add module argument
amodule.params['annotation'])
rg_should_exist = True
elif amodule.params['state'] == 'absent':
# nop
decon.result['failed'] = False
decon.result['changed'] = False
decon.result['msg'] = ("No state change required for RG ID {} because of its "
"current status '{}'").format(rg_id,
rg_facts['status'])
rg_should_exist = False
elif amodule.params['state'] == 'disabled':
# error
decon.result['failed'] = True
decon.result['changed'] = False
decon.result['msg'] = ("Invalid target state '{}' requested for RG ID {} in the "
"current status '{}'").format(rg_id,
amodule.params['state'],
rg_facts['status'])
else:
# Preexisting RG was not found.
rg_should_exist = False # we will change it back to True if RG is explicitly created or restored
# If requested state is 'absent' - nothing to do
if amodule.params['state'] == 'absent':
decon.result['failed'] = False
decon.result['changed'] = False
decon.result['msg'] = ("Nothing to do as target state 'absent' was requested for "
"non-existent RG name '{}'").format(amodule.params['rg_name'])
elif amodule.params['state'] in ('present', 'enabled'):
# Target RG does not exist yet - create it and store the returned ID in rg_id variable for later use
# To create RG we need account name (or account ID) and RG name - check
# that these parameters are present and proceed.
decon.check_amodule_argument('rg_name')
# as we already have account ID we can create RG and get rg_id on success
# pass empty string for location code, rg_provision will select the 1st location
rg_id = decon.rg_provision(validated_acc_id,
amodule.params['rg_name'], decon.decort_username,
amodule.params['quotas'],
"", # this is location code. TODO: add module argument
amodule.params['annotation'])
rg_should_exist = True
elif amodule.params['state'] == 'disabled':
decon.result['failed'] = True
decon.result['changed'] = False
decon.result['msg'] = ("Invalid target state '{}' requested for non-existent "
"RG name '{}' ").format(amodule.params['state'],
amodule.params['rg_name'])
#
# conditional switch end - complete module run
if decon.result['failed']:
amodule.fail_json(**decon.result)
else:
if decon.rg_should_exist:
decon.result['facts'] = decon.package_facts(amodule.check_mode)
amodule.exit_json(**decon.result)
else:
amodule.exit_json(**decon.result)
# prepare RG facts to be returned as part of decon.result and then call exit_json(...)
# rg_facts = None
if rg_should_exist:
if decon.result['changed']:
# If we arrive here, there is a good chance that the RG is present - get fresh RG facts from
# the cloud by RG ID.
# Otherwise, RG facts from previous call (when the RG was still in existence) will be returned.
_, rg_facts = decon.rg_find(arg_account_id=0, arg_rg_id=rg_id)
decon.result['facts'] = decort_rg_package_facts(rg_facts, amodule.check_mode)
amodule.exit_json(**decon.result)
if __name__ == "__main__":
main()

View File

@@ -6,6 +6,9 @@
# Apache License 2.0 (see http://www.apache.org/licenses/LICENSE-2.0.txt)
#
#
# Author: Sergey Shubin (sergey.shubin@digitalenergy.online)
#
ANSIBLE_METADATA = {'metadata_version': '1.1',
'status': ['preview'],
@@ -20,13 +23,14 @@ description: >
modify its characteristics, and delete it.
version_added: "2.2"
author:
- Sergey Shubin <sergey.shubin@digitalenergy.online>
requirements:
- python >= 3.8
- python >= 2.6
- PyJWT Python module
- requests Python module
- netaddr Python module
- decort_utils utility library (module)
- DECORT cloud platform version 3.8.6 or higher
- DECORT cloud platform version 3.6.1 or higher
notes:
- Environment variables can be used to pass selected parameters to the module, see details below.
- Specified Oauth2 provider must be trusted by the DECORT cloud controller on which JWT will be used.
@@ -256,15 +260,14 @@ class decort_vins(DecortController):
if arg_amodule.params['vins_id']:
# expect existing ViNS with the specified ID
# This call to vins_find will abort the module if no ViNS with such ID is present
self.vins_id, self.vins_facts = self.vins_find(arg_amodule.params['vins_id'],check_state=False)
if self.vins_id == 0:
self.vins_id, self.vins_facts = self.vins_find(arg_amodule.params['vins_id'])
if not self.vins_id:
self.result['failed'] = True
self.result['msg'] = "Specified ViNS ID {} not found.".format(arg_amodule.params['vins_id'])
self.fail_json(**self.result)
self.vins_level = "ID"
#raise Exception(self.vins_facts)
validated_acc_id = self.vins_facts['accountId']
validated_rg_id = self.vins_facts['rgId']
validated_acc_id = vins_facts['accountId']
validated_rg_id = vins_facts['rgId']
elif arg_amodule.params['rg_id']:
# expect ViNS @ RG level in the RG with specified ID
@@ -288,7 +291,7 @@ class decort_vins(DecortController):
self.result['failed'] = True
self.result['msg'] = ("Current user does not have access to the requested account "
"or non-existent account specified.")
self.amodule.fail_json(**self.result)
self.fail_json(**self.result)
if arg_amodule.params['rg_name'] != "": # at this point we know that rg_id=0
# expect ViNS @ RG level in the RG with specified name under specified account
# RG with the specified name must be present under the account, otherwise abort the module
@@ -297,7 +300,7 @@ class decort_vins(DecortController):
rg_facts['status'] in ["DESTROYING", "DESTROYED", "DELETING", "DELETED", "DISABLING", "ENABLING"]):
self.result['failed'] = True
self.result['msg'] = "RG name '{}' not found or has invalid state.".format(arg_amodule.params['rg_name'])
self.amodule.fail_json(**self.result)
self.fail_json(**self.result)
# This call to vins_find may return vins_id=0 if no ViNS with this name found under specified RG
self.vins_id, self.vins_facts = self.vins_find(vins_id=0, vins_name=arg_amodule.params['vins_name'],
account_id=0, # set to 0, as we are looking for ViNS under RG
@@ -320,13 +323,12 @@ class decort_vins(DecortController):
# this is "invalid arguments combination" sink
# if we end up here, it means that module was invoked with vins_id=0 and rg_id=0
self.result['failed'] = True
self.result['msg'] = "Cannot find ViNS by name"
if arg_amodule.params['account_id'] == 0 and arg_amodule.params['account_name'] == '':
if arg_amodule.params['account_id'] == 0 and arg_amodule.params['account_name'] == "":
self.result['msg'] = "Cannot find ViNS by name when account name is empty and account ID is 0."
if arg_amodule.params['rg_name'] == "":
# rg_name without account specified
self.result['msg'] = "Cannot find ViNS by name when RG name is empty and RG ID is 0."
self.amodule.fail_json(**self.result)
self.fail_json(**self.result)
return
self.rg_id = validated_rg_id
@@ -463,6 +465,11 @@ class decort_vins(DecortController):
ret_dict['ext_ip_addr'] = ""
ret_dict['ext_net_id'] = -1
# arg_vins_facts['vnfs']['GW']['config']
# ext_ip_addr -> ext_net_ip
# ??? -> ext_net_id
# tech_status -> techStatus
return ret_dict
@staticmethod
@@ -471,7 +478,7 @@ class decort_vins(DecortController):
by AnsibleModule utility class."""
return dict(
account_id=dict(type='int', required=False,default=0),
account_id=dict(type='int', required=False),
account_name=dict(type='str', required=False, default=''),
annotation=dict(type='str', required=False, default=''),
app_id=dict(type='str',
@@ -514,7 +521,7 @@ class decort_vins(DecortController):
rg_name=dict(type='str', required=False, default=''),
verify_ssl=dict(type='bool', required=False, default=True),
vins_id=dict(type='int', required=False, default=0),
vins_name=dict(type='str', required=False,default=""),
vins_name=dict(type='str', required=True),
workflow_callback=dict(type='str', required=False),
workflow_context=dict(type='str', required=False),
)
@@ -541,9 +548,6 @@ def main():
['app_id', 'app_secret'],
['user', 'password'],
],
required_one_of=[
['vins_id', 'vins_name'],
],
)
decon = decort_vins(amodule)

View File

@@ -1,10 +1,13 @@
#
# Digital Enegry Cloud Orchestration Technology (DECORT) modules for Ansible
# Copyright: (c) 2018-2023 Digital Energy Cloud Solutions LLC
# Copyright: (c) 2018-2021 Digital Energy Cloud Solutions LLC
#
# Apache License 2.0 (see http://www.apache.org/licenses/LICENSE-2.0.txt)
#
#
# Author: Sergey Shubin (sergey.shubin@digitalenergy.online)
#
"""
This is the library of utility functions and classes for managing DECORT cloud platform.
@@ -25,7 +28,7 @@ Requirements:
- PyJWT Python module
- requests Python module
- netaddr Python module
- DECORT cloud platform version 3.8.6 or higher
- DECORT cloud platform version 3.6.1 or higher
"""
import json
@@ -36,6 +39,17 @@ import requests
from ansible.module_utils.basic import AnsibleModule
#
# TODO: the following functionality to be implemented and/or tested
# 4) workflow callbacks
# 5) run phase states
# 6) vm_tags - set/manage VM tags
# 7) vm_attributes - change VM attributes (name, annotation) after VM creation - do we need this in Ansible?
# 9) test vm_restore() method and execution plans that involve vm_restore()
#
class DecortController(object):
"""DecortController is a utility class that holds target controller context and handles API requests formatting
based on the requested authentication type.
@@ -541,7 +555,7 @@ class DecortController(object):
return
def compute_delete(self, comp_id, permanently=False,detach=True):
def compute_delete(self, comp_id, permanently=False):
"""Delete a Compute instance identified by its ID. It is assumed that the Compute with the specified
ID exists.
@@ -559,8 +573,7 @@ class DecortController(object):
return
api_params = dict(computeId=comp_id,
permanently=permanently,
detachDisks=detach, )
permanently=permanently, )
self.decort_api_call(requests.post, "/restmachine/cloudapi/compute/delete", api_params)
# On success the above call will return here. On error it will abort execution by calling fail_json.
self.result['failed'] = False
@@ -658,7 +671,7 @@ class DecortController(object):
# if we have validated RG ID at this point, look up Compute by name in this RG
# rg.vms list contains IDs of compute instances registered with this RG until compute is
# destroyed. So we may see here computes in "active" and DELETED states.
for runner in comp_list['data']:
for runner in comp_list:
if runner['name'] == comp_name and runner['rgId'] == rg_id:
if not check_state or runner['status'] not in COMP_INVALID_STATES:
ret_comp_id = runner['id']
@@ -748,12 +761,9 @@ class DecortController(object):
def kvmvm_provision(self, rg_id,
comp_name, arch,
cpu, ram,
boot_disk,
image_id,
boot_disk, image_id,
annotation="",
userdata=None,
sep_id=None,
pool_name=None,
start_on_create=True):
"""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.
@@ -798,10 +808,8 @@ class DecortController(object):
cpu=cpu, ram=ram,
imageId=image_id,
bootDisk=boot_disk,
sepId=sep_id,
pool=pool_name,
start=start_on_create, # start_machine parameter requires DECORT API ver 3.3.1 or higher
interfaces='[]') # we create VM without any network connections
netType="NONE") # we create VM without any network connections
if userdata:
api_params['userdata'] = json.dumps(userdata) # we need to pass a string object as "userdata"
@@ -897,7 +905,7 @@ class DecortController(object):
vins_iface_list.append(iface_data)
elif iface['connType'] == 'VLAN':
ip_addr = netaddr.IPAddress(iface['ipAddress'])
for erunner in extnet_list['data']:
for erunner in extnet_list:
# match by IP address range
# if iface['ipAddress'] <-> erunner['name']
# enet_iface_list.append(erunner['id'])
@@ -1180,135 +1188,47 @@ class DecortController(object):
return False
def compute_affinity(self,comp_dict,tags,aff,aaff,label):
"""
Manage Compute Tags,Affinitylabel and rules
@param (dict) comp_dict: dictionary of the Compute parameters
@param (dict) tags: dictionary of the tags
@param (list) aff: affinity rules
@param (list) aaff: antiaffinity rules
@param (str) label: affinity group label
"""
def compute_affinity(self,comp_dict,tags,aff,aaff,label=""):
self.result['waypoints'] = "{} -> {}".format(self.result['waypoints'], "compute_affinity")
api_params = dict(computeId=comp_dict['id'])
self.decort_api_call(requests.post, "/restmachine/cloudapi/compute/affinityRulesClear", api_params)
self.decort_api_call(requests.post, "/restmachine/cloudapi/compute/antiAffinityRulesClear", api_params)
if tags:
for tag in tags.items():
if tag not in comp_dict['tags'].items():
api_params = dict(computeId=comp_dict['id'],
key=tag[0],
value=tag[1], )
self.decort_api_call(requests.post, "/restmachine/cloudapi/compute/tagAdd", api_params)
self.result['failed'] = False
self.result['changed'] = True
if comp_dict['tags']:
for tag in comp_dict['tags'].items():
if tag not in tags.items():
api_params = dict(computeId=comp_dict['id'],
key=tag[0],)
self.decort_api_call(requests.post, "/restmachine/cloudapi/compute/tagRemove", api_params)
self.result['failed'] = False
self.result['changed'] = True
else:
if comp_dict['tags']:
for tag in comp_dict['tags'].items():
api_params = dict(computeId=comp_dict['id'],
key=tag[0],)
self.decort_api_call(requests.post, "/restmachine/cloudapi/compute/tagRemove", api_params)
self.result['failed'] = False
self.result['changed'] = True
for tag in tags:
api_params = dict(computeId=comp_dict['id'],
key=tag['key'],
value=tag['value'], )
self.decort_api_call(requests.post, "/restmachine/cloudapi/compute/tagAdd", api_params)
if label:
if comp_dict['affinityLabel'] == "":
api_params = dict(computeId=comp_dict['id'],
affinityLabel=label,)
self.decort_api_call(requests.post, "/restmachine/cloudapi/compute/affinityLabelSet", api_params)
self.result['failed'] = False
self.result['changed'] = True
else:
if comp_dict['affinityLabel'] != "":
api_params = dict(computeId=comp_dict['id'])
self.decort_api_call(requests.post, "/restmachine/cloudapi/compute/affinityLabelRemove", api_params)
self.result['failed'] = False
self.result['changed'] = True
affrule_del = []
affrule_add = []
aaffrule_del = []
aaffrule_add = []
#AFFINITY
if comp_dict['affinityRules']:
for rule in comp_dict['affinityRules']:
del rule['guid']
if rule not in aff:
affrule_del.append(rule)
api_params = dict(computeId=comp_dict['id'],
affinityLabel=label,)
self.decort_api_call(requests.post, "/restmachine/cloudapi/compute/affinityLabelSet", api_params)
if aff:
for rule in aff:
if rule not in comp_dict['affinityRules']:
affrule_add.append(rule)
#ANTI AFFINITY
if comp_dict['antiAffinityRules']:
for rule in comp_dict['antiAffinityRules']:
del rule['guid']
if rule not in aaff:
aaffrule_del.append(rule)
if len(aff)>0:
for rule in aff:
api_params = dict(computeId=comp_dict['id'],
key=rule['key'],
value=rule['value'],
topology=rule['topology'],
mode=rule['mode'],
policy=rule['policy'],)
self.decort_api_call(requests.post, "/restmachine/cloudapi/compute/affinityRuleAdd", api_params)
if aaff:
for rule in aaff:
if rule not in comp_dict['antiAffinityRules']:
aaffrule_add.append(rule)
#AFFINITY
if len (affrule_del):
for rule in affrule_del:
api_params = dict(computeId=comp_dict['id'],
key=rule['key'],
value=rule['value'],
topology=rule['topology'],
mode=rule['mode'],
policy=rule['policy'],)
self.decort_api_call(requests.post, "/restmachine/cloudapi/compute/affinityRuleRemove", api_params)
self.result['failed'] = False
self.result['changed'] = True
if len(affrule_add)>0:
for rule in affrule_add:
api_params = dict(computeId=comp_dict['id'],
key=rule['key'],
value=rule['value'],
topology=rule['topology'],
mode=rule['mode'],
policy=rule['policy'],)
self.decort_api_call(requests.post, "/restmachine/cloudapi/compute/affinityRuleAdd", api_params)
self.result['failed'] = False
self.result['changed'] = True
#ANTI AFFINITY
if len(aaffrule_del):
for rule in aaffrule_del:
api_params = dict(computeId=comp_dict['id'],
key=rule['key'],
value=rule['value'],
topology=rule['topology'],
mode=rule['mode'],
policy=rule['policy'],)
self.decort_api_call(requests.post, "/restmachine/cloudapi/compute/antiAffinityRuleRemove", api_params)
self.result['failed'] = False
self.result['changed'] = True
if len(aaff)>0:
for rule in aaff:
api_params = dict(computeId=comp_dict['id'],
key=rule['key'],
value=rule['value'],
topology=rule['topology'],
mode=rule['mode'],
policy=rule['policy'],)
self.decort_api_call(requests.post, "/restmachine/cloudapi/compute/antiAffinityRuleAdd", api_params)
if len(aaffrule_add)>0:
for rule in aaffrule_add:
api_params = dict(computeId=comp_dict['id'],
key=rule['key'],
value=rule['value'],
topology=rule['topology'],
mode=rule['mode'],
policy=rule['policy'],)
self.decort_api_call(requests.post, "/restmachine/cloudapi/compute/antiAffinityRuleAdd", api_params)
self.result['failed'] = False
self.result['changed'] = True
return
self.result['failed'] = False
self.result['changed'] = True
###################################
# OS image manipulation methods
###################################
@@ -1522,7 +1442,7 @@ class DecortController(object):
###################################
# Resource Group (RG) manipulation methods
###################################
def rg_delete(self, rg_id, permanently):
def rg_delete(self, rg_id, permanently=False):
"""Deletes specified VDC.
@param (int) rg_id: integer value that identifies the RG to be deleted.
@@ -1580,27 +1500,7 @@ class DecortController(object):
return ret_rg_id, ret_rg_dict
def rg_access(self, arg_rg_id, arg_access):
self.result['waypoints'] = "{} -> {}".format(self.result['waypoints'], "rg_access")
if self.amodule.check_mode:
self.result['failed'] = False
self.result['msg'] = ("rg_access() in check mode: access to RG id '{}' was "
"requested with '{}'.").format(arg_rg_id, arg_access)
return 0
api_params=dict(rgId=arg_rg_id,)
if arg_access['action'] == "grant":
api_params['user']=arg_access['user'],
api_params['right']=arg_access['right'],
api_resp = self.decort_api_call(requests.post, "/restmachine/cloudapi/rg/accessGrant", api_params)
else:
api_params['user']=arg_access['user'],
api_resp = self.decort_api_call(requests.post, "/restmachine/cloudapi/rg/accessRevoke", api_params)
self.result['changed'] = True
return
def rg_find(self, arg_account_id, arg_rg_id, arg_rg_name="", arg_check_state=True):
def rg_find(self, arg_account_id, arg_rg_id=0, arg_rg_name="", arg_check_state=True):
"""Returns non zero RG ID and a dictionary with RG details on success, 0 and empty dictionary otherwise.
This method does not fail the run if RG cannot be located by its name (arg_rg_name), because this could be
an indicator of the requested RG never existed before.
@@ -1629,7 +1529,7 @@ class DecortController(object):
self.result['waypoints'] = "{} -> {}".format(self.result['waypoints'], "rg_find")
ret_rg_id = arg_rg_id
ret_rg_id = 0
api_params = dict()
ret_rg_dict = None
@@ -1651,18 +1551,15 @@ 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['includedeleted'] = False
#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)
api_params['accountId'] = arg_account_id
api_resp = self.decort_api_call(requests.post, "/restmachine/cloudapi/account/listRG", api_params)
if api_resp.status_code == 200:
account_specs = json.loads(api_resp.content.decode('utf8'))
#api_params.pop('accountId')
for rg_item in account_specs['data']:
#
if rg_item['name'] == arg_rg_name:
api_params.pop('accountId')
for rg_item in account_specs:
got_id, got_specs = self._rg_get_by_id(rg_item['id'])
if got_id and got_specs['name'] == arg_rg_name:
# name matches
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
@@ -1677,44 +1574,7 @@ class DecortController(object):
return ret_rg_id, ret_rg_dict
def rg_setDefNet(self, arg_rg_id, arg_net_type, arg_net_id):
self.result['waypoints'] = "{} -> {}".format(self.result['waypoints'], "rg_setDefNet")
if self.amodule.check_mode:
self.result['failed'] = False
self.result['msg'] = ("rg_setDefNet() in check mode: setDefNet RG id '{}' was "
"requested.").format(arg_rg_id)
return 0
if arg_net_type == "NONE":
arg_net_type = "PRIVATE"
api_params = dict(rgId=arg_rg_id,
netType=arg_net_type,
netId=arg_net_id,)
api_resp = self.decort_api_call(requests.post, "/restmachine/cloudapi/rg/setDefNet", api_params)
self.result['changed'] = True
return
def rg_enable(self, arg_rg_id, arg_state):
self.result['waypoints'] = "{} -> {}".format(self.result['waypoints'], "rg_enable")
if self.amodule.check_mode:
self.result['failed'] = False
self.result['msg'] = ("rg_enable() in check mode: '{}' RG id '{}' was "
"requested.").format(arg_state, arg_rg_id)
return 0
api_params = dict(rgId=arg_rg_id)
if arg_state == "enabled":
api_resp = self.decort_api_call(requests.post, "/restmachine/cloudapi/rg/enable", api_params)
else:
api_resp = self.decort_api_call(requests.post, "/restmachine/cloudapi/rg/disable", api_params)
self.result['changed'] = True
return
def rg_provision(self, arg_account_id, arg_rg_name, arg_username, arg_desc, restype, arg_net_type, ipcidr, arg_extNetId, arg_extIp, arg_quota={}, location=""):
def rg_provision(self, arg_account_id, arg_rg_name, arg_username, arg_quota={}, arg_location="", arg_desc=""):
"""Provision new RG according to the specified arguments.
If critical error occurs the embedded call to API function will abort further execution of the script
and relay error to Ansible.
@@ -1727,7 +1587,7 @@ class DecortController(object):
access to the newly created RG.
@param arg_quota: dictionary that defines quotas to set on the RG to be created. Valid keys are: cpu, ram,
disk and ext_ips.
@param (string) location: location code, which identifies the location where RG will be created. If
@param (string) arg_location: location code, which identifies the location where RG will be created. If
empty string is passed, the first location under current DECORT controller will be selected.
@param (string) arg_desc: optional text description of this resource group.
@@ -1742,23 +1602,20 @@ class DecortController(object):
"requested.").format(arg_rg_name)
return 0
target_gid = self.gid_get(location)
target_gid = self.gid_get(arg_location)
if not target_gid:
self.result['failed'] = True
self.result['msg'] = ("rg_provision() failed to obtain valid Grid ID for location '{}'").format(
location)
arg_location)
self.amodule.fail_json(**self.result)
api_params = dict(accountId=arg_account_id,
gid=target_gid,
name=arg_rg_name,
owner=arg_username,
def_net=arg_net_type,
extNetId=arg_extNetId,
extIp=arg_extIp,
def_net="NONE",
# maxMemoryCapacity=-1, maxVDiskCapacity=-1,
# maxCPUCapacity=-1, maxNumPublicIP=-1,
# maxNetworkPeerTransfer=-1,
)
if arg_quota:
if 'ram' in arg_quota:
@@ -1769,20 +1626,9 @@ class DecortController(object):
api_params['maxCPUCapacity'] = arg_quota['cpu']
if 'ext_ips' in arg_quota:
api_params['maxNumPublicIP'] = arg_quota['ext_ips']
if 'net_transfer' in arg_quota:
api_params['maxNetworkPeerTransfer'] = arg_quota['net_transfer']
if restype:
api_params['resourceTypes'] = restype
if arg_desc:
api_params['desc'] = arg_desc
api_params['def_net'] = arg_net_type
if arg_net_type == "PRIVATE" and ipcidr !="":
api_params['ipcidr'] = ipcidr
api_resp = self.decort_api_call(requests.post, "/restmachine/cloudapi/rg/create", api_params)
# On success the above call will return here. On error it will abort execution by calling fail_json.
@@ -1793,8 +1639,7 @@ class DecortController(object):
return ret_rg_id
# TODO: this method will not work in its current implementation. Update it for new .../rg/update specs.
def rg_update(self, arg_rg_dict, arg_quotas, arg_res_types, arg_newname):
def rg_quotas(self, arg_rg_dict, arg_quotas):
"""Manage quotas for an existing RG.
@param arg_rg_dict: dictionary with RG facts as returned by rg_find(...) method or .../rg/get API
@@ -1808,59 +1653,46 @@ class DecortController(object):
# TODO: this method may need update since we introduced GPU functionality and corresponding GPU quota management.
#
self.result['waypoints'] = "{} -> {}".format(self.result['waypoints'], "rg_update")
self.result['waypoints'] = "{} -> {}".format(self.result['waypoints'], "rg_quotas")
if self.amodule.check_mode:
self.result['failed'] = False
self.result['msg'] = ("rg_update() in check mode: setting quotas on RG ID {}, RG name '{}' was "
self.result['msg'] = ("rg_quotas() in check mode: setting quotas on RG ID {}, RG name '{}' was "
"requested.").format(arg_rg_dict['id'], arg_rg_dict['name'])
return
update_required = False
api_params = dict(rgId=arg_rg_dict['id'],)
if arg_res_types:
if arg_rg_dict['resourceTypes'] != arg_res_types:
api_params['resourceTypes'] = arg_res_types
update_required = True
else:
api_params['resourceTypes'] = arg_rg_dict['resourceTypes']
if arg_newname != "" and arg_newname!=arg_rg_dict['name']:
api_params['name'] = arg_newname
update_required = True
# 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_D',
ext_ips='CU_I',
net_transfer='CU_NP',)
ext_ips='CU_I', )
set_key_map = dict(cpu='maxCPUCapacity',
ram='maxMemoryCapacity',
disk='maxVDiskCapacity',
ext_ips='maxNumPublicIP',
net_transfer='maxNetworkPeerTransfer',)
ext_ips='maxNumPublicIP', )
api_params = dict(rgId=arg_rg_dict['id'], )
quota_change_required = False
for new_limit in ('cpu', 'ram', 'disk', 'ext_ips', 'net_transfer'):
for new_limit in ('cpu', 'ram', 'disk', 'ext_ips'):
if arg_quotas:
if new_limit 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]
quota_change_required = True
else:
api_params[set_key_map[new_limit]] = arg_rg_dict['resourceLimits'][query_key_map[new_limit]]
# This resource type limit not found in the desired quotas. It means that no limit for this
# resource type - reset VDC limit for this resource type regardless of the current VDC settings.
api_params[set_key_map[new_limit]] = -1
# quota_change_required = True
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[new_limit]] = -1
if update_required:
if quota_change_required:
self.decort_api_call(requests.post, "/restmachine/cloudapi/rg/update", api_params)
# On success the above call will return here. On error it will abort execution by calling fail_json.
self.result['failed'] = False
@@ -1890,6 +1722,66 @@ class DecortController(object):
self.result['changed'] = True
return
def rg_state(self, arg_rg_dict, arg_desired_state):
"""Enable or disable RG.
@param arg_rg_dict: dictionary with the target RG facts as returned by rg_find(...) method or
.../rg/get API call.
@param arg_desired_state: the desired state for this RG. Valid states are 'enabled' and 'disabled'.
"""
self.result['waypoints'] = "{} -> {}".format(self.result['waypoints'], "rg_state")
NOP_STATES_FOR_RG_CHANGE = ["MODELED", "DISABLING", "ENABLING", "DELETING", "DELETED", "DESTROYING",
"DESTROYED"]
VALID_TARGET_STATES = ["enabled", "disabled"]
if arg_rg_dict['status'] in NOP_STATES_FOR_RG_CHANGE:
self.result['failed'] = False
self.result['msg'] = ("rg_state(): no state change possible for RG ID {} "
"in its current state '{}'.").format(arg_rg_dict['id'], arg_rg_dict['status'])
return
if arg_desired_state not in VALID_TARGET_STATES:
self.result['failed'] = False
self.result['warning'] = ("rg_state(): unrecognized desired state '{}' requested "
"for RG ID {}. No RG state change will be done.").format(arg_desired_state,
arg_rg_dict['id'])
return
if self.amodule.check_mode:
self.result['failed'] = False
self.result['msg'] = ("rg_state() in check mode: setting state of RG ID {}, name '{}' to "
"'{}' was requested.").format(arg_rg_dict['id'], arg_rg_dict['name'],
arg_desired_state)
return
rgstate_api = "" # this string will also be used as a flag to indicate that API call is necessary
api_params = dict(rgId=arg_rg_dict['id'],
reason='Changed by DECORT Ansible module, rg_state method.')
expected_state = ""
if arg_rg_dict['status'] in ["CREATED", "ENABLED"] and arg_desired_state == 'disabled':
rgstate_api = "/restmachine/cloudapi/rg/disable"
expected_state = "DISABLED"
elif arg_rg_dict['status'] == "DISABLED" and arg_desired_state == 'enabled':
rgstate_api = "/restmachine/cloudapi/rg/enable"
expected_state = "ENABLED"
if rgstate_api != "":
self.decort_api_call(requests.post, rgstate_api, api_params)
# 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
arg_rg_dict['status'] = expected_state
else:
self.result['failed'] = False
self.result['msg'] = ("rg_state(): no state change required for RG ID {} from current "
"state '{}' to desired state '{}'.").format(arg_rg_dict['id'],
arg_rg_dict['status'],
arg_desired_state)
return
def account_find(self, account_name, account_id=0):
"""Find cloud account specified by the name and return facts about the account. Knowing account is
required for certain cloud resource management tasks (e.g. creating new RG).
@@ -1924,7 +1816,7 @@ class DecortController(object):
# Parse response to see if a account matching arg_account_name is found in the output
# If it is found, assign its ID to the return variable and copy dictionary with the facts
accounts_list = json.loads(api_resp.content.decode('utf8'))
for runner in accounts_list['data']:
for runner in accounts_list:
if runner['name'] == account_name:
# get detailed information about the account from "accounts/get" call as
# "accounts/list" does not return all necessary fields
@@ -2029,7 +1921,7 @@ class DecortController(object):
if api_resp.status_code == 200:
ret_gpu_list = json.loads(api_resp.content.decode('utf8'))
return ret_gpu_list['data']
return ret_gpu_list
###################################
# Workflow callback stub methods - not fully implemented yet
@@ -2079,15 +1971,12 @@ class DecortController(object):
ret_gid = 0
api_params = dict()
api_resp = self.decort_api_call(requests.post, "/restmachine/cloudapi/locations/list", api_params)
if api_resp.status_code == 200:
locations = json.loads(api_resp.content.decode('utf8'))
if location_code == "" and locations:
ret_gid = locations['data'][0]['gid']
ret_gid = locations['gid']
else:
for runner in locations['data']:
for runner in locations:
if runner['locationCode'] == location_code:
# location code matches
ret_gid = runner['gid']
@@ -2179,7 +2068,7 @@ class DecortController(object):
"response {}.").format(rg_id, api_resp.status_code, api_resp.reason)
return []
return ret_rg_vins_list['data']
return ret_rg_vins_list
def vins_find(self, vins_id, vins_name="", account_id=0, rg_id=0, rg_facts="", check_state=True):
"""Find specified ViNS.
@@ -2627,8 +2516,7 @@ class DecortController(object):
else:
self.result['warning'] = ("get_all_account_vinses(): failed to get list VINS in Account ID {}. HTTP code {}, "
"response {}.").format(acc_id, api_resp.status_code, api_resp.reason)
return []
return ret_listvins_dict['data']
return ret_listvins_dict
def _vins_vnf_addmgmtaddr(self,dev_id,mgmtip):
api_params = dict(devId=dev_id,ip=mgmtip)
@@ -2679,54 +2567,7 @@ class DecortController(object):
# Disk management
#
##############################
def disk_check_iotune_arg(self,iotune_list):
self.result['waypoints'] = "{} -> {}".format(self.result['waypoints'], "disk_check_iotune_arg")
MIN_IOPS = 80
total_bytes_sec=iotune_list['total_bytes_sec']
read_bytes_sec=iotune_list['read_bytes_sec']
write_bytes_sec=iotune_list['write_bytes_sec']
total_iops_sec=iotune_list['total_iops_sec']
read_iops_sec=iotune_list['read_iops_sec']
write_iops_sec=iotune_list['write_iops_sec']
total_bytes_sec_max=iotune_list['total_bytes_sec_max']
read_bytes_sec_max=iotune_list['read_bytes_sec_max']
write_bytes_sec_max=iotune_list['write_bytes_sec_max']
total_iops_sec_max=iotune_list['total_iops_sec_max']
read_iops_sec_max=iotune_list['read_iops_sec_max']
write_iops_sec_max=iotune_list['write_iops_sec_max']
size_iops_sec=iotune_list['size_iops_sec']
if total_iops_sec and (read_iops_sec or write_iops_sec):
self.result['failed'] = True
self.result['changed'] = False
self.result['msg'] = (f"total and read/write of iops_sec cannot be set at the same time")
if total_bytes_sec and (read_bytes_sec or write_bytes_sec):
self.result['failed'] = True
self.result['changed'] = False
self.result['msg'] = (f"total and read/write of bytes_sec cannot be set at the same time")
if total_bytes_sec_max and (read_bytes_sec_max or write_bytes_sec_max):
self.result['failed'] = True
self.result['changed'] = False
self.result['msg'] =(f"total and read/write of bytes_sec_max cannot be set at the same time")
if total_iops_sec_max and (read_iops_sec_max or write_iops_sec_max):
self.result['failed'] = True
self.result['changed'] = False
self.result['msg'] =(f"total and read/write of iops_sec_max cannot be set at the same time")
for arg, val in iotune_list.items():
if arg in (
"total_iops_sec",
"read_iops_sec",
"write_iops_sec",
"total_iops_sec_max",
"read_iops_sec_max",
"write_iops_sec_max",
"size_iops_sec",
):
if val and val < self.MIN_IOPS:
self.result['msg'] = (f"{arg} was set below the minimum iops {MIN_IOPS}: {val} provided")
return
def disk_delete(self, disk_id, permanently, detach, reason):
"""Deletes specified Disk.
@@ -2785,7 +2626,7 @@ class DecortController(object):
return ret_disk_id, ret_disk_dict
def disk_find(self, disk_id=0, name="", account_id=0, check_state=False):
def disk_find(self, disk_id, name, account_id, check_state=False):
"""Find specified Disk.
@param (int) disk_id: ID of the Disk. If non-zero disk_id is specified, all other arguments
@@ -2809,13 +2650,13 @@ class DecortController(object):
if self.amodule.check_mode:
self.result['failed'] = False
self.result['msg'] = "disk_find() in check mode: find Disk ID {} / name '{}' was requested.".format(disk_id,
name)
disk_name)
return
ret_disk_id = 0
ret_disk_facts = None
if disk_id:
if disk_id > 0:
ret_disk_id, ret_disk_facts = self._disk_get_by_id(disk_id)
if not ret_disk_id:
self.result['failed'] = True
@@ -2825,20 +2666,19 @@ class DecortController(object):
return ret_disk_id, ret_disk_facts
else:
return 0, None
elif name:
if account_id:
api_params = dict(accountId=account_id,name=name)
api_resp = self.decort_api_call(requests.post, "/restmachine/cloudapi/disks/search", api_params)
elif name != "":
if account_id > 0:
api_params = dict(accountId=account_id)
api_resp = self.decort_api_call(requests.post, "/restmachine/cloudapi/disks/list", api_params)
# the above call may return more than one matching disk
disks_list = json.loads(api_resp.content.decode('utf8'))
if len(disks_list) == 0:
return 0, None
elif len(disks_list) > 1:
self.result['failed'] = True
self.result['msg'] = "disk_find(): Found more then one Disk with Name: {}.".format(name)
self.amodule.fail_json(**self.result)
for runner in disks_list:
# return the first disk of the specified name that fulfills status matching rule
if runner['name'] == name:
if not check_state or runner['status']:
return runner['id'], runner
else:
return disks_list[0]['id'], disks_list[0]
return 0, None
else: # we are missing meaningful account_id - fail the module
self.result['failed'] = True
self.result['msg'] = ("disk_find(): cannot find Disk by name '{}' "
@@ -2851,7 +2691,7 @@ class DecortController(object):
return 0, None
def disk_create(self, accountId, name, description, size, type, iops, sep_id, pool):
def disk_create(self, accountId, gid, name, description, size, type, iops, sep_id, pool):
"""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
@@ -2871,13 +2711,13 @@ class DecortController(object):
self.result['waypoints'] = "{} -> {}".format(self.result['waypoints'], "disk_creation")
api_params = dict(accountId=accountId,
gid=0, # depricated
gid=gid,
name=name,
description=description,
size=size,
type=type,
iops=iops,
sep_id=sep_id,
sepId=sep_id,
pool=pool )
api_resp = self.decort_api_call(requests.post, "/restmachine/cloudapi/disks/create", api_params)
if api_resp.status_code == 200:
@@ -2939,21 +2779,32 @@ class DecortController(object):
return
def disk_limitIO(self,disk_id, limits):
def disk_limitIO(self, limits, diskId):
"""Limits already created Disk identified by its ID.
@param (dict) limits: Dictionary with limits.
@param (int) diskId: ID of the Disk to limit.
@returns: nothing on success. On error this method will abort module execution.
"""
self.result['waypoints'] = "{} -> {}".format(self.result['waypoints'], "disk_limitIO")
api_params = dict(diskId=disk_id,
**limits)
api_params = dict(diskId=diskId,
total_bytes_sec=limits['total_bytes_sec'],
read_bytes_sec=limits['read_bytes_sec'],
write_bytes_sec=limits['write_bytes_sec'],
total_iops_sec=limits['total_iops_sec'],
read_iops_sec=limits['read_iops_sec'],
write_iops_sec=limits['write_iops_sec'],
total_bytes_sec_max=limits['total_bytes_sec_max'],
read_bytes_sec_max=limits['read_bytes_sec_max'],
write_bytes_sec_max=limits['write_bytes_sec_max'],
total_iops_sec_max=limits['total_iops_sec_max'],
read_iops_sec_max=limits['read_iops_sec_max'],
write_iops_sec_max=limits['write_iops_sec_max'],
size_iops_sec=limits['size_iops_sec'])
self.decort_api_call(requests.post, "/restmachine/cloudapi/disks/limitIO", api_params)
self.result['changed'] = True
self.result['msg'] = "Specified Disk ID {} limited successfully.".format(disk_id)
self.result['msg'] = "Specified Disk ID {} limited successfully.".format(self.validated_disk_id)
return
def disk_rename(self, disk_id, name):
def disk_rename(self, diskId, name):
"""Renames disk to the specified new name.
@param disk_id: ID of the Disk to rename.
@@ -2962,13 +2813,12 @@ class DecortController(object):
@returns: nothing on success. On error this method will abort module execution.
"""
self.result['waypoints'] = "{} -> {}".format(self.result['waypoints'], "disk_rename")
api_params = dict(diskId=disk_id,
api_params = dict(diskId=diskId,
name=name)
self.decort_api_call(requests.post, "/restmachine/cloudapi/disks/rename", api_params)
# 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
self.result['msg'] = ("Disk with id '{}',successfully renamed to '{}'.").format(disk_id, name)
return
def disk_restore(self, disk_id):
@@ -2994,30 +2844,6 @@ class DecortController(object):
self.result['failed'] = False
self.result['changed'] = True
return
def disk_share(self, disk_id, share='false'):
"""Share data disk
@param disk_id: ID of the Disk to share.
@param share: share status of the disk
@returns: nothing on success. On error this method will abort module execution.
"""
self.result['waypoints'] = "{} -> {}".format(self.result['waypoints'], "disk_share")
if self.amodule.check_mode:
self.result['failed'] = False
self.result['msg'] = "disk_share() in check mode: share Disk ID {} was requested.".format(disk_id)
return
api_params = dict(diskId=disk_id)
if share:
self.decort_api_call(requests.post, "/restmachine/cloudapi/disks/share", api_params)
else:
self.decort_api_call(requests.post, "/restmachine/cloudapi/disks/unshare", api_params)
# 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
##############################
#
@@ -3037,7 +2863,7 @@ class DecortController(object):
return all_rules
filtered_rules = []
for runner in all_rules['data']:
for runner in all_rules:
if runner['vmId'] == comp_id:
filtered_rules.append(runner)
@@ -3283,13 +3109,9 @@ class DecortController(object):
api_resp = self.decort_api_call(requests.post, "/restmachine/cloudapi/k8s/list", api_params)
if api_resp.status_code == 200:
k8s_list = json.loads(api_resp.content.decode('utf8'))
for k8s_item in k8s_list['data']:
for k8s_item in k8s_list:
if k8s_item['name'] == k8s_name and k8s_item['rgId'] == rg_id:
if not check_state or k8s_item['status'] not in K8S_INVALID_STATES:
# TODO: rework after k8s/get wilb be updated
self.k8s_vins_id = None
self.k8s_vins_id = k8s_item['vinsId']
#
ret_k8s_id = k8s_item['id']
_, ret_k8s_dict = self._k8s_get_by_id(ret_k8s_id)
@@ -3414,7 +3236,7 @@ class DecortController(object):
return
def k8s_provision(self, k8s_name,
k8ci_id,rg_id,plugin,master_count,
k8ci_id,rg_id, master_count,
master_cpu, master_ram,
master_disk, default_worker, extnet_id,
with_lb, annotation, ):
@@ -3440,7 +3262,6 @@ class DecortController(object):
rgId=rg_id,
k8ciId=k8ci_id,
workerGroupName=def_wg_name,
networkPlugin=plugin,
masterNum=master_count,
masterCpu=master_cpu,
masterRam=master_ram,
@@ -3477,7 +3298,6 @@ class DecortController(object):
return
elif ret_info['status'] == "OK":
k8s_id = ret_info['result']
self.result['msg'] = f"k8s_provision(): K8s cluster {k8s_name} created successful"
self.result['changed'] = True
return k8s_id
else:
@@ -3491,9 +3311,6 @@ class DecortController(object):
else:
self.result['msg'] = ("k8s_provision(): Can't create cluster")
self.result['failed'] = True
self.result['changed'] = False
self.fail_json(**self.result)
return
def k8s_workers_modify(self,arg_k8swg,arg_modwg):
@@ -3578,7 +3395,7 @@ class DecortController(object):
k8ci_id_present = False
if api_resp.status_code == 200:
ret_k8ci_list = json.loads(api_resp.content.decode('utf8'))
for k8ci_item in ret_k8ci_list['data']:
for k8ci_item in ret_k8ci_list:
if k8ci_item['id'] == arg_k8ci_id:
k8ci_id_present = True
break
@@ -3638,8 +3455,7 @@ class DecortController(object):
else:
self.result['warning'] = ("bservice_rg_list(): failed to get B-service list. HTTP code {}, "
"response {}.").format(api_resp.status_code, api_resp.reason)
return []
return ret_bs_dict['data']
return ret_bs_dict
def bservice_find(self,account_id,rg_id,bservice_name="",bservice_id = 0,check_state=True):
@@ -3995,7 +3811,7 @@ class DecortController(object):
"response {}.").format(rg_id, api_resp.status_code, api_resp.reason)
return []
return ret_rg_vins_list['data']
return ret_rg_vins_list
def lb_find(self,lb_id=0,lb_name="",rg_id=0):
"""Find specified LB.