Repository: ansible/ansible

Issue:
eos_eapi: http_port not set/read correctly
##### ISSUE TYPE
 - Bug Report

##### COMPONENT NAME
eos_eapi

##### ANSIBLE VERSION
```
ansible 2.3.0 (test_ios_again 1dde9971ae) last updated 2017/02/23 09:43:19 (GMT +100)

```

##### SUMMARY
```yaml
- name: Change endpoint ports
  eos_eapi:
    enable_http: false
    http_port: 81
    https_port: 4443
    enable_local_http: yes 
    local_http_port: 8181
    provider: "{{ cli }}"
  register: eos_eapi_output
  connection: local


```
isn't setting ` http_port` to 81


##### STEPS TO REPRODUCE
 time ANSIBLE_ROLES_PATH=targets  ansible-playbook -vvvvv  -i  inventory.network-jobarker eos.yaml  -e "limit_to=eos_eapi" 

```yaml

TASK [eos_eapi : Change endpoint ports] **********************************************************************************************************************************************************************************************************
task path: /home/johnb/git/ansible-inc/ansible/test/integration/targets/eos_eapi/tests/cli/configure.yaml:3
<veos01> connection transport is cli
<veos01> socket_path: /home/johnb/.ansible/pc/ca5960d27a
Using module file /home/johnb/git/ansible-inc/ansible/lib/ansible/modules/network/eos/eos_eapi.py
<veos01> ESTABLISH LOCAL CONNECTION FOR USER: johnb
<veos01> EXEC /bin/sh -c 'echo ~ && sleep 0'
<veos01> EXEC /bin/sh -c '( umask 77 && mkdir -p "` echo /home/johnb/.ansible/tmp/ansible-tmp-1487952531.0-181989874477958 `" && echo ansible-tmp-1487952531.0-181989874477958="` echo /home/johnb/.ansible/tmp/ansible-tmp-1487952531.0-181989874477958 `" ) && sleep 0'
<veos01> PUT /tmp/tmpod07C4 TO /home/johnb/.ansible/tmp/ansible-tmp-1487952531.0-181989874477958/eos_eapi.py
<veos01> EXEC /bin/sh -c 'chmod u+x /home/johnb/.ansible/tmp/ansible-tmp-1487952531.0-181989874477958/ /home/johnb/.ansible/tmp/ansible-tmp-1487952531.0-181989874477958/eos_eapi.py && sleep 0'
<veos01> EXEC /bin/sh -c 'python /home/johnb/.ansible/tmp/ansible-tmp-1487952531.0-181989874477958/eos_eapi.py; rm -rf "/home/johnb/.ansible/tmp/ansible-tmp-1487952531.0-181989874477958/" > /dev/null 2>&1 && sleep 0'
changed: [veos01] => {
    "ansible_facts": {
        "eos_eapi_urls": {
            "Management1": [
                "https://172.26.4.24:4443"
            ]
        }
    }, 
    "changed": true, 
    "commands": [
        "management api http-commands", 
        "no protocol http", 
        "protocol https port 4443", 
        "protocol http localhost port 8181", 
        "no shutdown"
    ], 
    "invocation": {
        "module_args": {
            "auth_pass": null, 
            "authorize": true, 
            "enable_http": false, 
            "enable_local_http": true, 
            "host": "VALUE_SPECIFIED_IN_NO_LOG_PARAMETER", 
            "http": false, 
            "http_port": 81, 
            "https": null, 
            "https_port": 4443, 
            "local_http": true, 
            "local_http_port": 8181, 
            "password": "VALUE_SPECIFIED_IN_NO_LOG_PARAMETER", 
            "port": null, 
            "provider": {
                "auth_pass": null, 
                "authorize": true, 
                "host": "VALUE_SPECIFIED_IN_NO_LOG_PARAMETER", 
                "password": "VALUE_SPECIFIED_IN_NO_LOG_PARAMETER", 
                "port": null, 
                "ssh_keyfile": null, 
                "timeout": null, 
                "transport": "VALUE_SPECIFIED_IN_NO_LOG_PARAMETER", 
                "use_ssl": null, 
                "username": "VALUE_SPECIFIED_IN_NO_LOG_PARAMETER", 
                "validate_certs": null
            }, 
            "socket": null, 
            "ssh_keyfile": null, 
            "state": "started", 
            "timeout": null, 
            "transport": "VALUE_SPECIFIED_IN_NO_LOG_PARAMETER", 
            "use_ssl": null, 
            "username": "VALUE_SPECIFIED_IN_NO_LOG_PARAMETER", 
            "validate_certs": null, 
            "vrf": "default"
        }
    }, 
    "session_name": "ansible_1487952531"
}

TASK [eos_eapi : eos_command] ********************************************************************************************************************************************************************************************************************
task path: /home/johnb/git/ansible-inc/ansible/test/integration/targets/eos_eapi/tests/cli/configure.yaml:14
<veos01> connection transport is cli
<veos01> socket_path: /home/johnb/.ansible/pc/ca5960d27a
Using module file /home/johnb/git/ansible-inc/ansible/lib/ansible/modules/network/eos/eos_command.py
<veos01> ESTABLISH LOCAL CONNECTION FOR USER: johnb
<veos01> EXEC /bin/sh -c 'echo ~ && sleep 0'
<veos01> EXEC /bin/sh -c '( umask 77 && mkdir -p "` echo /home/johnb/.ansible/tmp/ansible-tmp-1487952535.46-243845812890914 `" && echo ansible-tmp-1487952535.46-243845812890914="` echo /home/johnb/.ansible/tmp/ansible-tmp-1487952535.46-243845812890914 `" ) && sleep 0'
<veos01> PUT /tmp/tmp0qer_B TO /home/johnb/.ansible/tmp/ansible-tmp-1487952535.46-243845812890914/eos_command.py
<veos01> EXEC /bin/sh -c 'chmod u+x /home/johnb/.ansible/tmp/ansible-tmp-1487952535.46-243845812890914/ /home/johnb/.ansible/tmp/ansible-tmp-1487952535.46-243845812890914/eos_command.py && sleep 0'
<veos01> EXEC /bin/sh -c 'python /home/johnb/.ansible/tmp/ansible-tmp-1487952535.46-243845812890914/eos_command.py; rm -rf "/home/johnb/.ansible/tmp/ansible-tmp-1487952535.46-243845812890914/" > /dev/null 2>&1 && sleep 0'
ok: [veos01] => {
    "changed": false, 
    "invocation": {
        "module_args": {
            "auth_pass": null, 
            "authorize": true, 
            "commands": [
                "show management api http-commands | json"
            ], 
            "host": "VALUE_SPECIFIED_IN_NO_LOG_PARAMETER", 
            "interval": 1, 
            "match": "all", 
            "password": "VALUE_SPECIFIED_IN_NO_LOG_PARAMETER", 
            "port": null, 
            "provider": {
                "auth_pass": null, 
                "authorize": true, 
                "host": "VALUE_SPECIFIED_IN_NO_LOG_PARAMETER", 
                "password": "VALUE_SPECIFIED_IN_NO_LOG_PARAMETER", 
                "port": null, 
                "ssh_keyfile": null, 
                "timeout": null, 
                "transport": "VALUE_SPECIFIED_IN_NO_LOG_PARAMETER", 
                "use_ssl": null, 
                "username": "VALUE_SPECIFIED_IN_NO_LOG_PARAMETER", 
                "validate_certs": null
            }, 
            "retries": 10, 
            "ssh_keyfile": null, 
            "timeout": null, 
            "transport": "VALUE_SPECIFIED_IN_NO_LOG_PARAMETER", 
            "use_ssl": null, 
            "username": "VALUE_SPECIFIED_IN_NO_LOG_PARAMETER", 
            "validate_certs": null, 
            "wait_for": null
        }
    }, 
    "stdout": [
        {
            "bytesIn": 4709220, 
            "bytesOut": 27832846, 
            "commandCount": 86844, 
            "corsOrigins": [], 
            "dscpValue": 0, 
            "enabled": true, 
            "executionTime": 9497.167350313473, 
            "hitCount": 25393, 
            "httpServer": {
                "configured": false, 
                "port": 80, 
                "running": false
            }, 
            "httpsServer": {
                "configured": true, 
                "port": 4443, 
                "running": false
            }, 
            "lastHitTime": 1487951691.6624587, 
            "localHttpServer": {
                "configured": true, 
                "port": 8181, 
                "running": false
            }, 
            "requestCount": 25162, 
            "unixSocketServer": {
                "configured": true, 
                "running": false
            }, 
            "urls": [
                "Management1 : https://172.26.4.24:4443"
            ], 
            "users": {
                "admin": {
                    "bytesIn": 4709220, 
                    "bytesOut": 27832846, 
                    "lastHitTime": 1487951691.662783, 
                    "requestCount": 25162
                }
            }, 
            "vrf": "default"
        }
    ], 
    "stdout_lines": [
        {
            "bytesIn": 4709220, 
            "bytesOut": 27832846, 
            "commandCount": 86844, 
            "corsOrigins": [], 
            "dscpValue": 0, 
            "enabled": true, 
            "executionTime": 9497.167350313473, 
            "hitCount": 25393, 
            "httpServer": {
                "configured": false, 
                "port": 80, 
                "running": false
            }, 
            "httpsServer": {
                "configured": true, 
                "port": 4443, 
                "running": false
            }, 
            "lastHitTime": 1487951691.6624587, 
            "localHttpServer": {
                "configured": true, 
                "port": 8181, 
                "running": false
            }, 
            "requestCount": 25162, 
            "unixSocketServer": {
                "configured": true, 
                "running": false
            }, 
            "urls": [
                "Management1 : https://172.26.4.24:4443"
            ], 
            "users": {
                "admin": {
                    "bytesIn": 4709220, 
                    "bytesOut": 27832846, 
                    "lastHitTime": 1487951691.662783, 
                    "requestCount": 25162
                }
            }, 
            "vrf": "default"
        }
    ]
}

TASK [eos_eapi : Expect endpoint ports to be set] ************************************************************************************************************************************************************************************************
task path: /home/johnb/git/ansible-inc/ansible/test/integration/targets/eos_eapi/tests/cli/configure.yaml:21
fatal: [veos01]: FAILED! => {
    "assertion": "http_config.stdout[0].httpServer.port == 81", 
    "changed": false, 
    "evaluated_to": false, 
    "failed": true
}

```


##### EXPECTED RESULTS

##### ACTUAL RESULTS
```yaml

```



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

Resolving patch:
diff --git a/lib/ansible/modules/network/eos/eos_eapi.py b/lib/ansible/modules/network/eos/eos_eapi.py
--- a/lib/ansible/modules/network/eos/eos_eapi.py
+++ b/lib/ansible/modules/network/eos/eos_eapi.py
@@ -180,6 +180,7 @@
   sample: ansible_1479315771
 """
 import re
+import time
 
 from ansible.module_utils.basic import AnsibleModule
 from ansible.module_utils.eos import run_commands, load_config
@@ -205,7 +206,7 @@ def validate_vrf(value, module):
     if value not in configured_vrfs:
         module.fail_json(msg='vrf `%s` is not configured on the system' % value)
 
-def map_obj_to_commands(updates, module):
+def map_obj_to_commands(updates, module, warnings):
     commands = list()
     want, have = updates
 
@@ -220,22 +221,38 @@ def add(cmd):
         if want['http'] is False:
             add('no protocol http')
         else:
-            port = want['http_port'] or 80
-            add('protocol http port %s' % port)
+            if have['http'] is False and want['http'] in (False, None):
+                warnings.append('protocol http is not enabled, not configuring http port value')
+            else:
+                port = want['http_port'] or 80
+                add('protocol http port %s' % port)
 
     if any((needs_update('https'), needs_update('https_port'))):
         if want['https'] is False:
             add('no protocol https')
         else:
-            port = want['https_port'] or 443
-            add('protocol https port %s' % port)
+            if have['https'] is False and want['https'] in (False, None):
+                warnings.append('protocol https is not enabled, not configuring https port value')
+            else:
+                port = want['https_port'] or 443
+                add('protocol https port %s' % port)
 
     if any((needs_update('local_http'), needs_update('local_http_port'))):
         if want['local_http'] is False:
             add('no protocol http localhost')
         else:
-            port = want['local_http_port'] or 8080
-            add('protocol http localhost port %s' % port)
+            if have['local_http'] is False and want['local_http'] in (False, None):
+                warnings.append('protocol local_http is not enabled, not configuring local_http port value')
+            else:
+                port = want['local_http_port'] or 8080
+                add('protocol http localhost port %s' % port)
+
+    if any((needs_update('socket'), needs_update('socket'))):
+        if want['socket'] is False:
+            add('no protocol unix-socket')
+        else:
+            add('protocol unix-socket')
+
 
     if needs_update('vrf'):
         add('vrf %s' % want['vrf'])
@@ -290,6 +307,34 @@ def map_params_to_obj(module):
 
     return obj
 
+def verify_state(updates, module):
+    want, have = updates
+
+    invalid_state = [('http', 'httpServer'),
+                     ('https', 'httpsServer'),
+                     ('local_http', 'localHttpServer'),
+                     ('socket', 'unixSocketServer')]
+
+    timeout = module.params['timeout'] or 30
+    state = module.params['state']
+
+    while invalid_state:
+        out = run_commands(module, ['show management api http-commands | json'])
+        for index, item in enumerate(invalid_state):
+            want_key, eapi_key = item
+            if want[want_key] is not None:
+                if want[want_key] == out[0][eapi_key]['running']:
+                    del invalid_state[index]
+            elif state == 'stopped':
+                if not out[0][eapi_key]['running']:
+                    del invalid_state[index]
+            else:
+                del invalid_state[index]
+        time.sleep(1)
+        timeout -= 1
+        if timeout == 0:
+            module.fail_json(msg='timeout expired before eapi running state changed')
+
 def collect_facts(module, result):
     out = run_commands(module, ['show management api http-commands | json'])
     facts = dict(eos_eapi_urls=dict())
@@ -318,6 +363,7 @@ def main():
 
         vrf=dict(default='default'),
 
+        config=dict(),
         state=dict(default='started', choices=['stopped', 'started']),
     )
 
@@ -326,12 +372,17 @@ def main():
     module = AnsibleModule(argument_spec=argument_spec,
                            supports_check_mode=True)
 
+
     result = {'changed': False}
 
+    warnings = list()
+    if module.params['config']:
+        warnings.append('config parameter is no longer necessary and will be ignored')
+
     want = map_params_to_obj(module)
     have = map_config_to_obj(module)
 
-    commands = map_obj_to_commands((want, have), module)
+    commands = map_obj_to_commands((want, have), module, warnings)
     result['commands'] = commands
 
     if commands:
@@ -342,8 +393,14 @@ def main():
         result['session_name'] = response.get('session')
         result['changed'] = True
 
+    if result['changed']:
+        verify_state((want, have), module)
+
     collect_facts(module, result)
 
+    if warnings:
+        result['warnings'] = warnings
+
     module.exit_json(**result)
 
 


Test patch:
diff --git a/test/integration/targets/eos_eapi/tests/cli/badtransport.yaml b/test/integration/targets/eos_eapi/tests/cli/badtransport.1
similarity index 100%
rename from test/integration/targets/eos_eapi/tests/cli/badtransport.yaml
rename to test/integration/targets/eos_eapi/tests/cli/badtransport.1
diff --git a/test/integration/targets/eos_eapi/tests/cli/configure.yaml b/test/integration/targets/eos_eapi/tests/cli/configure.yaml
--- a/test/integration/targets/eos_eapi/tests/cli/configure.yaml
+++ b/test/integration/targets/eos_eapi/tests/cli/configure.yaml
@@ -21,13 +21,13 @@
 - name: Expect endpoint ports to be set
   assert:
     that:
-        - http_config.stdout[0].httpServer.port == 81
+        - http_config.stdout[0].httpServer.port == 80
         - http_config.stdout[0].httpsServer.port == 4443
         - http_config.stdout[0].localHttpServer.port == 8181
 
 - name: Change endpoint ports again
   eos_eapi:
-    http_port: 81
+    http_port: 80
     https_port: 4443
     enable_local_http: yes
     local_http_port: 8181
diff --git a/test/integration/targets/eos_eapi/tests/cli/vrf.yaml b/test/integration/targets/eos_eapi/tests/cli/vrf.yaml
--- a/test/integration/targets/eos_eapi/tests/cli/vrf.yaml
+++ b/test/integration/targets/eos_eapi/tests/cli/vrf.yaml
@@ -15,7 +15,7 @@
     that:
       - "eos_eapi_output.failed == true"
       - "eos_eapi_output.changed == false"
-      - eos_eapi_output.msg == "vrf 'foobar' is not configured"
+      - eos_eapi_output.msg == "vrf `foobar` is not configured on the system"
 
 #----
 - name: Set VRF to default
diff --git a/test/integration/targets/eos_eapi/tests/cli/zzz_reset.yaml b/test/integration/targets/eos_eapi/tests/cli/zzz_reset.1
similarity index 84%
rename from test/integration/targets/eos_eapi/tests/cli/zzz_reset.yaml
rename to test/integration/targets/eos_eapi/tests/cli/zzz_reset.1
--- a/test/integration/targets/eos_eapi/tests/cli/zzz_reset.yaml
+++ b/test/integration/targets/eos_eapi/tests/cli/zzz_reset.1
@@ -1,8 +1,9 @@
 - debug: msg="START CLI/RESET.YAML"
 
 - name: Change endpoint ports back to default values
-  eos_eapi:
-    enable_local_http: yes
+  eos_config:
+    lines: default management api http-commands
+    match: none
     provider: "{{ cli }}"
   register: eos_eapi_output
   connection: local
@@ -22,8 +23,9 @@
         - http_config.stdout[0].localHttpServer.port == 8080
 
 - name: Change endpoint ports back to default values again
-  eos_eapi:
-    enable_local_http: yes
+  eos_config:
+    lines: default management api http-commands
+    match: none
     provider: "{{ cli }}"
   register: eos_eapi_output
   connection: local
diff --git a/test/units/modules/network/eos/test_eos_eapi.py b/test/units/modules/network/eos/test_eos_eapi.py
--- a/test/units/modules/network/eos/test_eos_eapi.py
+++ b/test/units/modules/network/eos/test_eos_eapi.py
@@ -39,12 +39,22 @@ def setUp(self):
         self.mock_load_config = patch('ansible.modules.network.eos.eos_eapi.load_config')
         self.load_config = self.mock_load_config.start()
 
+        self.mock_verify_state = patch('ansible.modules.network.eos.eos_eapi.verify_state')
+        self.verify_state = self.mock_verify_state.start()
+
         self.command_fixtures = {}
 
     def tearDown(self):
         self.mock_run_commands.stop()
         self.mock_load_config.stop()
 
+        # hack for older version of mock
+        # should be using patch.stopall() but CI is still failing
+        try:
+            self.mock_verify_state.stop()
+        except RuntimeError:
+            pass
+
     def load_fixtures(self, commands=None):
         def run_commands(module, commands, **kwargs):
             output = list()
@@ -137,3 +147,14 @@ def test_eos_eapi_state_absent(self):
         commands = ['management api http-commands', 'shutdown']
         self.start_configured(changed=True, commands=commands)
 
+    def test_eos_eapi_state_failed(self):
+        self.mock_verify_state.stop()
+        set_module_args(dict(state='stopped', timeout=1))
+        result = self.start_configured(failed=True)
+        'timeout expired before eapi running state changed' in result['msg']
+
+    def test_eos_eapi_state_failed(self):
+        self.mock_verify_state.stop()
+        set_module_args(dict(state='stopped', timeout=1))
+        result = self.start_configured(failed=True)
+        'timeout expired before eapi running state changed' in result['msg']