diff --git a/library/decort_lb.py b/library/decort_lb.py new file mode 100644 index 0000000..3296e79 --- /dev/null +++ b/library/decort_lb.py @@ -0,0 +1,328 @@ +#!/usr/bin/python +# +# Digital Enegry Cloud Orchestration Technology (DECORT) modules for Ansible +# Copyright: (c) 2018-2022 Digital Energy Cloud Solutions LLC +# +# Apache License 2.0 (see http://www.apache.org/licenses/LICENSE-2.0.txt) +# + +ANSIBLE_METADATA = {'metadata_version': '1.1', + 'status': ['preview'], + 'supported_by': 'community'} + +DOCUMENTATION = ''' + TODO +''' + +EXAMPLES = ''' + TODO +''' + + +from ansible.module_utils.basic import AnsibleModule +from ansible.module_utils.basic import env_fallback + +from ansible.module_utils.decort_utils import * + +class decort_lb(DecortController): + def __init__(self,arg_amodule) -> None: + super(decort_lb,self).__init__(arg_amodule) + + self.lb_id = 0 + self.lb_facts = None + self.vins_id = 0 + self.vins_facts = None + self.rg_id = 0 + self.rg_facts = None + self.acc_id = 0 + self.acc_facts = None + self.default_server_check = "enabled" + self.default_alg = "roundrobin" + self.default_settings = { + "downinter": 10000, + "fall": 2, + "inter": 5000, + "maxconn": 250, + "maxqueue": 256, + "rise": 2, + "slowstart": 60000, + "weight": 100, + } + if arg_amodule.params['lb_id']: + self.lb_id, self.lb_facts = self.lb_find(arg_amodule.params['lb_id']) + if not self.lb_id: + self.result['failed'] = True + self.result['msg'] = "Specified LB ID {} not found."\ + .format(arg_amodule.params['lb _id']) + self.fail_json(**self.result) + self.acc_id = self.lb_facts['accountId'] + self.rg_id = self.lb_facts['rgId'] + self.vins_id = self.lb_facts['vinsId'] + return + + if arg_amodule.params['rg_id']: + self.rg_id, self.rg_facts = self.rg_find(0,arg_amodule.params['rg_id'], arg_rg_name="") + if not self.rg_id: + self.result['failed'] = True + self.result['msg'] = "Specified RG ID {} not found.".format(arg_amodule.params['vins_id']) + self.fail_json(**self.result) + + if arg_amodule.params['vins_id']: + self.vins_id, self.vins_facts = self.vins_find(arg_amodule.params['vins_id']) + if not self.vins_id: + self.result['failed'] = True + self.result['msg'] = "Specified ViNS ID {} not found.".format(arg_amodule.params['vins_id']) + self.fail_json(**self.result) + + elif arg_amodule.params['account_id'] or arg_amodule.params['account_name'] != "": + + if arg_amodule.params['rg_name']: + self.result['failed'] = True + self.result['msg'] = ("RG name must be specified with account present") + self.fail_json(**self.result) + self.acc_id, self.acc_facts = self.account_find(arg_amodule.params['account_name'], + arg_amodule.params['account_id']) + if not self.acc_id: + 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) + self.rg_id, self.rg_facts = self.rg_find(self._acc_id,0, arg_rg_name=arg_amodule.params['rg_name']) + + if self.rg_id and self.vins_id: + self.lb_id, self.lb_facts = self.lb_find(0,arg_amodule.params['lb_name'],self.rg_id) + return + + def create(self): + self.lb_id = self.lb_provision(self.amodule.params['lb_name'], + self.rg_id,self.vins_id, + self.amodule.params['ext_net_id'], + self.amodule.params['annotation']) + if self.amodule.params['backends'] or self.amodule.params['frontends']: + self.lb_id, self.lb_facts = self.lb_find(0,self.amodule.params['lb_name'],self.rg_id) + self.lb_update( + self.lb_facts['backends'], + self.lb_facts['frontends'], + self.amodule.params['backends'], + self.amodule.params['servers'], + self.amodule.params['frontends'] + ) + return + + def action(self,d_state='',restore=False): + if restore == True: + self.lb_restore(arg_vins_id=self.lb_id) + self.lb_state(self.vins_facts, 'enabled') + self.lb_facts['status'] = "ENABLED" + self.lb_facts['techStatus'] = "STARTED" + + self.lb_update( + self.lb_facts['backends'], + self.lb_facts['frontends'], + self.amodule.params['backends'], + self.amodule.params['servers'], + self.amodule.params['frontends'] + ) + + if d_state != '': + self.lb_state(self.lb_facts, d_state) + return + + def delete(self): + self.lb_delete(self.lb_id, self.amodule.params['permanently']) + self.lb_facts['status'] = 'DESTROYED' + return + def nop(self): + """No operation (NOP) handler for LB management by decort_lb module. + This function is intended to be called from the main switch construct of the module + when current state -> desired state change logic does not require any changes to + the actual LB state. + """ + self.result['failed'] = False + self.result['changed'] = False + if self.lb_id: + self.result['msg'] = ("No state change required for LB ID {} because of its " + "current status '{}'.").format(self.lb_id, self.vins_facts['status']) + else: + self.result['msg'] = ("No state change to '{}' can be done for " + "non-existent LB instance.").format(self.amodule.params['state']) + return + def error(self): + self.result['failed'] = True + self.result['changed'] = False + if self.vins_id: + self.result['failed'] = True + self.result['changed'] = False + self.result['msg'] = ("Invalid target state '{}' requested for LB ID {} in the " + "current status '{}'").format(self.lb_id, + self.amodule.params['state'], + self.lb_facts['status']) + else: + self.result['failed'] = True + self.result['changed'] = False + self.result['msg'] = ("Invalid target state '{}' requested for non-existent " + "LB name '{}'").format(self.amodule.params['state'], + self.amodule.params['lb_name']) + return + def package_facts(self, arg_check_mode=False): + """Package a dictionary of LB facts according to the decort_lb module specification. + This dictionary will be returned to the upstream Ansible engine at the completion of + the module run. + + @param arg_check_mode: boolean that tells if this Ansible module is run in check mode + """ + + ret_dict = dict(id=0, + name="none", + state="CHECK_MODE", + ) + + if arg_check_mode: + # in check mode return immediately with the default values + return ret_dict + + if self.vins_facts is None: + # if void facts provided - change state value to ABSENT and return + ret_dict['state'] = "ABSENT" + return ret_dict + + ret_dict['id'] = self.lb_facts['id'] + ret_dict['name'] = self.lb_facts['name'] + ret_dict['state'] = self.lb_facts['status'] + #ret_dict['account_id'] = self.lb_facts['accountId'] + ret_dict['rg_id'] = self.lb_facts['rgId'] + ret_dict['gid'] = self.lb_facts['gid'] + if self.amodule.params['state']!="absent": + ret_dict['backends'] = self.lb_facts['backends'] + ret_dict['frontends'] = self.lb_facts['frontends'] + return ret_dict + @staticmethod + def build_parameters(): + """Build and return a dictionary of parameters expected by decort_vins module in a form accepted + by AnsibleModule utility class.""" + + return dict( + account_id=dict(type='int', required=False), + account_name=dict(type='str', required=False, default=''), + annotation=dict(type='str', required=False, default='Managed by Ansible module decort_lb'), + app_id=dict(type='str', + required=False, + fallback=(env_fallback, ['DECORT_APP_ID'])), + app_secret=dict(type='str', + required=False, + fallback=(env_fallback, ['DECORT_APP_SECRET']), + no_log=True), + authenticator=dict(type='str', + required=True, + choices=['legacy', 'oauth2', 'jwt']), + controller_url=dict(type='str', required=True), + # datacenter=dict(type='str', required=False, default=''), + ext_net_id=dict(type='int', required=False, default=-1), + ext_ip_addr=dict(type='str', required=False, default=''), + jwt=dict(type='str', + required=False, + fallback=(env_fallback, ['DECORT_JWT']), + no_log=True), + oauth2_url=dict(type='str', + required=False, + fallback=(env_fallback, ['DECORT_OAUTH2_URL'])), + password=dict(type='str', + required=False, + fallback=(env_fallback, ['DECORT_PASSWORD']), + no_log=True), + state=dict(type='str', + default='present', + choices=['absent', 'disabled', 'enabled', 'present','restart']), + user=dict(type='str', + required=False, + fallback=(env_fallback, ['DECORT_USER'])), + rg_id=dict(type='int', required=False, default=0), + rg_name=dict(type='str', required=False, default=''), + vins_name=dict(type='str', required=False, default=''), + vins_id=dict(type='int', required=False, default=0), + verify_ssl=dict(type='bool', required=False, default=True), + lb_id=dict(type='int', required=False, default=0), + lb_name=dict(type='str', required=True), + backends=dict(type='list',required=False,default=[]), + frontends=dict(type='list',required=False,default=[]), + servers=dict(type='list',required=False,default=[]), + permanently=dict(type='bool', required=False, default=False), + workflow_callback=dict(type='str', required=False), + workflow_context=dict(type='str', required=False), + ) + +def main(): + module_parameters = decort_lb.build_parameters() + + amodule = AnsibleModule(argument_spec=module_parameters, + supports_check_mode=True, + mutually_exclusive=[ + ['oauth2', 'password'], + ['password', 'jwt'], + ['jwt', 'oauth2'], + ], + required_together=[ + ['app_id', 'app_secret'], + ['user', 'password'] + ], + required_one_of=[ + ['rg_id','rg_name'], + ['lb_id','lb_name'], + ['vins_id','vins_name'] + ] + ) + decon = decort_lb(amodule) + if decon.lb_id: + if decon.lb_facts['status'] in ["MODELED", "DISABLING", "ENABLING", "DELETING","DESTROYING","RESTORING"]: + decon.result['failed'] = True + decon.result['changed'] = False + decon.result['msg'] = ("No change can be done for existing LB ID {} because of its current " + "status '{}'").format(decon.lb_id, decon.lb_facts['status']) + elif decon.lb_facts['status'] == "DISABLED": + if amodule.params['state'] == 'absent': + decon.delete() + elif amodule.params['state'] in ('present', 'disabled'): + decon.action() + elif amodule.params['state'] == 'enabled': + decon.action('enabled') + elif decon.lb_facts['status'] in ["CREATED", "ENABLED"]: + if amodule.params['state'] == 'absent': + decon.delete() + elif amodule.params['state'] in ('present', 'enabled'): + decon.action() + elif amodule.params['state'] == 'disabled': + decon.action('disabled') + elif amodule.params['state'] in ('stopped', 'started','restart'): + decon.action(amodule.params['state']) + elif decon.lb_facts['status'] == "DELETED": + if amodule.params['state'] in ['present', 'enabled']: + decon.action(restore=True) + elif amodule.params['state'] == 'absent': + decon.delete() + elif amodule.params['state'] == 'disabled': + decon.error() + elif decon.lb_facts['status'] == "DESTROYED": + if amodule.params['state'] in ('present', 'enabled'): + decon.create() + elif amodule.params['state'] == 'absent': + decon.nop() + elif amodule.params['state'] == 'disabled': + decon.error() + else: + if amodule.params['state'] == 'absent': + decon.nop() + elif amodule.params['state'] in ('present', 'enabled'): + decon.create() + elif amodule.params['state'] == 'disabled': + decon.error() + + if decon.result['failed']: + amodule.fail_json(**decon.result) + else: + if decon.result['changed'] and amodule.params['state'] != 'absent': + _, decon.lb_facts = decon.lb_find(decon.lb_id) + if decon.lb_id: + decon.result['facts'] = decon.package_facts(amodule.check_mode) + amodule.exit_json(**decon.result) +if __name__ == "__main__": + main() \ No newline at end of file diff --git a/library/decort_vins.py b/library/decort_vins.py index f94058e..a50faa1 100644 --- a/library/decort_vins.py +++ b/library/decort_vins.py @@ -249,8 +249,8 @@ class decort_vins(DecortController): def __init__(self,arg_amodule): super(decort_vins, self).__init__(arg_amodule) - vins_id = 0 - vins_level = "" # "ID" if specified by ID, "RG" - at resource group, "ACC" - at account level + self.vins_id = 0 + self.vins_level = "" # "ID" if specified by ID, "RG" - at resource group, "ACC" - at account level vins_facts = None # will hold ViNS facts validated_rg_id = 0 rg_facts = None # will hold RG facts @@ -261,17 +261,17 @@ class decort_vins(DecortController): # expect existing ViNS with the specified ID # This call to vins_find will abort the module if no ViNS with such ID is present self.vins_id, self.vins_facts = self.vins_find(arg_amodule.params['vins_id']) - if not vins_id: + if not self.vins_id: self.result['failed'] = True self.result['msg'] = "Specified ViNS ID {} not found.".format(arg_amodule.params['vins_id']) self.fail_json(**self.result) - vins_level = "ID" + self.vins_level = "ID" validated_acc_id = vins_facts['accountId'] validated_rg_id = vins_facts['rgId'] elif arg_amodule.params['rg_id']: # expect ViNS @ RG level in the RG with specified ID - vins_level = "RG" + self.vins_level = "RG" # This call to rg_find will abort the module if no RG with such ID is present validated_rg_id, rg_facts = self.rg_find(0, # account ID set to 0 as we search for RG by RG ID arg_amodule.params['rg_id'], arg_rg_name="") @@ -307,7 +307,7 @@ class decort_vins(DecortController): rg_id=validated_rg_id, rg_facts=rg_facts, check_state=False) - vins_level = "RG" + self.vins_level = "RG" # TODO: add checks and setup ViNS presence flags accordingly else: # At this point we know for sure that rg_name="" and rg_id=0 # So we expect ViNS @ account level @@ -317,7 +317,7 @@ class decort_vins(DecortController): rg_id=0, rg_facts=rg_facts, check_state=False) - vins_level = "ACC" + self.vins_level = "ACC" # TODO: add checks and setup ViNS presence flags accordingly else: # this is "invalid arguments combination" sink diff --git a/module_utils/decort_utils.py b/module_utils/decort_utils.py index 918cbee..db5421b 100644 --- a/module_utils/decort_utils.py +++ b/module_utils/decort_utils.py @@ -31,7 +31,6 @@ Requirements: - DECORT cloud platform version 3.6.1 or higher """ -import copy import json import jwt import netaddr @@ -1207,7 +1206,7 @@ class DecortController(object): affinityLabel=label,) self.decort_api_call(requests.post, "/restmachine/cloudapi/compute/affinityLabelSet", api_params) if aff: - if len(aff[0])>0: + if len(aff)>0: for rule in aff: api_params = dict(computeId=comp_dict['id'], key=rule['key'], @@ -1217,7 +1216,7 @@ class DecortController(object): policy=rule['policy'],) self.decort_api_call(requests.post, "/restmachine/cloudapi/compute/affinityRuleAdd", api_params) if aaff: - if len(aaff[0])>0: + if len(aaff)>0: for rule in aaff: api_params = dict(computeId=comp_dict['id'], key=rule['key'], @@ -1975,7 +1974,7 @@ class DecortController(object): if api_resp.status_code == 200: locations = json.loads(api_resp.content.decode('utf8')) if location_code == "" and locations: - ret_gid = locations[0]['gid'] + ret_gid = locations['gid'] else: for runner in locations: if runner['locationCode'] == location_code: @@ -2060,7 +2059,7 @@ class DecortController(object): self.result['msg'] = "_rg_listvins(): zero RG ID specified." self.amodule.fail_json(**self.result) - api_params = dict(rgId=rg_id, ) + api_params = dict(rgId=rg_id) api_resp = self.decort_api_call(requests.post, "/restmachine/cloudapi/rg/listVins", api_params) if api_resp.status_code == 200: ret_rg_vins_list = json.loads(api_resp.content.decode('utf8')) @@ -3270,7 +3269,7 @@ class DecortController(object): self.result['failed'] = True return elif ret_info['status'] == "OK": - k8s_id = ret_info['result'][0] + k8s_id = ret_info['result'] self.result['changed'] = True return k8s_id else: @@ -3365,18 +3364,17 @@ class DecortController(object): api_params = dict(includeDisabled=False) api_resp = self.decort_api_call(requests.post, "/restmachine/cloudapi/k8ci/list", api_params) - k8ci_id_present = False + if api_resp.status_code == 200: ret_k8ci_list = json.loads(api_resp.content.decode('utf8')) for k8ci_item in ret_k8ci_list: if k8ci_item['id'] == arg_k8ci_id: - k8ci_id_present = True break - if k8ci_id_present == False: - self.result['failed'] = True - self.result['msg'] = ("Cannot find k8ci id: {}.").format(arg_k8ci_id) - self.amodule.fail_json(**self.result) + else: + self.result['failed'] = True + self.result['msg'] = ("Cannot find k8ci id: {}.").format(arg_k8ci_id) + self.amodule.fail_json(**self.result) else: self.result['failed'] = True self.result['msg'] = ("Failed to get k8ci list HTTP code {}.").format(api_resp.status_code) @@ -3736,3 +3734,699 @@ class DecortController(object): self.result['msg'] = "group_delete() Group ID {} was deleted.".format(gr_id) self.result['changed'] = True return +#################### +### LB MANAGMENT ### +#################### + def _lb_get_by_id(self,lb_id): + """Helper function that locates LB by ID and returns LB facts. This function + expects that the ViNS exists (albeit in DELETED or DESTROYED state) and will return + 0 LB ID if not found. + + @param (int) vins_id: ID of the LB to find and return facts for. + + @return: LB ID and a dictionary of LB facts as provided by LB/get API call. + """ + ret_lb_id = 0 + ret_lb_dict = dict() + + if not lb_id: + self.result['failed'] = True + self.result['msg'] = "lb_get_by_id(): zero LB ID specified." + self.amodule.fail_json(**self.result) + + api_params = dict(lbId=lb_id) + api_resp = self.decort_api_call(requests.post, "/restmachine/cloudapi/lb/get", api_params) + if api_resp.status_code == 200: + ret_lb_id = lb_id + ret_lb_dict = json.loads(api_resp.content.decode('utf8')) + else: + self.result['warning'] = ("lb_get_by_id(): failed to get LB by ID {}. HTTP code {}, " + "response {}.").format(lb_id, api_resp.status_code, api_resp.reason) + + return ret_lb_id, ret_lb_dict + def _rg_listlb(self,rg_id): + """List all LB in the resource group + @param (int) rg_id: id onr resource group + """ + if not rg_id: + self.result['failed'] = True + self.result['msg'] = "_rg_listlb(): zero RG ID specified." + self.amodule.fail_json(**self.result) + + api_params = dict(rgId=rg_id) + api_resp = self.decort_api_call(requests.post, "/restmachine/cloudapi/rg/listLb", api_params) + if api_resp.status_code == 200: + ret_rg_vins_list = json.loads(api_resp.content.decode('utf8')) + else: + self.result['warning'] = ("rg_listlb(): failed to get RG by ID {}. HTTP code {}, " + "response {}.").format(rg_id, api_resp.status_code, api_resp.reason) + return [] + + return ret_rg_vins_list + def lb_find(self,lb_id=0,lb_name="",rg_id=0): + """Find specified LB. + + @returns: LB ID and dictionary with LB facts. + """ + LB_INVALID_STATES = ["ENABLING", "DISABLING", "DELETING", "DELETED", "DESTROYING", "DESTROYED"] + + ret_lb_id = 0 + ret_lb_facts = None + + self.result['waypoints'] = "{} -> {}".format(self.result['waypoints'], "lb_find") + + if lb_id > 0: + ret_lb_id, ret_lb_facts = self._lb_get_by_id(lb_id) + if not ret_lb_id: + self.result['failed'] = True + self.result['msg'] = "lb_find(): cannot find LB by ID {}.".format(lb_id) + self.amodule.fail_json(**self.result) + if not self.amodule.check_mode or ret_lb_facts['status'] not in LB_INVALID_STATES: + return ret_lb_id, ret_lb_facts + else: + return 0, None + elif lb_name != "": + if rg_id > 0: + list_lb = self._rg_listlb(rg_id) + for lb in list_lb: + if lb['name'] == lb_name: + ret_lb_id, ret_lb_facts = self._lb_get_by_id(lb['id']) + if not self.amodule.check_mode or ret_lb_facts['status'] not in LB_INVALID_STATES: + return ret_lb_id, ret_lb_facts + else: + return 0, None + else: + self.result['failed'] = True + self.result['msg'] = ("vins_lb(): cannot find LB by name '{}' " + "when no account ID or RG ID is specified.").format(lb_name) + self.amodule.fail_json(**self.result) + else: + self.result['failed'] = True + self.result['msg'] = "vins_find(): cannot find LB by zero ID and empty name." + self.amodule.fail_json(**self.result) + + return 0, None + def lb_provision(self,lb_name,rg_id,vins_id,ext_net_id,annotation,start=True): + """Provision LB according to the specified arguments. + If critical error occurs the embedded call to API function will abort further execution of + the script and relay error to Ansible. + Note, that when creating ViNS at account level, default location under DECORT controller + will be selected automatically. + """ + + self.result['waypoints'] = "{} -> {}".format(self.result['waypoints'], "lb_provision") + + if self.amodule.check_mode: + self.result['failed'] = False + self.result['msg'] = ("vins_lb() in check mode: provision LB name '{}' was " + "requested in RG with id: {}.").format(lb_name,rg_id) + return 0 + + if lb_name == "": + self.result['failed'] = True + self.result['msg'] = "lb_provision(): LB name cannot be empty." + self.amodule.fail_json(**self.result) + + api_url = "/restmachine/cloudapi/lb/create" + api_params = dict( + name=lb_name, + rgId=rg_id, + extnetId=ext_net_id, + vinsId=vins_id, + start=start, + decs=annotation + ) + api_resp = self.decort_api_call(requests.post, api_url, 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 + ret_lb_id = int(api_resp.content.decode('utf8')) + return ret_lb_id + def lb_delete(self,lb_id,permanently=False): + """Deletes specified LB. + + @param (int) lb_id: integer value that identifies the ViNS to be deleted. + @param (bool) permanently: a bool that tells if deletion should be permanent. If False, the LB will be + marked as DELETED and placed into a trash bin for predefined period of time (usually, a few days). Until + this period passes this LB can be restored by calling the corresponding 'restore' method. + """ + + self.result['waypoints'] = "{} -> {}".format(self.result['waypoints'], "lb_delete") + + if self.amodule.check_mode: + self.result['failed'] = False + self.result['msg'] = "lb_delete() in check mode: delete ViNS ID {} was requested.".format(lb_id) + return + + api_params = dict(lbId=lb_id, + permanently=permanently) + self.decort_api_call(requests.post, "/restmachine/cloudapi/lb/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 + self.result['changed'] = True + return + def lb_state(self, lb_dict, desired_state): + """Change state for LB. + """ + + self.result['waypoints'] = "{} -> {}".format(self.result['waypoints'], "lb_state") + + NOP_STATES_FOR_LB_CHANGE = ["MODELED", "DISABLING", "ENABLING", "DELETING", "DELETED", "DESTROYING", + "DESTROYED"] + VALID_TARGET_STATES = ["enabled", "disabled","restart"] + VALID_TARGET_TSTATES = ["STARTED","STOPPED"] + + if lb_dict['status'] in NOP_STATES_FOR_LB_CHANGE: + self.result['failed'] = False + self.result['msg'] = ("lb_state(): no state change possible for LB ID {} " + "in its current state '{}'.").format(lb_dict['id'], lb_dict['status']) + return + + if desired_state not in VALID_TARGET_STATES: + self.result['failed'] = False + self.result['warning'] = ("lb_state(): unrecognized desired state '{}' requested " + "for LB ID {}. No LB state change will be done.").format(desired_state, + lb_dict['id']) + return + + if self.amodule.check_mode: + self.result['failed'] = False + self.result['msg'] = ("lb_state() in check mode: setting state of LB ID {}, name '{}' to " + "'{}' was requested.").format(lb_dict['id'], lb_dict['name'], + desired_state) + return + + state_api = "" + api_params = dict(lbId=lb_dict['id']) + expected_state = "" + if lb_dict['status'] in ["CREATED", "ENABLED"]: + if desired_state == 'disabled': + state_api = "/restmachine/cloudapi/lb/disable" + expected_state = "DISABLED" + if lb_dict['techStatus'] == "STARTED": + if desired_state == 'stopped': + state_api = "/restmachine/cloudapi/lb/stop" + if desired_state == 'restart': + state_api = "/restmachine/cloudapi/lb/restart" + elif lb_dict['techStatus'] == "STOPPED": + if desired_state == 'started': + state_api = "/restmachine/cloudapi/lb/start" + elif lb_dict['status'] == "DISABLED" and desired_state == 'enabled': + state_api = "/restmachine/cloudapi/lb/enable" + expected_state = "ENABLED" + + if state_api != "": + self.decort_api_call(requests.post, state_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 + lb_dict['status'] = expected_state + else: + self.result['failed'] = False + self.result['msg'] = ("lb_state(): no state change required for LB ID {} from current " + "state '{}' to desired state '{}'.").format(lb_dict['id'], + lb_dict['status'], + desired_state) + return + def lb_restore(self, lb_id): + """Restores previously deleted LB identified by its ID. + + @param lb_id: ID of the LB to restore. + + @returns: nothing on success. On error this method will abort module execution. + """ + + self.result['waypoints'] = "{} -> {}".format(self.result['waypoints'], "lb_restore") + + if self.amodule.check_mode: + self.result['failed'] = False + self.result['msg'] = "lb_restore() in check mode: restore LB ID {} was requested.".format(lb_id) + return + + api_params = dict(lbId=lb_id) + self.decort_api_call(requests.post, "/restmachine/cloudapi/lb/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 + self.result['changed'] = True + return + + def lb_update_backends(self,lb_backends,mod_backends,mod_servers): + """ + backends + """ + self.result['waypoints'] = "{} -> {}".format(self.result['waypoints'], "lb_update_backends") + + if mod_backends: + backs_out = [rec['name'] for rec in mod_backends] + else: + backs_out="" + backs_in = [rec['name'] for rec in lb_backends] + del_backs = set(backs_in).difference(backs_out) + add_becks = set(backs_out).difference(backs_in) + upd_becks = set(backs_in).intersection(backs_out) + + for item in del_backs: + #need delete frontend + api_params = dict(lbId=self.lb_id,backendName = item) + api_resp = self.decort_api_call(requests.post, "/restmachine/cloudapi/lb/backendDelete", api_params) + self.result['changed'] = True + for item in add_becks: + backend, = list(filter(lambda i: i['name'] == item,mod_backends)) + api_params = dict( + lbId=self.lb_id, + backendName = backend['name'], + algorithm = backend['algorithm'] if "algorithm" in backend else None, + **backend['default_settings'] if "default_settings" in backend else {}, + ) + api_resp = self.decort_api_call(requests.post, "/restmachine/cloudapi/lb/backendCreate", api_params) + self.result['changed'] = True + for item in upd_becks.union(add_becks): + + backend, = list(filter(lambda i: i['name'] == item,lb_backends)) + mod_backend, = list(filter(lambda i: i['name'] == item,mod_backends)) + servers_in = [rec['name'] for rec in backend['servers']] + servers_out = [] + #oO rework + if mod_servers: + for serv in mod_servers: + for bend in serv['backends']: + if bend['name'] == item: + servers_out.append(serv['name']) + + del_srv = set(servers_in).difference(servers_out) + add_srv = set(servers_out).difference(servers_in) + upd_srv = set(servers_in).intersection(servers_out) + + del backend['serverDefaultSettings']['guid'] + + if "default_settings" not in mod_backend: + mod_backend["default_settings"] = self.default_settings + + if "algorithm" not in mod_backend: + mod_backend["algorithm"] = self.default_alg + + if backend['serverDefaultSettings'] != mod_backend["default_settings"] or\ + mod_backend["algorithm"] != backend['algorithm']: + api_params = dict( + lbId=self.lb_id, + backendName = item, + algorithm = mod_backend['algorithm'], + **mod_backend['default_settings'] if "default_settings" in mod_backend else {}, + ) + api_resp = self.decort_api_call(requests.post, "/restmachine/cloudapi/lb/backendUpdate", api_params) + self.result['changed'] = True + + for srv in del_srv: + api_params = dict(lbId=self.lb_id,backendName = item,serverName=srv) + api_resp = self.decort_api_call(requests.post, "/restmachine/cloudapi/lb/backendServerDelete", api_params) + self.result['changed'] = True + for srv in add_srv: + server, = list(filter(lambda i: i['name'] == srv,mod_servers)) + back, = list(filter(lambda i: i['name'] == item,server['backends'])) + api_params = dict( + lbId=self.lb_id, + backendName = item, + serverName = server['name'], + address = server['address'], + port = back['port'], + check = server['check'] if "check" in server else None, + **server['server_settings'] if "server_settings" in server else {}, + ) + api_resp = self.decort_api_call(requests.post, "/restmachine/cloudapi/lb/backendServerAdd", api_params) + self.result['changed'] = True + for srv in upd_srv: + mod_server, = list(filter(lambda i: i['name'] == srv,mod_servers)) + mod_server_back, = list(filter(lambda i: i['name'] == item,mod_server['backends'])) + server, = list(filter(lambda i: i['name'] == srv, backend['servers'])) + + del server['serverSettings']['guid'] + + if "server_settings" not in mod_server_back: + mod_server_back['server_settings'] = self.default_settings + if "check" not in mod_server: + mod_server['check'] = self.default_server_check + + if (mod_server['address'] != server['address'] or\ + server['check']!=mod_server["check"]) or\ + mod_server_back['server_settings'] != server['serverSettings']: + api_params = dict( + lbId=self.lb_id, + backendName = item, + serverName = mod_server['name'], + address = mod_server['address'], + port = mod_server_back['port'], + check = mod_server_back['check'] if "check" in mod_server_back else None, + **mod_server_back['server_settings'] if "server_settings" in mod_server_back else {}, + ) + api_resp = self.decort_api_call(requests.post, "/restmachine/cloudapi/lb/backendServerUpdate", api_params) + self.result['changed'] = True + + return + def lb_update_frontends(self,lb_frontends,mod_frontends): + """ + frontends + """ + self.result['waypoints'] = "{} -> {}".format(self.result['waypoints'], "lb_update_frontends") + if mod_frontends: + front_out = [rec['name'] for rec in mod_frontends] + else: + front_out="" + front_in = [rec['name'] for rec in lb_frontends] + + del_front = set(front_in).difference(front_out) + add_front = set(front_out).difference(front_in) + upd_front = set(front_in).intersection(front_out) + + for front in del_front: + delete_front, = list(filter(lambda i: i['name'] == front,lb_frontends)) + api_params = dict( + lbId=self.lb_id, + frontendName=delete_front['name'], + ) + api_resp = self.decort_api_call(requests.post, "/restmachine/cloudapi/lb/frontendDelete", api_params) + self.result['changed'] = True + for front in add_front: + create_front, = list(filter(lambda i: i['name'] == front,mod_frontends)) + api_params = dict( + lbId=self.lb_id, + frontendName=create_front['name'], + backendName=create_front['backend'], + ) + api_resp = self.decort_api_call(requests.post, "/restmachine/cloudapi/lb/frontendCreate", api_params) + self.result['changed'] = True + if "bindings" in create_front: + for bind in create_front['bindings']: + self._lb_bind_frontend( + create_front['name'], + bind['name'], + bind['address'] if "address" in bind else None, + bind['port'] if "port" in bind else None + ) + for front in upd_front: + update_front, = list(filter(lambda i: i['name'] == front,mod_frontends)) + lb_front, = list(filter(lambda i: i['name'] == front,lb_frontends)) + mod_bind = [rec['name'] for rec in update_front['bindings']] + lb_bind = [rec['name'] for rec in lb_front['bindings']] + + del_bind_list = set(lb_bind).difference(mod_bind) + add_bind_list = set(mod_bind).difference(lb_bind) + upd_bind_list = set(lb_bind).intersection(mod_bind) + + for bind_name in del_bind_list: + del_bind, = list(filter(lambda i: i['name'] == bind_name,lb_front['bindings'])) + api_params = dict( + lbId=self.lb_id, + frontendName=update_front['name'], + bindingName=del_bind['name'], + ) + api_resp = self.decort_api_call(requests.post, "/restmachine/cloudapi/lb/frontendBindDelete", api_params) + self.result['changed'] = True + for bind_name in add_bind_list: + add_bind, = list(filter(lambda i: i['name'] == bind_name,update_front['bindings'])) + self._lb_bind_frontend( + update_front['name'], + add_bind['name'], + add_bind['address'] if "address" in add_bind else None, + add_bind['port'] if "port" in add_bind else None + ) + for bind_name in upd_bind_list: + lb_act_bind, = list(filter(lambda i: i['name'] == bind_name,lb_front['bindings'])) + mod_act_bind, = list(filter(lambda i: i['name'] == bind_name,update_front['bindings'])) + del lb_act_bind['guid'] + if lb_act_bind != mod_act_bind: + self._lb_bind_frontend( + update_front['name'], + mod_act_bind['name'], + mod_act_bind['address'] if "address" in mod_act_bind else None, + mod_act_bind['port'] if "port" in mod_act_bind else None, + update=True + ) + return + + def _lb_bind_frontend(self,front_name,bind_name,bind_addr=None,bind_port=None,update=False): + api_params = dict( + lbId=self.lb_id, + frontendName=front_name, + bindingName=bind_name, + bindingAddress=bind_addr, + bindingPort=bind_port, + ) + if update == True: + api_url = "/restmachine/cloudapi/lb/frontendBindingUpdate" + else: + api_url = "/restmachine/cloudapi/lb/frontendBind" + api_resp = self.decort_api_call(requests.post, api_url, api_params) + self.result['changed'] = True + + def lb_update(self,lb_backends=[],lb_frontends=[],mod_backends=[],mod_servers=[],mod_frontends=[]): + #lists from module and cloud + mod_backs_list = [back['name'] for back in mod_backends] + lb_backs_list = [back['name'] for back in lb_backends] + #ADD\DEL\UPDATE LISTS OF BACKENDS + del_list_backs = set(lb_backs_list).difference(mod_backs_list) + add_back_list = set(mod_backs_list).difference(lb_backs_list) + upd_back_list = set(lb_backs_list).intersection(mod_backs_list) + + #FE + + mod_front_list = [front['name'] for front in mod_frontends] + lb_front_list = [front['name'] for front in lb_frontends] + + if del_list_backs: + + self._lb_delete_backends( + del_list_backs, + lb_frontends + ) + + if add_back_list: + self._lb_create_backends( + add_back_list, + mod_backends, + mod_servers + ) + + if upd_back_list: + self._lb_update_backends( + upd_back_list, + lb_backends, + mod_backends, + mod_servers + ) + + del_list_fronts = set(lb_front_list).difference(mod_front_list) + add_list_fronts = set(mod_front_list).difference(lb_front_list) + upd_front_list = set(lb_front_list).intersection(mod_front_list) + + if del_list_fronts: + self._lb_delete_fronts(del_list_fronts) + + if add_list_fronts: + self._lb_add_fronts(add_list_fronts,mod_frontends) + if upd_front_list: + self._lb_update_fronts(upd_front_list,lb_frontends,mod_frontends) + + return + + def _lb_delete_backends(self,back_list,lb_fronts): + + #delete frontends with that backend + for back in back_list: + fronts = list(filter(lambda i: i['backend'] == back,lb_fronts)) + if fronts: + self._lb_delete_fronts(fronts) + api_params = dict( + lbId=self.lb_id, + backendName = back + ) + api_resp = self.decort_api_call(requests.post, "/restmachine/cloudapi/lb/backendDelete", api_params) + self.result['changed'] = True + return + def _lb_delete_fronts(self,d_fronts): + for front in d_fronts: + api_params = dict( + lbId=self.lb_id, + frontendName=front['name'] if "name" in front else front, + ) + api_resp = self.decort_api_call(requests.post, "/restmachine/cloudapi/lb/frontendDelete", api_params) + #del from cloud dict + if type(front)==dict: + del self.lb_facts['frontends'][front['name']] + self.result['changed'] = True + + return + def _lb_add_fronts(self,front_list,mod_fronts): + for front in front_list: + add_front, = list(filter(lambda i: i['name'] == front,mod_fronts)) + api_params = dict( + lbId=self.lb_id, + frontendName=add_front['name'], + backendName=add_front['backend'], + ) + api_resp = self.decort_api_call(requests.post, "/restmachine/cloudapi/lb/frontendCreate", api_params) + for bind in add_front['bindings']: + self._lb_bind_frontend( + add_front['name'], + bind['name'], + bind['address']if "address" in bind else None, + bind['port'] if "port" in bind else None, + ) + return + def _lb_create_backends(self,back_list,mod_backs,mod_serv): + ''' + Create backends and add servers to them + ''' + for back in back_list: + backend, = list(filter(lambda i: i['name'] == back,mod_backs)) + api_params = dict( + lbId=self.lb_id, + backendName = back, + algorithm = backend['algorithm'] if "algorithm" in backend else None, + **backend['default_settings'] if "default_settings" in backend else {}, + ) + api_resp = self.decort_api_call(requests.post, "/restmachine/cloudapi/lb/backendCreate", api_params) + + for server in mod_serv: + try: + srv_back, = list(filter(lambda i: i['name'] == back,server['backends'])) + except: + continue + api_params = dict( + lbId=self.lb_id, + backendName = back, + serverName = server['name'], + address = server['address'], + port = srv_back['port'], + check = srv_back['check'] if "check" in srv_back else None, + **srv_back['server_settings'] if "server_settings" in srv_back else {}, + ) + api_resp = self.decort_api_call(requests.post, "/restmachine/cloudapi/lb/backendServerAdd", api_params) + + self.result['changed'] = True + + return + + def _lb_update_backends(self,back_list,lb_backs,mod_backs,mod_serv): + + lb_backs = list(filter(lambda i: i['name'] in back_list,lb_backs)) + #mod_back = list(filter(lambda i: i['name'] in back_list,mod_backs)) + + for back in lb_backs: + + del back['serverDefaultSettings']['guid'] + mod_back, = list(filter(lambda i: i['name']==back['name'],mod_backs)) + #mod_servers = list(filter(lambda i: i['name']==back['name'],mod_serv)) + #raise Exception(mod_servers) + if "default_settings" not in mod_back: + mod_back["default_settings"] = self.default_settings + else: + for param,value in self.default_settings.items(): + if param not in mod_back["default_settings"]: + mod_back["default_settings"].update(param,value) + + + if "algorithm" not in mod_back: + mod_back["algorithm"] = self.default_alg + + if back['serverDefaultSettings'] != mod_back["default_settings"] or\ + mod_back["algorithm"] != back['algorithm']: + api_params = dict( + lbId=self.lb_id, + backendName = back['name'], + algorithm = mod_back['algorithm'], + **mod_back['default_settings'], + ) + api_resp = self.decort_api_call(requests.post, "/restmachine/cloudapi/lb/backendUpdate", api_params) + self.result['changed'] = True + + lb_servers_list = [srv['name'] for srv in back['servers']] + for server in mod_serv: + try: + mod_back, = list(filter(lambda i: i['name'] == back['name'],server['backends'])) + except: + continue + if server['name'] not in lb_servers_list: + api_params = dict( + lbId=self.lb_id, + backendName = mod_back['name'], + serverName = server['name'], + address = server['address'], + port = mod_back['port'], + check = server['check'] if "check" in server else None, + **server['server_settings'] if "server_settings" in server else {}, + ) + api_resp = self.decort_api_call(requests.post, "/restmachine/cloudapi/lb/backendServerAdd", api_params) + self.result['changed'] = True + else: + lb_server, = list(filter(lambda i: i['name'] == server['name'],back['servers'])) + del lb_server['serverSettings']['guid'] + + if "server_settings" not in mod_back: + mod_back['server_settings'] = self.default_settings + else: + for param,value in self.default_settings.items(): + if param not in mod_back["server_settings"]: + mod_back["server_settings"].update(param,value) + + if "check" not in mod_back: + mod_back['check'] = self.default_server_check + + if (server['address'] != lb_server['address'] or\ + lb_server['check']!=mod_back['check']) or\ + mod_back['server_settings'] != lb_server['serverSettings']: + api_params = dict( + lbId=self.lb_id, + backendName = mod_back['name'], + serverName = server['name'], + address = server['address'], + port = mod_back['port'], + check = mod_back['check'], + **mod_back['server_settings'], + ) + api_resp = self.decort_api_call(requests.post, "/restmachine/cloudapi/lb/backendServerUpdate", api_params) + self.result['changed'] = True + lb_servers_list.remove(server['name']) + for server in lb_servers_list: + api_params = dict(lbId=self.lb_id,backendName = back['name'],serverName=server) + api_resp = self.decort_api_call(requests.post, "/restmachine/cloudapi/lb/backendServerDelete", api_params) + self.result['changed'] = True + return + def _lb_update_fronts(self,upd_front_list,lb_frontends,mod_frontends): + + for front in upd_front_list: + mod_front, = list(filter(lambda i: i['name'] == front,mod_frontends)) + lb_front, = list(filter(lambda i: i['name'] == front,lb_frontends)) + lb_binds_list = [bind['name'] for bind in lb_front['bindings']] + for bind in mod_front['bindings']: + if bind['name'] not in lb_binds_list: + pass + self._lb_bind_frontend( + front, + bind['name'], + bind['address']if "address" in bind else None, + bind['port'] if "port" in bind else None, + ) + else: + lb_bind, = list(filter(lambda i: i['name'] == bind['name'],lb_front['bindings'])) + del lb_bind['guid'] + + if dict(sorted(bind.items())) != dict(sorted(lb_bind.items())): + self._lb_bind_frontend( + front, + bind['name'], + bind['address']if "address" in bind else None, + bind['port'] if "port" in bind else None, + update=True, + ) + lb_binds_list.remove(bind['name']) + + for lb_bind in lb_binds_list: + api_params = dict( + lbId=self.lb_id, + frontendName=front, + bindingName=lb_bind, + ) + api_resp = self.decort_api_call(requests.post, "/restmachine/cloudapi/lb/frontendBindDelete", api_params) + self.result['changed'] = True + + return