Some impruvments and updates

This commit is contained in:
Alex_geth
2023-04-21 14:23:07 +03:00
parent b03b82e492
commit ae85826129
8 changed files with 523 additions and 439 deletions

View File

@@ -1,13 +1,10 @@
#
# 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)
#
#
# Author: Sergey Shubin (sergey.shubin@digitalenergy.online)
#
"""
This is the library of utility functions and classes for managing DECORT cloud platform.
@@ -28,7 +25,7 @@ Requirements:
- PyJWT Python module
- requests 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
@@ -39,17 +36,6 @@ 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.
@@ -761,9 +747,12 @@ 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.
@@ -808,6 +797,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
netType="NONE") # we create VM without any network connections
if userdata:
@@ -1189,46 +1180,118 @@ 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
"""
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:
for tag in tags.items():
if tag not in comp_dict['tags'].items():
api_params = dict(computeId=comp_dict['id'],
key=tag['key'],
value=tag['value'], )
key=tag[0],
value=tag[1], )
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'],
affinityLabel=label,)
self.decort_api_call(requests.post, "/restmachine/cloudapi/compute/affinityLabelSet", api_params)
if aff:
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:
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)
self.result['failed'] = False
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
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:
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'],
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(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
###################################
@@ -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.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_resp = self.decort_api_call(requests.post, "/restmachine/cloudapi/account/listRG", api_params)
#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)
if api_resp.status_code == 200:
account_specs = json.loads(api_resp.content.decode('utf8'))
api_params.pop('accountId')
#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:
#
if rg_item['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
@@ -2595,7 +2661,54 @@ 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.
@@ -2654,7 +2767,7 @@ class DecortController(object):
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.
@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:
self.result['failed'] = False
self.result['msg'] = "disk_find() in check mode: find Disk ID {} / name '{}' was requested.".format(disk_id,
disk_name)
name)
return
ret_disk_id = 0
ret_disk_facts = None
if disk_id > 0:
if disk_id:
ret_disk_id, ret_disk_facts = self._disk_get_by_id(disk_id)
if not ret_disk_id:
self.result['failed'] = True
@@ -2694,19 +2807,20 @@ class DecortController(object):
return ret_disk_id, ret_disk_facts
else:
return 0, None
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)
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)
# the above call may return more than one matching disk
disks_list = json.loads(api_resp.content.decode('utf8'))
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:
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)
else:
return disks_list[0]['id'], disks_list[0]
else: # we are missing meaningful account_id - fail the module
self.result['failed'] = True
self.result['msg'] = ("disk_find(): cannot find Disk by name '{}' "
@@ -2719,7 +2833,7 @@ class DecortController(object):
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.
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
@@ -2739,13 +2853,13 @@ class DecortController(object):
self.result['waypoints'] = "{} -> {}".format(self.result['waypoints'], "disk_creation")
api_params = dict(accountId=accountId,
gid=gid,
gid=0, # depricated
name=name,
description=description,
size=size,
type=type,
iops=iops,
sepId=sep_id,
sep_id=sep_id,
pool=pool )
api_resp = self.decort_api_call(requests.post, "/restmachine/cloudapi/disks/create", api_params)
if api_resp.status_code == 200:
@@ -2807,32 +2921,21 @@ class DecortController(object):
return
def disk_limitIO(self, limits, diskId):
def disk_limitIO(self,disk_id, limits):
"""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=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'])
api_params = dict(diskId=disk_id,
**limits)
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
def disk_rename(self, diskId, name):
def disk_rename(self, disk_id, name):
"""Renames disk to the specified new name.
@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.
"""
self.result['waypoints'] = "{} -> {}".format(self.result['waypoints'], "disk_rename")
api_params = dict(diskId=diskId,
api_params = dict(diskId=disk_id,
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):
@@ -2872,6 +2976,30 @@ 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
##############################
#
@@ -3140,6 +3268,10 @@ class DecortController(object):
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)
@@ -3264,7 +3396,7 @@ class DecortController(object):
return
def k8s_provision(self, k8s_name,
k8ci_id,rg_id, master_count,
k8ci_id,rg_id,plugin,master_count,
master_cpu, master_ram,
master_disk, default_worker, extnet_id,
with_lb, annotation, ):
@@ -3290,6 +3422,7 @@ class DecortController(object):
rgId=rg_id,
k8ciId=k8ci_id,
workerGroupName=def_wg_name,
networkPlugin=plugin,
masterNum=master_count,
masterCpu=master_cpu,
masterRam=master_ram,
@@ -3326,6 +3459,7 @@ 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:
@@ -3339,6 +3473,9 @@ 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):