Merge branch 'feature/decort_rg_refactoring' into 'master'

Feature/decort rg refactoring

See merge request rudecs/decort-ansible!4
rc-5.2.3
Алексей Даньков 2 years ago
commit 15893f58bb

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

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

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

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

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

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

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

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

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

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

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

@ -1442,7 +1442,7 @@ class DecortController(object):
################################### ###################################
# Resource Group (RG) manipulation methods # Resource Group (RG) manipulation methods
################################### ###################################
def rg_delete(self, rg_id, permanently=False): def rg_delete(self, rg_id, permanently):
"""Deletes specified VDC. """Deletes specified VDC.
@param (int) rg_id: integer value that identifies the RG to be deleted. @param (int) rg_id: integer value that identifies the RG to be deleted.
@ -1500,7 +1500,27 @@ class DecortController(object):
return ret_rg_id, ret_rg_dict return ret_rg_id, ret_rg_dict
def rg_find(self, arg_account_id, arg_rg_id=0, arg_rg_name="", arg_check_state=True): def rg_access(self, arg_rg_id, arg_access):
self.result['waypoints'] = "{} -> {}".format(self.result['waypoints'], "rg_access")
if self.amodule.check_mode:
self.result['failed'] = False
self.result['msg'] = ("rg_access() in check mode: access to RG id '{}' was "
"requested with '{}'.").format(arg_rg_id, arg_access)
return 0
api_params=dict(rgId=arg_rg_id,)
if arg_access['action'] == "grant":
api_params['user']=arg_access['user'],
api_params['right']=arg_access['right'],
api_resp = self.decort_api_call(requests.post, "/restmachine/cloudapi/rg/accessGrant", api_params)
else:
api_params['user']=arg_access['user'],
api_resp = self.decort_api_call(requests.post, "/restmachine/cloudapi/rg/accessRevoke", api_params)
self.result['changed'] = True
return
def rg_find(self, arg_account_id, arg_rg_id, arg_rg_name="", arg_check_state=True):
"""Returns non zero RG ID and a dictionary with RG details on success, 0 and empty dictionary otherwise. """Returns non zero RG ID and a dictionary with RG details on success, 0 and empty dictionary otherwise.
This method does not fail the run if RG cannot be located by its name (arg_rg_name), because this could be This method does not fail the run if RG cannot be located by its name (arg_rg_name), because this could be
an indicator of the requested RG never existed before. an indicator of the requested RG never existed before.
@ -1529,7 +1549,7 @@ class DecortController(object):
self.result['waypoints'] = "{} -> {}".format(self.result['waypoints'], "rg_find") self.result['waypoints'] = "{} -> {}".format(self.result['waypoints'], "rg_find")
ret_rg_id = 0 ret_rg_id = arg_rg_id
api_params = dict() api_params = dict()
ret_rg_dict = None ret_rg_dict = None
@ -1574,7 +1594,44 @@ class DecortController(object):
return ret_rg_id, ret_rg_dict return ret_rg_id, ret_rg_dict
def rg_provision(self, arg_account_id, arg_rg_name, arg_username, arg_quota={}, arg_location="", arg_desc=""): def rg_setDefNet(self, arg_rg_id, arg_net_type, arg_net_id):
self.result['waypoints'] = "{} -> {}".format(self.result['waypoints'], "rg_setDefNet")
if self.amodule.check_mode:
self.result['failed'] = False
self.result['msg'] = ("rg_setDefNet() in check mode: setDefNet RG id '{}' was "
"requested.").format(arg_rg_id)
return 0
if arg_net_type == "NONE":
arg_net_type = "PRIVATE"
api_params = dict(rgId=arg_rg_id,
netType=arg_net_type,
netId=arg_net_id,)
api_resp = self.decort_api_call(requests.post, "/restmachine/cloudapi/rg/setDefNet", api_params)
self.result['changed'] = True
return
def rg_enable(self, arg_rg_id, arg_state):
self.result['waypoints'] = "{} -> {}".format(self.result['waypoints'], "rg_enable")
if self.amodule.check_mode:
self.result['failed'] = False
self.result['msg'] = ("rg_enable() in check mode: '{}' RG id '{}' was "
"requested.").format(arg_state, arg_rg_id)
return 0
api_params = dict(rgId=arg_rg_id)
if arg_state == "enabled":
api_resp = self.decort_api_call(requests.post, "/restmachine/cloudapi/rg/enable", api_params)
else:
api_resp = self.decort_api_call(requests.post, "/restmachine/cloudapi/rg/disable", api_params)
self.result['changed'] = True
return
def rg_provision(self, arg_account_id, arg_rg_name, arg_username, arg_desc, restype, arg_net_type, ipcidr, arg_extNetId, arg_extIp, arg_quota={}, location=""):
"""Provision new RG according to the specified arguments. """Provision new RG according to the specified arguments.
If critical error occurs the embedded call to API function will abort further execution of the script If critical error occurs the embedded call to API function will abort further execution of the script
and relay error to Ansible. and relay error to Ansible.
@ -1587,7 +1644,7 @@ class DecortController(object):
access to the newly created RG. access to the newly created RG.
@param arg_quota: dictionary that defines quotas to set on the RG to be created. Valid keys are: cpu, ram, @param arg_quota: dictionary that defines quotas to set on the RG to be created. Valid keys are: cpu, ram,
disk and ext_ips. disk and ext_ips.
@param (string) arg_location: location code, which identifies the location where RG will be created. If @param (string) location: location code, which identifies the location where RG will be created. If
empty string is passed, the first location under current DECORT controller will be selected. empty string is passed, the first location under current DECORT controller will be selected.
@param (string) arg_desc: optional text description of this resource group. @param (string) arg_desc: optional text description of this resource group.
@ -1602,20 +1659,23 @@ class DecortController(object):
"requested.").format(arg_rg_name) "requested.").format(arg_rg_name)
return 0 return 0
target_gid = self.gid_get(arg_location) target_gid = self.gid_get(location)
if not target_gid: if not target_gid:
self.result['failed'] = True self.result['failed'] = True
self.result['msg'] = ("rg_provision() failed to obtain valid Grid ID for location '{}'").format( self.result['msg'] = ("rg_provision() failed to obtain valid Grid ID for location '{}'").format(
arg_location) location)
self.amodule.fail_json(**self.result) self.amodule.fail_json(**self.result)
api_params = dict(accountId=arg_account_id, api_params = dict(accountId=arg_account_id,
gid=target_gid, gid=target_gid,
name=arg_rg_name, name=arg_rg_name,
owner=arg_username, owner=arg_username,
def_net="NONE", def_net=arg_net_type,
extNetId=arg_extNetId,
extIp=arg_extIp,
# maxMemoryCapacity=-1, maxVDiskCapacity=-1, # maxMemoryCapacity=-1, maxVDiskCapacity=-1,
# maxCPUCapacity=-1, maxNumPublicIP=-1, # maxCPUCapacity=-1, maxNumPublicIP=-1,
# maxNetworkPeerTransfer=-1,
) )
if arg_quota: if arg_quota:
if 'ram' in arg_quota: if 'ram' in arg_quota:
@ -1626,10 +1686,21 @@ class DecortController(object):
api_params['maxCPUCapacity'] = arg_quota['cpu'] api_params['maxCPUCapacity'] = arg_quota['cpu']
if 'ext_ips' in arg_quota: if 'ext_ips' in arg_quota:
api_params['maxNumPublicIP'] = arg_quota['ext_ips'] api_params['maxNumPublicIP'] = arg_quota['ext_ips']
if 'net_transfer' in arg_quota:
api_params['maxNetworkPeerTransfer'] = arg_quota['net_transfer']
if restype:
api_params['resourceTypes'] = restype
if arg_desc: if arg_desc:
api_params['desc'] = arg_desc api_params['desc'] = arg_desc
api_params['def_net'] = arg_net_type
if arg_net_type == "PRIVATE" and ipcidr !="":
api_params['ipcidr'] = ipcidr
api_resp = self.decort_api_call(requests.post, "/restmachine/cloudapi/rg/create", api_params) api_resp = self.decort_api_call(requests.post, "/restmachine/cloudapi/rg/create", api_params)
# On success the above call will return here. On error it will abort execution by calling fail_json. # On success the above call will return here. On error it will abort execution by calling fail_json.
# API /restmachine/cloudapi/rg/create returns ID of the newly created RG on success # API /restmachine/cloudapi/rg/create returns ID of the newly created RG on success
@ -1639,7 +1710,8 @@ class DecortController(object):
return ret_rg_id return ret_rg_id
# TODO: this method will not work in its current implementation. Update it for new .../rg/update specs. # TODO: this method will not work in its current implementation. Update it for new .../rg/update specs.
def rg_quotas(self, arg_rg_dict, arg_quotas):
def rg_update(self, arg_rg_dict, arg_quotas, arg_res_types, arg_newname):
"""Manage quotas for an existing RG. """Manage quotas for an existing RG.
@param arg_rg_dict: dictionary with RG facts as returned by rg_find(...) method or .../rg/get API @param arg_rg_dict: dictionary with RG facts as returned by rg_find(...) method or .../rg/get API
@ -1653,46 +1725,59 @@ class DecortController(object):
# TODO: this method may need update since we introduced GPU functionality and corresponding GPU quota management. # TODO: this method may need update since we introduced GPU functionality and corresponding GPU quota management.
# #
self.result['waypoints'] = "{} -> {}".format(self.result['waypoints'], "rg_quotas") self.result['waypoints'] = "{} -> {}".format(self.result['waypoints'], "rg_update")
if self.amodule.check_mode: if self.amodule.check_mode:
self.result['failed'] = False self.result['failed'] = False
self.result['msg'] = ("rg_quotas() in check mode: setting quotas on RG ID {}, RG name '{}' was " self.result['msg'] = ("rg_update() in check mode: setting quotas on RG ID {}, RG name '{}' was "
"requested.").format(arg_rg_dict['id'], arg_rg_dict['name']) "requested.").format(arg_rg_dict['id'], arg_rg_dict['name'])
return return
update_required = False
api_params = dict(rgId=arg_rg_dict['id'],)
if arg_res_types:
if arg_rg_dict['resourceTypes'] != arg_res_types:
api_params['resourceTypes'] = arg_res_types
update_required = True
else:
api_params['resourceTypes'] = arg_rg_dict['resourceTypes']
if arg_newname != "" and arg_newname!=arg_rg_dict['name']:
api_params['name'] = arg_newname
update_required = True
# One more inconsistency in API keys: # One more inconsistency in API keys:
# - when setting resource limits, the keys are in the form 'max{{ RESOURCE_NAME }}Capacity' # - when setting resource limits, the keys are in the form 'max{{ RESOURCE_NAME }}Capacity'
# - when quering resource limits, the keys are in the form of cloud units (CU_*) # - when quering resource limits, the keys are in the form of cloud units (CU_*)
query_key_map = dict(cpu='CU_C', query_key_map = dict(cpu='CU_C',
ram='CU_M', ram='CU_M',
disk='CU_D', disk='CU_D',
ext_ips='CU_I', ) ext_ips='CU_I',
net_transfer='CU_NP',)
set_key_map = dict(cpu='maxCPUCapacity', set_key_map = dict(cpu='maxCPUCapacity',
ram='maxMemoryCapacity', ram='maxMemoryCapacity',
disk='maxVDiskCapacity', disk='maxVDiskCapacity',
ext_ips='maxNumPublicIP', ) ext_ips='maxNumPublicIP',
api_params = dict(rgId=arg_rg_dict['id'], ) net_transfer='maxNetworkPeerTransfer',)
quota_change_required = False
for new_limit in ('cpu', 'ram', 'disk', 'ext_ips'): for new_limit in ('cpu', 'ram', 'disk', 'ext_ips', 'net_transfer'):
if arg_quotas: if arg_quotas:
if new_limit in arg_quotas: if new_limit in arg_quotas:
# If this resource type limit is found in the desired quotas, check if the desired setting is # If this resource type limit is found in the desired quotas, check if the desired setting is
# different from the current settings of VDC. If it is different, set the new one. # different from the current settings of VDC. If it is different, set the new one.
if arg_quotas[new_limit] != arg_rg_dict['resourceLimits'][query_key_map[new_limit]]: if arg_quotas[new_limit] != arg_rg_dict['resourceLimits'][query_key_map[new_limit]]:
api_params[set_key_map[new_limit]] = arg_quotas[new_limit] api_params[set_key_map[new_limit]] = arg_quotas[new_limit]
quota_change_required = True update_required = True
elif arg_quotas[new_limit] == arg_rg_dict['resourceLimits'][query_key_map[new_limit]]:
api_params[set_key_map[new_limit]] = arg_quotas[new_limit]
else: else:
# This resource type limit not found in the desired quotas. It means that no limit for this api_params[set_key_map[new_limit]] = arg_rg_dict['resourceLimits'][query_key_map[new_limit]]
# resource type - reset VDC limit for this resource type regardless of the current VDC settings.
api_params[set_key_map[new_limit]] = -1
# quota_change_required = True
else: else:
# if quotas dictionary is None, it means that no quotas should be set - reset the limits # if quotas dictionary is None, it means that no quotas should be set - reset the limits
api_params[set_key_map[new_limit]] = -1 api_params[set_key_map[new_limit]] = arg_rg_dict['resourceLimits'][query_key_map[new_limit]]
if quota_change_required: if update_required:
self.decort_api_call(requests.post, "/restmachine/cloudapi/rg/update", api_params) self.decort_api_call(requests.post, "/restmachine/cloudapi/rg/update", api_params)
# On success the above call will return here. On error it will abort execution by calling fail_json. # 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
@ -1722,66 +1807,6 @@ class DecortController(object):
self.result['changed'] = True self.result['changed'] = True
return return
def rg_state(self, arg_rg_dict, arg_desired_state):
"""Enable or disable RG.
@param arg_rg_dict: dictionary with the target RG facts as returned by rg_find(...) method or
.../rg/get API call.
@param arg_desired_state: the desired state for this RG. Valid states are 'enabled' and 'disabled'.
"""
self.result['waypoints'] = "{} -> {}".format(self.result['waypoints'], "rg_state")
NOP_STATES_FOR_RG_CHANGE = ["MODELED", "DISABLING", "ENABLING", "DELETING", "DELETED", "DESTROYING",
"DESTROYED"]
VALID_TARGET_STATES = ["enabled", "disabled"]
if arg_rg_dict['status'] in NOP_STATES_FOR_RG_CHANGE:
self.result['failed'] = False
self.result['msg'] = ("rg_state(): no state change possible for RG ID {} "
"in its current state '{}'.").format(arg_rg_dict['id'], arg_rg_dict['status'])
return
if arg_desired_state not in VALID_TARGET_STATES:
self.result['failed'] = False
self.result['warning'] = ("rg_state(): unrecognized desired state '{}' requested "
"for RG ID {}. No RG state change will be done.").format(arg_desired_state,
arg_rg_dict['id'])
return
if self.amodule.check_mode:
self.result['failed'] = False
self.result['msg'] = ("rg_state() in check mode: setting state of RG ID {}, name '{}' to "
"'{}' was requested.").format(arg_rg_dict['id'], arg_rg_dict['name'],
arg_desired_state)
return
rgstate_api = "" # this string will also be used as a flag to indicate that API call is necessary
api_params = dict(rgId=arg_rg_dict['id'],
reason='Changed by DECORT Ansible module, rg_state method.')
expected_state = ""
if arg_rg_dict['status'] in ["CREATED", "ENABLED"] and arg_desired_state == 'disabled':
rgstate_api = "/restmachine/cloudapi/rg/disable"
expected_state = "DISABLED"
elif arg_rg_dict['status'] == "DISABLED" and arg_desired_state == 'enabled':
rgstate_api = "/restmachine/cloudapi/rg/enable"
expected_state = "ENABLED"
if rgstate_api != "":
self.decort_api_call(requests.post, rgstate_api, api_params)
# On success the above call will return here. On error it will abort execution by calling fail_json.
self.result['failed'] = False
self.result['changed'] = True
arg_rg_dict['status'] = expected_state
else:
self.result['failed'] = False
self.result['msg'] = ("rg_state(): no state change required for RG ID {} from current "
"state '{}' to desired state '{}'.").format(arg_rg_dict['id'],
arg_rg_dict['status'],
arg_desired_state)
return
def account_find(self, account_name, account_id=0): def account_find(self, account_name, account_id=0):
"""Find cloud account specified by the name and return facts about the account. Knowing account is """Find cloud account specified by the name and return facts about the account. Knowing account is
required for certain cloud resource management tasks (e.g. creating new RG). required for certain cloud resource management tasks (e.g. creating new RG).
@ -1971,10 +1996,13 @@ class DecortController(object):
ret_gid = 0 ret_gid = 0
api_params = dict() api_params = dict()
api_resp = self.decort_api_call(requests.post, "/restmachine/cloudapi/locations/list", api_params) api_resp = self.decort_api_call(requests.post, "/restmachine/cloudapi/locations/list", api_params)
if api_resp.status_code == 200: if api_resp.status_code == 200:
locations = json.loads(api_resp.content.decode('utf8')) locations = json.loads(api_resp.content.decode('utf8'))
if location_code == "" and locations: if location_code == "" and locations:
ret_gid = locations['gid'] ret_gid = locations[0]['gid']
else: else:
for runner in locations: for runner in locations:
if runner['locationCode'] == location_code: if runner['locationCode'] == location_code:

Loading…
Cancel
Save