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