8.0.0
This commit is contained in:
@@ -3,14 +3,26 @@ from datetime import datetime
|
||||
from enum import Enum
|
||||
import json
|
||||
import re
|
||||
from typing import Any, Callable, Iterable, Literal, Optional, Tuple
|
||||
from functools import wraps
|
||||
from typing import (
|
||||
Any,
|
||||
Callable,
|
||||
Iterable,
|
||||
Literal,
|
||||
Optional,
|
||||
ParamSpec,
|
||||
TypeVar,
|
||||
)
|
||||
import time
|
||||
|
||||
import jwt
|
||||
import requests
|
||||
from ansible.module_utils.basic import AnsibleModule, env_fallback
|
||||
|
||||
|
||||
P = ParamSpec('P')
|
||||
R = TypeVar('R')
|
||||
|
||||
|
||||
class DecortController(object):
|
||||
"""DecortController is a utility class that holds target controller context and handles API requests formatting
|
||||
based on the requested authentication type.
|
||||
@@ -112,7 +124,7 @@ class DecortController(object):
|
||||
IMAGE_TYPES = [
|
||||
'cdrom',
|
||||
'linux',
|
||||
'other',
|
||||
'unknown',
|
||||
'virtual',
|
||||
'windows',
|
||||
]
|
||||
@@ -165,6 +177,14 @@ class DecortController(object):
|
||||
DPDK = 'DPDK'
|
||||
|
||||
|
||||
class AuditsSortableField(Enum):
|
||||
Call = 'Call'
|
||||
Guid = 'Guid'
|
||||
ResponseTime = 'Response Time'
|
||||
StatusCode = 'Status Code'
|
||||
Time = 'Time'
|
||||
|
||||
|
||||
class MESSAGES:
|
||||
@staticmethod
|
||||
def ssl_error(url: None | str = None):
|
||||
@@ -283,6 +303,13 @@ class DecortController(object):
|
||||
|
||||
return msg
|
||||
|
||||
@staticmethod
|
||||
def default_value_used(param_name: str, default_value: Any) -> str:
|
||||
return (
|
||||
f'{param_name} parameter is not specified, '
|
||||
f'default value "{default_value}" will be used.'
|
||||
)
|
||||
|
||||
def __init__(self, arg_amodule: AnsibleModule):
|
||||
"""
|
||||
Instantiate DecortController() class at the beginning of any DECORT module run to have the following:
|
||||
@@ -325,10 +352,6 @@ class DecortController(object):
|
||||
# if self.workflow_callback != "":
|
||||
# self.workflow_callback_present = True
|
||||
|
||||
# The following will be initialized to the name of the user in DECORT controller, who corresponds to
|
||||
# the credentials supplied as authentication information parameters.
|
||||
self.decort_username = ''
|
||||
|
||||
# self.run_phase may eventually be deprecated in favor of self.results['waypoints']
|
||||
self.run_phase = "Run phase: Initializing DecortController instance."
|
||||
|
||||
@@ -392,40 +415,37 @@ class DecortController(object):
|
||||
if self.authenticator == "jwt":
|
||||
# validate supplied JWT on the DECORT controller
|
||||
self.validate_jwt() # this call will abort the script if validation fails
|
||||
jwt_decoded = jwt.decode(self.jwt, algorithms=["ES384"], options={"verify_signature": False})
|
||||
self.decort_username = jwt_decoded['username'] + "@" + jwt_decoded['iss']
|
||||
else:
|
||||
# Oauth2 based authorization mode
|
||||
# obtain JWT from Oauth2 provider and validate on the DECORT controller
|
||||
self.obtain_jwt()
|
||||
if self.controller_url is not None:
|
||||
self.validate_jwt() # this call will abort the script if validation fails
|
||||
jwt_decoded = jwt.decode(self.jwt, algorithms=["ES384"], options={"verify_signature": False})
|
||||
self.decort_username = jwt_decoded['username'] + "@" + jwt_decoded['iss']
|
||||
|
||||
# self.run_phase = "Initializing DecortController instance complete."
|
||||
return
|
||||
|
||||
@staticmethod
|
||||
def waypoint(orig_f: Callable) -> Callable:
|
||||
def waypoint(orig_f: Callable[P, R]) -> Callable[P, R]:
|
||||
"""
|
||||
A decorator for adding the name of called method to the string
|
||||
`self.result['waypoints']`.
|
||||
"""
|
||||
@wraps(orig_f)
|
||||
def new_f(self, *args, **kwargs):
|
||||
self.result['waypoints'] += f' -> {orig_f.__name__}'
|
||||
return orig_f(self, *args, **kwargs)
|
||||
new_f.__name__ = orig_f.__name__
|
||||
return new_f
|
||||
|
||||
@staticmethod
|
||||
def checkmode(orig_f: Callable) -> Callable:
|
||||
def checkmode(orig_f: Callable[P, R]) -> Callable[P, R | None]:
|
||||
"""
|
||||
A decorator for methods that should not executed in
|
||||
Ansible Check Mode.
|
||||
Instead of executing these methods, a message will be added
|
||||
with the method name and the arguments with which it was called.
|
||||
"""
|
||||
@wraps(orig_f)
|
||||
def new_f(self, *args, **kwargs):
|
||||
if self.amodule.check_mode:
|
||||
self.message(
|
||||
@@ -437,7 +457,6 @@ class DecortController(object):
|
||||
)
|
||||
else:
|
||||
return orig_f(self, *args, **kwargs)
|
||||
new_f.__name__ = orig_f.__name__
|
||||
return new_f
|
||||
|
||||
@staticmethod
|
||||
@@ -978,6 +997,7 @@ class DecortController(object):
|
||||
a['createdTime_readable'] = self.sec_to_dt_str(a['createdTime'])
|
||||
a['deletedTime_readable'] = self.sec_to_dt_str(a['deletedTime'])
|
||||
a['updatedTime_readable'] = self.sec_to_dt_str(a['updatedTime'])
|
||||
a['description'] = a.pop('desc')
|
||||
|
||||
return accounts
|
||||
|
||||
@@ -1009,12 +1029,19 @@ class DecortController(object):
|
||||
start_unix_time: None | int = None,
|
||||
end_unix_time: None | int = None,
|
||||
page_number: int = 1,
|
||||
sort_by_asc: bool = True,
|
||||
sort_by_field: None | AuditsSortableField = None,
|
||||
) -> dict[str, Any]:
|
||||
"""
|
||||
Implementation of the functionality of API method
|
||||
`/cloudapi/user/getAudit`.
|
||||
"""
|
||||
|
||||
sort_by = None
|
||||
if sort_by_field:
|
||||
sort_by_prefix = '+' if sort_by_asc else '-'
|
||||
sort_by = f'{sort_by_prefix}{sort_by_field.value}'
|
||||
|
||||
api_params = {
|
||||
'call': api_method,
|
||||
'minStatusCode': min_status_code,
|
||||
@@ -1023,6 +1050,7 @@ class DecortController(object):
|
||||
'timestampTo': end_unix_time,
|
||||
'page': page_number,
|
||||
'size': page_size,
|
||||
'sortBy': sort_by,
|
||||
}
|
||||
api_resp = self.decort_api_call(
|
||||
arg_req_function=requests.post,
|
||||
@@ -1457,7 +1485,11 @@ class DecortController(object):
|
||||
cpu_pin: bool = False,
|
||||
hp_backed: bool = False,
|
||||
numa_affinity: Literal['none', 'loose', 'strict'] = 'none',
|
||||
preferred_cpu_cores: list[int] | None = None):
|
||||
preferred_cpu_cores: list[int] | None = None,
|
||||
boot_mode: Literal['bios', 'uefi'] = 'bios',
|
||||
boot_loader_type: Literal['linux', 'windows', 'unknown'] = 'unknown',
|
||||
network_interface_naming: Literal['eth', 'ens'] = 'ens',
|
||||
hot_resize: bool = False,):
|
||||
"""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.
|
||||
|
||||
@@ -1501,6 +1533,10 @@ class DecortController(object):
|
||||
|
||||
if not image_id:
|
||||
api_url = '/restmachine/cloudapi/kvmx86/createBlank'
|
||||
api_params['bootType'] = boot_mode
|
||||
api_params['loaderType'] = boot_loader_type
|
||||
api_params['networkInterfaceNaming'] = network_interface_naming
|
||||
api_params['hotResize'] = hot_resize
|
||||
else:
|
||||
api_url = '/restmachine/cloudapi/kvmx86/create'
|
||||
api_params['imageId'] = image_id
|
||||
@@ -1534,6 +1570,7 @@ class DecortController(object):
|
||||
ifaces_for_delete = []
|
||||
nets_for_attach = []
|
||||
nets_for_change_ip = []
|
||||
nets_for_change_mac_dict = {}
|
||||
|
||||
# Either only attaching or only detaching networks
|
||||
if not ifaces or not new_networks:
|
||||
@@ -1607,14 +1644,21 @@ class DecortController(object):
|
||||
else:
|
||||
nets_for_attach.append(net)
|
||||
|
||||
# Adding networks for change IP address
|
||||
for net_key, net in unchangeable_nets_dict.items():
|
||||
# Adding networks for change IP address
|
||||
if net['type'] in ('VINS', 'EXTNET'):
|
||||
old_ip = ifaces_dict[net_key]['ipAddress']
|
||||
current_ip = ifaces_dict[net_key]['ipAddress']
|
||||
new_ip = net['ip_addr']
|
||||
if new_ip and old_ip != new_ip:
|
||||
if new_ip and current_ip != new_ip:
|
||||
nets_for_change_ip.append(net)
|
||||
|
||||
# Adding networks for change MAC address
|
||||
if net['type'] != self.VMNetType.EMPTY.value:
|
||||
current_mac = ifaces_dict[net_key]['mac']
|
||||
new_mac = net['mac']
|
||||
if new_mac and current_mac != new_mac:
|
||||
nets_for_change_mac_dict[net_key] = net
|
||||
|
||||
# Detaching networks
|
||||
for iface in ifaces_for_delete:
|
||||
self.decort_api_call(
|
||||
@@ -1639,6 +1683,7 @@ class DecortController(object):
|
||||
'netId': net.get('id') or 0,
|
||||
'ipAddr': net.get('ip_addr'),
|
||||
'mtu': net.get('mtu'),
|
||||
'mac_addr': net.get('mac'),
|
||||
},
|
||||
)
|
||||
self.set_changed()
|
||||
@@ -1649,10 +1694,23 @@ class DecortController(object):
|
||||
arg_req_function=requests.post,
|
||||
arg_api_name='/restmachine/cloudapi/compute/changeIp',
|
||||
arg_params={
|
||||
'computeId': vm_id,
|
||||
'netType': net['type'],
|
||||
'netId': net['id'],
|
||||
'ipAddr': net['ip_addr'],
|
||||
'compute_id': vm_id,
|
||||
'net_type': net['type'],
|
||||
'net_id': net['id'],
|
||||
'ip_addr': net['ip_addr'],
|
||||
},
|
||||
)
|
||||
self.set_changed()
|
||||
|
||||
# Changing MAC adresses
|
||||
for net_key, net in nets_for_change_mac_dict.items():
|
||||
self.decort_api_call(
|
||||
arg_req_function=requests.post,
|
||||
arg_api_name='/restmachine/cloudapi/compute/changeMac',
|
||||
arg_params={
|
||||
'compute_id': vm_id,
|
||||
'current_mac_address': ifaces_dict[net_key]['mac'],
|
||||
'new_mac_address': net['mac'],
|
||||
},
|
||||
)
|
||||
self.set_changed()
|
||||
@@ -1717,8 +1775,7 @@ class DecortController(object):
|
||||
self.result['msg'] = "compute_restore() in check mode: restore Compute ID {} was requested.".format(comp_id)
|
||||
return
|
||||
|
||||
api_params = dict(computeId=comp_id,
|
||||
reason="Restored on user {} request by Ansible DECORT module.".format(self.decort_username))
|
||||
api_params = dict(computeId=comp_id)
|
||||
self.decort_api_call(requests.post, "/restmachine/cloudapi/compute/restore", api_params)
|
||||
# On success the above call will return here. On error it will abort execution by calling fail_json.
|
||||
self.result['failed'] = False
|
||||
@@ -1997,6 +2054,10 @@ class DecortController(object):
|
||||
description: Optional[str] = None,
|
||||
auto_start: Optional[bool] = None,
|
||||
preferred_cpu_cores: list[int] | None = None,
|
||||
boot_mode: None | Literal['bios', 'uefi'] = None,
|
||||
boot_loader_type: None | Literal['linux', 'windows', 'unknown'] = None,
|
||||
network_interface_naming: None | Literal['eth', 'ens'] = None,
|
||||
hot_resize: None | bool = None,
|
||||
):
|
||||
OBJ = 'compute'
|
||||
|
||||
@@ -2015,6 +2076,10 @@ class DecortController(object):
|
||||
'preferredCpu': (
|
||||
[-1] if preferred_cpu_cores == [] else preferred_cpu_cores
|
||||
),
|
||||
'bootType': boot_mode,
|
||||
'loaderType': boot_loader_type,
|
||||
'networkInterfaceNaming': network_interface_naming,
|
||||
'hotResize': hot_resize,
|
||||
},
|
||||
)
|
||||
|
||||
@@ -2029,6 +2094,10 @@ class DecortController(object):
|
||||
'description': description,
|
||||
'auto_start': auto_start,
|
||||
'preferred_cpu_cores': preferred_cpu_cores,
|
||||
'boot_mode': boot_mode,
|
||||
'loader_type': boot_loader_type,
|
||||
'network_interface_naming': network_interface_naming,
|
||||
'hot_resize': hot_resize,
|
||||
}
|
||||
for param, value in params_to_check.items():
|
||||
if value is not None:
|
||||
@@ -2315,18 +2384,43 @@ class DecortController(object):
|
||||
return 0, None
|
||||
|
||||
|
||||
def image_create(self,img_name,url,gid,boottype,imagetype,drivers,hotresize,username,password,account_Id,usernameDL,passwordDL,sepId,poolName):
|
||||
def image_create(
|
||||
self,
|
||||
img_name,
|
||||
url,
|
||||
gid,
|
||||
drivers,
|
||||
username,
|
||||
password,
|
||||
account_Id,
|
||||
usernameDL,
|
||||
passwordDL,
|
||||
sepId,
|
||||
poolName,
|
||||
boot_mode: Literal['bios', 'uefi'] = 'bios',
|
||||
boot_loader_type: Literal['linux', 'windows', 'unknown'] = 'unknown',
|
||||
network_interface_naming: Literal['eth', 'ens'] = 'ens',
|
||||
hot_resize: bool = False,
|
||||
):
|
||||
self.result['waypoints'] = "{} -> {}".format(self.result['waypoints'], "image_create")
|
||||
|
||||
api_params = dict(name=img_name, url=url,
|
||||
gid=gid, boottype=boottype,
|
||||
imagetype=imagetype,
|
||||
drivers=drivers, accountId=account_Id,
|
||||
hotresize=hotresize, username=username,
|
||||
password=password, usernameDL=usernameDL,
|
||||
passwordDL=passwordDL, sepId=sepId,
|
||||
poolName=poolName,
|
||||
)
|
||||
api_params = {
|
||||
'name': img_name,
|
||||
'url': url,
|
||||
'gid': gid,
|
||||
'boottype': boot_mode,
|
||||
'imagetype': boot_loader_type,
|
||||
'drivers': drivers,
|
||||
'accountId': account_Id,
|
||||
'hotresize': hot_resize,
|
||||
'username': username,
|
||||
'password': password,
|
||||
'usernameDL': usernameDL,
|
||||
'passwordDL': passwordDL,
|
||||
'sepId': sepId,
|
||||
'poolName': poolName,
|
||||
'networkInterfaceNaming': network_interface_naming,
|
||||
}
|
||||
api_resp = self.decort_api_call(requests.post, "/restmachine/cloudapi/image/create", api_params)
|
||||
# On success the above call will return here. On error it will abort execution by calling fail_json.
|
||||
virt_image_dict = json.loads(api_resp.content.decode('utf8'))
|
||||
@@ -2775,8 +2869,7 @@ class DecortController(object):
|
||||
self.result['msg'] = "rg_restore() in check mode: restore RG ID {} was requested.".format(arg_rg_id)
|
||||
return
|
||||
|
||||
api_params = dict(rgId=arg_rg_id,
|
||||
reason="Restored on user {} request by DECORT Ansible module.".format(self.decort_username), )
|
||||
api_params = dict(rgId=arg_rg_id)
|
||||
self.decort_api_call(requests.post, "/restmachine/cloudapi/rg/restore", api_params)
|
||||
# On success the above call will return here. On error it will abort execution by calling fail_json.
|
||||
self.result['failed'] = False
|
||||
@@ -2919,6 +3012,7 @@ class DecortController(object):
|
||||
|
||||
account_details['computes_amount'] = account_details.pop('computes')
|
||||
account_details['vinses_amount'] = account_details.pop('vinses')
|
||||
account_details['description'] = account_details.pop('desc')
|
||||
|
||||
account_details['createdTime_readable'] = self.sec_to_dt_str(
|
||||
account_details['createdTime']
|
||||
@@ -3082,6 +3176,7 @@ class DecortController(object):
|
||||
rg['createdTime_readable'] = self.sec_to_dt_str(rg['createdTime'])
|
||||
rg['deletedTime_readable'] = self.sec_to_dt_str(rg['deletedTime'])
|
||||
rg['updatedTime_readable'] = self.sec_to_dt_str(rg['updatedTime'])
|
||||
rg['description'] = rg.pop('desc')
|
||||
|
||||
return resource_groups
|
||||
|
||||
@@ -3682,7 +3777,8 @@ class DecortController(object):
|
||||
gpu_quota: None | int = None,
|
||||
public_ip_quota: None | int = None,
|
||||
ram_quota: None | int = None,
|
||||
sep_pools: None | Iterable[str] = None,) -> None:
|
||||
sep_pools: None | Iterable[str] = None,
|
||||
description: None | str = None,) -> None:
|
||||
"""
|
||||
Implementation of functionality of the API method
|
||||
`/cloudapi/account/update`.
|
||||
@@ -3707,6 +3803,7 @@ class DecortController(object):
|
||||
'name': name,
|
||||
'sendAccessEmails': access_emails,
|
||||
'uniqPools': sep_pools,
|
||||
'desc': description,
|
||||
},
|
||||
not_fail_codes=[404]
|
||||
)
|
||||
@@ -4078,8 +4175,7 @@ class DecortController(object):
|
||||
self.result['msg'] = "vins_restore() in check mode: restore ViNS ID {} was requested.".format(vins_id)
|
||||
return
|
||||
|
||||
api_params = dict(vinsId=vins_id,
|
||||
reason="Restored on user {} request by DECORT Ansible module.".format(self.decort_username), )
|
||||
api_params = dict(vinsId=vins_id)
|
||||
self.decort_api_call(requests.post, "/restmachine/cloudapi/vins/restore", api_params)
|
||||
# On success the above call will return here. On error it will abort execution by calling fail_json.
|
||||
self.result['failed'] = False
|
||||
@@ -4121,8 +4217,7 @@ class DecortController(object):
|
||||
return
|
||||
|
||||
vinsstate_api = "" # this string will also be used as a flag to indicate that API call is necessary
|
||||
api_params = dict(vinsId=vins_dict['id'],
|
||||
reason='Changed by DECORT Ansible module, vins_state method.')
|
||||
api_params = dict(vinsId=vins_dict['id'])
|
||||
expected_state = ""
|
||||
|
||||
if vins_dict['status'] in ["CREATED", "ENABLED"] and desired_state == 'disabled':
|
||||
@@ -4466,8 +4561,7 @@ class DecortController(object):
|
||||
|
||||
api_params = dict(diskId=disk_id,
|
||||
detach=detach,
|
||||
permanently=permanently,
|
||||
reason=reason)
|
||||
permanently=permanently)
|
||||
self.decort_api_call(requests.post, "/restmachine/cloudapi/disks/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
|
||||
@@ -4594,7 +4688,6 @@ class DecortController(object):
|
||||
|
||||
self.result['waypoints'] = "{} -> {}".format(self.result['waypoints'], "disk_creation")
|
||||
api_params = dict(accountId=accountId,
|
||||
gid=0, # depricated
|
||||
name=name,
|
||||
description=description,
|
||||
size=size,
|
||||
@@ -4709,8 +4802,7 @@ class DecortController(object):
|
||||
self.result['msg'] = "disk_restore() in check mode: restore Disk ID {} was requested.".format(disk_id)
|
||||
return
|
||||
|
||||
api_params = dict(diskId=disk_id,
|
||||
reason="Restored on user {} request by DECORT Ansible module.".format(self.decort_username), )
|
||||
api_params = dict(diskId=disk_id)
|
||||
self.decort_api_call(requests.post, "/restmachine/cloudapi/disks/restore", api_params)
|
||||
# On success the above call will return here. On error it will abort execution by calling fail_json.
|
||||
self.result['failed'] = False
|
||||
@@ -5757,7 +5849,13 @@ class DecortController(object):
|
||||
self.result['msg'] = ("group_state(): no start/stop action required for B-service ID {} "
|
||||
"to desired state '{}'.").format(bs_id,desired_state)
|
||||
return
|
||||
def group_resize_count(self,bs_id,gr_dict,desired_count):
|
||||
def group_resize_count(
|
||||
self,
|
||||
bs_id,
|
||||
gr_dict,
|
||||
desired_count,
|
||||
chipset: Literal['Q35', 'i440fx'] = 'i440fx',
|
||||
):
|
||||
|
||||
self.result['waypoints'] = "{} -> {}".format(self.result['waypoints'], "group_resize_count")
|
||||
|
||||
@@ -5767,7 +5865,8 @@ class DecortController(object):
|
||||
serviceId=bs_id,
|
||||
compgroupId=gr_dict['id'],
|
||||
count=desired_count,
|
||||
mode="ABSOLUTE"
|
||||
mode="ABSOLUTE",
|
||||
chipset=chipset,
|
||||
)
|
||||
api_url = "/restmachine/cloudapi/bservice/groupResize"
|
||||
self.decort_api_call(requests.post, api_url, api_params)
|
||||
@@ -5826,10 +5925,10 @@ class DecortController(object):
|
||||
#rly need connect group to extnet ?
|
||||
return
|
||||
def group_provision(
|
||||
self,bs_id,arg_name,arg_count=1,arg_cpu=1,arg_ram=1024,
|
||||
self,bs_id,arg_name,chipset: Literal['Q35', 'i440fx'],arg_count=1,arg_cpu=1,arg_ram=1024,
|
||||
arg_boot_disk=10,arg_image_id=0,arg_driver="KVM_X86",arg_role="",
|
||||
arg_network=None,arg_timeout=0
|
||||
):
|
||||
arg_network=None,arg_timeout=0,
|
||||
):
|
||||
|
||||
self.result['waypoints'] = "{} -> {}".format(self.result['waypoints'], "group_provision")
|
||||
|
||||
@@ -5848,7 +5947,8 @@ class DecortController(object):
|
||||
role = arg_role,
|
||||
vinses = [n['id'] for n in arg_network if n['type'] == 'VINS'],
|
||||
extnets = [n['id'] for n in arg_network if n['type'] == 'EXTNET'],
|
||||
timeoutStart = arg_timeout
|
||||
timeoutStart = arg_timeout,
|
||||
chipset=chipset,
|
||||
)
|
||||
api_resp = self.decort_api_call(requests.post, api_url, api_params)
|
||||
new_bsgroup_id = int(api_resp.text)
|
||||
|
||||
Reference in New Issue
Block a user