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