Repository: ansible/ansible

Issue:
nios_zone: Unable to update comment for an existing Zone
<!---
Verify first that your issue/request is not already reported on GitHub.
Also test if the latest release, and devel branch are affected too.
-->

##### ISSUE TYPE
<!--- Pick one below and delete the rest -->
 - Bug Report

##### COMPONENT NAME
<!---
Name of the module, plugin, task or feature
Do not include extra details here, e.g. "vyos_command" not "the network module vyos_command" or the full path
-->
- nios_zone

##### ANSIBLE VERSION
<!--- Paste verbatim output from "ansible --version" between quotes below -->
```
ansible 2.5.0b2
  config file = None
  configured module search path = [u'/root/.ansible/plugins/modules', u'/usr/share/ansible/plugins/modules']
  ansible python module location = /usr/local/lib/python2.7/dist-packages/ansible-2.5.0b2-py2.7.egg/ansible
  executable location = /usr/local/bin/ansible
  python version = 2.7.12 (default, Nov 20 2017, 18:23:56) [GCC 5.4.0 20160609]
```


##### OS / ENVIRONMENT
<!---
Mention the OS you are running Ansible from, and the OS you are
managing, or say "N/A" for anything that is not platform-specific.
Also mention the specific version of what you are trying to control,
e.g. if this is a network bug the version of firmware on the network device.
-->
- Ubuntu 16.04.3 LTS

##### SUMMARY
<!--- Explain the problem briefly -->
Unable to update comment for an existing Zone

##### STEPS TO REPRODUCE
<!---
For bugs, show exactly how to reproduce the problem, using a minimal test-case.
For new features, show how the feature would be used.
-->
- Run a playbook to create Zone with Comment
<!--- Paste example playbooks or commands between quotes below -->
```yaml
    - name: Create Zone with Comment
      nios_zone:
        name: ansibletest.com
        comment: testing
        view: ansibledv1
        provider: "{{ provider }}"
        state: present
```
Zone created with comment Successfully

- Now run again playbook to update the Comment for the created Zone
```yaml
    - name: Update Zone with Comment
      nios_zone:
        name: ansibletest.com
        comment: testing-update
        view: ansibledv1
        provider: "{{ provider }}"
        state: present
```
<!--- You can also paste gist.github.com links for larger files -->

##### EXPECTED RESULTS
<!--- What did you expect to happen when running the steps above? -->
- Comment should be updated for the Zone successfully
##### ACTUAL RESULTS
<!--- What actually happened? If possible run with extra verbosity (-vvvv) -->

<!--- Paste verbatim command output between quotes below -->
```
The full traceback is:
  File "/tmp/ansible_yUCSRd/ansible_modlib.zip/ansible/module_utils/net_tools/nios/api.py", line 146, in _invoke_method
    return method(*args, **kwargs)
  File "/usr/local/lib/python2.7/dist-packages/********_client/connector.py", line 41, in callee
    return func(*args, **kwargs)
  File "/usr/local/lib/python2.7/dist-packages/********_client/connector.py", line 430, in update_object
    code=r.status_code)

fatal: [10.35.2.77]: FAILED! => {
    "changed": false,
    "code": "Client.Ibap.Proto",
    "failed": true,
    "invocation": {
        "module_args": {
            "comment": "testing-update",
            "extattrs": null,
            "fqdn": "ansibletest.com",
            "grid_primary": null,
            "grid_secondaries": null,
            "name": "ansibletest.com",
            "provider": {
                "host": "10.35.2.77",
                "http_pool_connections": 10,
                "http_pool_maxsize": 10,
                "http_request_timeout": 10,
                "max_results": 1000,
                "max_retries": 3,
                "password": "VALUE_SPECIFIED_IN_NO_LOG_PARAMETER",
                "silent_ssl_warnings": true,
                "ssl_verify": false,
                "username": "admin",
                "wapi_version": "1.4"
            },
            "state": "present",
            "view": "ansibledv1"
        }
    },
    "msg": "Field is not allowed for update: fqdn",
    "operation": "update_object",
    "type": "AdmConProtoError"
}
        to retry, use: --limit @/root/Ansible_Tests/zone-testcases.retry

```



Discussion:
Files identified in the description:
* [lib/ansible/modules/net_tools/nios/nios_zone.py](https://github.com/ansible/ansible/blob/devel/lib/ansible/modules/net_tools/nios/nios_zone.py)

If these files are inaccurate, please update the `component name` section of the description or use the `!component` bot command.

[click here for bot help](https://github.com/ansible/ansibullbot/blob/master/ISSUE_HELP.md)
<!--- boilerplate: components_banner --->
cc @Qalthos @ganeshrn @gundalow @kedarX @privateip @rcarrillocruz @trishnaguha
[click here for bot help](https://github.com/ansible/ansibullbot/blob/master/ISSUE_HELP.md)
<!--- boilerplate: notify --->

Resolving patch:
diff --git a/lib/ansible/module_utils/net_tools/nios/api.py b/lib/ansible/module_utils/net_tools/nios/api.py
--- a/lib/ansible/module_utils/net_tools/nios/api.py
+++ b/lib/ansible/module_utils/net_tools/nios/api.py
@@ -240,6 +240,7 @@ def run(self, ib_obj_type, ib_spec):
                 elif 'view' in proposed_object:
                     self.check_if_dns_view_exists(proposed_object['view'])
                 if not self.module.check_mode:
+                    proposed_object = self.on_update(proposed_object, ib_spec)
                     res = self.update_object(ref, proposed_object)
                 result['changed'] = True
 
@@ -321,3 +322,23 @@ def compare_objects(self, current_object, proposed_object):
                     return False
 
         return True
+
+    def on_update(self, proposed_object, ib_spec):
+        ''' Event called before the update is sent to the API endpoing
+
+        This method will allow the final proposed object to be changed
+        and/or keys filtered before it is sent to the API endpoint to
+        be processed.
+
+        :args proposed_object: A dict item that will be encoded and sent
+            the the API endpoint with the updated data structure
+
+        :returns: updated object to be sent to API endpoint
+        '''
+        keys = set()
+        for key, value in iteritems(proposed_object):
+            update = ib_spec[key].get('update', True)
+            if not update:
+                keys.add(key)
+
+        return dict([(k, v) for k, v in iteritems(proposed_object) if k not in keys])
diff --git a/lib/ansible/modules/net_tools/nios/nios_zone.py b/lib/ansible/modules/net_tools/nios/nios_zone.py
--- a/lib/ansible/modules/net_tools/nios/nios_zone.py
+++ b/lib/ansible/modules/net_tools/nios/nios_zone.py
@@ -134,7 +134,7 @@ def main():
     )
 
     ib_spec = dict(
-        fqdn=dict(required=True, aliases=['name'], ib_req=True),
+        fqdn=dict(required=True, aliases=['name'], ib_req=True, update=False),
         view=dict(default='default', aliases=['dns_view'], ib_req=True),
 
         grid_primary=dict(type='list', elements='dict', options=grid_spec),


Test patch:
diff --git a/test/units/module_utils/net_tools/nios/test_api.py b/test/units/module_utils/net_tools/nios/test_api.py
--- a/test/units/module_utils/net_tools/nios/test_api.py
+++ b/test/units/module_utils/net_tools/nios/test_api.py
@@ -97,6 +97,32 @@ def test_wapi_change(self):
         self.assertTrue(res['changed'])
         wapi.update_object.called_once_with(test_object)
 
+    def test_wapi_change_false(self):
+        self.module.params = {'provider': None, 'state': 'present', 'name': 'default',
+                              'comment': 'updated comment', 'extattrs': None, 'fqdn': 'foo'}
+
+        test_object = [
+            {
+                "comment": "test comment",
+                "_ref": "networkview/ZG5zLm5ldHdvcmtfdmlldyQw:default/true",
+                "name": "default",
+                "extattrs": {}
+            }
+        ]
+
+        test_spec = {
+            "name": {"ib_req": True},
+            "fqdn": {"ib_req": True, 'update': False},
+            "comment": {},
+            "extattrs": {}
+        }
+
+        wapi = self._get_wapi(test_object)
+        res = wapi.run('testobject', test_spec)
+
+        self.assertTrue(res['changed'])
+        wapi.update_object.called_once_with(test_object)
+
     def test_wapi_extattrs_change(self):
         self.module.params = {'provider': None, 'state': 'present', 'name': 'default',
                               'comment': 'test comment', 'extattrs': {'Site': 'update'}}