diff --git a/lib/ansible/modules/remote_management/oneview/oneview_logical_interconnect_group.py b/lib/ansible/modules/remote_management/oneview/oneview_logical_interconnect_group.py new file mode 100644 index 0000000000..96dff5796a --- /dev/null +++ b/lib/ansible/modules/remote_management/oneview/oneview_logical_interconnect_group.py @@ -0,0 +1,167 @@ +#!/usr/bin/python +# Copyright (c) 2016-2017 Hewlett Packard Enterprise Development LP +# 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: oneview_logical_interconnect_group +short_description: Manage OneView Logical Interconnect Group resources +description: + - Provides an interface to manage Logical Interconnect Group resources. Can create, update, or delete. +version_added: "2.5" +requirements: + - hpOneView >= 4.0.0 +author: + - Felipe Bulsoni (@fgbulsoni) + - Thiago Miotto (@tmiotto) + - Adriane Cardozo (@adriane-cardozo) +options: + state: + description: + - Indicates the desired state for the Logical Interconnect Group resource. + C(present) will ensure data properties are compliant with OneView. + C(absent) will remove the resource from OneView, if it exists. + default: present + choices: ['present', 'absent'] + data: + description: + - List with the Logical Interconnect Group properties. + required: true +extends_documentation_fragment: + - oneview + - oneview.validateetag +''' + +EXAMPLES = ''' +- name: Ensure that the Logical Interconnect Group is present + oneview_logical_interconnect_group: + config: /etc/oneview/oneview_config.json + state: present + data: + name: 'Test Logical Interconnect Group' + uplinkSets: [] + enclosureType: 'C7000' + interconnectMapTemplate: + interconnectMapEntryTemplates: + - logicalDownlinkUri: ~ + logicalLocation: + locationEntries: + - relativeValue: "1" + type: "Bay" + - relativeValue: 1 + type: "Enclosure" + permittedInterconnectTypeName: 'HP VC Flex-10/10D Module' + # Alternatively you can inform permittedInterconnectTypeUri + delegate_to: localhost + +- name: Ensure that the Logical Interconnect Group has the specified scopes + oneview_logical_interconnect_group: + config: /etc/oneview/oneview_config.json + state: present + data: + name: Test Logical Interconnect Group + scopeUris: + - /rest/scopes/00SC123456 + - /rest/scopes/01SC123456 + delegate_to: localhost + +- name: Ensure that the Logical Interconnect Group is present with name 'Test' + oneview_logical_interconnect_group: + config: /etc/oneview/oneview_config.json + state: present + data: + name: New Logical Interconnect Group + newName: Test + delegate_to: localhost + +- name: Ensure that the Logical Interconnect Group is absent + oneview_logical_interconnect_group: + config: /etc/oneview/oneview_config.json + state: absent + data: + name: New Logical Interconnect Group + delegate_to: localhost +''' + +RETURN = ''' +logical_interconnect_group: + description: Has the facts about the OneView Logical Interconnect Group. + returned: On state 'present'. Can be null. + type: dict +''' + +from ansible.module_utils.oneview import OneViewModuleBase, OneViewModuleResourceNotFound + + +class LogicalInterconnectGroupModule(OneViewModuleBase): + MSG_CREATED = 'Logical Interconnect Group created successfully.' + MSG_UPDATED = 'Logical Interconnect Group updated successfully.' + MSG_DELETED = 'Logical Interconnect Group deleted successfully.' + MSG_ALREADY_PRESENT = 'Logical Interconnect Group is already present.' + MSG_ALREADY_ABSENT = 'Logical Interconnect Group is already absent.' + MSG_INTERCONNECT_TYPE_NOT_FOUND = 'Interconnect Type was not found.' + + RESOURCE_FACT_NAME = 'logical_interconnect_group' + + def __init__(self): + argument_spec = dict( + state=dict(default='present', choices=['present', 'absent']), + data=dict(required=True, type='dict') + ) + + super(LogicalInterconnectGroupModule, self).__init__(additional_arg_spec=argument_spec, + validate_etag_support=True) + self.resource_client = self.oneview_client.logical_interconnect_groups + + def execute_module(self): + resource = self.get_by_name(self.data['name']) + + if self.state == 'present': + return self.__present(resource) + elif self.state == 'absent': + return self.resource_absent(resource) + + def __present(self, resource): + scope_uris = self.data.pop('scopeUris', None) + + self.__replace_name_by_uris(self.data) + result = self.resource_present(resource, self.RESOURCE_FACT_NAME) + + if scope_uris is not None: + result = self.resource_scopes_set(result, 'logical_interconnect_group', scope_uris) + + return result + + def __replace_name_by_uris(self, data): + map_template = data.get('interconnectMapTemplate') + + if map_template: + map_entry_templates = map_template.get('interconnectMapEntryTemplates') + if map_entry_templates: + for value in map_entry_templates: + permitted_interconnect_type_name = value.pop('permittedInterconnectTypeName', None) + if permitted_interconnect_type_name: + value['permittedInterconnectTypeUri'] = self.__get_interconnect_type_by_name( + permitted_interconnect_type_name).get('uri') + + def __get_interconnect_type_by_name(self, name): + i_type = self.oneview_client.interconnect_types.get_by('name', name) + if i_type: + return i_type[0] + else: + raise OneViewModuleResourceNotFound(self.MSG_INTERCONNECT_TYPE_NOT_FOUND) + + +def main(): + LogicalInterconnectGroupModule().run() + + +if __name__ == '__main__': + main() diff --git a/test/units/modules/remote_management/oneview/test_oneview_logical_interconnect_group.py b/test/units/modules/remote_management/oneview/test_oneview_logical_interconnect_group.py new file mode 100644 index 0000000000..467b8d2dd0 --- /dev/null +++ b/test/units/modules/remote_management/oneview/test_oneview_logical_interconnect_group.py @@ -0,0 +1,258 @@ +# Copyright (c) 2016-2017 Hewlett Packard Enterprise Development LP +# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt) + +from copy import deepcopy + +from ansible.compat.tests import unittest, mock +from oneview_module_loader import OneViewModuleBase +from ansible.modules.remote_management.oneview.oneview_logical_interconnect_group import LogicalInterconnectGroupModule +from hpe_test_utils import OneViewBaseTestCase + + +FAKE_MSG_ERROR = 'Fake message error' + +DEFAULT_LIG_NAME = 'Test Logical Interconnect Group' +RENAMED_LIG = 'Renamed Logical Interconnect Group' + +DEFAULT_LIG_TEMPLATE = dict( + name=DEFAULT_LIG_NAME, + uplinkSets=[], + enclosureType='C7000', + interconnectMapTemplate=dict( + interconnectMapEntryTemplates=[] + ) +) + +PARAMS_LIG_TEMPLATE_WITH_MAP = dict( + config='config.json', + state='present', + data=dict( + name=DEFAULT_LIG_NAME, + uplinkSets=[], + enclosureType='C7000', + interconnectMapTemplate=dict( + interconnectMapEntryTemplates=[ + { + "logicalDownlinkUri": None, + "logicalLocation": { + "locationEntries": [ + { + "relativeValue": "1", + "type": "Bay" + }, + { + "relativeValue": 1, + "type": "Enclosure" + } + ] + }, + "permittedInterconnectTypeName": "HP VC Flex-10/10D Module" + }] + ) + )) + +PARAMS_FOR_PRESENT = dict( + config='config.json', + state='present', + data=dict(name=DEFAULT_LIG_NAME) +) + +PARAMS_TO_RENAME = dict( + config='config.json', + state='present', + data=dict(name=DEFAULT_LIG_NAME, + newName=RENAMED_LIG) +) + +PARAMS_WITH_CHANGES = dict( + config='config.json', + state='present', + data=dict(name=DEFAULT_LIG_NAME, + description='It is an example') +) + +PARAMS_FOR_ABSENT = dict( + config='config.json', + state='absent', + data=dict(name=DEFAULT_LIG_NAME) +) + + +class LogicalInterconnectGroupGeneralSpec(unittest.TestCase, + OneViewBaseTestCase): + def setUp(self): + self.configure_mocks(self, LogicalInterconnectGroupModule) + self.resource = self.mock_ov_client.logical_interconnect_groups + + def test_should_create_new_lig(self): + self.resource.get_by.return_value = [] + self.resource.create.return_value = DEFAULT_LIG_TEMPLATE + + self.mock_ansible_module.params = PARAMS_FOR_PRESENT + + LogicalInterconnectGroupModule().run() + + self.mock_ansible_module.exit_json.assert_called_once_with( + changed=True, + msg=LogicalInterconnectGroupModule.MSG_CREATED, + ansible_facts=dict(logical_interconnect_group=DEFAULT_LIG_TEMPLATE) + ) + + def test_should_create_new_with_named_permitted_interconnect_type(self): + self.resource.get_by.return_value = [] + self.resource.create.return_value = PARAMS_FOR_PRESENT + + self.mock_ansible_module.params = deepcopy(PARAMS_LIG_TEMPLATE_WITH_MAP) + + LogicalInterconnectGroupModule().run() + + self.mock_ansible_module.exit_json.assert_called_once_with( + changed=True, + msg=LogicalInterconnectGroupModule.MSG_CREATED, + ansible_facts=dict(logical_interconnect_group=PARAMS_FOR_PRESENT.copy()) + ) + + def test_should_fail_when_permitted_interconnect_type_name_not_exists(self): + self.resource.get_by.return_value = [] + self.resource.create.return_value = PARAMS_FOR_PRESENT + self.mock_ov_client.interconnect_types.get_by.return_value = [] + + self.mock_ansible_module.params = deepcopy(PARAMS_LIG_TEMPLATE_WITH_MAP) + + LogicalInterconnectGroupModule().run() + + self.mock_ansible_module.fail_json.assert_called_once_with( + exception=mock.ANY, + msg=LogicalInterconnectGroupModule.MSG_INTERCONNECT_TYPE_NOT_FOUND) + + def test_should_not_update_when_data_is_equals(self): + self.resource.get_by.return_value = [DEFAULT_LIG_TEMPLATE] + + self.mock_ansible_module.params = PARAMS_FOR_PRESENT + + LogicalInterconnectGroupModule().run() + + self.mock_ansible_module.exit_json.assert_called_once_with( + changed=False, + msg=LogicalInterconnectGroupModule.MSG_ALREADY_PRESENT, + ansible_facts=dict(logical_interconnect_group=DEFAULT_LIG_TEMPLATE) + ) + + def test_update_when_data_has_modified_attributes(self): + data_merged = DEFAULT_LIG_TEMPLATE.copy() + data_merged['description'] = 'New description' + + self.resource.get_by.return_value = [DEFAULT_LIG_TEMPLATE] + self.resource.update.return_value = data_merged + + self.mock_ansible_module.params = PARAMS_WITH_CHANGES + + LogicalInterconnectGroupModule().run() + + self.mock_ansible_module.exit_json.assert_called_once_with( + changed=True, + msg=LogicalInterconnectGroupModule.MSG_UPDATED, + ansible_facts=dict(logical_interconnect_group=data_merged) + ) + + def test_rename_when_resource_exists(self): + data_merged = DEFAULT_LIG_TEMPLATE.copy() + data_merged['name'] = RENAMED_LIG + params_to_rename = PARAMS_TO_RENAME.copy() + + self.resource.get_by.return_value = [DEFAULT_LIG_TEMPLATE] + self.resource.update.return_value = data_merged + + self.mock_ansible_module.params = params_to_rename + + LogicalInterconnectGroupModule().run() + + self.resource.update.assert_called_once_with(data_merged) + + def test_create_with_newName_when_resource_not_exists(self): + data_merged = DEFAULT_LIG_TEMPLATE.copy() + data_merged['name'] = RENAMED_LIG + params_to_rename = PARAMS_TO_RENAME.copy() + + self.resource.get_by.return_value = [] + self.resource.create.return_value = DEFAULT_LIG_TEMPLATE + + self.mock_ansible_module.params = params_to_rename + + LogicalInterconnectGroupModule().run() + + self.resource.create.assert_called_once_with(PARAMS_TO_RENAME['data']) + + def test_should_remove_lig(self): + self.resource.get_by.return_value = [DEFAULT_LIG_TEMPLATE] + + self.mock_ansible_module.params = PARAMS_FOR_ABSENT + + LogicalInterconnectGroupModule().run() + + self.mock_ansible_module.exit_json.assert_called_once_with( + changed=True, + msg=LogicalInterconnectGroupModule.MSG_DELETED + ) + + def test_should_do_nothing_when_lig_not_exist(self): + self.resource.get_by.return_value = [] + + self.mock_ansible_module.params = PARAMS_FOR_ABSENT + + LogicalInterconnectGroupModule().run() + + self.mock_ansible_module.exit_json.assert_called_once_with( + changed=False, + msg=LogicalInterconnectGroupModule.MSG_ALREADY_ABSENT + ) + + def test_update_scopes_when_different(self): + params_to_scope = PARAMS_FOR_PRESENT.copy() + params_to_scope['data']['scopeUris'] = ['test'] + self.mock_ansible_module.params = params_to_scope + + resource_data = DEFAULT_LIG_TEMPLATE.copy() + resource_data['scopeUris'] = ['fake'] + resource_data['uri'] = 'rest/lig/fake' + self.resource.get_by.return_value = [resource_data] + + patch_return = resource_data.copy() + patch_return['scopeUris'] = ['test'] + self.resource.patch.return_value = patch_return + + LogicalInterconnectGroupModule().run() + + self.resource.patch.assert_called_once_with('rest/lig/fake', + operation='replace', + path='/scopeUris', + value=['test']) + + self.mock_ansible_module.exit_json.assert_called_once_with( + changed=True, + ansible_facts=dict(logical_interconnect_group=patch_return), + msg=LogicalInterconnectGroupModule.MSG_UPDATED + ) + + def test_should_do_nothing_when_scopes_are_the_same(self): + params_to_scope = PARAMS_FOR_PRESENT.copy() + params_to_scope['data']['scopeUris'] = ['test'] + self.mock_ansible_module.params = params_to_scope + + resource_data = DEFAULT_LIG_TEMPLATE.copy() + resource_data['scopeUris'] = ['test'] + self.resource.get_by.return_value = [resource_data] + + LogicalInterconnectGroupModule().run() + + self.resource.patch.not_been_called() + + self.mock_ansible_module.exit_json.assert_called_once_with( + changed=False, + ansible_facts=dict(logical_interconnect_group=resource_data), + msg=LogicalInterconnectGroupModule.MSG_ALREADY_PRESENT + ) + + +if __name__ == '__main__': + unittest.main()