diff --git a/lib/ansible/modules/storage/netapp/na_ontap_security_key_manager.py b/lib/ansible/modules/storage/netapp/na_ontap_security_key_manager.py new file mode 100644 index 00000000000..598bacbd1ce --- /dev/null +++ b/lib/ansible/modules/storage/netapp/na_ontap_security_key_manager.py @@ -0,0 +1,229 @@ +#!/usr/bin/python + +# (c) 2019, NetApp, Inc +# 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 = ''' + +module: na_ontap_security_key_manager + +short_description: NetApp ONTAP security key manager. +extends_documentation_fragment: + - netapp.na_ontap +version_added: '2.8' +author: NetApp Ansible Team (@carchi8py) + +description: +- Add or delete or setup key management on NetApp ONTAP. + +options: + + state: + description: + - Whether the specified key manager should exist or not. + choices: ['present', 'absent'] + default: 'present' + + ip_address: + description: + - The IP address of the key management server. + required: true + + tcp_port: + description: + - The TCP port on which the key management server listens for incoming connections. + default: 5696 + + node: + description: + - The node which key management server runs on. + +''' + +EXAMPLES = """ + + - name: Delete Key Manager + tags: + - delete + na_ontap_security_key_manager: + state: absent + node: swenjun-vsim1 + hostname: "{{ hostname }}" + username: "{{ netapp_username }}" + password: "{{ netapp_password }}" + https: False + ip_address: 0.0.0.0 + + - name: Add Key Manager + tags: + - add + na_ontap_security_key_manager: + state: present + node: swenjun-vsim1 + hostname: "{{ hostname }}" + username: "{{ netapp_username }}" + password: "{{ netapp_password }}" + https: False + ip_address: 0.0.0.0 + +""" + +RETURN = """ +""" + +import traceback +import ansible.module_utils.netapp as netapp_utils +from ansible.module_utils.netapp_module import NetAppModule +from ansible.module_utils.basic import AnsibleModule +from ansible.module_utils._text import to_native + +HAS_NETAPP_LIB = netapp_utils.has_netapp_lib() + + +class NetAppOntapSecurityKeyManager(object): + '''class with key manager operations''' + + def __init__(self): + '''Initialize module parameters''' + self.argument_spec = netapp_utils.na_ontap_host_argument_spec() + self.argument_spec.update( + state=dict(required=False, choices=['present', 'absent'], default='present'), + ip_address=dict(required=True, type='str'), + node=dict(required=False, type='str'), + tcp_port=dict(required=False, type='int', default=5696) + ) + self.module = AnsibleModule( + argument_spec=self.argument_spec, + supports_check_mode=True + ) + self.na_helper = NetAppModule() + self.parameters = self.na_helper.set_parameters(self.module.params) + + if HAS_NETAPP_LIB is False: + self.module.fail_json( + msg="the python NetApp-Lib module is required" + ) + else: + self.cluster = netapp_utils.setup_na_ontap_zapi(module=self.module) + + def get_key_manager(self): + """ + get key manager by ip address. + :return: a dict of key manager + """ + key_manager_info = netapp_utils.zapi.NaElement('security-key-manager-get-iter') + query_details = netapp_utils.zapi.NaElement.create_node_with_children( + 'key-manager-info', **{'key-manager-ip-address': self.parameters['ip_address']}) + query = netapp_utils.zapi.NaElement('query') + query.add_child_elem(query_details) + key_manager_info.add_child_elem(query) + + try: + result = self.cluster.invoke_successfully(key_manager_info, enable_tunneling=False) + except netapp_utils.zapi.NaApiError as error: + self.module.fail_json(msg='Error fetching key manager %s : %s' + % (self.parameters['node'], to_native(error)), + exception=traceback.format_exc()) + + return_value = None + if result.get_child_by_name('num-records') and int(result.get_child_content('num-records')) > 0: + key_manager = result.get_child_by_name('attributes-list').get_child_by_name('key-manager-info') + return_value = {} + if key_manager.get_child_by_name('key-manager-ip-address'): + return_value['ip_address'] = key_manager.get_child_content('key-manager-ip-address') + if key_manager.get_child_by_name('key-manager-server-status'): + return_value['server_status'] = key_manager.get_child_content('key-manager-server-status') + if key_manager.get_child_by_name('key-manager-tcp-port'): + return_value['tcp_port'] = key_manager.get_child_content('key-manager-tcp-port') + if key_manager.get_child_by_name('node-name'): + return_value['node'] = key_manager.get_child_content('node-name') + + return return_value + + def key_manager_setup(self): + """ + set up external key manager. + """ + key_manager_setup = netapp_utils.zapi.NaElement('security-key-manager-setup') + # if specify on-boarding passphrase, it is on-boarding key management. + # it not, then it's external key management. + try: + self.cluster.invoke_successfully(key_manager_setup, True) + except netapp_utils.zapi.NaApiError as error: + self.module.fail_json(msg='Error setting up key manager %s : %s' + % (self.parameters['node'], to_native(error)), + exception=traceback.format_exc()) + + def create_key_manager(self): + """ + add key manager. + """ + key_manager_create = netapp_utils.zapi.NaElement('security-key-manager-add') + key_manager_create.add_new_child('key-manager-ip-address', self.parameters['ip_address']) + if self.parameters.get('tcp_port'): + key_manager_create.add_new_child('key-manager-tcp-port', str(self.parameters['tcp_port'])) + try: + self.cluster.invoke_successfully(key_manager_create, True) + except netapp_utils.zapi.NaApiError as error: + self.module.fail_json(msg='Error creating key manager %s : %s' + % (self.parameters['node'], to_native(error)), + exception=traceback.format_exc()) + + def delete_key_manager(self): + """ + delete key manager. + """ + key_manager_delete = netapp_utils.zapi.NaElement('security-key-manager-delete') + key_manager_delete.add_new_child('key-manager-ip-address', self.parameters['ip_address']) + try: + self.cluster.invoke_successfully(key_manager_delete, True) + except netapp_utils.zapi.NaApiError as error: + self.module.fail_json(msg='Error deleting key manager %s : %s' + % (self.parameters['node'], to_native(error)), + exception=traceback.format_exc()) + + def apply(self): + self.asup_log_for_cserver("na_ontap_security_key_manager") + self.key_manager_setup() + current = self.get_key_manager() + cd_action = None + cd_action = self.na_helper.get_cd_action(current, self.parameters) + if self.na_helper.changed: + if self.module.check_mode: + pass + else: + if cd_action == 'create': + self.create_key_manager() + elif cd_action == 'delete': + self.delete_key_manager() + self.module.exit_json(changed=self.na_helper.changed) + + def asup_log_for_cserver(self, event_name): + """ + Fetch admin vserver for the given cluster + Create and Autosupport log event with the given module name + :param event_name: Name of the event log + :return: None + """ + results = netapp_utils.get_cserver(self.cluster) + cserver = netapp_utils.setup_na_ontap_zapi(module=self.module, vserver=results) + netapp_utils.ems_log_event(event_name, cserver) + + +def main(): + '''Apply volume operations from playbook''' + obj = NetAppOntapSecurityKeyManager() + obj.apply() + + +if __name__ == '__main__': + main() diff --git a/test/units/modules/storage/netapp/test_na_ontap_security_key_manager.py b/test/units/modules/storage/netapp/test_na_ontap_security_key_manager.py new file mode 100644 index 00000000000..d1e7a7a6a73 --- /dev/null +++ b/test/units/modules/storage/netapp/test_na_ontap_security_key_manager.py @@ -0,0 +1,173 @@ +# (c) 2019, NetApp, Inc +# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt) + +''' unit test template for ONTAP Ansible module ''' + +from __future__ import print_function +import json +import pytest + +from units.compat import unittest +from units.compat.mock import patch, Mock +from ansible.module_utils import basic +from ansible.module_utils._text import to_bytes +import ansible.module_utils.netapp as netapp_utils + +from ansible.modules.storage.netapp.na_ontap_security_key_manager \ + import NetAppOntapSecurityKeyManager as key_manager_module # module under test + +if not netapp_utils.has_netapp_lib(): + pytestmark = pytest.mark.skip('skipping as missing required netapp_lib') + + +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) # pylint: disable=protected-access + + +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): # pylint: disable=unused-argument + """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): # pylint: disable=unused-argument + """function to patch over fail_json; package return data into an exception""" + kwargs['failed'] = True + raise AnsibleFailJson(kwargs) + + +class MockONTAPConnection(object): + ''' mock server connection to ONTAP host ''' + + def __init__(self, kind=None, data=None): + ''' save arguments ''' + self.type = kind + self.data = data + self.xml_in = None + self.xml_out = None + + def invoke_successfully(self, xml, enable_tunneling): # pylint: disable=unused-argument + ''' mock invoke_successfully returning xml data ''' + self.xml_in = xml + if self.type == 'key_manager': + xml = self.build_port_info(self.data) + self.xml_out = xml + return xml + + @staticmethod + def build_port_info(key_manager_details): + ''' build xml data for-key-manager-info ''' + xml = netapp_utils.zapi.NaElement('xml') + attributes = { + 'num-records': 1, + 'attributes-list': { + 'key-manager-info': { + 'key-manager-ip-address': '0.0.0.0', + 'key-manager-server-status': 'available', + 'key-manager-tcp-port': '5696', + 'node-name': 'test_node' + } + } + } + xml.translate_struct(attributes) + return xml + + +class TestMyModule(unittest.TestCase): + ''' a group of related Unit Tests ''' + + def setUp(self): + self.mock_module_helper = patch.multiple(basic.AnsibleModule, + exit_json=exit_json, + fail_json=fail_json) + self.mock_module_helper.start() + self.addCleanup(self.mock_module_helper.stop) + self.mock_key_manager = { + 'node_name': 'test_node', + 'tcp_port': 5696, + 'ip_address': '0.0.0.0', + 'server_status': 'available' + } + + def mock_args(self): + return { + 'node': self.mock_key_manager['node_name'], + 'tcp_port': self.mock_key_manager['tcp_port'], + 'ip_address': self.mock_key_manager['ip_address'], + 'hostname': 'test', + 'username': 'test_user', + 'password': 'test_pass!', + 'https': 'False' + } + + def get_key_manager_mock_object(self, kind=None): + """ + Helper method to return an na_ontap_security_key_manager object + :param kind: passes this param to MockONTAPConnection() + :return: na_ontap_security_key_manager object + """ + obj = key_manager_module() + obj.asup_log_for_cserver = Mock(return_value=None) + obj.cluster = Mock() + obj.cluster.invoke_successfully = Mock() + if kind is None: + obj.cluster = MockONTAPConnection() + else: + obj.cluster = MockONTAPConnection(kind=kind, data=self.mock_key_manager) + return obj + + def test_module_fail_when_required_args_missing(self): + ''' required arguments are reported as errors ''' + with pytest.raises(AnsibleFailJson) as exc: + set_module_args({}) + key_manager_module() + print('Info: %s' % exc.value.args[0]['msg']) + + def test_get_nonexistent_key_manager(self): + ''' Test if get_key_manager() returns None for non-existent key manager ''' + set_module_args(self.mock_args()) + result = self.get_key_manager_mock_object().get_key_manager() + assert result is None + + def test_get_existing_key_manager(self): + ''' Test if get_key_manager() returns details for existing key manager ''' + set_module_args(self.mock_args()) + result = self.get_key_manager_mock_object('key_manager').get_key_manager() + assert result['ip_address'] == self.mock_key_manager['ip_address'] + + @patch('ansible.modules.storage.netapp.na_ontap_security_key_manager.NetAppOntapSecurityKeyManager.get_key_manager') + def test_successfully_add_key_manager(self, get_key_manager): + ''' Test successfully add key manager''' + data = self.mock_args() + data['state'] = 'present' + set_module_args(data) + get_key_manager.side_effect = [ + None + ] + obj = self.get_key_manager_mock_object('key_manager') + with pytest.raises(AnsibleExitJson) as exc: + obj.apply() + assert exc.value.args[0]['changed'] + + def test_successfully_delete_key_manager(self): + ''' Test successfully delete key manager''' + data = self.mock_args() + data['state'] = 'absent' + set_module_args(data) + obj = self.get_key_manager_mock_object('key_manager') + with pytest.raises(AnsibleExitJson) as exc: + obj.apply() + assert exc.value.args[0]['changed']