1#!/usr/local/bin/python3.8 2# -*- coding: utf-8 -*- 3 4# Copyright: (c) 2019, Dag Wieers (@dagwieers) <dag@wieers.com> 5# Copyright: (c) 2021, Anvitha Jain (@anvitha-jain) <anvjain@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_bd_l3out 18short_description: Manage site-local BD l3out's in schema template 19description: 20- Manage site-local BDs l3out's in schema template on Cisco ACI Multi-Site. 21author: 22- Dag Wieers (@dagwieers) 23- Anvitha Jain (@anvitha-jain) 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 bd: 41 description: 42 - The name of the BD. 43 type: str 44 required: yes 45 aliases: [ name ] 46 l3out: 47 description: 48 - The name of the l3out. 49 type: str 50 state: 51 description: 52 - Use C(present) or C(absent) for adding or removing. 53 - Use C(query) for listing an object or multiple objects. 54 type: str 55 choices: [ absent, present, query ] 56 default: present 57notes: 58- The ACI MultiSite PATCH API has a deficiency requiring some objects to be referenced by index. 59 This can cause silent corruption on concurrent access when changing/removing on object as 60 the wrong object may be referenced. This module is affected by this deficiency. 61seealso: 62- module: cisco.mso.mso_schema_site_bd 63- module: cisco.mso.mso_schema_template_bd 64extends_documentation_fragment: cisco.mso.modules 65''' 66 67EXAMPLES = r''' 68- name: Add a new site BD l3out 69 cisco.mso.mso_schema_site_bd_l3out: 70 host: mso_host 71 username: admin 72 password: SomeSecretPassword 73 schema: Schema1 74 site: Site1 75 template: Template1 76 bd: BD1 77 l3out: L3out1 78 state: present 79 delegate_to: localhost 80 81- name: Remove a site BD l3out 82 cisco.mso.mso_schema_site_bd_l3out: 83 host: mso_host 84 username: admin 85 password: SomeSecretPassword 86 schema: Schema1 87 site: Site1 88 template: Template1 89 bd: BD1 90 l3out: L3out1 91 state: absent 92 delegate_to: localhost 93 94- name: Query a specific site BD l3out 95 cisco.mso.mso_schema_site_bd_l3out: 96 host: mso_host 97 username: admin 98 password: SomeSecretPassword 99 schema: Schema1 100 site: Site1 101 template: Template1 102 bd: BD1 103 l3out: L3out1 104 state: query 105 delegate_to: localhost 106 register: query_result 107 108- name: Query all site BD l3outs 109 cisco.mso.mso_schema_site_bd_l3out: 110 host: mso_host 111 username: admin 112 password: SomeSecretPassword 113 schema: Schema1 114 site: Site1 115 template: Template1 116 bd: BD1 117 state: query 118 delegate_to: localhost 119 register: query_result 120''' 121 122RETURN = r''' 123''' 124 125from ansible.module_utils.basic import AnsibleModule 126from ansible_collections.cisco.mso.plugins.module_utils.mso import MSOModule, mso_argument_spec 127 128 129def main(): 130 argument_spec = mso_argument_spec() 131 argument_spec.update( 132 schema=dict(type='str', required=True), 133 site=dict(type='str', required=True), 134 template=dict(type='str', required=True), 135 bd=dict(type='str', required=True), 136 l3out=dict(type='str', aliases=['name']), # This parameter is not required for querying all objects 137 state=dict(type='str', default='present', choices=['absent', 'present', 'query']), 138 ) 139 140 module = AnsibleModule( 141 argument_spec=argument_spec, 142 supports_check_mode=True, 143 required_if=[ 144 ['state', 'absent', ['l3out']], 145 ['state', 'present', ['l3out']], 146 ], 147 ) 148 149 schema = module.params.get('schema') 150 site = module.params.get('site') 151 template = module.params.get('template').replace(' ', '') 152 bd = module.params.get('bd') 153 l3out = module.params.get('l3out') 154 state = module.params.get('state') 155 156 mso = MSOModule(module) 157 158 # Get schema objects 159 schema_id, schema_path, schema_obj = mso.query_schema(schema) 160 161 # Get template 162 templates = [t.get('name') for t in schema_obj.get('templates')] 163 if template not in templates: 164 mso.fail_json(msg="Provided template '{0}' does not exist. Existing templates '{1}'".format(template, ', '.join(templates))) 165 template_idx = templates.index(template) 166 167 # Get site 168 site_id = mso.lookup_site(site) 169 170 # Get site_idx 171 if 'sites' not in schema_obj: 172 mso.fail_json(msg="No site associated with template '{0}'. Associate the site with the template using mso_schema_site.".format(template)) 173 sites = [(s.get('siteId'), s.get('templateName')) for s in schema_obj.get('sites')] 174 if (site_id, template) not in sites: 175 mso.fail_json(msg="Provided site/template '{0}-{1}' does not exist. Existing sites/templates '{2}'".format(site, template, ', '.join(sites))) 176 177 # Schema-access uses indexes 178 site_idx = sites.index((site_id, template)) 179 # Path-based access uses site_id-template 180 site_template = '{0}-{1}'.format(site_id, template) 181 182 payload = dict() 183 ops = [] 184 op_path = '' 185 186 # Get BD 187 bd_ref = mso.bd_ref(schema_id=schema_id, template=template, bd=bd) 188 bds = [v.get('bdRef') for v in schema_obj.get('sites')[site_idx]['bds']] 189 bds_in_temp = [a.get('name') for a in schema_obj['templates'][template_idx]['bds']] 190 if bd not in bds_in_temp: 191 mso.fail_json(msg="Provided BD '{0}' does not exist. Existing BDs '{1}'".format(bd, ', '.join(bds_in_temp))) 192 193 # If bd not at site level but exists at template level 194 if bd_ref not in bds: 195 op_path = '/sites/{0}/bds'.format(site_template) 196 payload.update( 197 bdRef=dict( 198 schemaId=schema_id, 199 templateName=template, 200 bdName=bd, 201 ), 202 ) 203 else: 204 # Get bd index at site level 205 bd_idx = bds.index(bd_ref) 206 207 # Get L3out 208 # If bd is at site level 209 if 'bdRef' not in payload: 210 l3outs = schema_obj.get('sites')[site_idx]['bds'][bd_idx]['l3Outs'] 211 if l3out is not None and l3out in l3outs: 212 l3out_idx = l3outs.index(l3out) 213 # FIXME: Changes based on index are DANGEROUS 214 op_path = '/sites/{0}/bds/{1}/l3Outs/{2}'.format(site_template, bd, l3out_idx) 215 mso.existing = schema_obj.get('sites')[site_idx]['bds'][bd_idx]['l3Outs'][l3out_idx] 216 else: 217 op_path = '/sites/{0}/bds/{1}/l3Outs'.format(site_template, bd) 218 219 if state == 'query': 220 if l3out is None: 221 mso.existing = schema_obj.get('sites')[site_idx]['bds'][bd_idx]['l3Outs'] 222 elif not mso.existing: 223 mso.fail_json(msg="L3out '{l3out}' not found".format(l3out=l3out)) 224 mso.exit_json() 225 226 ops = [] 227 228 mso.previous = mso.existing 229 if state == 'absent': 230 if mso.existing: 231 mso.sent = mso.existing = {} 232 ops.append(dict(op='remove', path=op_path)) 233 234 elif state == 'present': 235 if not payload: 236 payload = l3out 237 else: 238 # If bd in payload, add l3out to payload 239 payload['l3Outs'] = [l3out] 240 241 mso.sanitize(payload, collate=True) 242 243 if not mso.existing: 244 ops.append(dict(op='add', path=op_path + '/-', value=payload)) 245 246 mso.existing = l3out 247 248 if not module.check_mode: 249 mso.request(schema_path, method='PATCH', data=ops) 250 251 mso.exit_json() 252 253 254if __name__ == "__main__": 255 main() 256