Netact cm command2 (#34873)

This commit is contained in:
Harri Tuominen 2018-01-30 21:00:29 +02:00 committed by John R Barker
parent f659cc5963
commit 234a877ea5
4 changed files with 550 additions and 0 deletions

View file

@ -0,0 +1,367 @@
#!/usr/bin/python
# Copyright: Nokia
# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt)
# pylint: disable=invalid-name
# pylint: disable=wrong-import-position
# pylint: disable=too-many-locals
# pylint: disable=too-many-branches
# pylint: disable=too-many-statements
"""
NetAct CM ansible command module
"""
from __future__ import absolute_import, division, print_function
__metaclass__ = type
ANSIBLE_METADATA = {
'metadata_version': '1.1',
'status': ['preview'],
'supported_by': 'community'
}
DOCUMENTATION = '''
---
module: netact_cm_command
short_description: Manage network configuration data in Nokia Core and Radio networks
version_added: "2.5"
description:
netact_cm_command can be used to run various configuration management operations.
This module requires that the target hosts have Nokia NetAct network management system installed.
Module will access the Configurator command line interface in NetAct to upload network configuration to NetAct,
run configuration export, plan import and configuration provision operations
To set the scope of the operation, define Distinguished Name (DN) or Working Set (WS) or
Maintenance Region (MR) as input
options:
operation:
description:
Supported operations allow user to upload actual configuration from the network, to import and
provision prepared plans, or export reference or actual configuration for planning purposes.
Provision_Mass_Modification enables provisioning the same parameters to multiple network elements.
This operation supports modifications only to one object class at a time. With this option
NetAct Configurator creates and provisions a plan to the network with the given scope and options.
required: true
choices:
- upload
- provision
- import
- export
- Provision_Mass_Modification
aliases:
- op
opsName:
description:
- user specified operation name
required: false
DN:
description:
Sets the exact scope of the operation in form of a list of managed object
Distinguished Names (DN) in the network.
A single DN or a list of DNs can be given (comma separated list without spaces).
Alternatively, if DN or a list of DNs is not given, working set (WS) or Maintenance Region (MR)
must be provided as parameter to set the scope of operation.
required: false
WS:
description:
Sets the scope of the operation to use one or more pre-defined working sets (WS) in NetAct.
A working set contains network elements selected by user according to defined criteria.
A single WS name, or multiple WSs can be provided (comma-separated list without spaces).
Alternatively, if a WS name or a list of WSs is not given, Distinguished Name (DN) or
Maintenance Region(MR) must be provided as parameter to set the scope of operation.
required: false
MR:
description:
Sets the scope of the operation to network elements assigned to a Maintenance Region (MR)
Value can be set as MR IDs including the Maintenance Region Collection (MRC)
information (for example MRC-FIN1/MR-Hel).
Multiple MRs can be given (comma-separated list without spaces)
The value of this parameter is searched through MR IDs under given MRC. If there is no match,
then it is searched from all MR names.
Alternatively, if MR ID or a list or MR IDs is not given, Distinguished Name (DN) or Working Set (WS)
must be provided as parameter to set the scope of operation.
required: false
planName:
description:
- Specifies a plan name.
required: false
typeOption:
description:
Specifies the type of the export operation.
required: false
choices:
- plan
- actual
- reference
- template
- siteTemplate
aliases:
- type
fileFormat:
description:
Indicates file format.
required: false
choices:
- RAML2
- CSV
- XLSX
fileName:
description:
- Specifies a file name. Valid for Import and Export operations.
required: false
inputFile:
description:
Specifies full path to plan file location for the import operation.
This parameter (inputFile) or the fileName parameter must be filled. If both are present then
the inputFile is used.
required: false
createBackupPlan:
description:
- Specifies if backup plan generation is enabled.
required: false
type: bool
backupPlanName:
description:
- Specifies a backup plan name
required: false
verbose:
description:
NetAct Configurator will print more info
required: false
extra_opts:
description:
Extra options to be set for operations. Check Configuration Management > Configuration Management
Operating Procedures > Command Line Operations in Nokia NetAct user documentation for further
information for extra options.
required: false
notes:
- Check mode is not currently supported
author:
- Harri Tuominen (@hatuomin)
'''
EXAMPLES = '''
# Pass in a message
- name: Upload
netact_cm_command:
operation: "Upload"
opsname: 'Uploading_test'
dn: "PLMN-PLMN/MRBTS-746"
extra_opts: '-btsContentInUse true'
- name: Provision
netact_cm_command:
operation: "Provision"
opsname: 'Provision_test'
dn: "PLMN-PLMN/MRBTS-746"
planName: 'mySiteTemplate'
type: 'actual'
createBackupPlan: true
backupPlanName: 'myBackupPlanName'
- name: Export and fetching data from target
netact_cm_command:
operation: "Export"
opsname: 'Export_test'
planName: 'mySiteTemplate'
type: 'actual'
fileName: 'exportTest.xml'
- fetch:
src: /var/opt/nokia/oss/global/racops/export/exportTest.xml
dest: fetched
- name: Import
netact_cm_command:
operation: "Import"
opsname: 'Import_test'
fileFormat: 'CSV'
type: 'plan'
fileName: 'myCSVFile'
planName: 'myPlanName'
extra_ops: 'enablePolicyPlans true'
# fail the module
- name: Test failure of the module
netact_cm_command:
name: fail me
'''
RETURN = '''
original_message:
description: The original name param that was passed in
returned: Command line
type: string
sample: '/opt/oss/bin/racclimx.sh -op Upload -opsName Uploading_testi -DN PLMN-PLMN/MRBTS-746'
message:
description: The output message that the netact_cm_command module generates
returned: Command output message
type: string
changed:
description: data changed
returned: true if data is changed
type: boolean
'''
from ansible.module_utils.basic import AnsibleModule
racclimx = '/opt/oss/bin/racclimx.sh'
def main():
"""
Main module where option are handled and command is executed
:return:
"""
# define the available arguments/parameters that a user can pass to
# the module
module_args = dict(
operation=dict(type='str', required=True,
aliases=['op'],
choices=['Upload', 'Provision', 'Import',
'Export', 'Provision_Mass_Modification']),
opsName=dict(type='str', required=False),
DN=dict(type='str', required=False),
WS=dict(type='str', required=False),
MR=dict(type='str', required=False),
planName=dict(type='str', required=False),
typeOption=dict(type='str', required=False, aliases=['type'],
choices=['plan', 'actual', 'reference', 'template', 'siteTemplate']),
fileFormat=dict(type='str', required=False, choices=['CSV', 'RAML2', 'XLSX']),
fileName=dict(type='str', required=False),
createBackupPlan=dict(type='bool', required=False),
backupPlanName=dict(type='str', required=False),
inputFile=dict(type='str', required=False),
verbose=dict(type='str', required=False),
extra_opts=dict(type='str', required=False)
)
# seed the result dict in the object
# we primarily care about changed and state
# change is if this module effectively modified the target
# state will include any data that you want your module to pass back
# for consumption, for example, in a subsequent task
result = dict(
changed=False,
original_message='',
cmd='',
message=''
)
# the AnsibleModule object will be our abstraction working with Ansible
# this includes instantiation, a couple of common attr would be the
# args/params passed to the execution, as well as if the module
# supports check mode
module = AnsibleModule(
argument_spec=module_args,
supports_check_mode=True
)
# if the user is working with this module in only check mode we do not
# want to make any changes to the environment, just return the current
# state with no modifications
if module.check_mode:
result['skipped'] = True
result['msg'] = 'check mode not (yet) supported for this module'
module.exit_json(**result)
# manipulate or modify the state as needed (this is going to be the
# part where your module will do what it needs to do)
operation = module.params.get('operation')
if not operation:
module.fail_json(msg='Operation not defined', **result)
opsname = module.params.get('opsName')
dn = module.params.get('DN')
ws = module.params.get('WS')
mr = module.params.get('MR')
planname = module.params.get('planName')
typeoption = module.params.get('typeOption')
fileformat = module.params.get('fileFormat')
filename = module.params.get('fileName')
createBackupPlan = module.params.get('createBackupPlan')
backupPlanName = module.params.get('backupPlanName')
inputfile = module.params.get('inputFile')
extra_opts = module.params.get('extra_opts')
verbose = module.params.get('verbose')
# parameter checks
command = [racclimx, '-op', operation]
if opsname:
command.append('-opsName')
command.append(opsname)
if dn:
command.append('-DN')
command.append(dn)
if ws:
command.append('-WS')
command.append(ws)
if mr:
command.append('-MR')
command.append(mr)
if planname:
command.append('-planName')
command.append(planname)
if typeoption:
command.append('-type')
command.append(typeoption)
if fileformat:
command.append('-fileFormat')
command.append(fileformat)
if filename:
command.append('-fileName')
command.append(filename)
if createBackupPlan:
command.append('-createBackupPlan')
command.append('true')
if backupPlanName:
command.append('-backupPlanName')
command.append(backupPlanName)
if inputfile:
command.append('-inputFile')
command.append(inputfile)
if extra_opts:
command = command + extra_opts.split(" ")
if verbose:
if verbose == 'True':
command.append("-v")
rc, out, err = module.run_command(command, check_rc=True)
if rc != 0:
result['changed'] = False
module.fail_json(msg=err)
else:
result['changed'] = True
result['original_message'] = out
result['cmd'] = command
result['message'] = out
# in the event of a successful module execution, you will want to
# simple AnsibleModule.exit_json(), passing the key/value results
module.exit_json(**result)
if __name__ == '__main__':
main()

View file

@ -0,0 +1,183 @@
"""
netact_cm_command unit tests
"""
# -*- coding: utf-8 -*-
# (c) 2017, Nokia
# This file is part of Ansible
#
# Ansible is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# Ansible is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with Ansible. If not, see <http://www.gnu.org/licenses/>.
# pylint: disable=invalid-name,protected-access,function-redefined,unused-argument
# pylint: disable=unused-import,redundant-unittest-assert
from __future__ import (absolute_import, division, print_function)
__metaclass__ = type
import json
from ansible.compat.tests import unittest
from ansible.module_utils import basic
from ansible.module_utils._text import to_bytes
from ansible.modules.network.netact import netact_cm_command
from ansible.compat.tests.mock import patch
from units.modules.utils import set_module_args as _set_module_args, \
AnsibleExitJson, AnsibleFailJson, ModuleTestCase
def set_module_args(args):
"""prepare arguments so that they will be picked up during module creation"""
args = json.dumps({'ANSIBLE_MODULE_ARGS': args})
basic._ANSIBLE_ARGS = to_bytes(args)
class AnsibleExitJson(Exception):
"""Exception class to be raised by module.exit_json and caught by the test case"""
pass
class AnsibleFailJson(Exception):
"""Exception class to be raised by module.fail_json and caught by the test case"""
pass
def exit_json(*args, **kwargs):
"""function to patch over exit_json; package return data into an exception"""
if 'changed' not in kwargs:
kwargs['changed'] = False
raise AnsibleExitJson(kwargs)
def fail_json(*args, **kwargs):
"""function to patch over fail_json; package return data into an exception"""
kwargs['failed'] = True
raise AnsibleFailJson(kwargs)
def get_bin_path(self, arg, required=False):
"""Mock AnsibleModule.get_bin_path"""
if arg.endswith('netact_cm_command'):
return '/usr/bin/my_command'
else:
if required:
fail_json(msg='%r not found !' % arg)
class TestClass(unittest.TestCase):
"""
Test cases
"""
def setUp(self):
self.mock_module_helper = patch.multiple(basic.AnsibleModule,
exit_json=exit_json,
fail_json=fail_json,
get_bin_path=get_bin_path)
self.mock_module_helper.start()
self.addCleanup(self.mock_module_helper.stop)
def test_module_fail_when_required_args_missing(self):
"""
Testing that command is failing if args are missing
:return:
"""
with self.assertRaises(AnsibleFailJson):
set_module_args({})
netact_cm_command.main()
self.assertTrue(False)
def test_ensure_command_called(self):
"""
Testing that command is executed with correct args
:return:
"""
set_module_args({
'operation': "Upload",
'opsName': 'Uploading_testi',
'DN': "PLMN-PLMN/MRBTS-746",
})
with patch.object(basic.AnsibleModule, 'run_command') as mock_run_command:
stdout = 'configuration updated'
stderr = ''
return_code = 0
mock_run_command.return_value = return_code, stdout, stderr # successful execution
with self.assertRaises(AnsibleExitJson) as result:
netact_cm_command.main()
print(result.exception.args)
self.assertTrue(result.exception.args[0]['changed']) # ensure result is changed
mock_run_command.assert_called_once_with(
['/opt/oss/bin/racclimx.sh', '-op', 'Upload', '-opsName', 'Uploading_testi',
'-DN', 'PLMN-PLMN/MRBTS-746'],
check_rc=True)
def test_ensure_backupPlanName_outputs_correctly(self):
"""
Testing that command is executed with correct args
:return:
"""
set_module_args({
'operation': "Provision",
'opsName': 'Provision_test',
'WS': "PLMN-PLMN/MRBTS-746",
'createBackupPlan': "Yes",
'backupPlanName': "backupPlanName"
})
with patch.object(basic.AnsibleModule, 'run_command') as mock_run_command:
stdout = 'configuration updated'
stderr = ''
return_code = 0
mock_run_command.return_value = return_code, stdout, stderr # successful execution
with self.assertRaises(AnsibleExitJson) as result:
netact_cm_command.main()
print(result.exception.args)
self.assertTrue(result.exception.args[0]['changed']) # ensure result is changed
mock_run_command.assert_called_once_with(
['/opt/oss/bin/racclimx.sh', '-op', 'Provision', '-opsName', 'Provision_test',
'-WS', 'PLMN-PLMN/MRBTS-746', '-createBackupPlan', 'true', '-backupPlanName', 'backupPlanName'],
check_rc=True)
def test_withwrongargs(self):
"""
Testing that wrong attribute causing error
:return:
"""
set_module_args({
'operation': "Upload",
'opsName': 'Uploading_testi',
'MR': "PLMN-PLMN/MRBTS-746",
'abc': 'abc'
})
with self.assertRaises(AnsibleFailJson):
with patch.object(basic.AnsibleModule, 'run_command') as mock_run_command:
stdout = 'configuration updated'
stderr = ''
return_code = 0
mock_run_command.return_value = return_code, stdout, stderr # successful execution
with self.assertRaises(AnsibleExitJson) as result:
netact_cm_command.main()
self.assertTrue(result.exception.args[0]['changed']) # ensure result is changed
self.assertFalse(True) # ensure result is changed