diff --git a/library/decort_k8s.py b/library/decort_k8s.py index 3abb4e4..11291ac 100644 --- a/library/decort_k8s.py +++ b/library/decort_k8s.py @@ -114,10 +114,10 @@ class decort_k8s(DecortController): return ret_dict def nop(self): - """No operation (NOP) handler for Compute management by decort_kvmvm module. + """No operation (NOP) handler for k8s cluster management by decort_k8s 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 Compute state. + the actual k8s cluster state. """ self.result['failed'] = False self.result['changed'] = False @@ -319,7 +319,7 @@ def main(): if amodule.params['state'] == 'absent': subj.nop() if amodule.params['state'] in ('present','started'): - subj.create() + subj.create() elif amodule.params['state'] in ('stopped', 'disabled','enabled'): subj.error() 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 749cfd3..a50faa1 100644 --- a/library/decort_vins.py +++ b/library/decort_vins.py @@ -242,110 +242,289 @@ facts: from ansible.module_utils.basic import AnsibleModule from ansible.module_utils.basic import env_fallback -import paramiko from ansible.module_utils.decort_utils import * +class decort_vins(DecortController): + def __init__(self,arg_amodule): + super(decort_vins, self).__init__(arg_amodule) -def decort_vins_package_facts(arg_vins_facts, arg_check_mode=False): - """Package a dictionary of ViNS facts according to the decort_vins module specification. - This dictionary will be returned to the upstream Ansible engine at the completion of - the module run. + 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 + validated_acc_id = 0 + acc_facts = None # will hold Account facts - @param arg_vins_facts: dictionary with viNS facts as returned by API call to .../vins/get - @param arg_check_mode: boolean that tells if this Ansible module is run in check mode - """ + if arg_amodule.params['vins_id']: + # expect existing ViNS with the specified ID + # This call to vins_find will abort the module if no ViNS with such ID is present + 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) + 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 + 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="") + + # This call to vins_find may return vins_id=0 if no ViNS found + self.vins_id, self.vins_facts = self.vins_find(vins_id=0, vins_name=arg_amodule.params['vins_name'], + account_id=0, + rg_id=arg_amodule.params['rg_id'], + rg_facts=rg_facts, + check_state=False) + # TODO: add checks and setup ViNS presence flags accordingly + pass + elif arg_amodule.params['account_id'] or arg_amodule.params['account_name'] != "": + # Specified account must be present and accessible by the user, otherwise abort the module + validated_acc_id, acc_facts = self.account_find(arg_amodule.params['account_name'], arg_amodule.params['account_id']) + if not validated_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) + if arg_amodule.params['rg_name'] != "": # at this point we know that rg_id=0 + # expect ViNS @ RG level in the RG with specified name under specified account + # RG with the specified name must be present under the account, otherwise abort the module + validated_rg_id, rg_facts = self.rg_find(validated_acc_id, 0, arg_amodule.params['rg_name']) + if (not validated_rg_id or + rg_facts['status'] in ["DESTROYING", "DESTROYED", "DELETING", "DELETED", "DISABLING", "ENABLING"]): + self.result['failed'] = True + self.result['msg'] = "RG name '{}' not found or has invalid state.".format(arg_amodule.params['rg_name']) + self.fail_json(**self.result) + # This call to vins_find may return vins_id=0 if no ViNS with this name found under specified RG + self.vins_id, self.vins_facts = self.vins_find(vins_id=0, vins_name=arg_amodule.params['vins_name'], + account_id=0, # set to 0, as we are looking for ViNS under RG + rg_id=validated_rg_id, + rg_facts=rg_facts, + check_state=False) + 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 + # This call to vins_find may return vins_id=0 if no ViNS found + self.vins_id, self.vins_facts = self.vins_find(vins_id=0, vins_name=arg_amodule.params['vins_name'], + account_id=validated_acc_id, + rg_id=0, + rg_facts=rg_facts, + check_state=False) + self.vins_level = "ACC" + # TODO: add checks and setup ViNS presence flags accordingly + else: + # this is "invalid arguments combination" sink + # if we end up here, it means that module was invoked with vins_id=0 and rg_id=0 + self.result['failed'] = True + if arg_amodule.params['account_id'] == 0 and arg_amodule.params['account_name'] == "": + self.result['msg'] = "Cannot find ViNS by name when account name is empty and account ID is 0." + if arg_amodule.params['rg_name'] == "": + # rg_name without account specified + self.result['msg'] = "Cannot find ViNS by name when RG name is empty and RG ID is 0." + self.fail_json(**self.result) - ret_dict = dict(id=0, - name="none", - state="CHECK_MODE", - ) + return + self.rg_id = validated_rg_id + self.acc_id = validated_acc_id + return + def create(self): + self.vins_id = self.vins_provision(self.amodule.params['vins_name'], + self.acc_id, self.rg_id, + self.amodule.params['ipcidr'], + self.amodule.params['ext_net_id'], self.amodule.params['ext_ip_addr'], + self.amodule.params['annotation']) + + if self.amodule.params['mgmtaddr'] or self.amodule.params['connect_to']: + _, self.vins_facts = self.vins_find(self.vins_id) + if self.amodule.params['connect_to']: + self.vins_update_ifaces(self.vins_facts,self.amodule.params['connect_to'],) + if self.amodule.params['mgmtaddr']: + self.vins_update_mgmt(self.vins_facts,self.amodule.params['mgmtaddr']) + + return + def action(self,d_state='',restore=False): + if restore == True: + self.vins_restore(arg_vins_id=self.vins_id) + self.vins_state(self.vins_facts, 'enabled') + self.vins_facts['status'] = "ENABLED" + self.vins_facts['VNFDev']['techStatus'] = "STARTED" + + self.vins_update_extnet(self.vins_facts, + self.amodule.params['ext_net_id'], + self.amodule.params['ext_ip_addr'], + ) + + if d_state == 'enabled' and self.vins_facts['status'] == "DISABLED": + self.vins_state(self.vins_facts, d_state) + self.vins_facts['status'] = "ENABLED" + self.vins_facts['VNFDev']['techStatus'] = "STARTED" + d_state = '' - if arg_check_mode: - # in check mode return immediately with the default values - return ret_dict + if self.vins_facts['status'] == "ENABLED" and self.vins_facts['VNFDev']['techStatus'] == "STARTED": + self.vins_update_ifaces(self.vins_facts, + self.amodule.params['connect_to'], + ) + if self.result['changed']: + _, self.vins_facts = self.vins_find(self.vins_id) + self.vins_update_mgmt(self.vins_facts, + self.amodule.params['mgmtaddr'], + ) + + if d_state != '': + self.vins_state(self.vins_facts, d_state) + return + def delete(self): + self.vins_delete(self.vins_id, permanently=True) + self.vins_facts['status'] = 'DESTROYED' + return + def nop(self): + """No operation (NOP) handler for ViNS management by decort_vins 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 ViNS state. + """ + self.result['failed'] = False + self.result['changed'] = False + if self.vins_id: + self.result['msg'] = ("No state change required for ViNS ID {} because of its " + "current status '{}'.").format(self.vins_id, self.vins_facts['status']) + else: + self.result['msg'] = ("No state change to '{}' can be done for " + "non-existent ViNS 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 ViNS ID {} in the " + "current status '{}'").format(self.vins_id, + self.amodule.params['state'], + self.vins_facts['status']) + else: + self.result['failed'] = True + self.result['changed'] = False + self.result['msg'] = ("Invalid target state '{}' requested for non-existent " + "ViNS name '{}'").format(self.amodule.params['state'], + self.amodule.params['vins_name']) + return + def package_facts(self, arg_check_mode=False): + """Package a dictionary of ViNS facts according to the decort_vins module specification. + This dictionary will be returned to the upstream Ansible engine at the completion of + the module run. - if arg_vins_facts is None: - # if void facts provided - change state value to ABSENT and return - ret_dict['state'] = "ABSENT" - return ret_dict + @param arg_check_mode: boolean that tells if this Ansible module is run in check mode + """ - ret_dict['id'] = arg_vins_facts['id'] - ret_dict['name'] = arg_vins_facts['name'] - ret_dict['state'] = arg_vins_facts['status'] - ret_dict['account_id'] = arg_vins_facts['accountId'] - ret_dict['rg_id'] = arg_vins_facts['rgId'] - ret_dict['int_net_addr'] = arg_vins_facts['network'] - ret_dict['gid'] = arg_vins_facts['gid'] + ret_dict = dict(id=0, + name="none", + state="CHECK_MODE", + ) - if arg_vins_facts['vnfs'].get('GW'): - gw_config = arg_vins_facts['vnfs']['GW']['config'] - ret_dict['ext_ip_addr'] = gw_config['ext_net_ip'] - ret_dict['ext_net_id'] = gw_config['ext_net_id'] - else: - ret_dict['ext_ip_addr'] = "" - ret_dict['ext_net_id'] = -1 + if arg_check_mode: + # in check mode return immediately with the default values + return ret_dict - # arg_vins_facts['vnfs']['GW']['config'] - # ext_ip_addr -> ext_net_ip - # ??? -> ext_net_id - # tech_status -> techStatus + if self.vins_facts is None: + # if void facts provided - change state value to ABSENT and return + ret_dict['state'] = "ABSENT" + return ret_dict - return ret_dict + ret_dict['id'] = self.vins_facts['id'] + ret_dict['name'] = self.vins_facts['name'] + ret_dict['state'] = self.vins_facts['status'] + ret_dict['account_id'] = self.vins_facts['accountId'] + ret_dict['rg_id'] = self.vins_facts['rgId'] + ret_dict['int_net_addr'] = self.vins_facts['network'] + ret_dict['gid'] = self.vins_facts['gid'] + custom_interfaces = list(filter(lambda i: i['type']=="CUSTOM",self.vins_facts['VNFDev']['interfaces'])) + if custom_interfaces: + ret_dict['custom_net_addr'] = [] + for runner in custom_interfaces: + ret_dict['custom_net_addr'].append(runner['ipAddress']) + mgmt_interfaces = list(filter(lambda i: i['listenSsh'] and i['name']!="ens9",self.vins_facts['VNFDev']['interfaces'])) + if mgmt_interfaces: + ret_dict['ssh_ipaddr'] = [] + for runner in mgmt_interfaces: + ret_dict['ssh_ipaddr'].append(runner['ipAddress']) + ret_dict['ssh_password'] = self.vins_facts['VNFDev']['config']['mgmt']['password'] + ret_dict['ssh_port'] = 9022 + if self.vins_facts['vnfs'].get('GW'): + gw_config = self.vins_facts['vnfs']['GW']['config'] + ret_dict['ext_ip_addr'] = gw_config['ext_net_ip'] + ret_dict['ext_net_id'] = gw_config['ext_net_id'] + else: + ret_dict['ext_ip_addr'] = "" + ret_dict['ext_net_id'] = -1 + # arg_vins_facts['vnfs']['GW']['config'] + # ext_ip_addr -> ext_net_ip + # ??? -> ext_net_id + # tech_status -> techStatus -def decort_vins_parameters(): - """Build and return a dictionary of parameters expected by decort_vins module in a form accepted - by AnsibleModule utility class.""" + return ret_dict - return dict( - account_id=dict(type='int', required=False), - account_name=dict(type='str', required=False, default=''), - annotation=dict(type='str', required=False, default=''), - app_id=dict(type='str', + @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=''), + 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=''), + ipcidr=dict(type='str', required=False, default=''), + mgmtaddr=dict(type='list',required=False, default=[]), + custom_config=dict(type='bool',required=False, default=False), + config_save=dict(type='bool',required=False, default=False), + connect_to=dict(type='list', default=[], required=False), + jwt=dict(type='str', required=False, - fallback=(env_fallback, ['DECORT_APP_ID'])), - app_secret=dict(type='str', + 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_APP_SECRET']), + fallback=(env_fallback, ['DECORT_PASSWORD']), 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=''), - ipcidr=dict(type='str', required=False, default=''), - mgmtaddr=dict(type='str',required=False, default=''), - custom_config=dict(type='bool',required=False, default=False), - config_save=dict(type='bool',required=False, default=False), - connect_to=dict(type='list', default=[], required=False), - 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']), - 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=''), - verify_ssl=dict(type='bool', required=False, default=True), - vins_id=dict(type='int', required=False, default=0), - vins_name=dict(type='str', required=True), - workflow_callback=dict(type='str', required=False), - workflow_context=dict(type='str', required=False), - ) + state=dict(type='str', + default='present', + choices=['absent', 'disabled', 'enabled', 'present']), + 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=''), + verify_ssl=dict(type='bool', required=False, default=True), + vins_id=dict(type='int', required=False, default=0), + vins_name=dict(type='str', required=True), + workflow_callback=dict(type='str', required=False), + workflow_context=dict(type='str', required=False), + ) # Workflow digest: @@ -356,7 +535,7 @@ def decort_vins_parameters(): # 5) report result to Ansible def main(): - module_parameters = decort_vins_parameters() + module_parameters = decort_vins.build_parameters() amodule = AnsibleModule(argument_spec=module_parameters, supports_check_mode=True, @@ -371,84 +550,7 @@ def main(): ], ) - decon = DecortController(amodule) - - vins_id = 0 - 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 - validated_acc_id = 0 - acc_facts = None # will hold Account facts - - if amodule.params['vins_id']: - # expect existing ViNS with the specified ID - # This call to vins_find will abort the module if no ViNS with such ID is present - vins_id, vins_facts = decon.vins_find(amodule.params['vins_id']) - if not vins_id: - decon.result['failed'] = True - decon.result['msg'] = "Specified ViNS ID {} not found.".format(amodule.params['vins_id']) - decon.fail_json(**decon.result) - vins_level = "ID" - validated_acc_id = vins_facts['accountId'] - validated_rg_id = vins_facts['rgId'] - - elif amodule.params['rg_id']: - # expect ViNS @ RG level in the RG with specified ID - 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 = decon.rg_find(0, # account ID set to 0 as we search for RG by RG ID - amodule.params['rg_id'], arg_rg_name="") - # This call to vins_find may return vins_id=0 if no ViNS found - vins_id, vins_facts = decon.vins_find(vins_id=0, vins_name=amodule.params['vins_name'], - account_id=0, - rg_id=amodule.params['rg_id'], - check_state=False) - # TODO: add checks and setup ViNS presence flags accordingly - pass - elif amodule.params['account_id'] or amodule.params['account_name'] != "": - # Specified account must be present and accessible by the user, otherwise abort the module - validated_acc_id, acc_facts = decon.account_find(amodule.params['account_name'], amodule.params['account_id']) - if not validated_acc_id: - 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) - if amodule.params['rg_name'] != "": # at this point we know that rg_id=0 - # expect ViNS @ RG level in the RG with specified name under specified account - # RG with the specified name must be present under the account, otherwise abort the module - validated_rg_id, rg_facts = decon.rg_find(validated_acc_id, 0, amodule.params['rg_name']) - if (not validated_rg_id or - rg_facts['status'] in ["DESTROYING", "DESTROYED", "DELETING", "DELETED", "DISABLING", "ENABLING"]): - decon.result['failed'] = True - decon.result['msg'] = "RG name '{}' not found or has invalid state.".format(amodule.params['rg_name']) - decon.fail_json(**decon.result) - # This call to vins_find may return vins_id=0 if no ViNS with this name found under specified RG - vins_id, vins_facts = decon.vins_find(vins_id=0, vins_name=amodule.params['vins_name'], - account_id=0, # set to 0, as we are looking for ViNS under RG - rg_id=validated_rg_id, - check_state=False) - 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 - # This call to vins_find may return vins_id=0 if no ViNS found - vins_id, vins_facts = decon.vins_find(vins_id=0, vins_name=amodule.params['vins_name'], - account_id=validated_acc_id, - rg_id=0, - check_state=False) - vins_level = "ACC" - # TODO: add checks and setup ViNS presence flags accordingly - else: - # this is "invalid arguments combination" sink - # if we end up here, it means that module was invoked with vins_id=0 and rg_id=0 - decon.result['failed'] = True - if amodule.params['account_id'] == 0 and amodule.params['account_name'] == "": - decon.result['msg'] = "Cannot find ViNS by name when account name is empty and account ID is 0." - if amodule.params['rg_name'] == "": - # rg_name without account specified - decon.result['msg'] = "Cannot find ViNS by name when RG name is empty and RG ID is 0." - decon.fail_json(**decon.result) + decon = decort_vins(amodule) # # Initial validation of module arguments is complete # @@ -465,127 +567,70 @@ def main(): # if cconfig_save is true, only config save without other updates vins_should_exist = False - if vins_id: + if decon.vins_id: vins_should_exist = True - if vins_facts['status'] in ["MODELED", "DISABLING", "ENABLING", "DELETING", "DESTROYING"]: + if decon.vins_facts['status'] in ["MODELED", "DISABLING", "ENABLING", "DELETING", "DESTROYING"]: # error: nothing can be done to existing ViNS 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 ViNS ID {} because of its current " - "status '{}'").format(vins_id, vins_facts['status']) - elif vins_facts['status'] == "DISABLED": + "status '{}'").format(decon.vins_id, decon.vins_facts['status']) + elif decon.vins_facts['status'] == "DISABLED": if amodule.params['state'] == 'absent': - decon.vins_delete(vins_id, permanently=True) - vins_facts['status'] = 'DESTROYED' + decon.delete() vins_should_exist = False elif amodule.params['state'] in ('present', 'disabled'): # update ViNS, leave in disabled state - decon.vins_update(vins_facts, - amodule.params['ext_net_id'], amodule.params['ext_ip_addr']) + decon.action() elif amodule.params['state'] == 'enabled': # update ViNS and enable - decon.vins_update(vins_facts, - amodule.params['ext_net_id'], amodule.params['ext_ip_addr']) - decon.vins_state(vins_facts, 'enabled') - elif vins_facts['status'] in ["CREATED", "ENABLED"]: + decon.action('enabled') + elif decon.vins_facts['status'] in ["CREATED", "ENABLED"]: if amodule.params['state'] == 'absent': - decon.vins_delete(vins_id, permanently=True) - vins_facts['status'] = 'DESTROYED' + decon.delete() vins_should_exist = False elif amodule.params['state'] in ('present', 'enabled'): # update ViNS - decon.vins_update(vins_facts, - amodule.params['ext_net_id'], amodule.params['ext_ip_addr'], - - ) - decon.vins_update_mgmt( - vins_facts, - amodule.params['mgmtaddr'], - ) - decon.vins_update_ifaces( - vins_facts, - amodule.params['connect_to'], - ) + decon.action() elif amodule.params['state'] == 'disabled': # disable and update ViNS - decon.vins_state(vins_facts, 'disabled') - decon.vins_update(vins_facts, - amodule.params['ext_net_id'], amodule.params['ext_ip_addr']) - elif vins_facts['status'] == "DELETED": + decon.action('disabled') + elif decon.vins_facts['status'] == "DELETED": if amodule.params['state'] in ['present', 'enabled']: # restore and enable - decon.vins_restore(arg_vins_id=vins_id) - decon.vins_state(vins_facts, 'enabled') + decon.action(restore=True) vins_should_exist = True elif amodule.params['state'] == 'absent': # destroy permanently - decon.vins_delete(vins_id, permanently=True) - vins_facts['status'] = 'DESTROYED' + decon.delete() vins_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 ViNS ID {} in the " - "current status '{}'").format(vins_id, - amodule.params['state'], - vins_facts['status']) + decon.error() vins_should_exist = False - elif vins_facts['status'] == "DESTROYED": + elif decon.vins_facts['status'] == "DESTROYED": if amodule.params['state'] in ('present', 'enabled'): - # need to re-provision ViNS; some attributes may be changed, some stay the same. - # account and RG - stays the same - # vins_name - stays the same - # IPcidr - take from module arguments - # ext IP address - take from module arguments - # annotation - take from module arguments - vins_id = decon.vins_provision(vins_facts['name'], - validated_acc_id, validated_rg_id, - amodule.params['ipcidr'], - amodule.params['ext_net_id'], amodule.params['ext_ip_addr'], - amodule.params['annotation']) + # need to re-provision ViNS; + decon.create() vins_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 ViNS ID {} because of its " - "current status '{}'").format(vins_id, - vins_facts['status']) + decon.nop() vins_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 ViNS ID {} in the " - "current status '{}'").format(vins_id, - amodule.params['state'], - vins_facts['status']) + decon.error() else: # Preexisting ViNS was not found. vins_should_exist = False # we will change it back to True if ViNS is 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 ViNS name '{}'").format(amodule.params['vins_name']) + decon.nop() elif amodule.params['state'] in ('present', 'enabled'): decon.check_amodule_argument('vins_name') # as we already have account ID and RG ID we can create ViNS and get vins_id on success - vins_id = decon.vins_provision(amodule.params['vins_name'], - validated_acc_id, validated_rg_id, - amodule.params['ipcidr'], - amodule.params['ext_net_id'], amodule.params['ext_ip_addr'], - amodule.params['annotation']) + decon.create() vins_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 " - "ViNS name '{}'").format(amodule.params['state'], - amodule.params['vins_name']) + decon.error() # # conditional switch end - complete module run # @@ -593,18 +638,9 @@ def main(): amodule.fail_json(**decon.result) else: # prepare ViNS facts to be returned as part of decon.result and then call exit_json(...) - if vins_should_exist: - if decon.result['changed']: - # If we arrive here, there is a good chance that the ViNS is present - get fresh ViNS - # facts from # the cloud by ViNS ID. - # Otherwise, ViNS facts from previous call (when the ViNS was still in existence) will - # be returned. - _, vins_facts = decon.vins_find(vins_id) - decon.result['facts'] = decort_vins_package_facts(vins_facts, amodule.check_mode) - # add password to facts if mgmtaddr is present - # need reworking - if amodule.params['mgmtaddr'] != "": - decon.result['facts'].update({'password': vins_facts['VNFDev']['config']['mgmt']['password']}) + if decon.result['changed']: + _, decon.vins_facts = decon.vins_find(decon.vins_id) + decon.result['facts'] = decon.package_facts(amodule.check_mode) amodule.exit_json(**decon.result) diff --git a/module_utils/decort_utils.py b/module_utils/decort_utils.py index cdc9ccf..db5613a 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: @@ -3298,7 +3297,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: @@ -3401,10 +3400,10 @@ class DecortController(object): 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) @@ -3764,3 +3763,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