# Upgrades a full mongo cluster, starting with the hidden members, then the
# secondary, and finally steps down the primary and upgrades it.  It checks along
# the way for a healthy cluster, failing if that is not true.
#
# This play expects to have access to a config file where MONGO_RS_CONFIG, as described
# in the mongo_3_0 role, is defined, as well as MONGO_ADMIN_USER and MONGO_ADMIN_PASSWORD.
#
# ansible-playbook -i 127.0.0.1, mongo_rolling_upgrade.yml -e@/path/to/config-file.yml
#
# This play uses MONGO_RS_CONFIG to find a host to connect to and fetch replset config and build an
# inventory, so you can just target localhost.
#
# If there are no hidden secondaries, the 'Upgrade hidden members' task block will just skip.
#
# This will process a hidden secondary twice - first as a 'hidden' server, then as a 'secondary' but
# this is effectively a no-op except for apt checking the versions and then checking that mongo is running.
# It is valid to have other types of hidden machines, so this seemed better than skipping.
#
# If you wish to avoid updating the primary, you can add -e 'SKIP_PRIMARY=true' to your ansible
# invocation.

- name: Find hidden secondaries
  hosts: 127.0.0.1
  connection: local
  gather_facts: False
  vars:
    - SKIP_PRIMARY: False
  tasks:
    - name: Get configuration of mongo cluster
      mongodb_rs_config:
        host: "{{ (MONGO_RS_CONFIG.members|map(attribute='host')|list)[0] }}"
        username: "{{ MONGO_ADMIN_USER }}"
        password: "{{ MONGO_ADMIN_PASSWORD }}"
      register: rs_config
    - name: Build inventory of hidden members
      add_host:
        hostname: "{{ (item.host.split(':'))[0] }}"
        instance_id: "{{ item._id }}"
        groups: hidden_hosts
        ansible_ssh_user: ubuntu
      with_items:
        - "{{ rs_config.hidden }}"
    - name: Build inventory of secondary members
      add_host:
        hostname: "{{ (item.host.split(':'))[0] }}"
        instance_id: "{{ item._id }}"
        groups: secondary_hosts
        ansible_ssh_user: ubuntu
      with_items:
        - "{{ rs_config.secondary }}"
    - name: Build inventory of primary members
      add_host:
        hostname: "{{ (item.host.split(':'))[0] }}"
        instance_id: "{{ item._id }}"
        groups: primary_hosts
        ansible_ssh_user: ubuntu
      with_items:
        - "{{ rs_config.primary }}"
      when: not SKIP_PRIMARY

- name: Upgrade hidden members
  hosts: hidden_hosts
  gather_facts: True
  become: True
  vars_files:
    - ../roles/mongo_3_0/defaults/main.yml
  tasks:
    - name: install mongo server and recommends
      apt:
        pkg: "{{ item }}"
        state: present
        install_recommends: yes
        force: yes
        update_cache: yes
      with_items: "{{ mongodb_debian_pkgs }}"
    - name: wait for mongo server to start
      wait_for:
        port: 27017
        delay: 2
    - name: Wait for the replica set to update and (if needed) elect a primary
      mongodb_rs_status:
          host: "{{ ansible_default_ipv4['address'] }}"
          username: "{{ MONGO_ADMIN_USER }}"
          password: "{{ MONGO_ADMIN_PASSWORD }}"
      register: status
      # This ensures that no servers are in a state other than PRIMARY or SECONDARY.  https://docs.mongodb.com/manual/reference/replica-states/
      until: status.status is defined and not (['PRIMARY','SECONDARY'] | symmetric_difference(status.status.members|map(attribute='stateStr')|list|unique))
      retries: 5
      delay: 2

- name: Upgrade secondary members
  hosts: secondary_hosts
  gather_facts: True
  become: True
  serial: 1
  vars_files:
    - ../roles/mongo_3_0/defaults/main.yml
  tasks:
    - name: install mongo server and recommends
      apt:
        pkg: "{{ item }}"
        state: present
        install_recommends: yes
        force: yes
        update_cache: yes
      with_items: "{{ mongodb_debian_pkgs }}"
    - name: wait for mongo server to start
      wait_for:
        port: 27017
        delay: 2
    - name: Wait for the replica set to update and (if needed) elect a primary
      mongodb_rs_status:
          host: "{{ ansible_default_ipv4['address'] }}"
          username: "{{ MONGO_ADMIN_USER }}"
          password: "{{ MONGO_ADMIN_PASSWORD }}"
      register: status
      # This ensures that no servers are in a state other than PRIMARY or SECONDARY.  https://docs.mongodb.com/manual/reference/replica-states/
      until: status.status is defined and not (['PRIMARY','SECONDARY'] | symmetric_difference(status.status.members|map(attribute='stateStr')|list|unique))
      retries: 5
      delay: 2

- name: Upgrade primary members
  hosts: primary_hosts
  gather_facts: True
  become: True
  vars_files:
    - ../roles/mongo_3_0/defaults/main.yml
  tasks:
    - name: Step down (this can take up to a minute to complete while the primary waits on a secondary)
      mongodb_step_down:
        host: "{{ ansible_default_ipv4['address'] }}"
        username: "{{ MONGO_ADMIN_USER }}"
        password: "{{ MONGO_ADMIN_PASSWORD }}"
    - name: install mongo server and recommends
      apt:
        pkg: "{{ item }}"
        state: present
        install_recommends: yes
        force: yes
        update_cache: yes
      with_items: "{{ mongodb_debian_pkgs }}"
    - name: wait for mongo server to start
      wait_for:
        port: 27017
        delay: 2
    - name: Wait for the replica set to update and (if needed) elect a primary
      mongodb_rs_status:
          host: "{{ ansible_default_ipv4['address'] }}"
          username: "{{ MONGO_ADMIN_USER }}"
          password: "{{ MONGO_ADMIN_PASSWORD }}"
      register: status
      # This ensures that no servers are in a state other than PRIMARY or SECONDARY.  https://docs.mongodb.com/manual/reference/replica-states/
      until: status.status is defined and not (['PRIMARY','SECONDARY'] | symmetric_difference(status.status.members|map(attribute='stateStr')|list|unique))
      retries: 5
      delay: 2
