ACI Config Rollback: Add new module to support config rollbacks in ACI (#28811)
* ACI Config Rollback: Add new module to support config rollbacks in ACI * Fix EXAMPLES * Fix whitespace
This commit is contained in:
parent
42db02c249
commit
8ffb842963
2 changed files with 268 additions and 0 deletions
|
@ -78,6 +78,7 @@ URL_MAPPING = dict(
|
|||
fc_policy=dict(aci_class='fcIfPol', mo='infra/fcIfPol-', key='name'),
|
||||
filter=dict(aci_class='vzFilter', mo='flt-', key='name'),
|
||||
gateway_addr=dict(aci_class='fvSubnet', mo='subnet-', key='ip'),
|
||||
import_policy=dict(aci_class='configImportP', mo='fabric/configimp-', key='name'),
|
||||
l2_policy=dict(aci_class='l2IfPol', mo='infra/l2IfP-', key='name'),
|
||||
lldp_policy=dict(aci_class='lldpIfPol', mo='infra/lldpIfP-', key='name'),
|
||||
mcp=dict(aci_class='mcpIfPol', mo='infra/mcpIfP-', key='name'),
|
||||
|
|
267
lib/ansible/modules/network/aci/aci_config_rollback.py
Normal file
267
lib/ansible/modules/network/aci/aci_config_rollback.py
Normal file
|
@ -0,0 +1,267 @@
|
|||
#!/usr/bin/python
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt)
|
||||
|
||||
from __future__ import absolute_import, division, print_function
|
||||
__metaclass__ = type
|
||||
|
||||
ANSIBLE_METADATA = {'metadata_version': '1.1',
|
||||
'status': ['preview'],
|
||||
'supported_by': 'community'}
|
||||
|
||||
DOCUMENTATION = r'''
|
||||
---
|
||||
module: aci_config_rollback
|
||||
short_description: Provides rollback and rollback preview functionality for Cisco ACI fabrics (config:ImportP)
|
||||
description:
|
||||
- Provides rollback and rollback preview functionality for Cisco ACI fabric.
|
||||
- Config Rollbacks are done using snapshots C(aci_snapshot) with the configImportP class.
|
||||
- More information from the internal APIC class
|
||||
I(config:ImportP) at U(https://developer.cisco.com/media/mim-ref/MO-configImportP.html).
|
||||
author:
|
||||
- Swetha Chunduri (@schunduri)
|
||||
- Dag Wieers (@dagwieers)
|
||||
- Jacob McGill (@jmcgill298)
|
||||
version_added: '2.4'
|
||||
requirements:
|
||||
- ACI Fabric 1.0(3f)+
|
||||
options:
|
||||
compare_export_policy:
|
||||
description:
|
||||
- The export policy that the C(compare_snapshot) is associated to.
|
||||
compare_snapshot:
|
||||
description:
|
||||
- The name of the snapshot to compare with C(snapshot).
|
||||
description:
|
||||
description:
|
||||
- The description for the Import Policy.
|
||||
aliases: [ descr ]
|
||||
export_policy:
|
||||
description:
|
||||
- The export policy that the C(snapshot) is associated to.
|
||||
required: yes
|
||||
fail_on_decrypt:
|
||||
description:
|
||||
- Determines if the APIC should fail the rollback if unable to decrypt secured data.
|
||||
- The APIC defaults new Import Policies to C(yes).
|
||||
type: bool
|
||||
default: 'yes'
|
||||
import_mode:
|
||||
description:
|
||||
- Determines how the import should be handled by the APIC.
|
||||
- The APIC defaults new Import Policies to C(atomic).
|
||||
choices: [ atomic, best-effort ]
|
||||
default: atomic
|
||||
import_policy:
|
||||
description:
|
||||
- The name of the Import Policy to use for config rollback.
|
||||
import_type:
|
||||
description:
|
||||
- Determines how the current and snapshot configuration should be compared for replacement.
|
||||
- The APIC defaults new Import Policies to C(replace).
|
||||
choices: [ merge, replace ]
|
||||
default: replace
|
||||
snapshot:
|
||||
description:
|
||||
- The name of the snapshot to rollback to, or the base snapshot to use for comparison.
|
||||
- The C(aci_snapshot) module can be used to query the list of available snapshots.
|
||||
required: yes
|
||||
state:
|
||||
description:
|
||||
- Use C(preview) for previewing the diff between two snapshots.
|
||||
- Use C(rollback) for reverting the configuration to a previous snapshot.
|
||||
choices: [ preview, rollback ]
|
||||
default: rollback
|
||||
extends_documentation_fragment: aci
|
||||
'''
|
||||
|
||||
EXAMPLES = r'''
|
||||
---
|
||||
- name: Create a Snapshot
|
||||
aci_config_snapshot:
|
||||
hostname: apic
|
||||
username: admin
|
||||
password: SomeSecretPassword
|
||||
state: present
|
||||
export_policy: config_backup
|
||||
|
||||
- name: Query Existing Snapshots
|
||||
aci_config_snapshot:
|
||||
hostname: apic
|
||||
username: admin
|
||||
password: SomeSecretPassword
|
||||
state: query
|
||||
export_policy: config_backup
|
||||
|
||||
- name: Compare Snapshot Files
|
||||
aci_config_rollback:
|
||||
hostname: apic
|
||||
username: admin
|
||||
password: SomeSecretPassword
|
||||
state: preview
|
||||
export_policy: config_backup
|
||||
snapshot: 'run-2017-08-28T06-24-01'
|
||||
compare_export_policy: config_backup
|
||||
compare_snapshot: 'run-2017-08-27T23-43-56'
|
||||
|
||||
- name: Rollback Configuration
|
||||
aci_config_rollback:
|
||||
hostname: apic
|
||||
username: admin
|
||||
password: SomeSecretPassword
|
||||
state: rollback
|
||||
import_policy: rollback_config
|
||||
export_policy: config_backup
|
||||
snapshot: 'run-2017-08-28T06-24-01'
|
||||
|
||||
- name: Rollback Configuration
|
||||
aci_config_rollback:
|
||||
hostname: apic
|
||||
username: admin
|
||||
password: SomeSecretPassword
|
||||
state: rollback
|
||||
import_policy: rollback_config
|
||||
export_policy: config_backup
|
||||
snapshot: 'run-2017-08-28T06-24-01'
|
||||
description: 'Rollback 8-27 changes'
|
||||
import_mode: atomic
|
||||
import_type: replace
|
||||
fail_on_decrypt: yes
|
||||
'''
|
||||
|
||||
RETURN = r'''
|
||||
#
|
||||
'''
|
||||
|
||||
from ansible.module_utils.aci import ACIModule, aci_argument_spec
|
||||
from ansible.module_utils.basic import AnsibleModule
|
||||
from ansible.module_utils._text import to_bytes
|
||||
from ansible.module_utils.urls import fetch_url
|
||||
|
||||
# Optional, only used for rollback preview
|
||||
try:
|
||||
import lxml.etree
|
||||
from xmljson import cobra
|
||||
XML_TO_JSON = True
|
||||
except ImportError:
|
||||
XML_TO_JSON = False
|
||||
|
||||
|
||||
def main():
|
||||
argument_spec = aci_argument_spec
|
||||
argument_spec.update(
|
||||
compare_export_policy=dict(type='str'),
|
||||
compare_snapshot=dict(type='str'),
|
||||
description=dict(type='str', aliases=['descr']),
|
||||
export_policy=dict(type='str'),
|
||||
fail_on_decrypt=dict(type='bool'),
|
||||
import_mode=dict(type='str', choices=['atomic', 'best-effort']),
|
||||
import_policy=dict(type='str'),
|
||||
import_type=dict(type='str', choices=['merge', 'replace']),
|
||||
snapshot=dict(type='str', required=True),
|
||||
state=dict(type='str', default='rollback', choices=['preview', 'rollback']),
|
||||
)
|
||||
|
||||
module = AnsibleModule(
|
||||
argument_spec=argument_spec,
|
||||
supports_check_mode=False,
|
||||
required_if=[
|
||||
['state', 'preview', ['compare_export_policy', 'compare_snapshot']],
|
||||
['state', 'rollback', ['import_policy']],
|
||||
],
|
||||
)
|
||||
|
||||
description = module.params['description']
|
||||
export_policy = module.params['export_policy']
|
||||
fail_on_decrypt = module.params['fail_on_decrypt']
|
||||
if fail_on_decrypt is True:
|
||||
fail_on_decrypt = 'yes'
|
||||
elif fail_on_decrypt is False:
|
||||
fail_on_decrypt = 'no'
|
||||
import_mode = module.params['import_mode']
|
||||
import_policy = module.params['import_policy']
|
||||
import_type = module.params['import_type']
|
||||
snapshot = module.params['snapshot']
|
||||
state = module.params['state']
|
||||
|
||||
aci = ACIModule(module)
|
||||
|
||||
if state == 'rollback':
|
||||
if snapshot.startswith('run-'):
|
||||
snapshot = snapshot.replace('run-', '', 1)
|
||||
|
||||
if not snapshot.endswith('.tar.gz'):
|
||||
snapshot += '.tar.gz'
|
||||
|
||||
filename = 'ce2_{0}-{1}'.format(export_policy, snapshot)
|
||||
|
||||
aci.construct_url(root_class="import_policy")
|
||||
aci.get_existing()
|
||||
|
||||
# Filter out module parameters with null values
|
||||
aci.payload(
|
||||
aci_class='configImportP',
|
||||
class_config=dict(
|
||||
adminSt='triggered',
|
||||
descr=description,
|
||||
failOnDecryptErrors=fail_on_decrypt,
|
||||
fileName=filename,
|
||||
importMode=import_mode,
|
||||
importType=import_type,
|
||||
name=import_policy,
|
||||
snapshot='yes',
|
||||
),
|
||||
)
|
||||
|
||||
# Generate config diff which will be used as POST request body
|
||||
aci.get_diff(aci_class='configImportP')
|
||||
|
||||
# Submit changes if module not in check_mode and the proposed is different than existing
|
||||
aci.post_config()
|
||||
|
||||
elif state == 'preview':
|
||||
aci.result['url'] = '%(protocol)s://%(hostname)s/mqapi2/snapshots.diff.xml' % module.params
|
||||
aci.result['filter_string'] = (
|
||||
'?s1dn=uni/backupst/snapshots-[uni/fabric/configexp-%(export_policy)s]/snapshot-%(snapshot)s&'
|
||||
's2dn=uni/backupst/snapshots-[uni/fabric/configexp-%(compare_export_policy)s]/snapshot-%(compare_snapshot)s'
|
||||
) % module.params
|
||||
|
||||
# Generate rollback comparison
|
||||
get_preview(aci)
|
||||
|
||||
module.exit_json(**aci.result)
|
||||
|
||||
|
||||
def get_preview(aci):
|
||||
'''
|
||||
This function is used to generate a preview between two snapshots and add the parsed results to the aci module return data.
|
||||
'''
|
||||
uri = aci.result['url'] + aci.result['filter_string']
|
||||
resp, info = fetch_url(aci.module, uri, headers=aci.headers, method='GET', timeout=aci.module.params['timeout'], use_proxy=aci.module.params['use_proxy'])
|
||||
aci.result['response'] = info['msg']
|
||||
aci.result['status'] = info['status']
|
||||
aci.result['method'] = 'GET'
|
||||
|
||||
# Handle APIC response
|
||||
if info['status'] == 200:
|
||||
xml_to_json(aci, resp.read())
|
||||
else:
|
||||
aci.result['apic_response'] = resp.read()
|
||||
aci.module.fail_json(msg='Request failed: %(error_code)s %(error_text)s' % aci.result, **aci.result)
|
||||
|
||||
|
||||
def xml_to_json(aci, response_data):
|
||||
'''
|
||||
This function is used to convert preview XML data into JSON.
|
||||
'''
|
||||
if XML_TO_JSON:
|
||||
xml = lxml.etree.fromstring(to_bytes(response_data))
|
||||
xmldata = cobra.data(xml)
|
||||
aci.result['diff'] = xmldata
|
||||
else:
|
||||
aci.result['diff'] = response_data
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
Loading…
Reference in a new issue