1#!/usr/local/bin/python3.8 2# -*- coding: utf-8 -*- 3 4# Copyright: (c) 2019, Dag Wieers (@dagwieers) <dag@wieers.com> 5# Copyright: (c) 2020, Lionel Hercot (@lhercot) <lhercot@cisco.com> 6# GNU General Public License v3.0+ (see LICENSE or https://www.gnu.org/licenses/gpl-3.0.txt) 7 8from __future__ import absolute_import, division, print_function 9__metaclass__ = type 10 11ANSIBLE_METADATA = {'metadata_version': '1.1', 12 'status': ['preview'], 13 'supported_by': 'community'} 14 15DOCUMENTATION = r''' 16--- 17module: mso_schema_site_vrf_region_cidr 18short_description: Manage site-local VRF region CIDRs in schema template 19description: 20- Manage site-local VRF region CIDRs in schema template on Cisco ACI Multi-Site. 21author: 22- Dag Wieers (@dagwieers) 23- Lionel Hercot (@lhercot) 24options: 25 schema: 26 description: 27 - The name of the schema. 28 type: str 29 required: yes 30 site: 31 description: 32 - The name of the site. 33 type: str 34 required: yes 35 template: 36 description: 37 - The name of the template. 38 type: str 39 required: yes 40 vrf: 41 description: 42 - The name of the VRF. 43 type: str 44 required: yes 45 region: 46 description: 47 - The name of the region. 48 type: str 49 required: yes 50 cidr: 51 description: 52 - The name of the region CIDR to manage. 53 type: str 54 aliases: [ ip ] 55 primary: 56 description: 57 - Whether this is the primary CIDR. 58 type: bool 59 default: true 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: cisco.mso.mso_schema_site_vrf_region 73- module: cisco.mso.mso_schema_site_vrf_region_cidr_subnet 74- module: cisco.mso.mso_schema_template_vrf 75extends_documentation_fragment: cisco.mso.modules 76''' 77 78EXAMPLES = r''' 79- name: Add a new site VRF region CIDR 80 cisco.mso.mso_schema_site_vrf_region_cidr: 81 host: mso_host 82 username: admin 83 password: SomeSecretPassword 84 schema: Schema1 85 site: Site1 86 template: Template1 87 vrf: VRF1 88 region: us-west-1 89 cidr: 14.14.14.1/24 90 state: present 91 delegate_to: localhost 92 93- name: Remove a site VRF region CIDR 94 cisco.mso.mso_schema_site_vrf_region_cidr: 95 host: mso_host 96 username: admin 97 password: SomeSecretPassword 98 schema: Schema1 99 site: Site1 100 template: Template1 101 vrf: VRF1 102 region: us-west-1 103 cidr: 14.14.14.1/24 104 state: absent 105 delegate_to: localhost 106 107- name: Query a specific site VRF region CIDR 108 cisco.mso.mso_schema_site_vrf_region_cidr: 109 host: mso_host 110 username: admin 111 password: SomeSecretPassword 112 schema: Schema1 113 site: Site1 114 template: Template1 115 vrf: VRF1 116 region: us-west-1 117 cidr: 14.14.14.1/24 118 state: query 119 delegate_to: localhost 120 register: query_result 121 122- name: Query all site VRF region CIDR 123 cisco.mso.mso_schema_site_vrf_region_cidr: 124 host: mso_host 125 username: admin 126 password: SomeSecretPassword 127 schema: Schema1 128 site: Site1 129 template: Template1 130 vrf: VRF1 131 region: us-west-1 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 vrf=dict(type='str', required=True), 151 region=dict(type='str', required=True), 152 cidr=dict(type='str', aliases=['ip']), # This parameter is not required for querying all objects 153 primary=dict(type='bool', default=True), 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', ['cidr']], 162 ['state', 'present', ['cidr']], 163 ], 164 ) 165 166 schema = module.params.get('schema') 167 site = module.params.get('site') 168 template = module.params.get('template').replace(' ', '') 169 vrf = module.params.get('vrf') 170 region = module.params.get('region') 171 cidr = module.params.get('cidr') 172 primary = module.params.get('primary') 173 state = module.params.get('state') 174 175 mso = MSOModule(module) 176 177 # Get schema objects 178 schema_id, schema_path, schema_obj = mso.query_schema(schema) 179 180 # Get template 181 templates = [t.get('name') for t in schema_obj.get('templates')] 182 if template not in templates: 183 mso.fail_json(msg="Provided template '{0}' does not exist. Existing templates: {1}".format(template, ', '.join(templates))) 184 template_idx = templates.index(template) 185 186 payload = dict() 187 op_path = '' 188 new_cidr = dict( 189 ip=cidr, 190 primary=primary, 191 ) 192 193 # Get site 194 site_id = mso.lookup_site(site) 195 196 # Get site_idx 197 all_sites = schema_obj.get('sites') 198 sites = [] 199 if all_sites is not None: 200 sites = [(s.get('siteId'), s.get('templateName')) for s in all_sites] 201 202 # Get VRF 203 vrf_ref = mso.vrf_ref(schema_id=schema_id, template=template, vrf=vrf) 204 template_vrfs = [a.get('name') for a in schema_obj['templates'][template_idx]['vrfs']] 205 if vrf not in template_vrfs: 206 mso.fail_json(msg="Provided vrf '{0}' does not exist. Existing vrfs: {1}".format(vrf, ', '.join(template_vrfs))) 207 208 # if site-template does not exist, create it 209 if (site_id, template) not in sites: 210 op_path = '/sites/-' 211 payload.update( 212 siteId=site_id, 213 templateName=template, 214 vrfs=[dict( 215 vrfRef=dict( 216 schemaId=schema_id, 217 templateName=template, 218 vrfName=vrf, 219 ), 220 regions=[dict( 221 name=region, 222 cidrs=[new_cidr] 223 )] 224 )] 225 ) 226 227 else: 228 # Schema-access uses indexes 229 site_idx = sites.index((site_id, template)) 230 # Path-based access uses site_id-template 231 site_template = '{0}-{1}'.format(site_id, template) 232 233 # If vrf not at site level but exists at template level 234 vrfs = [v.get('vrfRef') for v in schema_obj.get('sites')[site_idx]['vrfs']] 235 if vrf_ref not in vrfs: 236 op_path = '/sites/{0}/vrfs/-'.format(site_template) 237 payload.update( 238 vrfRef=dict( 239 schemaId=schema_id, 240 templateName=template, 241 vrfName=vrf, 242 ), 243 regions=[dict( 244 name=region, 245 cidrs=[new_cidr] 246 )] 247 ) 248 else: 249 # Update vrf index at site level 250 vrf_idx = vrfs.index(vrf_ref) 251 252 # Get Region 253 regions = [r.get('name') for r in schema_obj.get('sites')[site_idx]['vrfs'][vrf_idx]['regions']] 254 if region not in regions: 255 op_path = '/sites/{0}/vrfs/{1}/regions/-'.format(site_template, vrf) 256 payload.update( 257 name=region, 258 cidrs=[new_cidr] 259 ) 260 else: 261 region_idx = regions.index(region) 262 263 # Get CIDR 264 cidrs = [c.get('ip') for c in schema_obj.get('sites')[site_idx]['vrfs'][vrf_idx]['regions'][region_idx]['cidrs']] 265 if cidr is not None: 266 if cidr in cidrs: 267 cidr_idx = cidrs.index(cidr) 268 # FIXME: Changes based on index are DANGEROUS 269 cidr_path = '/sites/{0}/vrfs/{1}/regions/{2}/cidrs/{3}'.format(site_template, vrf, region, cidr_idx) 270 mso.existing = schema_obj.get('sites')[site_idx]['vrfs'][vrf_idx]['regions'][region_idx]['cidrs'][cidr_idx] 271 op_path = '/sites/{0}/vrfs/{1}/regions/{2}/cidrs/-'.format(site_template, vrf, region) 272 payload = new_cidr 273 274 if state == 'query': 275 if (site_id, template) not in sites: 276 mso.fail_json(msg="Provided site-template association '{0}-{1}' does not exist.".format(site, template)) 277 elif vrf_ref not in vrfs: 278 mso.fail_json(msg="Provided vrf '{0}' does not exist at site level.".format(vrf)) 279 elif not regions or region not in regions: 280 mso.fail_json(msg="Provided region '{0}' does not exist. Existing regions: {1}".format(region, ', '.join(regions))) 281 elif cidr is None and not payload: 282 mso.existing = schema_obj.get('sites')[site_idx]['vrfs'][vrf_idx]['regions'][region_idx]['cidrs'] 283 elif not mso.existing: 284 mso.fail_json(msg="CIDR IP '{cidr}' not found".format(cidr=cidr)) 285 mso.exit_json() 286 287 ops = [] 288 289 mso.previous = mso.existing 290 if state == 'absent': 291 if mso.existing: 292 mso.sent = mso.existing = {} 293 ops.append(dict(op='remove', path=cidr_path)) 294 295 elif state == 'present': 296 mso.sanitize(payload, collate=True) 297 298 if mso.existing: 299 ops.append(dict(op='replace', path=cidr_path, value=mso.sent)) 300 else: 301 ops.append(dict(op='add', path=op_path, value=mso.sent)) 302 303 mso.existing = new_cidr 304 305 if not module.check_mode and mso.previous != mso.existing: 306 mso.request(schema_path, method='PATCH', data=ops) 307 308 mso.exit_json() 309 310 311if __name__ == "__main__": 312 main() 313