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