1#!/usr/bin/python 2# -*- coding: utf-8 -*- 3 4# Copyright: (c) 2019, Dag Wieers (@dagwieers) <dag@wieers.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': 'community'} 13 14DOCUMENTATION = r''' 15--- 16module: mso_schema_site_anp_epg_staticleaf 17short_description: Manage site-local EPG static leafs in schema template 18description: 19- Manage site-local EPG static leafs in schema template on Cisco ACI Multi-Site. 20author: 21- Dag Wieers (@dagwieers) 22version_added: '2.8' 23options: 24 schema: 25 description: 26 - The name of the schema. 27 type: str 28 required: yes 29 site: 30 description: 31 - The name of the site. 32 type: str 33 required: yes 34 template: 35 description: 36 - The name of the template. 37 type: str 38 required: yes 39 anp: 40 description: 41 - The name of the ANP. 42 type: str 43 epg: 44 description: 45 - The name of the EPG. 46 type: str 47 pod: 48 description: 49 - The pod of the static leaf. 50 type: str 51 leaf: 52 description: 53 - The path of the static leaf. 54 type: str 55 aliases: [ name ] 56 vlan: 57 description: 58 - The VLAN id of the static leaf. 59 type: int 60 state: 61 description: 62 - Use C(present) or C(absent) for adding or removing. 63 - Use C(query) for listing an object or multiple objects. 64 type: str 65 choices: [ absent, present, query ] 66 default: present 67notes: 68- The ACI MultiSite PATCH API has a deficiency requiring some objects to be referenced by index. 69 This can cause silent corruption on concurrent access when changing/removing on object as 70 the wrong object may be referenced. This module is affected by this deficiency. 71seealso: 72- module: mso_schema_site_anp_epg 73- module: mso_schema_template_anp_epg 74extends_documentation_fragment: mso 75''' 76 77EXAMPLES = r''' 78- name: Add a new static leaf to a site EPG 79 mso_schema_site_anp_epg_staticleaf: 80 host: mso_host 81 username: admin 82 password: SomeSecretPassword 83 schema: Schema1 84 site: Site1 85 template: Template1 86 anp: ANP1 87 epg: EPG1 88 leaf: Leaf1 89 vlan: 123 90 state: present 91 delegate_to: localhost 92 93- name: Remove a static leaf from a site EPG 94 mso_schema_site_anp_epg_staticleaf: 95 host: mso_host 96 username: admin 97 password: SomeSecretPassword 98 schema: Schema1 99 site: Site1 100 template: Template1 101 anp: ANP1 102 epg: EPG1 103 leaf: Leaf1 104 state: absent 105 delegate_to: localhost 106 107- name: Query a specific site EPG static leaf 108 mso_schema_site_anp_epg_staticleaf: 109 host: mso_host 110 username: admin 111 password: SomeSecretPassword 112 schema: Schema1 113 site: Site1 114 template: Template1 115 anp: ANP1 116 epg: EPG1 117 leaf: Leaf1 118 state: query 119 delegate_to: localhost 120 register: query_result 121 122- name: Query all site EPG static leafs 123 mso_schema_site_anp_epg_staticleaf: 124 host: mso_host 125 username: admin 126 password: SomeSecretPassword 127 schema: Schema1 128 site: Site1 129 template: Template1 130 anp: ANP1 131 state: query 132 delegate_to: localhost 133 register: query_result 134''' 135 136RETURN = r''' 137''' 138 139from ansible.module_utils.basic import AnsibleModule 140from ansible.module_utils.network.aci.mso import MSOModule, mso_argument_spec 141 142 143def main(): 144 argument_spec = mso_argument_spec() 145 argument_spec.update( 146 schema=dict(type='str', required=True), 147 site=dict(type='str', required=True), 148 template=dict(type='str', required=True), 149 anp=dict(type='str', required=True), 150 epg=dict(type='str', required=True), 151 pod=dict(type='str'), # This parameter is not required for querying all objects 152 leaf=dict(type='str', aliases=['name']), 153 vlan=dict(type='int'), 154 state=dict(type='str', default='present', choices=['absent', 'present', 'query']), 155 ) 156 157 module = AnsibleModule( 158 argument_spec=argument_spec, 159 supports_check_mode=True, 160 required_if=[ 161 ['state', 'absent', ['pod', 'leaf', 'vlan']], 162 ['state', 'present', ['pod', 'leaf', 'vlan']], 163 ], 164 ) 165 166 schema = module.params['schema'] 167 site = module.params['site'] 168 template = module.params['template'] 169 anp = module.params['anp'] 170 epg = module.params['epg'] 171 pod = module.params['pod'] 172 leaf = module.params['leaf'] 173 vlan = module.params['vlan'] 174 state = module.params['state'] 175 176 leafpath = 'topology/{0}/node-{1}'.format(pod, leaf) 177 178 mso = MSOModule(module) 179 180 # Get schema_id 181 schema_obj = mso.get_obj('schemas', displayName=schema) 182 if not schema_obj: 183 mso.fail_json(msg="Provided schema '{0}' does not exist".format(schema)) 184 185 schema_path = 'schemas/{id}'.format(**schema_obj) 186 schema_id = schema_obj['id'] 187 188 # Get site 189 site_id = mso.lookup_site(site) 190 191 # Get site_idx 192 sites = [(s['siteId'], s['templateName']) for s in schema_obj['sites']] 193 if (site_id, template) not in sites: 194 mso.fail_json(msg="Provided site/template '{0}-{1}' does not exist. Existing sites/templates: {2}".format(site, template, ', '.join(sites))) 195 196 # Schema-access uses indexes 197 site_idx = sites.index((site_id, template)) 198 # Path-based access uses site_id-template 199 site_template = '{0}-{1}'.format(site_id, template) 200 201 # Get ANP 202 anp_ref = mso.anp_ref(schema_id=schema_id, template=template, anp=anp) 203 anps = [a['anpRef'] for a in schema_obj['sites'][site_idx]['anps']] 204 if anp_ref not in anps: 205 mso.fail_json(msg="Provided anp '{0}' does not exist. Existing anps: {1}".format(anp, ', '.join(anps))) 206 anp_idx = anps.index(anp_ref) 207 208 # Get EPG 209 epg_ref = mso.epg_ref(schema_id=schema_id, template=template, anp=anp, epg=epg) 210 epgs = [e['epgRef'] for e in schema_obj['sites'][site_idx]['anps'][anp_idx]['epgs']] 211 if epg_ref not in epgs: 212 mso.fail_json(msg="Provided epg '{0}' does not exist. Existing epgs: {1}".format(epg, ', '.join(epgs))) 213 epg_idx = epgs.index(epg_ref) 214 215 # Get Leaf 216 leafs = [(l['path'], l['portEncapVlan']) for l in schema_obj['sites'][site_idx]['anps'][anp_idx]['epgs'][epg_idx]['staticLeafs']] 217 if (leafpath, vlan) in leafs: 218 leaf_idx = leafs.index((leafpath, vlan)) 219 # FIXME: Changes based on index are DANGEROUS 220 leaf_path = '/sites/{0}/anps/{1}/epgs/{2}/staticLeafs/{3}'.format(site_template, anp, epg, leaf_idx) 221 mso.existing = schema_obj['sites'][site_idx]['anps'][anp_idx]['epgs'][epg_idx]['staticLeafs'][leaf_idx] 222 223 if state == 'query': 224 if leaf is None or vlan is None: 225 mso.existing = schema_obj['sites'][site_idx]['anps'][anp_idx]['epgs'][epg_idx]['staticLeafs'] 226 elif not mso.existing: 227 mso.fail_json(msg="Static leaf '{leaf}/{vlan}' not found".format(leaf=leaf, vlan=vlan)) 228 mso.exit_json() 229 230 leafs_path = '/sites/{0}/anps/{1}/epgs/{2}/staticLeafs'.format(site_template, anp, epg) 231 ops = [] 232 233 mso.previous = mso.existing 234 if state == 'absent': 235 if mso.existing: 236 mso.sent = mso.existing = {} 237 ops.append(dict(op='remove', path=leaf_path)) 238 239 elif state == 'present': 240 payload = dict( 241 path=leafpath, 242 portEncapVlan=vlan, 243 ) 244 245 mso.sanitize(payload, collate=True) 246 247 if mso.existing: 248 ops.append(dict(op='replace', path=leaf_path, value=mso.sent)) 249 else: 250 ops.append(dict(op='add', path=leafs_path + '/-', value=mso.sent)) 251 252 mso.existing = mso.proposed 253 254 if not module.check_mode: 255 mso.request(schema_path, method='PATCH', data=ops) 256 257 mso.exit_json() 258 259 260if __name__ == "__main__": 261 main() 262