1#!/usr/bin/python 2# -*- coding: utf-8 -*- 3 4# Copyright: (c) 2017, Bruno Calogero <brunocalogero@hotmail.com> 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 10ANSIBLE_METADATA = {'metadata_version': '1.1', 11 'status': ['preview'], 12 'supported_by': 'certified'} 13 14DOCUMENTATION = r''' 15--- 16module: aci_static_binding_to_epg 17short_description: Bind static paths to EPGs (fv:RsPathAtt) 18description: 19- Bind static paths to EPGs on Cisco ACI fabrics. 20version_added: '2.5' 21options: 22 tenant: 23 description: 24 - Name of an existing tenant. 25 type: str 26 aliases: [ tenant_name ] 27 ap: 28 description: 29 - Name of an existing application network profile, that will contain the EPGs. 30 type: str 31 aliases: [ app_profile, app_profile_name ] 32 epg: 33 description: 34 - The name of the end point group. 35 type: str 36 aliases: [ epg_name ] 37 description: 38 description: 39 - Description for the static path to EPG binding. 40 type: str 41 aliases: [ descr ] 42 version_added: '2.7' 43 encap_id: 44 description: 45 - The encapsulation ID associating the C(epg) with the interface path. 46 - This acts as the secondary C(encap_id) when using micro-segmentation. 47 - Accepted values are any valid encap ID for specified encap, currently ranges between C(1) and C(4096). 48 type: int 49 aliases: [ vlan, vlan_id ] 50 primary_encap_id: 51 description: 52 - Determines the primary encapsulation ID associating the C(epg) 53 with the interface path when using micro-segmentation. 54 - Accepted values are any valid encap ID for specified encap, currently ranges between C(1) and C(4096). 55 type: int 56 aliases: [ primary_vlan, primary_vlan_id ] 57 deploy_immediacy: 58 description: 59 - The Deployment Immediacy of Static EPG on PC, VPC or Interface. 60 - The APIC defaults to C(lazy) when unset during creation. 61 type: str 62 choices: [ immediate, lazy ] 63 interface_mode: 64 description: 65 - Determines how layer 2 tags will be read from and added to frames. 66 - Values C(802.1p) and C(native) are identical. 67 - Values C(access) and C(untagged) are identical. 68 - Values C(regular), C(tagged) and C(trunk) are identical. 69 - The APIC defaults to C(trunk) when unset during creation. 70 type: str 71 choices: [ 802.1p, access, native, regular, tagged, trunk, untagged ] 72 aliases: [ interface_mode_name, mode ] 73 interface_type: 74 description: 75 - The type of interface for the static EPG deployment. 76 type: str 77 choices: [ fex, port_channel, switch_port, vpc ] 78 default: switch_port 79 pod_id: 80 description: 81 - The pod number part of the tDn. 82 - C(pod_id) is usually an integer below C(10). 83 type: int 84 aliases: [ pod, pod_number ] 85 leafs: 86 description: 87 - The switch ID(s) that the C(interface) belongs to. 88 - When C(interface_type) is C(switch_port), C(port_channel), or C(fex), then C(leafs) is a string of the leaf ID. 89 - When C(interface_type) is C(vpc), then C(leafs) is a list with both leaf IDs. 90 - The C(leafs) value is usually something like '101' or '101-102' depending on C(connection_type). 91 type: list 92 aliases: [ leaves, nodes, paths, switches ] 93 interface: 94 description: 95 - The C(interface) string value part of the tDn. 96 - Usually a policy group like C(test-IntPolGrp) or an interface of the following format C(1/7) depending on C(interface_type). 97 type: str 98 extpaths: 99 description: 100 - The C(extpaths) integer value part of the tDn. 101 - C(extpaths) is only used if C(interface_type) is C(fex). 102 - Usually something like C(1011). 103 type: int 104 state: 105 description: 106 - Use C(present) or C(absent) for adding or removing. 107 - Use C(query) for listing an object or multiple objects. 108 type: str 109 choices: [ absent, present, query ] 110 default: present 111extends_documentation_fragment: aci 112notes: 113- The C(tenant), C(ap), C(epg) used must exist before using this module in your playbook. 114 The M(aci_tenant), M(aci_ap), M(aci_epg) modules can be used for this. 115seealso: 116- module: aci_tenant 117- module: aci_ap 118- module: aci_epg 119- name: APIC Management Information Model reference 120 description: More information about the internal APIC class B(fv:RsPathAtt). 121 link: https://developer.cisco.com/docs/apic-mim-ref/ 122author: 123- Bruno Calogero (@brunocalogero) 124''' 125 126EXAMPLES = r''' 127- name: Deploy Static Path binding for given EPG 128 aci_static_binding_to_epg: 129 host: apic 130 username: admin 131 password: SomeSecretPassword 132 tenant: accessport-code-cert 133 ap: accessport_code_app 134 epg: accessport_epg1 135 encap_id: 222 136 deploy_immediacy: lazy 137 interface_mode: untagged 138 interface_type: switch_port 139 pod_id: 1 140 leafs: 101 141 interface: '1/7' 142 state: present 143 delegate_to: localhost 144 145- name: Remove Static Path binding for given EPG 146 aci_static_binding_to_epg: 147 host: apic 148 username: admin 149 password: SomeSecretPassword 150 tenant: accessport-code-cert 151 ap: accessport_code_app 152 epg: accessport_epg1 153 interface_type: switch_port 154 pod: 1 155 leafs: 101 156 interface: '1/7' 157 state: absent 158 delegate_to: localhost 159 160- name: Get specific Static Path binding for given EPG 161 aci_static_binding_to_epg: 162 host: apic 163 username: admin 164 password: SomeSecretPassword 165 tenant: accessport-code-cert 166 ap: accessport_code_app 167 epg: accessport_epg1 168 interface_type: switch_port 169 pod: 1 170 leafs: 101 171 interface: '1/7' 172 state: query 173 delegate_to: localhost 174 register: query_result 175''' 176 177RETURN = r''' 178current: 179 description: The existing configuration from the APIC after the module has finished 180 returned: success 181 type: list 182 sample: 183 [ 184 { 185 "fvTenant": { 186 "attributes": { 187 "descr": "Production environment", 188 "dn": "uni/tn-production", 189 "name": "production", 190 "nameAlias": "", 191 "ownerKey": "", 192 "ownerTag": "" 193 } 194 } 195 } 196 ] 197error: 198 description: The error information as returned from the APIC 199 returned: failure 200 type: dict 201 sample: 202 { 203 "code": "122", 204 "text": "unknown managed object class foo" 205 } 206raw: 207 description: The raw output returned by the APIC REST API (xml or json) 208 returned: parse error 209 type: str 210 sample: '<?xml version="1.0" encoding="UTF-8"?><imdata totalCount="1"><error code="122" text="unknown managed object class foo"/></imdata>' 211sent: 212 description: The actual/minimal configuration pushed to the APIC 213 returned: info 214 type: list 215 sample: 216 { 217 "fvTenant": { 218 "attributes": { 219 "descr": "Production environment" 220 } 221 } 222 } 223previous: 224 description: The original configuration from the APIC before the module has started 225 returned: info 226 type: list 227 sample: 228 [ 229 { 230 "fvTenant": { 231 "attributes": { 232 "descr": "Production", 233 "dn": "uni/tn-production", 234 "name": "production", 235 "nameAlias": "", 236 "ownerKey": "", 237 "ownerTag": "" 238 } 239 } 240 } 241 ] 242proposed: 243 description: The assembled configuration from the user-provided parameters 244 returned: info 245 type: dict 246 sample: 247 { 248 "fvTenant": { 249 "attributes": { 250 "descr": "Production environment", 251 "name": "production" 252 } 253 } 254 } 255filter_string: 256 description: The filter string used for the request 257 returned: failure or debug 258 type: str 259 sample: ?rsp-prop-include=config-only 260method: 261 description: The HTTP method used for the request to the APIC 262 returned: failure or debug 263 type: str 264 sample: POST 265response: 266 description: The HTTP response from the APIC 267 returned: failure or debug 268 type: str 269 sample: OK (30 bytes) 270status: 271 description: The HTTP status from the APIC 272 returned: failure or debug 273 type: int 274 sample: 200 275url: 276 description: The HTTP url used for the request to the APIC 277 returned: failure or debug 278 type: str 279 sample: https://10.11.12.13/api/mo/uni/tn-production.json 280''' 281 282from ansible.module_utils.basic import AnsibleModule 283from ansible.module_utils.network.aci.aci import ACIModule, aci_argument_spec 284 285INTERFACE_MODE_MAPPING = { 286 '802.1p': 'native', 287 'access': 'untagged', 288 'native': 'native', 289 'regular': 'regular', 290 'tagged': 'regular', 291 'trunk': 'regular', 292 'untagged': 'untagged', 293} 294 295INTERFACE_TYPE_MAPPING = dict( 296 fex='topology/pod-{pod_id}/paths-{leafs}/extpaths-{extpaths}/pathep-[eth{interface}]', 297 port_channel='topology/pod-{pod_id}/paths-{leafs}/pathep-[{interface}]', 298 switch_port='topology/pod-{pod_id}/paths-{leafs}/pathep-[eth{interface}]', 299 vpc='topology/pod-{pod_id}/protpaths-{leafs}/pathep-[{interface}]', 300) 301 302# TODO: change 'deploy_immediacy' to 'resolution_immediacy' (as seen in aci_epg_to_domain)? 303 304 305def main(): 306 argument_spec = aci_argument_spec() 307 argument_spec.update( 308 tenant=dict(type='str', aliases=['tenant_name']), # Not required for querying all objects 309 ap=dict(type='str', aliases=['app_profile', 'app_profile_name']), # Not required for querying all objects 310 epg=dict(type='str', aliases=['epg_name']), # Not required for querying all objects 311 description=dict(type='str', aliases=['descr']), 312 encap_id=dict(type='int', aliases=['vlan', 'vlan_id']), 313 primary_encap_id=dict(type='int', aliases=['primary_vlan', 'primary_vlan_id']), 314 deploy_immediacy=dict(type='str', choices=['immediate', 'lazy']), 315 interface_mode=dict(type='str', choices=['802.1p', 'access', 'native', 'regular', 'tagged', 'trunk', 'untagged'], 316 aliases=['interface_mode_name', 'mode']), 317 interface_type=dict(type='str', default='switch_port', choices=['fex', 'port_channel', 'switch_port', 'vpc']), 318 pod_id=dict(type='int', aliases=['pod', 'pod_number']), # Not required for querying all objects 319 leafs=dict(type='list', aliases=['leaves', 'nodes', 'paths', 'switches']), # Not required for querying all objects 320 interface=dict(type='str'), # Not required for querying all objects 321 extpaths=dict(type='int'), 322 state=dict(type='str', default='present', choices=['absent', 'present', 'query']), 323 ) 324 325 module = AnsibleModule( 326 argument_spec=argument_spec, 327 supports_check_mode=True, 328 required_if=[ 329 ['interface_type', 'fex', ['extpaths']], 330 ['state', 'absent', ['ap', 'epg', 'interface', 'leafs', 'pod_id', 'tenant']], 331 ['state', 'present', ['ap', 'encap_id', 'epg', 'interface', 'leafs', 'pod_id', 'tenant']], 332 ], 333 ) 334 335 tenant = module.params['tenant'] 336 ap = module.params['ap'] 337 epg = module.params['epg'] 338 description = module.params['description'] 339 encap_id = module.params['encap_id'] 340 primary_encap_id = module.params['primary_encap_id'] 341 deploy_immediacy = module.params['deploy_immediacy'] 342 interface_mode = module.params['interface_mode'] 343 interface_type = module.params['interface_type'] 344 pod_id = module.params['pod_id'] 345 leafs = module.params['leafs'] 346 if leafs is not None: 347 # Process leafs, and support dash-delimited leafs 348 leafs = [] 349 for leaf in module.params['leafs']: 350 # Users are likely to use integers for leaf IDs, which would raise an exception when using the join method 351 leafs.extend(str(leaf).split('-')) 352 if len(leafs) == 1: 353 if interface_type == 'vpc': 354 module.fail_json(msg='A interface_type of "vpc" requires 2 leafs') 355 leafs = leafs[0] 356 elif len(leafs) == 2: 357 if interface_type != 'vpc': 358 module.fail_json(msg='The interface_types "switch_port", "port_channel", and "fex" \ 359 do not support using multiple leafs for a single binding') 360 leafs = "-".join(leafs) 361 else: 362 module.fail_json(msg='The "leafs" parameter must not have more than 2 entries') 363 interface = module.params['interface'] 364 extpaths = module.params['extpaths'] 365 state = module.params['state'] 366 367 if encap_id is not None: 368 if encap_id not in range(1, 4097): 369 module.fail_json(msg='Valid VLAN assigments are from 1 to 4096') 370 encap_id = 'vlan-{0}'.format(encap_id) 371 372 if primary_encap_id is not None: 373 if primary_encap_id not in range(1, 4097): 374 module.fail_json(msg='Valid VLAN assigments are from 1 to 4096') 375 primary_encap_id = 'vlan-{0}'.format(primary_encap_id) 376 377 static_path = INTERFACE_TYPE_MAPPING[interface_type].format(pod_id=pod_id, leafs=leafs, extpaths=extpaths, interface=interface) 378 379 path_target_filter = {} 380 if pod_id is not None and leafs is not None and interface is not None and (interface_type != 'fex' or extpaths is not None): 381 path_target_filter = {'tDn': static_path} 382 383 if interface_mode is not None: 384 interface_mode = INTERFACE_MODE_MAPPING[interface_mode] 385 386 aci = ACIModule(module) 387 aci.construct_url( 388 root_class=dict( 389 aci_class='fvTenant', 390 aci_rn='tn-{0}'.format(tenant), 391 module_object=tenant, 392 target_filter={'name': tenant}, 393 ), 394 subclass_1=dict( 395 aci_class='fvAp', 396 aci_rn='ap-{0}'.format(ap), 397 module_object=ap, 398 target_filter={'name': ap}, 399 ), 400 subclass_2=dict( 401 aci_class='fvAEPg', 402 aci_rn='epg-{0}'.format(epg), 403 module_object=epg, 404 target_filter={'name': epg}, 405 ), 406 subclass_3=dict( 407 aci_class='fvRsPathAtt', 408 aci_rn='rspathAtt-[{0}]'.format(static_path), 409 module_object=static_path, 410 target_filter=path_target_filter, 411 ), 412 ) 413 414 aci.get_existing() 415 416 if state == 'present': 417 aci.payload( 418 aci_class='fvRsPathAtt', 419 class_config=dict( 420 descr=description, 421 encap=encap_id, 422 primaryEncap=primary_encap_id, 423 instrImedcy=deploy_immediacy, 424 mode=interface_mode, 425 tDn=static_path, 426 ), 427 ) 428 429 aci.get_diff(aci_class='fvRsPathAtt') 430 431 aci.post_config() 432 433 elif state == 'absent': 434 aci.delete_config() 435 436 aci.exit_json() 437 438 439if __name__ == "__main__": 440 main() 441