Some impruvments and updates

rc-5.2.4
Alex_geth 2 years ago
parent b03b82e492
commit ae85826129

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

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

@ -1,19 +1,68 @@
#!/usr/bin/python #!/usr/bin/python
# #
# Digital Enegry Cloud Orchestration Technology (DECORT) modules for Ansible # Digital Enegry Cloud Orchestration Technology (DECORT) modules for Ansible
# Copyright: (c) 2018-2021 Digital Energy Cloud Solutions LLC # Copyright: (c) 2018-2023 Digital Energy Cloud Solutions LLC
# #
# Apache License 2.0 (see http://www.apache.org/licenses/LICENSE-2.0.txt) # 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', ANSIBLE_METADATA = {'metadata_version': '1.1',
'status': ['preview'], 'status': ['preview'],
'supported_by': 'community'} '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 AnsibleModule
from ansible.module_utils.basic import env_fallback from ansible.module_utils.basic import env_fallback
from ansible.module_utils.decort_utils import * from ansible.module_utils.decort_utils import *
@ -26,6 +75,12 @@ class decort_k8s(DecortController):
validated_rg_id = 0 validated_rg_id = 0
validated_rg_facts = None validated_rg_facts = None
validated_k8ci_id = 0 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: if arg_amodule.params['name'] == "" and arg_amodule.params['id'] == 0:
self.result['failed'] = True self.result['failed'] = True
@ -108,9 +163,11 @@ class decort_k8s(DecortController):
ret_dict['techStatus'] = self.k8s_info['techStatus'] ret_dict['techStatus'] = self.k8s_info['techStatus']
ret_dict['state'] = self.k8s_info['status'] ret_dict['state'] = self.k8s_info['status']
ret_dict['rg_id'] = self.rg_id ret_dict['rg_id'] = self.rg_id
ret_dict['vins_id'] = self.k8s_vins_id
ret_dict['account_id'] = self.acc_id ret_dict['account_id'] = self.acc_id
if self.amodule.params['getConfig'] and self.k8s_info['techStatus'] == "STARTED": if self.amodule.params['getConfig'] and self.k8s_info['techStatus'] == "STARTED":
ret_dict['config'] = self.k8s_getConfig() ret_dict['config'] = self.k8s_getConfig()
return ret_dict return ret_dict
def nop(self): def nop(self):
@ -149,10 +206,11 @@ class decort_k8s(DecortController):
self.k8s_provision(self.amodule.params['name'], self.k8s_provision(self.amodule.params['name'],
self.amodule.params['k8ci_id'], self.amodule.params['k8ci_id'],
self.amodule.params['rg_id'], self.amodule.params['rg_id'],
self.amodule.params['network_plugin'],
self.amodule.params['master_count'], self.amodule.params['master_count'],
self.amodule.params['master_cpu'], self.amodule.params['master_cpu'],
self.amodule.params['master_ram_mb'], self.amodule.params['master_ram'],
self.amodule.params['master_disk_gb'], self.amodule.params['master_disk'],
self.amodule.params['workers'][0], self.amodule.params['workers'][0],
self.amodule.params['extnet_id'], self.amodule.params['extnet_id'],
self.amodule.params['with_lb'], self.amodule.params['with_lb'],
@ -165,12 +223,12 @@ class decort_k8s(DecortController):
if self.k8s_id: if self.k8s_id:
self.k8s_should_exist = True self.k8s_should_exist = True
if self.k8s_id and self.amodule.params['workers'][1]: if self.k8s_id and len(self.amodule.params['workers'])>1 :
self.k8s_workers_modify(self.k8s_info,self.amodule.params['workers']) self.k8s_workers_modify(self.k8s_info,self.amodule.params['workers'])
return return
def destroy(self): def destroy(self):
self.k8s_delete(self.k8s_id) self.k8s_delete(self.k8s_id,self.amodule.params['permanent'])
self.k8s_info['status'] = 'DELETED' self.k8s_info['status'] = 'DELETED'
self.k8s_should_exist = False self.k8s_should_exist = False
return return
@ -232,16 +290,17 @@ class decort_k8s(DecortController):
rg_id=dict(type='int', default=0), rg_id=dict(type='int', default=0),
rg_name=dict(type='str',default=""), rg_name=dict(type='str',default=""),
k8ci_id=dict(type='int', required=True), k8ci_id=dict(type='int', required=True),
network_plugin=dict(type='str',required=False,default="flannel"),
wg_name=dict(type='str', required=False), wg_name=dict(type='str', required=False),
master_count=dict(type='int', default=1), master_count=dict(type='int', default=1),
master_cpu=dict(type='int', default=2), master_cpu=dict(type='int', default=2),
master_ram_mb=dict(type='int', default=2048), master_ram=dict(type='int', default=2048),
master_disk_gb=dict(type='int', default=10), master_disk=dict(type='int', default=10),
worker_count=dict(type='int', default=1), worker_count=dict(type='int', default=1),
worker_cpu=dict(type='int', default=1), worker_cpu=dict(type='int', default=1),
worker_ram_mb=dict(type='int', default=1024), worker_ram_mb=dict(type='int', default=1024),
worker_disk_gb=dict(type='int', default=10), worker_disk_gb=dict(type='int', default=10),
workers=dict(type='list'), workers=dict(type='list',required=True),
extnet_id=dict(type='int', default=0), extnet_id=dict(type='int', default=0),
description=dict(type='str', default="Created by decort ansible module"), description=dict(type='str', default="Created by decort ansible module"),
with_lb=dict(type='bool', default=True), with_lb=dict(type='bool', default=True),

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

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

@ -388,6 +388,8 @@ class decort_rg(DecortController):
ret_dict['resTypes'] = self.rg_facts['resourceTypes'] ret_dict['resTypes'] = self.rg_facts['resourceTypes']
ret_dict['defNetId'] = self.rg_facts['def_net_id'] ret_dict['defNetId'] = self.rg_facts['def_net_id']
ret_dict['defNetType'] = self.rg_facts['def_net_type'] ret_dict['defNetType'] = self.rg_facts['def_net_type']
ret_dict['ViNS'] = self.rg_facts['vins']
ret_dict['computes'] = self.rg_facts['vms']
return ret_dict return ret_dict

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

@ -1,13 +1,10 @@
# #
# Digital Enegry Cloud Orchestration Technology (DECORT) modules for Ansible # Digital Enegry Cloud Orchestration Technology (DECORT) modules for Ansible
# Copyright: (c) 2018-2021 Digital Energy Cloud Solutions LLC # Copyright: (c) 2018-2023 Digital Energy Cloud Solutions LLC
# #
# Apache License 2.0 (see http://www.apache.org/licenses/LICENSE-2.0.txt) # 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. This is the library of utility functions and classes for managing DECORT cloud platform.
@ -28,7 +25,7 @@ Requirements:
- PyJWT Python module - PyJWT Python module
- requests Python module - requests Python module
- netaddr Python module - netaddr Python module
- DECORT cloud platform version 3.6.1 or higher - DECORT cloud platform version 3.8.6 or higher
""" """
import json import json
@ -39,17 +36,6 @@ import requests
from ansible.module_utils.basic import AnsibleModule 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): class DecortController(object):
"""DecortController is a utility class that holds target controller context and handles API requests formatting """DecortController is a utility class that holds target controller context and handles API requests formatting
based on the requested authentication type. based on the requested authentication type.
@ -761,9 +747,12 @@ class DecortController(object):
def kvmvm_provision(self, rg_id, def kvmvm_provision(self, rg_id,
comp_name, arch, comp_name, arch,
cpu, ram, cpu, ram,
boot_disk, image_id, boot_disk,
image_id,
annotation="", annotation="",
userdata=None, userdata=None,
sep_id=None,
pool_name=None,
start_on_create=True): start_on_create=True):
"""Manage KVM VM provisioning. To remove existing KVM VM compute instance use compute_remove method, """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. to resize use compute_resize, to manage power state use compute_powerstate method.
@ -808,6 +797,8 @@ class DecortController(object):
cpu=cpu, ram=ram, cpu=cpu, ram=ram,
imageId=image_id, imageId=image_id,
bootDisk=boot_disk, 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 start=start_on_create, # start_machine parameter requires DECORT API ver 3.3.1 or higher
netType="NONE") # we create VM without any network connections netType="NONE") # we create VM without any network connections
if userdata: if userdata:
@ -1189,25 +1180,84 @@ class DecortController(object):
return False return False
def compute_affinity(self,comp_dict,tags,aff,aaff,label=""): 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
"""
self.result['waypoints'] = "{} -> {}".format(self.result['waypoints'], "compute_affinity") self.result['waypoints'] = "{} -> {}".format(self.result['waypoints'], "compute_affinity")
api_params = dict(computeId=comp_dict['id']) for tag in tags.items():
self.decort_api_call(requests.post, "/restmachine/cloudapi/compute/affinityRulesClear", api_params) if tag not in comp_dict['tags'].items():
self.decort_api_call(requests.post, "/restmachine/cloudapi/compute/antiAffinityRulesClear", api_params)
if tags:
for tag in tags:
api_params = dict(computeId=comp_dict['id'], api_params = dict(computeId=comp_dict['id'],
key=tag['key'], key=tag[0],
value=tag['value'], ) value=tag[1], )
self.decort_api_call(requests.post, "/restmachine/cloudapi/compute/tagAdd", api_params) self.decort_api_call(requests.post, "/restmachine/cloudapi/compute/tagAdd", api_params)
if label: self.result['failed'] = False
self.result['changed'] = True
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
if label and comp_dict['affinityLabel'] != label:
api_params = dict(computeId=comp_dict['id'], api_params = dict(computeId=comp_dict['id'],
affinityLabel=label,) affinityLabel=label,)
self.decort_api_call(requests.post, "/restmachine/cloudapi/compute/affinityLabelSet", api_params) self.decort_api_call(requests.post, "/restmachine/cloudapi/compute/affinityLabelSet", api_params)
if aff: self.result['failed'] = False
if len(aff)>0: self.result['changed'] = True
elif label == "" and 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
for rule in comp_dict['affinityRules']:
del rule['guid']
if rule not in aff:
affrule_del.append(rule)
for rule in aff: for rule in aff:
if rule not in comp_dict['affinityRules']:
affrule_add.append(rule)
#ANTI AFFINITY
for rule in comp_dict['antiAffinityRules']:
del rule['guid']
if rule not in aaff:
aaffrule_del.append(rule)
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'], api_params = dict(computeId=comp_dict['id'],
key=rule['key'], key=rule['key'],
value=rule['value'], value=rule['value'],
@ -1215,20 +1265,33 @@ class DecortController(object):
mode=rule['mode'], mode=rule['mode'],
policy=rule['policy'],) policy=rule['policy'],)
self.decort_api_call(requests.post, "/restmachine/cloudapi/compute/affinityRuleAdd", api_params) self.decort_api_call(requests.post, "/restmachine/cloudapi/compute/affinityRuleAdd", api_params)
if aaff: self.result['failed'] = False
if len(aaff)>0: self.result['changed'] = True
for rule in aaff: #ANTI AFFINITY
if len(aaffrule_del):
for rule in aaffrule_del:
api_params = dict(computeId=comp_dict['id'], api_params = dict(computeId=comp_dict['id'],
key=rule['key'], key=rule['key'],
value=rule['value'], value=rule['value'],
topology=rule['topology'], topology=rule['topology'],
mode=rule['mode'], mode=rule['mode'],
policy=rule['policy'],) policy=rule['policy'],)
self.decort_api_call(requests.post, "/restmachine/cloudapi/compute/antiAffinityRuleAdd", api_params) self.decort_api_call(requests.post, "/restmachine/cloudapi/compute/antiAffinityRuleRemove", api_params)
self.result['failed'] = False self.result['failed'] = False
self.result['changed'] = True self.result['changed'] = True
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
################################### ###################################
# OS image manipulation methods # OS image manipulation methods
################################### ###################################
@ -1571,15 +1634,18 @@ class DecortController(object):
self.result['msg'] = "rg_find(): cannot find RG by name if account ID is zero or less." self.result['msg'] = "rg_find(): cannot find RG by name if account ID is zero or less."
self.amodule.fail_json(**self.result) self.amodule.fail_json(**self.result)
# try to locate RG by name - start with getting all RGs IDs within the specified account # try to locate RG by name - start with getting all RGs IDs within the specified account
api_params['accountId'] = arg_account_id #api_params['accountId'] = arg_account_id
api_resp = self.decort_api_call(requests.post, "/restmachine/cloudapi/account/listRG", api_params) 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)
if api_resp.status_code == 200: if api_resp.status_code == 200:
account_specs = json.loads(api_resp.content.decode('utf8')) account_specs = json.loads(api_resp.content.decode('utf8'))
api_params.pop('accountId') #api_params.pop('accountId')
for rg_item in account_specs: 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: if rg_item['name'] == arg_rg_name:
# name matches # 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: if not arg_check_state or got_specs['status'] not in RG_INVALID_STATES:
ret_rg_id = got_id ret_rg_id = got_id
ret_rg_dict = got_specs ret_rg_dict = got_specs
@ -2595,6 +2661,53 @@ class DecortController(object):
# Disk management # 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): def disk_delete(self, disk_id, permanently, detach, reason):
"""Deletes specified Disk. """Deletes specified Disk.
@ -2654,7 +2767,7 @@ class DecortController(object):
return ret_disk_id, ret_disk_dict return ret_disk_id, ret_disk_dict
def disk_find(self, disk_id, name, account_id, check_state=False): def disk_find(self, disk_id=0, name="", account_id=0, check_state=False):
"""Find specified Disk. """Find specified Disk.
@param (int) disk_id: ID of the Disk. If non-zero disk_id is specified, all other arguments @param (int) disk_id: ID of the Disk. If non-zero disk_id is specified, all other arguments
@ -2678,13 +2791,13 @@ class DecortController(object):
if self.amodule.check_mode: if self.amodule.check_mode:
self.result['failed'] = False self.result['failed'] = False
self.result['msg'] = "disk_find() in check mode: find Disk ID {} / name '{}' was requested.".format(disk_id, self.result['msg'] = "disk_find() in check mode: find Disk ID {} / name '{}' was requested.".format(disk_id,
disk_name) name)
return return
ret_disk_id = 0 ret_disk_id = 0
ret_disk_facts = None ret_disk_facts = None
if disk_id > 0: if disk_id:
ret_disk_id, ret_disk_facts = self._disk_get_by_id(disk_id) ret_disk_id, ret_disk_facts = self._disk_get_by_id(disk_id)
if not ret_disk_id: if not ret_disk_id:
self.result['failed'] = True self.result['failed'] = True
@ -2694,19 +2807,20 @@ class DecortController(object):
return ret_disk_id, ret_disk_facts return ret_disk_id, ret_disk_facts
else: else:
return 0, None return 0, None
elif name != "": elif name:
if account_id > 0: if account_id:
api_params = dict(accountId=account_id) api_params = dict(accountId=account_id,name=name)
api_resp = self.decort_api_call(requests.post, "/restmachine/cloudapi/disks/list", api_params) api_resp = self.decort_api_call(requests.post, "/restmachine/cloudapi/disks/search", api_params)
# the above call may return more than one matching disk # the above call may return more than one matching disk
disks_list = json.loads(api_resp.content.decode('utf8')) disks_list = json.loads(api_resp.content.decode('utf8'))
for runner in disks_list: if len(disks_list) == 0:
# 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 0, None 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)
else:
return disks_list[0]['id'], disks_list[0]
else: # we are missing meaningful account_id - fail the module else: # we are missing meaningful account_id - fail the module
self.result['failed'] = True self.result['failed'] = True
self.result['msg'] = ("disk_find(): cannot find Disk by name '{}' " self.result['msg'] = ("disk_find(): cannot find Disk by name '{}' "
@ -2719,7 +2833,7 @@ class DecortController(object):
return 0, None return 0, None
def disk_create(self, accountId, gid, name, description, size, type, iops, sep_id, pool): def disk_create(self, accountId, name, description, size, type, iops, sep_id, pool):
"""Provision Disk according to the specified arguments. """Provision Disk according to the specified arguments.
Note that disks created by this method will be of type 'D' (data disks). 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 If critical error occurs the embedded call to API function will abort further execution
@ -2739,13 +2853,13 @@ class DecortController(object):
self.result['waypoints'] = "{} -> {}".format(self.result['waypoints'], "disk_creation") self.result['waypoints'] = "{} -> {}".format(self.result['waypoints'], "disk_creation")
api_params = dict(accountId=accountId, api_params = dict(accountId=accountId,
gid=gid, gid=0, # depricated
name=name, name=name,
description=description, description=description,
size=size, size=size,
type=type, type=type,
iops=iops, iops=iops,
sepId=sep_id, sep_id=sep_id,
pool=pool ) pool=pool )
api_resp = self.decort_api_call(requests.post, "/restmachine/cloudapi/disks/create", api_params) api_resp = self.decort_api_call(requests.post, "/restmachine/cloudapi/disks/create", api_params)
if api_resp.status_code == 200: if api_resp.status_code == 200:
@ -2807,32 +2921,21 @@ class DecortController(object):
return return
def disk_limitIO(self, limits, diskId): def disk_limitIO(self,disk_id, limits):
"""Limits already created Disk identified by its ID. """Limits already created Disk identified by its ID.
@param (dict) limits: Dictionary with limits. @param (dict) limits: Dictionary with limits.
@param (int) diskId: ID of the Disk to limit. @param (int) diskId: ID of the Disk to limit.
@returns: nothing on success. On error this method will abort module execution. @returns: nothing on success. On error this method will abort module execution.
""" """
self.result['waypoints'] = "{} -> {}".format(self.result['waypoints'], "disk_limitIO") self.result['waypoints'] = "{} -> {}".format(self.result['waypoints'], "disk_limitIO")
api_params = dict(diskId=diskId, api_params = dict(diskId=disk_id,
total_bytes_sec=limits['total_bytes_sec'], **limits)
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.decort_api_call(requests.post, "/restmachine/cloudapi/disks/limitIO", api_params)
self.result['msg'] = "Specified Disk ID {} limited successfully.".format(self.validated_disk_id) self.result['changed'] = True
self.result['msg'] = "Specified Disk ID {} limited successfully.".format(disk_id)
return return
def disk_rename(self, diskId, name): def disk_rename(self, disk_id, name):
"""Renames disk to the specified new name. """Renames disk to the specified new name.
@param disk_id: ID of the Disk to rename. @param disk_id: ID of the Disk to rename.
@ -2841,12 +2944,13 @@ class DecortController(object):
@returns: nothing on success. On error this method will abort module execution. @returns: nothing on success. On error this method will abort module execution.
""" """
self.result['waypoints'] = "{} -> {}".format(self.result['waypoints'], "disk_rename") self.result['waypoints'] = "{} -> {}".format(self.result['waypoints'], "disk_rename")
api_params = dict(diskId=diskId, api_params = dict(diskId=disk_id,
name=name) name=name)
self.decort_api_call(requests.post, "/restmachine/cloudapi/disks/rename", api_params) 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. # On success the above call will return here. On error it will abort execution by calling fail_json.
self.result['failed'] = False self.result['failed'] = False
self.result['changed'] = True self.result['changed'] = True
self.result['msg'] = ("Disk with id '{}',successfully renamed to '{}'.").format(disk_id, name)
return return
def disk_restore(self, disk_id): def disk_restore(self, disk_id):
@ -2872,6 +2976,30 @@ class DecortController(object):
self.result['failed'] = False self.result['failed'] = False
self.result['changed'] = True self.result['changed'] = True
return 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
############################## ##############################
# #
@ -3140,6 +3268,10 @@ class DecortController(object):
for k8s_item in k8s_list: for k8s_item in k8s_list:
if k8s_item['name'] == k8s_name and k8s_item['rgId'] == rg_id: 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: 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_id = k8s_item['id']
_, ret_k8s_dict = self._k8s_get_by_id(ret_k8s_id) _, ret_k8s_dict = self._k8s_get_by_id(ret_k8s_id)
@ -3264,7 +3396,7 @@ class DecortController(object):
return return
def k8s_provision(self, k8s_name, def k8s_provision(self, k8s_name,
k8ci_id,rg_id, master_count, k8ci_id,rg_id,plugin,master_count,
master_cpu, master_ram, master_cpu, master_ram,
master_disk, default_worker, extnet_id, master_disk, default_worker, extnet_id,
with_lb, annotation, ): with_lb, annotation, ):
@ -3290,6 +3422,7 @@ class DecortController(object):
rgId=rg_id, rgId=rg_id,
k8ciId=k8ci_id, k8ciId=k8ci_id,
workerGroupName=def_wg_name, workerGroupName=def_wg_name,
networkPlugin=plugin,
masterNum=master_count, masterNum=master_count,
masterCpu=master_cpu, masterCpu=master_cpu,
masterRam=master_ram, masterRam=master_ram,
@ -3326,6 +3459,7 @@ class DecortController(object):
return return
elif ret_info['status'] == "OK": elif ret_info['status'] == "OK":
k8s_id = ret_info['result'] k8s_id = ret_info['result']
self.result['msg'] = f"k8s_provision(): K8s cluster {k8s_name} created successful"
self.result['changed'] = True self.result['changed'] = True
return k8s_id return k8s_id
else: else:
@ -3339,6 +3473,9 @@ class DecortController(object):
else: else:
self.result['msg'] = ("k8s_provision(): Can't create cluster") self.result['msg'] = ("k8s_provision(): Can't create cluster")
self.result['failed'] = True self.result['failed'] = True
self.result['changed'] = False
self.fail_json(**self.result)
return return
def k8s_workers_modify(self,arg_k8swg,arg_modwg): def k8s_workers_modify(self,arg_k8swg,arg_modwg):

Loading…
Cancel
Save