1#!/usr/bin/python
2# (c) 2018, NetApp, Inc
3# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt)
4
5from __future__ import absolute_import, division, print_function
6__metaclass__ = type
7
8
9ANSIBLE_METADATA = {'metadata_version': '1.1',
10                    'status': ['preview'],
11                    'supported_by': 'certified'}
12
13
14DOCUMENTATION = '''
15
16module: na_elementsw_cluster_pair
17
18short_description: NetApp Element Software Manage Cluster Pair
19extends_documentation_fragment:
20    - netapp.solidfire
21version_added: '2.7'
22author: NetApp Ansible Team (@carchi8py) <ng-ansibleteam@netapp.com>
23description:
24- Create, delete cluster pair
25
26options:
27
28    state:
29      description:
30      - Whether the specified cluster pair should exist or not.
31      choices: ['present', 'absent']
32      default: present
33
34    dest_mvip:
35      description:
36      - Destination IP address of the cluster to be paired.
37      required: true
38
39    dest_username:
40      description:
41      - Destination username for the cluster to be paired.
42      - Optional if this is same as source cluster username.
43
44    dest_password:
45      description:
46      - Destination password for the cluster to be paired.
47      - Optional if this is same as source cluster password.
48
49'''
50
51EXAMPLES = """
52   - name: Create cluster pair
53     na_elementsw_cluster_pair:
54       hostname: "{{ src_hostname }}"
55       username: "{{ src_username }}"
56       password: "{{ src_password }}"
57       state: present
58       dest_mvip: "{{ dest_hostname }}"
59
60   - name: Delete cluster pair
61     na_elementsw_cluster_pair:
62       hostname: "{{ src_hostname }}"
63       username: "{{ src_username }}"
64       password: "{{ src_password }}"
65       state: absent
66       dest_mvip: "{{ dest_hostname }}"
67       dest_username: "{{ dest_username }}"
68       dest_password: "{{ dest_password }}"
69
70"""
71
72RETURN = """
73
74"""
75
76from ansible.module_utils.basic import AnsibleModule
77from ansible.module_utils._text import to_native
78import ansible.module_utils.netapp as netapp_utils
79from ansible.module_utils.netapp_elementsw_module import NaElementSWModule
80from ansible.module_utils.netapp_module import NetAppModule
81
82HAS_SF_SDK = netapp_utils.has_sf_sdk()
83try:
84    import solidfire.common
85except ImportError:
86    HAS_SF_SDK = False
87
88
89class ElementSWClusterPair(object):
90    """ class to handle cluster pairing operations """
91
92    def __init__(self):
93        """
94            Setup Ansible parameters and ElementSW connection
95        """
96        self.argument_spec = netapp_utils.ontap_sf_host_argument_spec()
97        self.argument_spec.update(dict(
98            state=dict(required=False, choices=['present', 'absent'],
99                       default='present'),
100            dest_mvip=dict(required=True, type='str'),
101            dest_username=dict(required=False, type='str'),
102            dest_password=dict(required=False, type='str', no_log=True)
103        ))
104
105        self.module = AnsibleModule(
106            argument_spec=self.argument_spec,
107            supports_check_mode=True
108        )
109
110        if HAS_SF_SDK is False:
111            self.module.fail_json(msg="Unable to import the SolidFire Python SDK")
112        else:
113            self.elem = netapp_utils.create_sf_connection(module=self.module)
114
115        self.elementsw_helper = NaElementSWModule(self.elem)
116        self.na_helper = NetAppModule()
117        self.parameters = self.na_helper.set_parameters(self.module.params)
118        # get element_sw_connection for destination cluster
119        # overwrite existing source host, user and password with destination credentials
120        self.module.params['hostname'] = self.parameters['dest_mvip']
121        # username and password is same as source,
122        # if dest_username and dest_password aren't specified
123        if self.parameters.get('dest_username'):
124            self.module.params['username'] = self.parameters['dest_username']
125        if self.parameters.get('dest_password'):
126            self.module.params['password'] = self.parameters['dest_password']
127        self.dest_elem = netapp_utils.create_sf_connection(module=self.module)
128        self.dest_elementsw_helper = NaElementSWModule(self.dest_elem)
129
130    def check_if_already_paired(self, paired_clusters, hostname):
131        for pair in paired_clusters.cluster_pairs:
132            if pair.mvip == hostname:
133                return pair.cluster_pair_id
134        return None
135
136    def get_src_pair_id(self):
137        """
138            Check for idempotency
139        """
140        # src cluster and dest cluster exist
141        paired_clusters = self.elem.list_cluster_pairs()
142        return self.check_if_already_paired(paired_clusters, self.parameters['dest_mvip'])
143
144    def get_dest_pair_id(self):
145        """
146        Getting destination cluster_pair_id
147        """
148        paired_clusters = self.dest_elem.list_cluster_pairs()
149        return self.check_if_already_paired(paired_clusters, self.parameters['hostname'])
150
151    def pair_clusters(self):
152        """
153            Start cluster pairing on source, and complete on target cluster
154        """
155        try:
156            pair_key = self.elem.start_cluster_pairing()
157            self.dest_elem.complete_cluster_pairing(
158                cluster_pairing_key=pair_key.cluster_pairing_key)
159        except solidfire.common.ApiServerError as err:
160            self.module.fail_json(msg="Error pairing cluster %s and %s"
161                                  % (self.parameters['hostname'],
162                                     self.parameters['dest_mvip']),
163                                  exception=to_native(err))
164
165    def unpair_clusters(self, pair_id_source, pair_id_dest):
166        """
167            Delete cluster pair
168        """
169        try:
170            self.elem.remove_cluster_pair(cluster_pair_id=pair_id_source)
171            self.dest_elem.remove_cluster_pair(cluster_pair_id=pair_id_dest)
172        except solidfire.common.ApiServerError as err:
173            self.module.fail_json(msg="Error unpairing cluster %s and %s"
174                                  % (self.parameters['hostname'],
175                                     self.parameters['dest_mvip']),
176                                  exception=to_native(err))
177
178    def apply(self):
179        """
180            Call create / delete cluster pair methods
181        """
182        pair_id_source = self.get_src_pair_id()
183        # If already paired, find the cluster_pair_id of destination cluster
184        if pair_id_source:
185            pair_id_dest = self.get_dest_pair_id()
186        # calling helper to determine action
187        cd_action = self.na_helper.get_cd_action(pair_id_source, self.parameters)
188        if cd_action == "create":
189            self.pair_clusters()
190        elif cd_action == "delete":
191            self.unpair_clusters(pair_id_source, pair_id_dest)
192        self.module.exit_json(changed=self.na_helper.changed)
193
194
195def main():
196    """ Apply cluster pair actions """
197    cluster_obj = ElementSWClusterPair()
198    cluster_obj.apply()
199
200
201if __name__ == '__main__':
202    main()
203