
    VhHI                         d Z ddlmZmZmZ eZdZdZdZ	ddl
Z
ddlmZmZ dZ	 ddlZddlmZ ddlZd	Zd Z G d d      Zd Zedk(  r e        yy# e$ rZd
Z e
j,                         ZY dZ[;dZ[ww xY w)z=short_description: Check or wait for migrations between nodes    )absolute_importdivisionprint_functiona(  
module: aerospike_migrations
short_description: Check or wait for migrations between nodes
description:
  - This can be used to check for migrations in a cluster. This makes it easy to do a rolling upgrade/update on Aerospike
    nodes.
  - If waiting for migrations is not desired, simply just poll until port 3000 if available or C(asinfo -v status) returns
    ok.
author: "Albert Autin (@Alb0t)"
extends_documentation_fragment:
  - community.general.attributes
attributes:
  check_mode:
    support: full
  diff_mode:
    support: none
options:
  host:
    description:
      - Which host do we use as seed for info connection.
    required: false
    type: str
    default: localhost
  port:
    description:
      - Which port to connect to Aerospike on (service port).
    required: false
    type: int
    default: 3000
  connect_timeout:
    description:
      - How long to try to connect before giving up (milliseconds).
    required: false
    type: int
    default: 1000
  consecutive_good_checks:
    description:
      - How many times should the cluster report "no migrations" consecutively before returning OK back to ansible?
    required: false
    type: int
    default: 3
  sleep_between_checks:
    description:
      - How long to sleep between each check (seconds).
    required: false
    type: int
    default: 60
  tries_limit:
    description:
      - How many times do we poll before giving up and failing?
    default: 300
    required: false
    type: int
  local_only:
    description:
      - Do you wish to only check for migrations on the local node before returning, or do you want all nodes in the cluster
        to finish before returning?
    required: true
    type: bool
  min_cluster_size:
    description:
      - Check will return bad until cluster size is met or until tries is exhausted.
    required: false
    type: int
    default: 1
  fail_on_cluster_change:
    description:
      - Fail if the cluster key changes if something else is changing the cluster, we may want to fail.
    required: false
    type: bool
    default: true
  migrate_tx_key:
    description:
      - The metric key used to determine if we have tx migrations remaining. Changeable due to backwards compatibility.
    required: false
    type: str
    default: migrate_tx_partitions_remaining
  migrate_rx_key:
    description:
      - The metric key used to determine if we have rx migrations remaining. Changeable due to backwards compatibility.
    required: false
    type: str
    default: migrate_rx_partitions_remaining
  target_cluster_size:
    description:
      - When all aerospike builds in the cluster are greater than version 4.3, then the C(cluster-stable) info command will
        be used. Inside this command, you can optionally specify what the target cluster size is - but it is not necessary.
        You can still rely on O(min_cluster_size) if you do not want to use this option.
      - If this option is specified on a cluster that has at least one host <4.3 then it will be ignored until the min version
        reaches 4.3.
    required: false
    type: int
a  
# check for migrations on local node
- name: Wait for migrations on local node before proceeding
  community.general.aerospike_migrations:
    host: "localhost"
    connect_timeout: 2000
    consecutive_good_checks: 5
    sleep_between_checks: 15
    tries_limit: 600
    local_only: false

# example playbook:
- name: Upgrade aerospike
  hosts: all
  become: true
  serial: 1
  tasks:
    - name: Install dependencies
      ansible.builtin.apt:
        name:
          - python
          - python-pip
          - python-setuptools
        state: latest
    - name: Setup aerospike
      ansible.builtin.pip:
        name: aerospike
# check for migrations every (sleep_between_checks)
# If at least (consecutive_good_checks) checks come back OK in a row, then return OK.
# Will exit if any exception, which can be caused by bad nodes,
# nodes not returning data, or other reasons.
# Maximum runtime before giving up in this case will be:
# Tries Limit * Sleep Between Checks * delay * retries
    - name: Wait for aerospike migrations
      community.general.aerospike_migrations:
        local_only: true
        sleep_between_checks: 1
        tries_limit: 5
        consecutive_good_checks: 3
        fail_on_cluster_change: true
        min_cluster_size: 3
        target_cluster_size: 4
      register: migrations_check
      until: migrations_check is succeeded
      changed_when: false
      delay: 60
      retries: 120
    - name: Another thing
      ansible.builtin.shell: |
        echo foo
    - name: Reboot
      ansible.builtin.reboot:
zC
# Returns only a success/failure result. Changed is always false.
N)AnsibleModulemissing_required_lib)sleepTFc                     t        t        ddd      t        ddd      t        ddd      t        ddd      t        ddd	      t        ddd
      t        dd      t        ddd      t        ddd      t        ddd      t        dddd      t        dddd            } t        d      }t        | d      }t        s |j                  t	        d      t
               	 |j                  rd\  }}n,t        |      }|j                  |j                  d         \  }}|r|j                  d|        |j                  di | y# t        $ r+}|j                  dj                  |             Y d}~Bd}~ww xY w)zrun ansible modulestrF	localhost)typerequireddefaultinti  i     <   i,  boolT)r   r      Nmigrate_tx_partitions_remaining)r   r   no_logr   migrate_rx_partitions_remaining)hostportconnect_timeoutconsecutive_good_checkssleep_between_checkstries_limit
local_onlymin_cluster_sizetarget_cluster_sizefail_on_cluster_changemigrate_tx_keymigrate_rx_key)changed)argument_specsupports_check_mode	aerospike)msg	exceptionFNr   zFailed.)r'   skip_reasonz
Error: {0}r'    )dictr   	LIB_FOUND	fail_jsonr   LIB_FOUND_ERR
check_mode
Migrationshas_migsparams	Exceptionformat	exit_json)module_argsresultmodulehas_migrationsr*   
migrationses          z/home/dcms/DCMS/lib/python3.12/site-packages/ansible_collections/community/general/plugins/modules/aerospike_migrations.py
run_moduler?      s   uukBuud;%%F $%% K!uubIeeSAVd355!D eeTJ#%Nu$EGu$EGK" F ! F 1+>#0 	 	25*5'NK#F+J*4*=*=l++'NK D Fv  5\003445s   )AE 	F!E>>Fc                       e Zd ZdZd Zd ZddZd Zd Zd Z	d	 Z
d
 ZddZddZd Zd Zd Zd Zd Zd Zd Zd ZddZy)r2   z, Check or wait for migrations between nodes c                 x   || _         | j                         j                         | _        i | _        | j                          i | _        | j                          t               | _	        | j                          t               | _        | j                          | j                  | j                  d      d   | _        y )Nr   cluster_key)r:   _create_clientconnect_client_nodes_update_nodes_list_cluster_statistics_update_cluster_statisticsset_namespaces_update_cluster_namespace_list_build_list_update_build_list_start_cluster_key)selfr:   s     r>   __init__zMigrations.__init__   s    **,446!#% '')5++-5!$$T[[^4]C 	    c                     | j                   j                  d   | j                   j                  d   fgd| j                   j                  d   id}t        j                  |      S )z TODO: add support for auth, tls, and other special features
         I won't use those features, so I'll wait until somebody complains
         or does it for me (Cross fingers)
         create the client objectr   r   timeoutr   )hostspolicies)r:   r4   r&   client)rP   configs     r>   rC   zMigrations._create_client   se     ##F+T[[-?-?-GH 4;;--.?@	
 ''rR   Nc                    || j                   d   }| j                  j                  ||      }|j                  d      }t	        |      dk7  r?t	        |      dk7  r1| j
                  j                  dt        t	        |            z          |d   }|j                  d      }|j                  |      }d	|v rt        d
 |D              }|S t	        |      dk(  r|d   }|S |}|S )zXdelimiter is for separate stats that come back, NOT for kv
        separation which is =r   	r      z6Unexpected number of values returned in info command: r+   z
=c              3   @   K   | ]  }|j                  d d        yw)r]   r   N)split).0metrics     r>   	<genexpr>z.Migrations._info_cmd_helper.<locals>.<genexpr>  s       )/S!$s   )
rF   rE   	info_noder_   lenr:   r/   r
   rstripr-   )rP   cmdnode	delimiterdatadata_arrretvals          r>   _info_cmd_helperzMigrations._info_cmd_helper  s     <;;q>D||%%c40zz$t9>c$i1nKK!!LCI " 
 Bx{{6"::i( $; 3; F 	 8}!!!  "rR   c                     t               | _        | j                  D ]/  }| j                  d|      }| j                  j	                  |       1 y)zJcreates self._build_list which is a unique list
        of build versions.buildN)rJ   rM   rF   rl   add)rP   rg   rn   s      r>   rN   zMigrations._update_build_list#  sG     5KK 	(D))'48E  '	(rR   c                 X    t        j                  dt        | j                              ryy)Nz^([0-3]\.|4\.[0-2])FT)researchminrM   rP   s    r>   _can_use_cluster_stablez"Migrations._can_use_cluster_stable,  s$     99+S1A1A-BCrR   c                     t               | _        | j                  D ]6  }| j                  d|      }|D ]  }| j                  j	                  |        8 y)z make a unique list of namespaces
        TODO: does this work on a rolling namespace add/deletion?
        thankfully if it doesn't, we dont need this on builds >=4.3
namespacesN)rJ   rK   rF   rl   ro   )rP   rg   rw   	namespaces       r>   rL   z)Migrations._update_cluster_namespace_list4  sV     5KK 	0D..|TBJ' 0	  $$Y/0	0rR   c                 r    i | _         | j                  D ]!  }| j                  d|      | j                   |<   # y)z0create a dict of nodes with their related stats 
statisticsN)rH   rF   rl   )rP   rg   s     r>   rI   z%Migrations._update_cluster_statistics>  s=    #% KK 	:D%%lD9 $$T*	:rR   c                     | j                   j                         | _        | j                  s| j                  j	                  d       yy)z!get a fresh list of all the nodesz#Failed to retrieve at least 1 node.N)rE   	get_nodesrF   r:   r/   rt   s    r>   rG   zMigrations._update_nodes_listE  s5    ll,,.{{KK!!"GH rR   c                    | j                  d|z   |      }	 t        || j                  j                  d            }t        || j                  j                  d            }d
k7  xs d
k7  S # t        $ r_ | j                  j                  d| j                  j                  d   z   dz   | j                  j                  d   z   dz   |z   dz          Y st        $ r | j                  j                  d	       Y w xY w)zreturns a True or False.
        Does the namespace have migrations for the node passed?
        If no node passed, uses the local node or the first one in the listz
namespace/r!   r"   z%Did not find partition remaining key:z or key:z in 'namespace/z	' output.r+   z)namespace stat returned was not numericalr   )rl   r   r:   r4   KeyErrorr/   	TypeError)rP   rx   rg   namespace_statsnamespace_txnamespace_rxs         r>   _namespace_has_migszMigrations._namespace_has_migsK  s$    //y0H$O	ODKK$6$67G$HIJ  ODKK$6$67G$HIJ   q 5LA$55  		KK!!;""#345 ""#345 "	"
   "   	KK!!? " 	s   A
A- -A%C;$C;:C;c                     d}| j                          | j                  D ]  }| j                  ||      s|dz  } |dk7  S )zPjust calls namespace_has_migs and
        if any namespace has migs returns truer   r   )rL   rK   r   )rP   rg   migsrx   s       r>   _node_has_migszMigrations._node_has_migse  sO     ++-)) 	I''	48		 qyrR   c                     i }| j                   D ]+  }| j                  |   d   }||vrd||<   ||xx   dz  cc<   - t        |j                               dk(  r| j                  |v ryy)zcreate a dictionary to store what each node
        returns the cluster key as. we should end up with only 1 dict key,
        with the key being the cluster key.rB   r   TF)rF   rH   rd   keysrO   )rP   cluster_keysrg   rB   s       r>   _cluster_key_consistentz"Migrations._cluster_key_consistento  s     KK 	/D2248K,.,-[)[)Q.)	/ |  "#q(''<7rR   c                 b    | j                   D ]   }| j                  d|      }|d   }|dk(  s  y y)z=ensure all nodes have 'migrate_allowed' in their stats outputrz   migrate_allowedfalseFT)rF   rl   )rP   rg   
node_statsalloweds       r>   _cluster_migrates_allowedz$Migrations._cluster_migrates_allowed  sB    KK 	D..|TBJ !23G'!		
 rR   c                 d    d}| j                   D ]  }| j                  |      s|dz  } |dk(  ryy)z!calls node_has_migs for each noder   r   FT)rF   r   )rP   r   rg   s      r>   _cluster_has_migszMigrations._cluster_has_migs  s@    KK 	D""4(		 19rR   c                 F    |r| j                         S | j                         S N)_local_node_has_migsr   )rP   locals     r>   	_has_migszMigrations._has_migs  s#    ,,..%%''rR   c                 $    | j                  d       S r   )r   rt   s    r>   r   zMigrations._local_node_has_migs  s    ""4((rR   c                     t               }| j                  D ],  }|j                  t        | j                  |   d                . t	        |      dkD  ryt        |      | j                  j                  d   k\  ryy)zxchecks that all nodes in the cluster are returning the
        minimum cluster size specified in their statistics outputcluster_sizer   Fr   T)rJ   rH   ro   r   rd   rs   r:   r4   )rP   sizesrg   s      r>   _is_min_cluster_sizezMigrations._is_min_cluster_size  sv     ,, 	KDIIc$2248HIJ	K J!J4;;--.@AArR   c                    t               }|j                  | j                  d      d          d}| j                  j                  d   }||dz   t        |      z   dz   }| j                  D ]$  }	 |j                  | j                  ||             & t        |      d
k(  ryy	# t        j                  j                  $ r}d|j                  v rY d}~ y	|d}~ww xY w)a  Added 4.3:
        cluster-stable:size=<target-cluster-size>;ignore-migrations=<yes/no>;namespace=<namespace-name>
        Returns the current 'cluster_key' when the following are satisfied:

         If 'size' is specified then the target node's 'cluster-size'
         must match size.
         If 'ignore-migrations' is either unspecified or 'false' then
         the target node's migrations counts must be zero for the provided
         'namespace' or all namespaces if 'namespace' is not provided.rz   rB   zcluster-stable:r   Nzsize=;zunstable-clusterFr   T)rJ   ro   rl   r:   r4   r
   rF   r&   r(   ServerErrorr'   rd   )rP   rB   rf   r   rg   r=   s         r>   _cluster_stablezMigrations._cluster_stable  s     e--l;MJK"kk001FG*-#&9"::S@CKK 	D 5 5c4 @A	 {q  &&22 %. s   /!B""C?CCCc                 v    | j                         dury| j                         dury| j                         duryy)zchecks a few things to make sure we're OK to say the cluster
        has no migs. It could be in a unhealthy condition that does not allow
        migs, or a split brainT)FzCluster key inconsistent.)FzCluster min size not reached.)Fz#migrate_allowed is false somewhere.)TzOK.)r   r   r   rt   s    r>   _cluster_good_statezMigrations._cluster_good_state  sC     '')55$$&d29))+47?rR   c                    d}d}t               }|t        | j                  j                  d         k  r|t        | j                  j                  d         k  rs| j	                          | j                          | j                         \  }}|dur$|j                  dt        |      z   dz   |z          n| j                         r<| j                         r|dz  }nd}|j                  dt        |      z   dz   dz          nY| j                  |      r&|j                  dt        |      z   dz   d	z          d}n"|dz  }|| j                  j                  d   k(  rns|dz  }t        | j                  j                  d
          |t        | j                  j                  d         k  r'|t        | j                  j                  d         k  rs|| j                  j                  d   k(  ryd|fS )z8returns a boolean, False if no migrations otherwise Truer   r   r   TzSkipping on try#z for reason:r   z cluster_stablez migrationsr   r)   )listr   r:   r4   rG   rI   r   appendr
   ru   r   r   r   )rP   r   consecutive_goodtry_numr*   stablereasons          r>   r3   zMigrations.has_migs  s   fc$++,,];<< DKK&&'@ABC ##%++- "557NFFT!""&W5"#%+,
 //1++-(A-(+,(#**.W=*+->? ^^E*&&*S\9&')67 ()$$)$'4;;+=+=5,7 7qLG$++$$%;<=M c$++,,];<< DKK&&'@ABCN t{{112KLL[  rR   )Nr   r   )T)__name__
__module____qualname____doc__rQ   rC   rl   rN   ru   rL   rI   rG   r   r   r   r   r   r   r   r   r   r   r3   r,   rR   r>   r2   r2      sf    6D(>(0:I64"(
)6
0!rR   r2   c                      t                y)zmain method for ansible moduleN)r?   r,   rR   r>   mainr     s    LrR   __main__)r   
__future__r   r   r   r   __metaclass__DOCUMENTATIONEXAMPLESRETURN	tracebackansible.module_utils.basicr   r   r0   r&   timer   rq   r.   ImportErrorie
format_excr?   r2   r   r   r,   rR   r>   <module>r      s    D
 C B\|4l
  J
 I-`[! [!|
 zF u
  +I(I((*M+s   A A4A//A4