1#!/usr/bin/python
2# -*- coding: utf-8 -*-
3
4# (c) 2017, Ansible by Red Hat, inc
5# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt)
6
7from __future__ import absolute_import, division, print_function
8__metaclass__ = type
9
10
11ANSIBLE_METADATA = {'metadata_version': '1.1',
12                    'status': ['deprecated'],
13                    'supported_by': 'network'}
14
15
16DOCUMENTATION = """
17---
18module: junos_linkagg
19version_added: "2.4"
20author: "Ganesh Nalawade (@ganeshrn)"
21short_description: Manage link aggregation groups on Juniper JUNOS network devices
22description:
23  - This module provides declarative management of link aggregation groups
24    on Juniper JUNOS network devices.
25deprecated:
26  removed_in: "2.13"
27  why: Updated modules released with more functionality
28  alternative: Use M(junos_lag_interfaces) instead.
29options:
30  name:
31    description:
32      - Name of the link aggregation group.
33    required: true
34  mode:
35    description:
36      - Mode of the link aggregation group. A value of C(on) will enable LACP in C(passive) mode.
37        C(active) configures the link to actively information about the state of the link,
38        or it can be configured in C(passive) mode ie. send link state information only when
39        received them from another link. A value of C(off) will disable LACP.
40    default: off
41    choices: ['on', 'off', 'active', 'passive']
42  members:
43    description:
44      - List of members interfaces of the link aggregation group. The value can be
45        single interface or list of interfaces.
46    required: true
47  min_links:
48    description:
49      - Minimum members that should be up
50        before bringing up the link aggregation group.
51  device_count:
52    description:
53      - Number of aggregated ethernet devices that can be configured.
54        Acceptable integer value is between 1 and 128.
55  description:
56    description:
57      - Description of Interface.
58  aggregate:
59    description: List of link aggregation definitions.
60  state:
61    description:
62      - State of the link aggregation group.
63    default: present
64    choices: ['present', 'absent', 'up', 'down']
65  active:
66    description:
67      - Specifies whether or not the configuration is active or deactivated
68    default: True
69    type: bool
70requirements:
71  - ncclient (>=v0.5.2)
72notes:
73  - This module requires the netconf system service be enabled on
74    the remote device being managed.
75  - Tested against vSRX JUNOS version 15.1X49-D15.4, vqfx-10000 JUNOS Version 15.1X53-D60.4.
76  - Recommended connection is C(netconf). See L(the Junos OS Platform Options,../network/user_guide/platform_junos.html).
77  - This module also works with C(local) connections for legacy playbooks.
78extends_documentation_fragment: junos
79"""
80
81EXAMPLES = """
82- name: configure link aggregation
83  junos_linkagg:
84    name: ae11
85    members:
86      - ge-0/0/5
87      - ge-0/0/6
88      - ge-0/0/7
89    lacp: active
90    device_count: 4
91    state: present
92
93- name: delete link aggregation
94  junos_linkagg:
95    name: ae11
96    members:
97      - ge-0/0/5
98      - ge-0/0/6
99      - ge-0/0/7
100    lacp: active
101    device_count: 4
102    state: delete
103
104- name: deactivate link aggregation
105  junos_linkagg:
106    name: ae11
107    members:
108      - ge-0/0/5
109      - ge-0/0/6
110      - ge-0/0/7
111    lacp: active
112    device_count: 4
113    state: present
114    active: False
115
116- name: Activate link aggregation
117  junos_linkagg:
118    name: ae11
119    members:
120      - ge-0/0/5
121      - ge-0/0/6
122      - ge-0/0/7
123    lacp: active
124    device_count: 4
125    state: present
126    active: True
127
128- name: Disable link aggregation
129  junos_linkagg:
130    name: ae11
131    state: down
132
133- name: Enable link aggregation
134  junos_linkagg:
135    name: ae11
136    state: up
137"""
138
139RETURN = """
140diff:
141  description: Configuration difference before and after applying change.
142  returned: when configuration is changed and diff option is enabled.
143  type: str
144  sample: >
145            [edit interfaces]
146            +   ge-0/0/6 {
147            +       ether-options {
148            +           802.3ad ae0;
149            +       }
150            +   }
151            [edit interfaces ge-0/0/7]
152            +   ether-options {
153            +       802.3ad ae0;
154            +   }
155            [edit interfaces]
156            +   ae0 {
157            +       description "configured by junos_linkagg";
158            +       aggregated-ether-options {
159            +           lacp {
160            +               active;
161            +           }
162            +       }
163            +   }
164"""
165import collections
166
167from copy import deepcopy
168
169from ansible.module_utils.basic import AnsibleModule
170from ansible.module_utils.network.common.utils import remove_default_spec
171from ansible.module_utils.network.junos.junos import junos_argument_spec, tostring
172from ansible.module_utils.network.junos.junos import load_config, map_params_to_obj, map_obj_to_ele, to_param_list
173from ansible.module_utils.network.junos.junos import commit_configuration, discard_changes, locked_config, get_configuration
174
175USE_PERSISTENT_CONNECTION = True
176
177
178def validate_device_count(value, module):
179    if value and not 1 <= value <= 128:
180        module.fail_json(msg='device_count must be between 1 and 128')
181
182
183def validate_min_links(value, module):
184    if value and not 1 <= value <= 8:
185        module.fail_json(msg='min_links must be between 1 and 8')
186
187
188def validate_param_values(module, obj, item):
189    for key in obj:
190        # validate the param value (if validator func exists)
191        validator = globals().get('validate_%s' % key)
192        if callable(validator):
193            validator(item.get(key), module)
194
195
196def configure_lag_params(module, requests, item):
197    top = 'interfaces/interface'
198    param_lag_to_xpath_map = collections.OrderedDict()
199    param_lag_to_xpath_map.update([
200        ('name', {'xpath': 'name', 'is_key': True}),
201        ('description', 'description'),
202        ('min_links', {'xpath': 'minimum-links', 'top': 'aggregated-ether-options'}),
203        ('disable', {'xpath': 'disable', 'tag_only': True}),
204        ('mode', {'xpath': item['mode'], 'tag_only': True, 'top': 'aggregated-ether-options/lacp'}),
205    ])
206
207    validate_param_values(module, param_lag_to_xpath_map, item)
208
209    want = map_params_to_obj(module, param_lag_to_xpath_map, param=item)
210    ele = map_obj_to_ele(module, want, top, param=item)
211    requests.append(ele)
212
213    if item['device_count']:
214        top = 'chassis/aggregated-devices/ethernet'
215        device_count_to_xpath_map = {'device_count': {'xpath': 'device-count', 'leaf_only': True}}
216
217        validate_param_values(module, device_count_to_xpath_map, item)
218
219        want = map_params_to_obj(module, device_count_to_xpath_map, param=item)
220        ele = map_obj_to_ele(module, want, top, param=item)
221        requests.append(ele)
222
223
224def configure_member_params(module, requests, item):
225    top = 'interfaces/interface'
226    members = item['members']
227
228    if members:
229        member_to_xpath_map = collections.OrderedDict()
230        member_to_xpath_map.update([
231            ('name', {'xpath': 'name', 'is_key': True, 'parent_attrib': False}),
232            ('bundle', {'xpath': 'bundle', 'leaf_only': True, 'top': 'ether-options/ieee-802.3ad', 'is_key': True}),
233        ])
234
235        # link aggregation bundle assigned to member
236        item['bundle'] = item['name']
237
238        for member in members:
239
240            if item['state'] == 'absent':
241                # if link aggregate bundle is not assigned to member, trying to
242                # delete it results in rpc-reply error, hence if is not assigned
243                # skip deleting it and continue to next member.
244                resp = get_configuration(module)
245                bundle = resp.xpath("configuration/interfaces/interface[name='%s']/ether-options/"
246                                    "ieee-802.3ad[bundle='%s']" % (member, item['bundle']))
247                if not bundle:
248                    continue
249            # Name of member to be assigned to link aggregation bundle
250            item['name'] = member
251
252            validate_param_values(module, member_to_xpath_map, item)
253
254            want = map_params_to_obj(module, member_to_xpath_map, param=item)
255            ele = map_obj_to_ele(module, want, top, param=item)
256            requests.append(ele)
257
258
259def main():
260    """ main entry point for module execution
261    """
262    element_spec = dict(
263        name=dict(),
264        mode=dict(default='on', choices=['on', 'off', 'active', 'passive']),
265        members=dict(type='list'),
266        min_links=dict(type='int'),
267        device_count=dict(type='int'),
268        description=dict(),
269        state=dict(default='present', choices=['present', 'absent', 'up', 'down']),
270        active=dict(default=True, type='bool')
271    )
272
273    aggregate_spec = deepcopy(element_spec)
274    aggregate_spec['name'] = dict(required=True)
275
276    # remove default in aggregate spec, to handle common arguments
277    remove_default_spec(aggregate_spec)
278
279    argument_spec = dict(
280        aggregate=dict(type='list', elements='dict', options=aggregate_spec),
281    )
282
283    argument_spec.update(element_spec)
284    argument_spec.update(junos_argument_spec)
285
286    required_one_of = [['name', 'aggregate']]
287    mutually_exclusive = [['name', 'aggregate']]
288
289    module = AnsibleModule(argument_spec=argument_spec,
290                           supports_check_mode=True,
291                           required_one_of=required_one_of,
292                           mutually_exclusive=mutually_exclusive)
293
294    warnings = list()
295    result = {'changed': False}
296
297    if warnings:
298        result['warnings'] = warnings
299
300    params = to_param_list(module)
301    requests = list()
302    for param in params:
303        # if key doesn't exist in the item, get it from module.params
304        for key in param:
305            if param.get(key) is None:
306                param[key] = module.params[key]
307
308        item = param.copy()
309        state = item.get('state')
310        item['disable'] = True if state == 'down' else False
311
312        if state in ('present', 'up', 'down'):
313            item['state'] = 'present'
314
315        else:
316            item['disable'] = True
317
318        mode = item.get('mode')
319        if mode == 'off':
320            item['mode'] = ''
321        elif mode == 'on':
322            item['mode'] = 'passive'
323
324        configure_lag_params(module, requests, item)
325        configure_member_params(module, requests, item)
326
327    diff = None
328    with locked_config(module):
329        for req in requests:
330            diff = load_config(module, tostring(req), warnings, action='merge')
331
332        commit = not module.check_mode
333        if diff:
334            if commit:
335                commit_configuration(module)
336            else:
337                discard_changes(module)
338            result['changed'] = True
339
340            if module._diff:
341                result['diff'] = {'prepared': diff}
342
343    module.exit_json(**result)
344
345
346if __name__ == "__main__":
347    main()
348