1#!/usr/bin/python
2
3# Copyright (c) 2018 Catalyst Cloud Ltd.
4# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt)
5
6from __future__ import absolute_import, division, print_function
7
8__metaclass__ = type
9
10ANSIBLE_METADATA = {'metadata_version': '1.1',
11                    'status': ['preview'],
12                    'supported_by': 'community'}
13
14DOCUMENTATION = '''
15---
16module: os_member
17short_description: Add/Delete a member for a pool in load balancer from OpenStack Cloud
18extends_documentation_fragment: openstack
19version_added: "2.7"
20author: "Lingxian Kong (@lingxiankong)"
21description:
22   - Add or Remove a member for a pool from the OpenStack load-balancer service.
23options:
24   name:
25     description:
26        - Name that has to be given to the member
27     required: true
28   state:
29     description:
30       - Should the resource be present or absent.
31     choices: [present, absent]
32     default: present
33   pool:
34     description:
35        - The name or id of the pool that this member belongs to.
36     required: true
37   protocol_port:
38     description:
39        - The protocol port number for the member.
40     default: 80
41   address:
42     description:
43        - The IP address of the member.
44   subnet_id:
45     description:
46        - The subnet ID the member service is accessible from.
47   wait:
48     description:
49        - If the module should wait for the load balancer to be ACTIVE.
50     type: bool
51     default: 'yes'
52   timeout:
53     description:
54        - The amount of time the module should wait for the load balancer to get
55          into ACTIVE state.
56     default: 180
57   availability_zone:
58     description:
59       - Ignored. Present for backwards compatibility
60requirements: ["openstacksdk"]
61'''
62
63RETURN = '''
64id:
65    description: The member UUID.
66    returned: On success when I(state) is 'present'
67    type: str
68    sample: "39007a7e-ee4f-4d13-8283-b4da2e037c69"
69member:
70    description: Dictionary describing the member.
71    returned: On success when I(state) is 'present'
72    type: complex
73    contains:
74        id:
75            description: Unique UUID.
76            type: str
77            sample: "39007a7e-ee4f-4d13-8283-b4da2e037c69"
78        name:
79            description: Name given to the member.
80            type: str
81            sample: "test"
82        description:
83            description: The member description.
84            type: str
85            sample: "description"
86        provisioning_status:
87            description: The provisioning status of the member.
88            type: str
89            sample: "ACTIVE"
90        operating_status:
91            description: The operating status of the member.
92            type: str
93            sample: "ONLINE"
94        is_admin_state_up:
95            description: The administrative state of the member.
96            type: bool
97            sample: true
98        protocol_port:
99            description: The protocol port number for the member.
100            type: int
101            sample: 80
102        subnet_id:
103            description: The subnet ID the member service is accessible from.
104            type: str
105            sample: "489247fa-9c25-11e8-9679-00224d6b7bc1"
106        address:
107            description: The IP address of the backend member server.
108            type: str
109            sample: "192.168.2.10"
110'''
111
112EXAMPLES = '''
113# Create a member, wait for the member to be created.
114- os_member:
115    cloud: mycloud
116    endpoint_type: admin
117    state: present
118    name: test-member
119    pool: test-pool
120    address: 192.168.10.3
121    protocol_port: 8080
122
123# Delete a listener
124- os_member:
125    cloud: mycloud
126    endpoint_type: admin
127    state: absent
128    name: test-member
129    pool: test-pool
130'''
131
132import time
133
134from ansible.module_utils.basic import AnsibleModule
135from ansible.module_utils.openstack import openstack_full_argument_spec, \
136    openstack_module_kwargs, openstack_cloud_from_module
137
138
139def _wait_for_member_status(module, cloud, pool_id, member_id, status,
140                            failures, interval=5):
141    timeout = module.params['timeout']
142
143    total_sleep = 0
144    if failures is None:
145        failures = []
146
147    while total_sleep < timeout:
148        member = cloud.load_balancer.get_member(member_id, pool_id)
149        provisioning_status = member.provisioning_status
150        if provisioning_status == status:
151            return member
152        if provisioning_status in failures:
153            module.fail_json(
154                msg="Member %s transitioned to failure state %s" %
155                    (member_id, provisioning_status)
156            )
157
158        time.sleep(interval)
159        total_sleep += interval
160
161    module.fail_json(
162        msg="Timeout waiting for member %s to transition to %s" %
163            (member_id, status)
164    )
165
166
167def main():
168    argument_spec = openstack_full_argument_spec(
169        name=dict(required=True),
170        state=dict(default='present', choices=['absent', 'present']),
171        pool=dict(required=True),
172        address=dict(default=None),
173        protocol_port=dict(default=80, type='int'),
174        subnet_id=dict(default=None),
175    )
176    module_kwargs = openstack_module_kwargs()
177    module = AnsibleModule(argument_spec, **module_kwargs)
178    sdk, cloud = openstack_cloud_from_module(module)
179    name = module.params['name']
180    pool = module.params['pool']
181
182    try:
183        changed = False
184
185        pool_ret = cloud.load_balancer.find_pool(name_or_id=pool)
186        if not pool_ret:
187            module.fail_json(msg='pool %s is not found' % pool)
188
189        pool_id = pool_ret.id
190        member = cloud.load_balancer.find_member(name, pool_id)
191
192        if module.params['state'] == 'present':
193            if not member:
194                member = cloud.load_balancer.create_member(
195                    pool_ret,
196                    address=module.params['address'],
197                    name=name,
198                    protocol_port=module.params['protocol_port'],
199                    subnet_id=module.params['subnet_id']
200                )
201                changed = True
202
203                if not module.params['wait']:
204                    module.exit_json(changed=changed,
205                                     member=member.to_dict(),
206                                     id=member.id)
207
208            if module.params['wait']:
209                member = _wait_for_member_status(module, cloud, pool_id,
210                                                 member.id, "ACTIVE",
211                                                 ["ERROR"])
212
213            module.exit_json(changed=changed, member=member.to_dict(),
214                             id=member.id)
215
216        elif module.params['state'] == 'absent':
217            if member:
218                cloud.load_balancer.delete_member(member, pool_ret)
219                changed = True
220
221            module.exit_json(changed=changed)
222    except sdk.exceptions.OpenStackCloudException as e:
223        module.fail_json(msg=str(e), extra_data=e.extra_data)
224
225
226if __name__ == "__main__":
227    main()
228