Repository: ansible/ansible

Issue:
ios_linkagg : one "end" command should not be here

##### SUMMARY
ios link aggregation is failing whith the following task due to an "end" sent at the wrong timing when using members option

##### ISSUE TYPE
 - Bug Report

##### COMPONENT NAME
ios_link_agg

##### ANSIBLE VERSION
```
ansible 2.5.3
  config file = /Users/jdsilberzahn/network-ansible/ansible.cfg
  configured module search path = [u'/Users/jdsilberzahn/network-ansible/library']
  ansible python module location = /Users/jdsilberzahn/.conda/envs/network-ansible-labs/lib/python2.7/site-packages/ansible
  executable location = /Users/jdsilberzahn/.conda/envs/network-ansible-labs/bin/ansible
  python version = 2.7.13 |Anaconda, Inc.| (default, Sep 30 2017, 13:16:00) [GCC 4.2.1 Compatible Clang 4.0.1 (tags/RELEASE_401/final)]
```

##### CONFIGURATION
<!--- If using Ansible 2.4 or above, paste, BELOW THIS COMMENT, the results of "ansible-config dump --only-changed"
Otherwise, mention any settings you have changed/added/removed in ansible.cfg
(or using the ANSIBLE_* environment variables).-->
```
ANSIBLE_FORCE_COLOR(/Users/jdsilberzahn/network-ansible/ansible.cfg) = True
ANSIBLE_NOCOWS(/Users/jdsilberzahn/network-ansible/ansible.cfg) = True
ANSIBLE_PIPELINING(/Users/jdsilberzahn/network-ansible/ansible.cfg) = True
ANSIBLE_SSH_ARGS(/Users/jdsilberzahn/network-ansible/ansible.cfg) = -o ControlMaster=auto -o ControlP
ANSIBLE_SSH_CONTROL_PATH(/Users/jdsilberzahn/network-ansible/ansible.cfg) = %(directory)s/%%h-%%p-%%r
ANSIBLE_SSH_RETRIES(/Users/jdsilberzahn/network-ansible/ansible.cfg) = 1
DEFAULT_FILTER_PLUGIN_PATH(/Users/jdsilberzahn/network-ansible/ansible.cfg) = [u'/Users/jdsilberzahn/
DEFAULT_FORKS(/Users/jdsilberzahn/network-ansible/ansible.cfg) = 16
DEFAULT_HASH_BEHAVIOUR(/Users/jdsilberzahn/network-ansible/ansible.cfg) = merge
DEFAULT_HOST_LIST(/Users/jdsilberzahn/network-ansible/ansible.cfg) = [u'/Users/jdsilberzahn/network-a
DEFAULT_MANAGED_STR(/Users/jdsilberzahn/network-ansible/ansible.cfg) = EVA (Ansible) managed file. No
DEFAULT_MODULE_PATH(/Users/jdsilberzahn/network-ansible/ansible.cfg) = [u'/Users/jdsilberzahn/network
DEFAULT_ROLES_PATH(/Users/jdsilberzahn/network-ansible/ansible.cfg) = [u'/Users/jdsilberzahn/network-
DEFAULT_TIMEOUT(/Users/jdsilberzahn/network-ansible/ansible.cfg) = 30
DISPLAY_SKIPPED_HOSTS(/Users/jdsilberzahn/network-ansible/ansible.cfg) = False
HOST_KEY_CHECKING(/Users/jdsilberzahn/network-ansible/ansible.cfg) = False
````
##### OS / ENVIRONMENT
N/A

##### STEPS TO REPRODUCE
```
 - name: Setup aggregates
   ios_linkagg:
       group: "{{ item | int + 1 }}"
       mode: active
       members:
           - "{{ interface_prefix }}/{{ portchannels.start  | int + (item | int) * 2 }}"
           - "{{ interface_prefix }}/{{ portchannels.start | int + (item | int) * 2 + 1 }}"
   with_sequence: start=0 end="{{ ((1 + portchannels.end - portchannels.start)/2) | int }}"
```

##### EXPECTED RESULTS
Should succeeds

##### ACTUAL RESULTS
Failing with incorrect cisco commands. Managed to "hack it working" by changing:
```
 def map_obj_to_commands(updates, module):
......
         if state == 'absent':
             if obj_in_have:
                 commands.append('no interface port-channel {0}'.format(group))

         elif state == 'present':
             cmd = ['interface port-channel {0}'.format(group),
                 'end']
```
into 
```
 def map_obj_to_commands(updates, module):
......
         if state == 'absent':
             if obj_in_have:
                 commands.append('no interface port-channel {0}'.format(group))

         elif state == 'present':
             cmd = ['interface port-channel {0}'.format(group)]
```
in 
```
modules/network/ios/ios_linkagg.py
```
line 314


Discussion:
Files identified in the description:
* [lib/ansible/modules/network/ios/ios_linkagg.py](https://github.com/ansible/ansible/blob/devel/lib/ansible/modules/network/ios/ios_linkagg.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 @privateip @rcarrillocruz @trishnaguha
[click here for bot help](https://github.com/ansible/ansibullbot/blob/master/ISSUE_HELP.md)
<!--- boilerplate: notify --->
@jdipsip 'end' command should not cause any problem as there are no configs which should be done under "interface port-channel <group>" . channel-group configuration is done under physical member interface so 'end' seems proper. Probably your playbook is failing for some other reason might be member physical interface does not exist on device.
Can you share Full running logs of ansible-playbook . Please run with -vvv option so we can get all debug information.
i don't have the ability to reproduce to gather the log  for the moment but 

Here is the role task that made everything failed

```
- name: Setup variables
   ios_linkagg:
       group: "{{ item | int + 1 }}"
       mode: active
       members:
           - "{{ interface_prefix }}/{{ portchannels.start  | int + (item | int) * 2 }}"
           - "{{ interface_prefix }}/{{ portchannels.start | int + (item | int) * 2 + 1 }}"
   with_sequence: start=0 end="{{ ((1 + portchannels.end - portchannels.start)/2) | int }}"
```

When following the code below in case members are defined

```
        elif state == 'present':
            cmd = ['interface port-channel {0}'.format(group),
                   'end']
            if not obj_in_have:
                if not group:
                    module.fail_json(msg='group is a required option')
                commands.extend(cmd)

                if members:
                    for m in members:
                        commands.append('interface {0}'.format(m))
                        commands.append('channel-group {0} mode {1}'.format(group, mode))
```
we have ... before this part of the code : entering in configuration mode
then:
```
interface port-channel XXX
end
interface GigabitEthernet YYYY
channel-group ....
```

as per cisco:
```
To end the current configuration session and return to privileged EXEC mode, 
use the end global configuration command.
```

with switch or router prompt this will give something like this:

```
Switch(config)# interface port-channel XXX                                                                                       
Switch(config-if)# end                                                                           
Switch# interface GigabitEthernet YYY
```
Last one will fail due to not being in conf mode

as i told the solution i put in place was hacky (no real time to dig, and had multiple port channel to provision over multiple switches) but if i am not mistaken a "more proper" fix might be replacing end by exit

I am unsure that i'll be able to get the logs soon ... but if i can get them ... i'll attach here.

(Hardware used : WS-C3650-48TD Version :  16.3.6)





Resolving patch:
diff --git a/lib/ansible/modules/network/ios/ios_linkagg.py b/lib/ansible/modules/network/ios/ios_linkagg.py
--- a/lib/ansible/modules/network/ios/ios_linkagg.py
+++ b/lib/ansible/modules/network/ios/ios_linkagg.py
@@ -130,7 +130,7 @@ def map_obj_to_commands(updates, module):
 
         elif state == 'present':
             cmd = ['interface port-channel {0}'.format(group),
-                   'end']
+                   'exit']
             if not obj_in_have:
                 if not group:
                     module.fail_json(msg='group is a required option')


Test patch: