#!/usr/bin/python DOCUMENTATION = r''' --- module: decort_lb description: See L(Module Documentation,https://repository.basistech.ru/BASIS/decort-ansible/wiki/Home). ''' 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) -> None: super(decort_lb,self).__init__(AnsibleModule(**self.amodule_init_args)) arg_amodule = self.amodule self.lb_id = 0 self.vins_id = 0 self.rg_id = 0 self.rg_model = 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, } self.is_lb_stopped_or_will_be_stopped: None | bool = None if arg_amodule.params['lb_id']: _, self._lb_info = self.lb_find(lb_id=arg_amodule.params['lb_id']) if self._lb_info is None: if arg_amodule.params['state'] == 'absent': self.nop() self.exit() else: self.message( self.MESSAGES.obj_not_found( obj='lb', id=arg_amodule.params['lb_id'], ) ) self.exit(fail=True) self.lb_id = self._lb_info.id self.rg_id = self._lb_info.rg_id self.vins_id = self._lb_info.vins_id elif arg_amodule.params['rg_id']: self.rg_id, self.rg_model = self.rg_find( 0, arg_amodule.params['rg_id'], arg_rg_name="", ) if not self.rg_id or not self.rg_model: self.result['failed'] = True self.result['msg'] = "Specified RG ID {} not found.".format(arg_amodule.params['rg_id']) self.amodule.fail_json(**self.result) self.acc_id = self.rg_model.account_id elif arg_amodule.params['account_id'] or arg_amodule.params['account_name'] != "": if not arg_amodule.params['rg_name']: self.result['failed'] = True self.result['msg'] = ("RG name must be specified with account present") self.amodule.fail_json(**self.result) self.acc_id, self._acc_info = 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.amodule.fail_json(**self.result) self.rg_id, self.rg_model = self.rg_find( self.acc_id, 0, arg_rg_name=arg_amodule.params['rg_name'], ) if arg_amodule.params['vins_id']: self.vins_id = self._vins_get_by_id( vins_id=arg_amodule.params['vins_id'] ).id elif arg_amodule.params['vins_name']: self.vins_id, _ = self.vins_find( vins_id=arg_amodule.params['vins_id'], vins_name=arg_amodule.params['vins_name'], rg_id=self.rg_id, ) if not self.vins_id: self.result['failed'] = True self.result['msg'] = ( f'Specified ViNS name {arg_amodule.params["vins_name"]}' f' not found in RG ID {self.rg_id}' ) self.amodule.fail_json(**self.result) if self.rg_id and arg_amodule.params['lb_name']: self.lb_id, self._lb_info = self.lb_find( 0, arg_amodule.params['lb_name'], self.rg_id, ) if ( self._lb_info and self._lb_info.status != sdk_types.LBStatus.DESTROYED ): self.acc_id = self._lb_info.account_id self.check_amodule_args_for_change() else: self.check_amodule_args_for_create() return def create(self): start_after_create = self.aparams['state'] != 'stopped' 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['ha_lb'], self.amodule.params['description'], sysctl=self.amodule.params['sysctl'], zone_id=self.aparams['zone_id'], start=start_after_create, ) if ( self.lb_id and ( self.amodule.params['backends'] or self.amodule.params['frontends'] ) ): self._lb_info = self._lb_get_by_id(lb_id=self.lb_id) self.lb_update( lb_model=self.lb_info, aparam_backends=self.amodule.params['backends'], aparam_frontends=self.amodule.params['frontends'], aparam_servers=self.amodule.params['servers'], ) return def action( self, d_state='', restore=False, ): if restore: self.sdk_checkmode(self.api.ca.lb.restore)( lb_id=self.lb_id, ) self._lb_info = self._lb_get_by_id(lb_id=self.lb_id) self.lb_state(self.lb_info, 'enabled') self._lb_info = self._lb_get_by_id(lb_id=self.lb_id) self.lb_update( lb_model=self.lb_info, aparam_backends=self.amodule.params['backends'], aparam_frontends=self.amodule.params['frontends'], aparam_servers=self.amodule.params['servers'], aparam_sysctl=self.aparams['sysctl'], ) self._lb_info = self._lb_get_by_id(lb_id=self.lb_id) if d_state != '': self.lb_state(self.lb_info, d_state) self._lb_info = self._lb_get_by_id(lb_id=self.lb_id) if ( d_state == 'enabled' and self.lb_info.status == sdk_types.LBStatus.ENABLED and self.lb_info.tech_status == sdk_types.LBTechStatus.STOPPED ): self.lb_state(self.lb_info, 'started') self._lb_info = self._lb_get_by_id(lb_id=self.lb_id) aparam_zone_id = self.aparams['zone_id'] if ( aparam_zone_id is not None and aparam_zone_id != self.lb_info.zone_id ): self.lb_migrate_to_zone( lb_id=self.lb_info.id, zone_id=aparam_zone_id, ) return def delete(self): self.sdk_checkmode(self.api.ca.lb.delete)( lb_id=self.lb_id, permanently=self.amodule.params['permanently'], ) self._lb_info = self._lb_get_by_id(lb_id=self.lb_id) 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_info: self.result['msg'] = ( f'No state change required for LB ID {self._lb_info.id} ' f'because of its current status "{self._lb_info.status}".' ) else: self.result['msg'] = ( f'No state change to "{self.amodule.params['state']}" ' f'can be done for non-existent LB instance.' ) return def error(self): self.result['failed'] = True self.result['changed'] = False if self._lb_info: self.result['failed'] = True self.result['changed'] = False self.result['msg'] = ( f'Invalid target state "{self.amodule.params['state']}" ' f'requested for LB ID {self._lb_info.id} in the current status ' f'"{self._lb_info.status}".' ) else: self.result['failed'] = True self.result['changed'] = False self.result['msg'] = ( f'Invalid target state "{self.amodule.params['state']}" ' f'requested for non-existent LB name ' f'"{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 = {} if arg_check_mode: # in check mode return immediately with the default values return ret_dict if self._lb_info is None: return ret_dict return self._lb_info.model_dump() @property def amodule_init_args(self) -> dict: return self.pack_amodule_init_args( argument_spec=dict( account_id=dict( type='int', ), account_name=dict( type='str', default='', ), description=dict( type='str', ), ext_net_id=dict( type='int', default=0, ), state=dict( type='str', choices=[ 'absent', 'disabled', 'enabled', 'present', 'restart', 'started', 'stopped', ], ), rg_id=dict( type='int', default=0, ), rg_name=dict( type='str', default='', ), vins_name=dict( type='str', default='', ), vins_id=dict( type='int', default=0, ), lb_id=dict( type='int', default=0, ), lb_name=dict( type='str', ), ha_lb=dict( type='bool', default=False, ), backends=dict( type='list', ), frontends=dict( type='list', ), servers=dict( type='list', ), permanently=dict( type='bool', default=False, ), sysctl=dict( type='dict', ), zone_id=dict( type=int, ), ), supports_check_mode=True, required_one_of=[ ('rg_id', 'rg_name'), ('lb_id', 'lb_name'), ('vins_id', 'vins_name'), ], ) def check_amodule_args_for_change(self): check_errors = False self.is_lb_stopped_or_will_be_stopped = ( ( self.lb_info.tech_status == sdk_types.LBTechStatus.STOPPED and ( self.aparams['state'] is None or self.aparams['state'] in ('present', 'stopped') ) ) or ( self.lb_info.tech_status != sdk_types.LBTechStatus.STOPPED and self.aparams['state'] == 'stopped' ) ) if self.check_aparam_zone_id() is False: check_errors = True if ( self.aparams['zone_id'] is not None and self.aparams['zone_id'] != self.lb_info.zone_id and not self.is_lb_stopped_or_will_be_stopped ): check_errors = True self.message( 'Check for parameter "zone_id" failed: ' 'Load balancer must be stopped to migrate to a zone.' ) if check_errors: self.exit(fail=True) def check_amodule_args_for_create(self): check_errors = False if self.check_aparam_zone_id() is False: check_errors = True if check_errors: self.exit(fail=True) @DecortController.handle_sdk_exceptions def run(self): amodule = self.amodule if self._lb_info: if self._lb_info.status in [ sdk_types.LBStatus.MODELED, sdk_types.LBStatus.DISABLING, sdk_types.LBStatus.ENABLING, sdk_types.LBStatus.DELETING, sdk_types.LBStatus.DESTROYING, sdk_types.LBStatus.RESTORING, ]: self.result['failed'] = True self.result['changed'] = False self.result['msg'] = ( f'No change can be done for existing LB ID ' f'{self._lb_info.id} because of its current status ' f'"{self._lb_info.status.name}".' ) elif self._lb_info.status in [ sdk_types.LBStatus.DISABLED, sdk_types.LBStatus.ENABLED, sdk_types.LBStatus.CREATED, ]: if amodule.params['state'] == 'absent': self.delete() else: self.action(d_state=amodule.params['state']) elif self._lb_info.status == sdk_types.LBStatus.DELETED: if amodule.params['state'] == 'present': self.action(restore=True) elif amodule.params['state'] == 'enabled': self.action(d_state='enabled', restore=True) elif (amodule.params['state'] == 'absent' and amodule.params['permanently']): self.delete() elif amodule.params['state'] == 'disabled': self.error() elif self._lb_info.status == sdk_types.LBStatus.DESTROYED: if amodule.params['state'] in ('present', 'enabled'): self.create() elif amodule.params['state'] == 'absent': self.nop() elif amodule.params['state'] == 'disabled': self.error() else: state = amodule.params['state'] if state is None: state = 'present' if state == 'absent': self.nop() elif state in ('present', 'enabled', 'stopped', 'started'): self.create() elif state == 'disabled': self.error() if self.result['failed']: amodule.fail_json(**self.result) else: if self.result['changed']: self._lb_info = self._lb_get_by_id(lb_id=self.lb_id) self.result['facts'] = self.package_facts(amodule.check_mode) amodule.exit_json(**self.result) def main(): decort_lb().run() if __name__ == '__main__': main()