---
- block:
  - name: Set common facts
    set_fact:
      drain_daemonset_name: "promotheus-dset"
      drain_pod_name: "pod-drain"
      drain_deployment_emptydir_name: "deployment-emptydir-drain"

  # It seems that the default ServiceAccount can take a bit to be created
  # right after a cluster is brought up. This can lead to the ServiceAccount
  # admission controller rejecting a Pod creation request because the
  # ServiceAccount does not yet exist.
  - name: Wait for default serviceaccount to be created
    k8s_info:
      kind: ServiceAccount
      name: default
      namespace: "{{ test_namespace }}"
      wait: yes

  - name: list cluster nodes
    k8s_info:
      kind: node
    register: nodes

  - name: Select uncordoned nodes
    set_fact:
      uncordoned_nodes: "{{ nodes.resources | selectattr('spec.unschedulable', 'undefined') | map(attribute='metadata.name') | list}}"

  - name: Assert that at least one node is schedulable
    assert:
      that:
      - uncordoned_nodes | length > 0

  - name: select node to drain
    set_fact:
      node_to_drain: '{{ uncordoned_nodes[0] }}'

  - name: Deploy daemonset on cluster
    k8s:
      namespace: '{{ test_namespace }}'
      definition:
        apiVersion: apps/v1
        kind: DaemonSet
        metadata:
          name: '{{ drain_daemonset_name }}'
        spec:
          affinity:
            nodeAffinity:
              requiredDuringSchedulingIgnoredDuringExecution:
                nodeSelectorTerms:
                - matchFields:
                  - key: metadata.name
                    operator: In
                    values:
                    - '{{ node_to_drain }}'
          selector:
            matchLabels:
              name: prometheus-exporter
          template:
            metadata:
              labels:
                name: prometheus-exporter
            spec:
              containers:
              - name: prometheus
                image: prom/node-exporter
                ports:
                - containerPort: 80

  - name: Create Pods not managed by ReplicationController, ReplicaSet, Job, DaemonSet or StatefulSet.
    k8s:
      namespace: '{{ test_namespace }}'
      wait: yes
      wait_timeout: "{{ k8s_wait_timeout | default(omit) }}"
      definition:
        apiVersion: v1
        kind: Pod
        metadata:
          name: '{{ drain_pod_name }}'
        spec:
          affinity:
            nodeAffinity:
              requiredDuringSchedulingIgnoredDuringExecution:
                nodeSelectorTerms:
                - matchFields:
                  - key: metadata.name
                    operator: In
                    values:
                    - '{{ node_to_drain }}'
          containers:
          - name: c0
            image: busybox
            command:
            - /bin/sh
            - -c
            - while true;do date;sleep 5; done

  - name: Create Deployment with an emptyDir volume.
    k8s:
      namespace: '{{ test_namespace }}'
      wait: yes
      wait_timeout: "{{ k8s_wait_timeout | default(omit) }}"
      definition:
        apiVersion: apps/v1
        kind: Deployment
        metadata:
          name: '{{ drain_deployment_emptydir_name }}'
        spec:
          replicas: 1
          selector:
            matchLabels:
              drain: emptyDir
          template:
            metadata:
              labels:
                drain: emptyDir
            spec:
              metadata:
                labels:
                  drain: emptyDir
              affinity:
                nodeAffinity:
                  requiredDuringSchedulingIgnoredDuringExecution:
                    nodeSelectorTerms:
                    - matchFields:
                      - key: metadata.name
                        operator: In
                        values:
                        - '{{ node_to_drain }}'
              containers:
              - name: c0
                image: busybox
                command:
                - /bin/sh
                - -c
                - while true;do date;sleep 5; done
                volumeMounts:
                - mountPath: /emptydir
                  name: emptydir
              volumes:
              - name: emptydir
                emptyDir: {}

  - name: Register emptyDir Pod name
    k8s_info:
      namespace: '{{ test_namespace }}'
      kind: Pod
      label_selectors:
      - "drain = emptyDir"
    register: emptydir_pod_result
    failed_when:
    - emptydir_pod_result.resources | length != 1

  - name: Cordon node
    k8s_drain:
      state: cordon
      name: '{{ node_to_drain }}'
    register: cordon

  - name: assert that cordon is changed
    assert:
      that:
      - cordon is changed

  - name: Test cordon idempotency
    k8s_drain:
      state: cordon
      name: '{{ node_to_drain }}'
    register: cordon

  - name: assert that cordon is not changed
    assert:
      that:
      - cordon is not changed

  - name: Get pods
    k8s_info:
      kind: Pod
      namespace: '{{ test_namespace }}'
    register: Pod

  - name: assert that pods are running on cordoned node
    assert:
      that:
      - Pod.resources | selectattr('status.phase', 'equalto', 'Running') | selectattr('spec.nodeName', 'equalto', node_to_drain) | list | length > 0

  - name: Uncordon node
    k8s_drain:
      state: uncordon
      name: '{{ node_to_drain }}'
    register: uncordon

  - name: assert that uncordon is changed
    assert:
      that:
      - uncordon is changed

  - name: Test uncordon idempotency
    k8s_drain:
      state: uncordon
      name: '{{ node_to_drain }}'
    register: uncordon

  - name: assert that uncordon is not changed
    assert:
      that:
      - uncordon is not changed

  - name: Drain node
    k8s_drain:
      state: drain
      name: '{{ node_to_drain }}'
    ignore_errors: true
    register: drain_result

  - name: assert that drain failed due to DaemonSet managed Pods
    assert:
      that:
      - drain_result is failed
      - '"cannot delete DaemonSet-managed Pods" in drain_result.msg'
      - '"cannot delete Pods not managed by ReplicationController, ReplicaSet, Job, DaemonSet or StatefulSet" in drain_result.msg'
      - '"cannot delete Pods with local storage" in drain_result.msg'

  - name: Drain node using ignore_daemonsets, force, and delete_emptydir_data options
    k8s_drain:
      state: drain
      name: '{{ node_to_drain }}'
      delete_options:
        force: true
        ignore_daemonsets: true
        delete_emptydir_data: true
        wait_timeout: 0
    register: drain_result

  - name: assert that node has been drained
    assert:
      that:
      - drain_result is changed
      - '"node "+node_to_drain+" marked unschedulable." in drain_result.result'

  - name: assert that unmanaged pod were deleted
    k8s_info:
      namespace: '{{ test_namespace }}'
      kind: Pod
      name: '{{ drain_pod_name }}'
    register: _result
    failed_when: _result.resources

  - name: assert that emptyDir pod was deleted
    k8s_info:
      namespace: '{{ test_namespace }}'
      kind: Pod
      name: "{{ emptydir_pod_result.resources[0].metadata.name }}"
    register: _result
    failed_when: _result.resources | length != 0

  - name: Test drain idempotency
    k8s_drain:
      state: drain
      name: '{{ node_to_drain }}'
      delete_options:
        force: true
        ignore_daemonsets: true
        delete_emptydir_data: true
    register: drain_result

  - name: Check idempotency
    assert:
      that:
      - drain_result is not changed

  - name: Get DaemonSet
    k8s_info:
      kind: DaemonSet
      namespace: '{{ test_namespace }}'
      name: '{{ drain_daemonset_name }}'
    register: dset_result

  - name: assert that daemonset managed pods were not removed
    assert:
      that:
      - dset_result.resources | list | length > 0

  # test: drain using disable_eviction=true
  - name: Uncordon node
    k8s_drain:
      state: uncordon
      name: '{{ node_to_drain }}'

  - name: Create another Pod
    k8s:
      namespace: '{{ test_namespace }}'
      wait: yes
      wait_timeout: "{{ k8s_wait_timeout | default(omit) }}"
      definition:
        apiVersion: v1
        kind: Pod
        metadata:
          name: '{{ drain_pod_name }}-01'
        spec:
          affinity:
            nodeAffinity:
              requiredDuringSchedulingIgnoredDuringExecution:
                nodeSelectorTerms:
                - matchFields:
                  - key: metadata.name
                    operator: In
                    values:
                    - '{{ node_to_drain }}'
          containers:
          - name: c0
            image: busybox
            command:
            - /bin/sh
            - -c
            - while true;do date;sleep 5; done
            volumeMounts:
            - mountPath: /emptydir
              name: emptydir
          volumes:
          - name: emptydir
            emptyDir: {}

  - name: Drain node using disable_eviction set to yes
    k8s_drain:
      state: drain
      name: '{{ node_to_drain }}'
      delete_options:
        force: true
        disable_eviction: yes
        terminate_grace_period: 0
        ignore_daemonsets: yes
        wait_timeout: 0
        delete_emptydir_data: true
    register: disable_evict

  - name: assert that node has been drained
    assert:
      that:
      - disable_evict is changed
      - '"node "+node_to_drain+" marked unschedulable." in disable_evict.result'

  - name: assert that unmanaged pod were deleted
    k8s_info:
      namespace: '{{ test_namespace }}'
      kind: Pod
      name: '{{ drain_pod_name }}-01'
    register: _result
    failed_when: _result.resources

  # test: drain using pod_selectors
  - name: Uncordon node
    k8s_drain:
      state: uncordon
      name: '{{ node_to_drain }}'

  - name: create a Pod for test
    k8s:
      namespace: '{{ test_namespace }}'
      wait: true
      wait_timeout: "{{ k8s_wait_timeout | default(omit) }}"
      definition:
        apiVersion: v1
        kind: Pod
        metadata:
          name: 'ansible-drain-pod'
          labels:
            app: ansible-drain
        spec:
          affinity:
            nodeAffinity:
              requiredDuringSchedulingIgnoredDuringExecution:
                nodeSelectorTerms:
                - matchFields:
                  - key: metadata.name
                    operator: In
                    values:
                    - '{{ node_to_drain }}'
          containers:
          - name: ansible-container
            image: busybox
            command:
            - '/bin/sh'
            - '-c'
            - 'while true; do echo $(date); sleep 10; done'

  - name: Drain node using pod_selectors 'app!=ansible-drain'
    k8s_drain:
      state: drain
      name: '{{ node_to_drain }}'
      pod_selectors:
      - app!=ansible-drain
      delete_options:
        terminate_grace_period: 0
        delete_emptydir_data: true
        force: true
        ignore_daemonsets: true
    register: drain_pod_selector

  - name: assert that node has been drained
    assert:
      that:
      - drain_pod_selector is changed
      - '"node "+node_to_drain+" marked unschedulable." in drain_pod_selector.result'

  - name: assert that pod created before is still running
    k8s_info:
      namespace: '{{ test_namespace }}'
      kind: Pod
      label_selectors:
      - app=ansible-drain
      field_selectors:
      - status.phase=Running
    register: pods
    failed_when: pods.resources == []

  - name: Drain node using pod_selectors 'app=ansible-drain'
    k8s_drain:
      state: drain
      name: '{{ node_to_drain }}'
      pod_selectors:
      - app=ansible-drain
      delete_options:
        terminate_grace_period: 0
        force: true
    register: drain_pod_selector_equal

  - name: assert that node was not drained
    assert:
      that:
      - drain_pod_selector_equal is changed
      - '"node "+node_to_drain+" already marked unschedulable." in drain_pod_selector_equal.result'
      - '"Deleting Pods not managed by ReplicationController, ReplicaSet, Job, DaemonSet or StatefulSet: "+test_namespace+"/ansible-drain-pod." in drain_pod_selector_equal.warnings'

  - name: Uncordon node
    k8s_drain:
      state: uncordon
      name: '{{ node_to_drain }}'

  always:
  - name: Uncordon node
    k8s_drain:
      state: uncordon
      name: '{{ node_to_drain }}'
    when: node_to_drain is defined
    ignore_errors: true

  - name: delete namespace
    k8s:
      state: absent
      kind: namespace
      name: '{{ test_namespace }}'
