From 3bad4d6a50aa95c84cd77ced8f560128d9048068 Mon Sep 17 00:00:00 2001
From: Trishna Guha <trishnaguha17@gmail.com>
Date: Tue, 23 Jul 2019 11:11:37 +0530
Subject: [PATCH] Add nxos_lag_interfaces resource module (#59031)

* Add nxos_lag_interfaces resource module

Signed-off-by: Trishna Guha <trishnaguha17@gmail.com>

* ix CI failure

Signed-off-by: Trishna Guha <trishnaguha17@gmail.com>

* module_utils nxos transport-provider fix

Signed-off-by: Trishna Guha <trishnaguha17@gmail.com>

* CI failure

Signed-off-by: Trishna Guha <trishnaguha17@gmail.com>

* nxos_lag_interfaces tests

Signed-off-by: Trishna Guha <trishnaguha17@gmail.com>

* Integration test

Signed-off-by: Trishna Guha <trishnaguha17@gmail.com>
---
 .../rst/porting_guides/porting_guide_2.9.rst  |   4 +-
 .../network/nxos/argspec/facts/facts.py       |   1 +
 .../nxos/argspec/lag_interfaces/__init__.py   |   0
 .../argspec/lag_interfaces/lag_interfaces.py  |  49 ++++
 .../nxos/config/lag_interfaces/__init__.py    |   0
 .../config/lag_interfaces/lag_interfaces.py   | 258 ++++++++++++++++++
 .../module_utils/network/nxos/facts/facts.py  |   3 +
 .../nxos/facts/lag_interfaces/__init__.py     |   0
 .../facts/lag_interfaces/lag_interfaces.py    | 124 +++++++++
 lib/ansible/module_utils/network/nxos/nxos.py |   5 +-
 .../module_utils/network/nxos/utils/utils.py  |  28 +-
 .../{nxos_linkagg.py => _nxos_linkagg.py}     |  11 +-
 .../modules/network/nxos/nxos_facts.py        |   2 +-
 .../network/nxos/nxos_lag_interfaces.py       | 231 ++++++++++++++++
 .../nxos_lag_interfaces/defaults/main.yaml    |   2 +
 .../targets/nxos_lag_interfaces/meta/main.yml |   2 +
 .../nxos_lag_interfaces/tasks/cli.yaml        |  20 ++
 .../nxos_lag_interfaces/tasks/main.yaml       |   2 +
 .../tests/cli/deleted.yaml                    |  66 +++++
 .../nxos_lag_interfaces/tests/cli/merged.yaml |  69 +++++
 .../tests/cli/overridden.yaml                 |  78 ++++++
 .../tests/cli/replaced.yaml                   |  73 +++++
 test/sanity/validate-modules/ignore.txt       |   2 -
 23 files changed, 997 insertions(+), 33 deletions(-)
 create mode 100644 lib/ansible/module_utils/network/nxos/argspec/lag_interfaces/__init__.py
 create mode 100644 lib/ansible/module_utils/network/nxos/argspec/lag_interfaces/lag_interfaces.py
 create mode 100644 lib/ansible/module_utils/network/nxos/config/lag_interfaces/__init__.py
 create mode 100644 lib/ansible/module_utils/network/nxos/config/lag_interfaces/lag_interfaces.py
 create mode 100644 lib/ansible/module_utils/network/nxos/facts/lag_interfaces/__init__.py
 create mode 100644 lib/ansible/module_utils/network/nxos/facts/lag_interfaces/lag_interfaces.py
 rename lib/ansible/modules/network/nxos/{nxos_linkagg.py => _nxos_linkagg.py} (98%)
 create mode 100644 lib/ansible/modules/network/nxos/nxos_lag_interfaces.py
 create mode 100644 test/integration/targets/nxos_lag_interfaces/defaults/main.yaml
 create mode 100644 test/integration/targets/nxos_lag_interfaces/meta/main.yml
 create mode 100644 test/integration/targets/nxos_lag_interfaces/tasks/cli.yaml
 create mode 100644 test/integration/targets/nxos_lag_interfaces/tasks/main.yaml
 create mode 100644 test/integration/targets/nxos_lag_interfaces/tests/cli/deleted.yaml
 create mode 100644 test/integration/targets/nxos_lag_interfaces/tests/cli/merged.yaml
 create mode 100644 test/integration/targets/nxos_lag_interfaces/tests/cli/overridden.yaml
 create mode 100644 test/integration/targets/nxos_lag_interfaces/tests/cli/replaced.yaml

diff --git a/docs/docsite/rst/porting_guides/porting_guide_2.9.rst b/docs/docsite/rst/porting_guides/porting_guide_2.9.rst
index 4f3f948adb1..8490ac51a55 100644
--- a/docs/docsite/rst/porting_guides/porting_guide_2.9.rst
+++ b/docs/docsite/rst/porting_guides/porting_guide_2.9.rst
@@ -60,7 +60,9 @@ The following modules no longer exist:
 Deprecation notices
 -------------------
 
-No notable changes
+The following modules will be removed in Ansible 2.13. Please update update your playbooks accordingly.
+
+* nxos_linkagg use :ref:`nxos_lag_interfaces <nxos_lag_interfaces_module>` instead.
 
 
 Noteworthy module changes
diff --git a/lib/ansible/module_utils/network/nxos/argspec/facts/facts.py b/lib/ansible/module_utils/network/nxos/argspec/facts/facts.py
index 6d741319c2d..00d13771b52 100644
--- a/lib/ansible/module_utils/network/nxos/argspec/facts/facts.py
+++ b/lib/ansible/module_utils/network/nxos/argspec/facts/facts.py
@@ -8,6 +8,7 @@ The arg spec for the nxos facts module.
 """
 CHOICES = [
     'all',
+    'lag_interfaces',
 ]
 
 
diff --git a/lib/ansible/module_utils/network/nxos/argspec/lag_interfaces/__init__.py b/lib/ansible/module_utils/network/nxos/argspec/lag_interfaces/__init__.py
new file mode 100644
index 00000000000..e69de29bb2d
diff --git a/lib/ansible/module_utils/network/nxos/argspec/lag_interfaces/lag_interfaces.py b/lib/ansible/module_utils/network/nxos/argspec/lag_interfaces/lag_interfaces.py
new file mode 100644
index 00000000000..dd200c177d9
--- /dev/null
+++ b/lib/ansible/module_utils/network/nxos/argspec/lag_interfaces/lag_interfaces.py
@@ -0,0 +1,49 @@
+# -*- coding: utf-8 -*-
+# Copyright 2019 Red Hat
+# GNU General Public License v3.0+
+# (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt)
+
+#############################################
+#                WARNING                    #
+#############################################
+#
+# This file is auto generated by the resource
+#   module builder playbook.
+#
+# Do not edit this file manually.
+#
+# Changes to this file will be over written
+#   by the resource module builder.
+#
+# Changes should be made in the model used to
+#   generate this file or in the resource module
+#   builder template.
+#
+#############################################
+
+"""
+The arg spec for the nxos_lag_interfaces module
+"""
+from __future__ import absolute_import, division, print_function
+__metaclass__ = type
+
+
+class Lag_interfacesArgs(object):
+    """The arg spec for the nxos_lag_interfaces module
+    """
+
+    def __init__(self, **kwargs):
+        pass
+
+    argument_spec = {'config': {'elements': 'dict',
+                                'options': {'members': {'elements': 'dict',
+                                                        'options': {'member': {'type': 'str'},
+                                                                    'mode': {'type': 'str',
+                                                                             'choices': ['active', 'on', 'passive']},
+                                                                    'force': {'type': 'bool'}},
+                                                        'type': 'list'},
+                                            'name': {'required': True, 'type': 'str'}},
+                                'type': 'list'},
+                     'state': {'choices': ['merged', 'replaced', 'overridden', 'deleted'],
+                               'default': 'merged',
+                               'type': 'str'}}
diff --git a/lib/ansible/module_utils/network/nxos/config/lag_interfaces/__init__.py b/lib/ansible/module_utils/network/nxos/config/lag_interfaces/__init__.py
new file mode 100644
index 00000000000..e69de29bb2d
diff --git a/lib/ansible/module_utils/network/nxos/config/lag_interfaces/lag_interfaces.py b/lib/ansible/module_utils/network/nxos/config/lag_interfaces/lag_interfaces.py
new file mode 100644
index 00000000000..1f7878194e3
--- /dev/null
+++ b/lib/ansible/module_utils/network/nxos/config/lag_interfaces/lag_interfaces.py
@@ -0,0 +1,258 @@
+#
+# -*- coding: utf-8 -*-
+# Copyright 2019 Red Hat
+# GNU General Public License v3.0+
+# (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt)
+"""
+The junos_lag_interfaces class
+It is in this file where the current configuration (as dict)
+is compared to the provided configuration (as dict) and the command set
+necessary to bring the current configuration to it's desired end-state is
+created
+"""
+from __future__ import absolute_import, division, print_function
+__metaclass__ = type
+
+from ansible.module_utils.network.common.cfg.base import ConfigBase
+from ansible.module_utils.network.common.utils import to_list, remove_empties
+from ansible.module_utils.network.nxos.facts.facts import Facts
+from ansible.module_utils.network.nxos.utils.utils import get_interface_type, normalize_interface, search_obj_in_list
+
+
+class Lag_interfaces(ConfigBase):
+    """
+    The nxos_lag_interfaces class
+    """
+
+    gather_subset = [
+        '!all',
+        '!min',
+    ]
+
+    gather_network_resources = [
+        'lag_interfaces',
+    ]
+
+    def __init__(self, module):
+        super(Lag_interfaces, self).__init__(module)
+
+    def get_lag_interfaces_facts(self):
+        """ Get the 'facts' (the current configuration)
+
+        :rtype: A dictionary
+        :returns: The current configuration as a dictionary
+        """
+        facts, _warnings = Facts(self._module).get_facts(self.gather_subset, self.gather_network_resources)
+        lag_interfaces_facts = facts['ansible_network_resources'].get('lag_interfaces')
+        if not lag_interfaces_facts:
+            return []
+        return lag_interfaces_facts
+
+    def execute_module(self):
+        """ Execute the module
+
+        :rtype: A dictionary
+        :returns: The result from module execution
+        """
+        result = {'changed': False}
+        commands = list()
+        warnings = list()
+
+        existing_lag_interfaces_facts = self.get_lag_interfaces_facts()
+        commands.extend(self.set_config(existing_lag_interfaces_facts))
+        if commands:
+            if not self._module.check_mode:
+                resp = self._connection.edit_config(commands)
+                if 'response' in resp:
+                    for item in resp['response']:
+                        if item:
+                            err_str = item
+                            if err_str.lower().startswith('cannot add'):
+                                self._module.fail_json(msg=err_str)
+            result['changed'] = True
+        result['commands'] = commands
+
+        changed_lag_interfaces_facts = self.get_lag_interfaces_facts()
+
+        result['before'] = existing_lag_interfaces_facts
+        if result['changed']:
+            result['after'] = changed_lag_interfaces_facts
+
+        result['warnings'] = warnings
+        return result
+
+    def set_config(self, existing_lag_interfaces_facts):
+        """ Collect the configuration from the args passed to the module,
+            collect the current configuration (as a dict from facts)
+
+        :rtype: A list
+        :returns: the commands necessary to migrate the current configuration
+                  to the desired configuration
+        """
+        want = self._module.params.get('config')
+        if want:
+            for w in want:
+                w.update(remove_empties(w))
+                if 'members' in w and w['members']:
+                    for item in w['members']:
+                        item.update({'member': normalize_interface(item['member'])})
+        have = existing_lag_interfaces_facts
+        resp = self.set_state(want, have)
+        return to_list(resp)
+
+    def set_state(self, want, have):
+        """ Select the appropriate function based on the state provided
+
+        :param want: the desired configuration as a dictionary
+        :param have: the current configuration as a dictionary
+        :rtype: A list
+        :returns: the commands necessary to migrate the current configuration
+                  to the desired configuration
+        """
+        state = self._module.params['state']
+        commands = list()
+
+        if state == 'overridden':
+            commands.extend(self._state_overridden(want, have))
+        elif state == 'deleted':
+            commands.extend(self._state_deleted(want, have))
+        else:
+            for w in want:
+                if state == 'merged':
+                    commands.extend(self._state_merged(w, have))
+                if state == 'replaced':
+                    commands.extend(self._state_replaced(w, have))
+        return commands
+
+    def _state_replaced(self, w, have):
+        """ The command generator when state is replaced
+
+        :rtype: A list
+        :returns: the commands necessary to migrate the current configuration
+                  to the desired configuration
+        """
+        commands = []
+        merged_commands = self.set_commands(w, have)
+        replaced_commands = self.del_intf_commands(w, have)
+        if merged_commands:
+            commands.extend(replaced_commands)
+            commands.extend(merged_commands)
+        return commands
+
+    def _state_overridden(self, want, have):
+        """ The command generator when state is overridden
+
+        :rtype: A list
+        :returns: the commands necessary to migrate the current configuration
+                  to the desired configuration
+        """
+        commands = []
+        for h in have:
+            obj_in_want = search_obj_in_list(h['name'], want, 'name')
+            if h == obj_in_want:
+                continue
+            commands.extend(self.del_all_commands(h))
+        for w in want:
+            commands.extend(self.set_commands(w, have))
+        return commands
+
+    def _state_merged(self, w, have):
+        """ The command generator when state is merged
+
+        :rtype: A list
+        :returns: the commands necessary to merge the provided into
+                  the current configuration
+        """
+        return self.set_commands(w, have)
+
+    def _state_deleted(self, want, have):
+        """ The command generator when state is deleted
+
+        :rtype: A list
+        :returns: the commands necessary to remove the current configuration
+                  of the provided objects
+        """
+        commands = []
+        if want:
+            for w in want:
+                obj_in_have = search_obj_in_list(w['name'], have, 'name')
+                commands.extend(self.del_all_commands(obj_in_have))
+        else:
+            if not have:
+                return commands
+            for h in have:
+                commands.extend(self.del_all_commands(h))
+        return commands
+
+    def diff_list_of_dicts(self, w, h):
+        diff = []
+        set_w = set(tuple(d.items()) for d in w)
+        set_h = set(tuple(d.items()) for d in h)
+        difference = set_w.difference(set_h)
+        for element in difference:
+            diff.append(dict((x, y) for x, y in element))
+        return diff
+
+    def intersect_list_of_dicts(self, w, h):
+        intersect = []
+        wmem = []
+        hmem = []
+        for d in w:
+            wmem.append({'member': d['member']})
+        for d in h:
+            hmem.append({'member': d['member']})
+        set_w = set(tuple(sorted(d.items())) for d in wmem)
+        set_h = set(tuple(sorted(d.items())) for d in hmem)
+        intersection = set_w.intersection(set_h)
+        for element in intersection:
+            intersect.append(dict((x, y) for x, y in element))
+        return intersect
+
+    def add_commands(self, diff, name):
+        commands = []
+        name = name.strip('port-channel')
+        for d in diff:
+            commands.append('interface' + ' ' + d['member'])
+            cmd = ''
+            group_cmd = 'channel-group {0}'.format(name)
+            if 'force' in d:
+                cmd = group_cmd + ' force ' + d['force']
+            if 'mode' in d:
+                if cmd:
+                    cmd = cmd + ' mode ' + d['mode']
+                else:
+                    cmd = group_cmd + ' mode ' + d['mode']
+            if not cmd:
+                cmd = group_cmd
+            commands.append(cmd)
+        return commands
+
+    def set_commands(self, w, have):
+        commands = []
+        obj_in_have = search_obj_in_list(w['name'], have, 'name')
+        if not obj_in_have:
+            commands = self.add_commands(w['members'], w['name'])
+        else:
+            diff = self.diff_list_of_dicts(w['members'], obj_in_have['members'])
+            commands = self.add_commands(diff, w['name'])
+        return commands
+
+    def del_all_commands(self, obj_in_have):
+        commands = []
+        if not obj_in_have:
+            return commands
+        for m in obj_in_have['members']:
+            commands.append('interface' + ' ' + m['member'])
+            commands.append('no channel-group')
+        return commands
+
+    def del_intf_commands(self, w, have):
+        commands = []
+        obj_in_have = search_obj_in_list(w['name'], have, 'name')
+        if obj_in_have:
+            lst_to_del = self.intersect_list_of_dicts(w['members'], obj_in_have['members'])
+            if lst_to_del:
+                for item in lst_to_del:
+                    commands.append('interface' + ' ' + item['member'])
+                    commands.append('no channel-group')
+        return commands
diff --git a/lib/ansible/module_utils/network/nxos/facts/facts.py b/lib/ansible/module_utils/network/nxos/facts/facts.py
index 26b3d9e3ff8..9db75c2f427 100644
--- a/lib/ansible/module_utils/network/nxos/facts/facts.py
+++ b/lib/ansible/module_utils/network/nxos/facts/facts.py
@@ -12,6 +12,8 @@ calls the appropriate facts gathering function
 from ansible.module_utils.network.nxos.argspec.facts.facts import FactsArgs
 from ansible.module_utils.network.common.facts.facts import FactsBase
 from ansible.module_utils.network.nxos.facts.legacy.base import Default, Legacy, Hardware, Config, Interfaces, Features
+from ansible.module_utils.network.nxos.facts.lag_interfaces.lag_interfaces import Lag_interfacesFacts
+
 
 FACT_LEGACY_SUBSETS = dict(
     default=Default,
@@ -22,6 +24,7 @@ FACT_LEGACY_SUBSETS = dict(
     features=Features,
 )
 FACT_RESOURCE_SUBSETS = dict(
+    lag_interfaces=Lag_interfacesFacts,
 )
 
 
diff --git a/lib/ansible/module_utils/network/nxos/facts/lag_interfaces/__init__.py b/lib/ansible/module_utils/network/nxos/facts/lag_interfaces/__init__.py
new file mode 100644
index 00000000000..e69de29bb2d
diff --git a/lib/ansible/module_utils/network/nxos/facts/lag_interfaces/lag_interfaces.py b/lib/ansible/module_utils/network/nxos/facts/lag_interfaces/lag_interfaces.py
new file mode 100644
index 00000000000..983212409dc
--- /dev/null
+++ b/lib/ansible/module_utils/network/nxos/facts/lag_interfaces/lag_interfaces.py
@@ -0,0 +1,124 @@
+#
+# -*- coding: utf-8 -*-
+# Copyright 2019 Red Hat
+# GNU General Public License v3.0+
+# (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt)#!/usr/bin/python
+"""
+The nxos lag_interfaces fact class
+It is in this file the configuration is collected from the device
+for a given resource, parsed, and the facts tree is populated
+based on the configuration.
+"""
+from __future__ import absolute_import, division, print_function
+__metaclass__ = type
+
+import re
+from copy import deepcopy
+
+from ansible.module_utils.network.common import utils
+from ansible.module_utils.network.nxos.argspec.lag_interfaces.lag_interfaces import Lag_interfacesArgs
+from ansible.module_utils.network.nxos.utils.utils import get_interface_type, normalize_interface
+
+
+class Lag_interfacesFacts(object):
+    """ The nxos lag_interfaces fact class
+    """
+
+    def __init__(self, module, subspec='config', options='options'):
+        self._module = module
+        self.argument_spec = Lag_interfacesArgs.argument_spec
+        spec = deepcopy(self.argument_spec)
+        if subspec:
+            if options:
+                facts_argument_spec = spec[subspec][options]
+            else:
+                facts_argument_spec = spec[subspec]
+        else:
+            facts_argument_spec = spec
+
+        self.generated_spec = utils.generate_dict(facts_argument_spec)
+
+    def populate_facts(self, connection, ansible_facts, data=None):
+        """ Populate the facts for lag_interfaces
+        :param connection: the device connection
+        :param data: previously collected conf
+        :rtype: dictionary
+        :returns: facts
+        """
+        objs = []
+        if not data:
+            data = connection.get('show running-config | include channel-group')
+        config = re.split('(\n  |)channel-group ', data)
+        config = list(dict.fromkeys(config))
+        for conf in config:
+            if conf:
+                obj = self.render_config(self.generated_spec, conf, connection)
+                if obj and len(obj.keys()) > 1:
+                    objs.append(obj)
+
+        ansible_facts['ansible_network_resources'].pop('lag_interfaces', None)
+        facts = {}
+        if objs:
+            facts['lag_interfaces'] = []
+            params = utils.validate_config(self.argument_spec, {'config': objs})
+            for cfg in params['config']:
+                facts['lag_interfaces'].append(utils.remove_empties(cfg))
+
+        ansible_facts['ansible_network_resources'].update(facts)
+        return ansible_facts
+
+    def get_members(self, id, connection):
+        """
+        Returns members associated with a channel-group
+
+        :param name: The channel group
+        :rtype: list
+        :returns: Members
+        """
+        members = []
+        data = connection.get('show port-channel summary')
+        match = re.search(r'{0} (.+)(|\n)'.format(id), data)
+        if match:
+            interfaces = re.search(r'Eth\d(.+)$', match.group())
+            if interfaces:
+                for i in interfaces.group().split():
+                    if get_interface_type(i[:-3]) != 'unknown':
+                        members.append(normalize_interface(i[:-3]))
+
+        return members
+
+    def render_config(self, spec, conf, connection):
+        """
+        Render config as dictionary structure and delete keys
+          from spec for null values
+
+        :param spec: The facts tree, generated from the argspec
+        :param conf: The configuration
+        :rtype: dictionary
+        :returns: The generated config
+        """
+        config = deepcopy(spec)
+        match = re.search(r'(\d+)( |)(force )?(mode \S+)?', conf, re.M)
+        if match:
+            matches = match.groups()
+            config['name'] = 'port-channel' + str(matches[0])
+            config['members'] = []
+            members = self.get_members(config['name'].strip('port-channel'), connection)
+            if members:
+                for m in members:
+                    m_dict = {}
+                    if matches[2]:
+                        m_dict['force'] = matches[2]
+                    if matches[3]:
+                        m_dict['mode'] = matches[3][5:]
+                    m_dict['member'] = m
+                    config['members'].append(m_dict)
+        else:
+            config = {}
+
+        lag_intf_cfg = utils.remove_empties(config)
+        # if lag interfaces config is not present return empty dict
+        if len(lag_intf_cfg) == 1:
+            return {}
+        else:
+            return lag_intf_cfg
diff --git a/lib/ansible/module_utils/network/nxos/nxos.py b/lib/ansible/module_utils/network/nxos/nxos.py
index 9248c6481b7..1f5d08bfcc9 100644
--- a/lib/ansible/module_utils/network/nxos/nxos.py
+++ b/lib/ansible/module_utils/network/nxos/nxos.py
@@ -1135,8 +1135,9 @@ def is_text(cmd):
 
 
 def is_local_nxapi(module):
-    transport = module.params['transport']
-    provider_transport = (module.params['provider'] or {}).get('transport')
+    transport = module.params.get('transport')
+    provider = module.params.get('provider')
+    provider_transport = provider['transport'] if provider else None
     return 'nxapi' in (transport, provider_transport)
 
 
diff --git a/lib/ansible/module_utils/network/nxos/utils/utils.py b/lib/ansible/module_utils/network/nxos/utils/utils.py
index 568d965f1bf..10f83d6f941 100644
--- a/lib/ansible/module_utils/network/nxos/utils/utils.py
+++ b/lib/ansible/module_utils/network/nxos/utils/utils.py
@@ -3,37 +3,13 @@ import socket
 from ansible.module_utils.six import iteritems
 
 
-def search_obj_in_list(name, lst):
+def search_obj_in_list(name, lst, identifier):
     for o in lst:
-        if o['name'] == name:
+        if o[identifier] == name:
             return o
     return None
 
 
-def eliminate_null_keys(cfg_dict):
-    if not cfg_dict:
-        return None
-    if isinstance(cfg_dict, list):
-        final_cfg = []
-        for cfg in cfg_dict:
-            final_cfg.append(_null_keys(cfg))
-    else:
-        final_cfg = _null_keys(cfg_dict)
-
-    return final_cfg
-
-
-def _null_keys(cfg):
-    final_cfg = {}
-    for k, v in iteritems(cfg):
-        if v:
-            if isinstance(v, dict):
-                final_cfg.update(eliminate_null_keys(v))
-            else:
-                final_cfg.update({k: v})
-    return final_cfg
-
-
 def validate_ipv4_addr(address):
     address = address.split('/')[0]
     try:
diff --git a/lib/ansible/modules/network/nxos/nxos_linkagg.py b/lib/ansible/modules/network/nxos/_nxos_linkagg.py
similarity index 98%
rename from lib/ansible/modules/network/nxos/nxos_linkagg.py
rename to lib/ansible/modules/network/nxos/_nxos_linkagg.py
index 2e6d6fd3cc0..03c3822a11d 100644
--- a/lib/ansible/modules/network/nxos/nxos_linkagg.py
+++ b/lib/ansible/modules/network/nxos/_nxos_linkagg.py
@@ -9,7 +9,7 @@ __metaclass__ = type
 
 
 ANSIBLE_METADATA = {'metadata_version': '1.1',
-                    'status': ['preview'],
+                    'status': ['deprecated'],
                     'supported_by': 'network'}
 
 DOCUMENTATION = """
@@ -21,6 +21,10 @@ short_description: Manage link aggregation groups on Cisco NXOS devices.
 description:
   - This module provides declarative management of link aggregation groups
     on Cisco NXOS devices.
+deprecated:
+  removed_in: '2.13'
+  alternative: nxos_lag_interfaces
+  why: Updated modules released with more functionality.
 author:
   - Trishna Guha (@trishnaguha)
 notes:
@@ -42,13 +46,16 @@ options:
       - Mode for the link aggregation group.
     choices: [ active, 'on', passive ]
     default: 'on'
+    type: str
   min_links:
     description:
       - Minimum number of ports required up
         before bringing up the link aggregation group.
+    type: int
   members:
     description:
       - List of interfaces that will be managed in the link aggregation group.
+    type: list
   force:
     description:
       - When true it forces link aggregation group members to match what
@@ -57,11 +64,13 @@ options:
     default: 'no'
   aggregate:
     description: List of link aggregation definitions.
+    type: list
   state:
     description:
       - State of the link aggregation group.
     default: present
     choices: ['present','absent']
+    type: str
   purge:
     description:
       - Purge links not defined in the I(aggregate) parameter.
diff --git a/lib/ansible/modules/network/nxos/nxos_facts.py b/lib/ansible/modules/network/nxos/nxos_facts.py
index 6335bfa93b0..b5965c4248c 100644
--- a/lib/ansible/modules/network/nxos/nxos_facts.py
+++ b/lib/ansible/modules/network/nxos/nxos_facts.py
@@ -57,7 +57,7 @@ options:
         to a given subset. Possible values for this argument include
         all and the resources like interfaces, vlans etc.
         Can specify a list of values to include a larger subset.
-    choices: ['all']
+    choices: ['all', 'lag_interfaces']
     required: false
     version_added: "2.9"
 """
diff --git a/lib/ansible/modules/network/nxos/nxos_lag_interfaces.py b/lib/ansible/modules/network/nxos/nxos_lag_interfaces.py
new file mode 100644
index 00000000000..b3a2ad75f43
--- /dev/null
+++ b/lib/ansible/modules/network/nxos/nxos_lag_interfaces.py
@@ -0,0 +1,231 @@
+#!/usr/bin/python
+# -*- coding: utf-8 -*-
+# Copyright 2019 Red Hat
+# GNU General Public License v3.0+
+# (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt)
+
+#############################################
+#                WARNING                    #
+#############################################
+#
+# This file is auto generated by the resource
+#   module builder playbook.
+#
+# Do not edit this file manually.
+#
+# Changes to this file will be over written
+#   by the resource module builder.
+#
+# Changes should be made in the model used to
+#   generate this file or in the resource module
+#   builder template.
+#
+#############################################
+
+"""
+The module file for nxos_lag_interfaces
+"""
+
+from __future__ import absolute_import, division, print_function
+__metaclass__ = type
+
+ANSIBLE_METADATA = {'metadata_version': '1.1',
+                    'status': ['preview'],
+                    'supported_by': 'network'}
+
+
+DOCUMENTATION = """
+---
+module: nxos_lag_interfaces
+version_added: 2.9
+short_description: Manages link aggregation groups of NX-OS Interfaces
+description: This module manages attributes of link aggregation groups of NX-OS Interfaces.
+author: Trishna Guha (@trishnaguha)
+options:
+  config:
+    description: A list of link aggregation group configurations.
+    type: list
+    suboptions:
+      name:
+        description:
+          - Name of the link aggregation group (LAG).
+        type: str
+        required: true
+      members:
+        description:
+          - The list of interfaces that are part of the group.
+        type: list
+        suboptions:
+          member:
+            description:
+              - The interface name.
+            type: str
+          mode:
+            description:
+              - Link aggregation group (LAG).
+            type: str
+            choices:
+              - active
+              - on
+              - passive
+          force:
+            description:
+              - When true it forces link aggregation group members to match what
+                is declared in the members param. This can be used to remove members.
+            type: bool
+  state:
+    description:
+      - The state the configuration should be left in.
+    type: str
+    choices:
+      - merged
+      - replaced
+      - overridden
+      - deleted
+    default: merged
+notes:
+  - Tested against NXOS 7.3.(0)D1(1) on VIRL.
+  - This module works with connection C(network_cli).
+"""
+EXAMPLES = """
+# Using merged
+
+# Before state:
+# -------------
+#
+# interface Ethernet1/4
+
+- name: Merge provided configuration with device configuration.
+  nxos_lag_interfaces:
+    config:
+      - name: port-channel99
+        members:
+          - member: Ethernet1/4
+    state: merged
+
+# After state:
+# ------------
+#
+# interface Ethernet1/4
+#   channel-group 99
+
+
+# Using replaced
+
+# Before state:
+# -------------
+#
+# interface Ethernet1/4
+#   channel-group 99 mode active
+
+- name: Replace device configuration of specified LAG attributes of given interfaces with provided configuration.
+  nxos_lag_interfaces:
+    config:
+      - name: port-channel10
+        members:
+          - member: Ethernet1/4
+    state: replaced
+
+# After state:
+# ------------
+#
+# interface Ethernet1/4
+#   channel-group 10
+
+
+# Using overridden
+
+# Before state:
+# -------------
+#
+# interface Ethernet1/4
+#   channel-group 10
+# interface Ethernet1/2
+#   channel-group 99 mode passive
+
+- name: Override device configuration of all LAG attributes of given interfaces on device with provided configuration.
+  nxos_lag_interfaces:
+    config:
+      - name: port-channel20
+        members:
+          - member: Ethernet1/6
+            force: True
+    state: overridden
+
+# After state:
+# ------------
+# interface Ethernet1/2
+# interface Ethernet1/4
+# interface Ethernet1/6
+#   channel-group 20 force
+
+
+# Using deleted
+
+# Before state:
+# -------------
+#
+# interface Ethernet1/4
+#   channel-group 99 mode active
+
+- name: Delete LAG attributes of given interface (This won't delete the port-channel itself).
+  nxos_lag_interfaces:
+    config:
+      - port-channel: port-channel99
+    state: deleted
+
+- name: Delete LAG attributes of all the interfaces
+  nxos_lag_interfaces:
+    state: deleted
+
+# After state:
+# ------------
+#
+# interface Ethernet1/4
+#   no channel-group 99
+
+
+"""
+RETURN = """
+before:
+  description: The configuration prior to the model invocation.
+  returned: always
+  type: list
+  sample: >
+    The configuration returned will always be in the same format
+     of the parameters above.
+after:
+  description: The resulting configuration model invocation.
+  returned: when changed
+  type: list
+  sample: >
+    The configuration returned will always be in the same format
+     of the parameters above.
+commands:
+  description: The set of commands pushed to the remote device.
+  returned: always
+  type: list
+  sample: ['command 1', 'command 2', 'command 3']
+"""
+
+
+from ansible.module_utils.basic import AnsibleModule
+from ansible.module_utils.network.nxos.argspec.lag_interfaces.lag_interfaces import Lag_interfacesArgs
+from ansible.module_utils.network.nxos.config.lag_interfaces.lag_interfaces import Lag_interfaces
+
+
+def main():
+    """
+    Main entry point for module execution
+
+    :returns: the result form module invocation
+    """
+    module = AnsibleModule(argument_spec=Lag_interfacesArgs.argument_spec,
+                           supports_check_mode=True)
+
+    result = Lag_interfaces(module).execute_module()
+    module.exit_json(**result)
+
+
+if __name__ == '__main__':
+    main()
diff --git a/test/integration/targets/nxos_lag_interfaces/defaults/main.yaml b/test/integration/targets/nxos_lag_interfaces/defaults/main.yaml
new file mode 100644
index 00000000000..5f709c5aac1
--- /dev/null
+++ b/test/integration/targets/nxos_lag_interfaces/defaults/main.yaml
@@ -0,0 +1,2 @@
+---
+testcase: "*"
diff --git a/test/integration/targets/nxos_lag_interfaces/meta/main.yml b/test/integration/targets/nxos_lag_interfaces/meta/main.yml
new file mode 100644
index 00000000000..ae741cbdc71
--- /dev/null
+++ b/test/integration/targets/nxos_lag_interfaces/meta/main.yml
@@ -0,0 +1,2 @@
+dependencies:
+  - prepare_nxos_tests
diff --git a/test/integration/targets/nxos_lag_interfaces/tasks/cli.yaml b/test/integration/targets/nxos_lag_interfaces/tasks/cli.yaml
new file mode 100644
index 00000000000..76743023ce0
--- /dev/null
+++ b/test/integration/targets/nxos_lag_interfaces/tasks/cli.yaml
@@ -0,0 +1,20 @@
+---
+- name: collect cli test cases
+  find:
+    paths: "{{ role_path }}/tests/cli"
+    patterns: "{{ testcase }}.yaml"
+  connection: local
+  register: test_cases
+
+- set_fact:
+    test_cases:
+      files: "{{ test_cases.files }}"
+
+- name: set test_items
+  set_fact: test_items="{{ test_cases.files | map(attribute='path') | list }}"
+
+- name: run test cases (connection=network_cli)
+  include: "{{ test_case_to_run }} ansible_connection=network_cli connection={{ cli }}"
+  with_items: "{{ test_items }}"
+  loop_control:
+    loop_var: test_case_to_run
diff --git a/test/integration/targets/nxos_lag_interfaces/tasks/main.yaml b/test/integration/targets/nxos_lag_interfaces/tasks/main.yaml
new file mode 100644
index 00000000000..415c99d8b12
--- /dev/null
+++ b/test/integration/targets/nxos_lag_interfaces/tasks/main.yaml
@@ -0,0 +1,2 @@
+---
+- { include: cli.yaml, tags: ['cli'] }
diff --git a/test/integration/targets/nxos_lag_interfaces/tests/cli/deleted.yaml b/test/integration/targets/nxos_lag_interfaces/tests/cli/deleted.yaml
new file mode 100644
index 00000000000..68bef590867
--- /dev/null
+++ b/test/integration/targets/nxos_lag_interfaces/tests/cli/deleted.yaml
@@ -0,0 +1,66 @@
+---
+- debug:
+    msg: "Start nxos_lag_interfaces merged integration tests connection={{ ansible_connection }}"
+
+- set_fact: test_int1="{{ nxos_int1 }}"
+- set_fact: test_int2="{{ nxos_int2 }}"
+
+- name: enable feature lacp
+  nxos_feature:
+    feature: lacp
+
+- name: setup
+  nxos_config:
+    lines:
+      - "channel-group 10"
+    parents: "{{ item }}"
+  loop:
+    - "interface {{ test_int1 }}"
+    - "interface {{ test_int2 }}"
+
+- name: Gather LAG interfaces facts
+  nxos_facts: &facts
+    gather_subset:
+      - '!all'
+      - '!min'
+    gather_network_resources: lag_interfaces
+
+- name: deleted
+  nxos_lag_interfaces: &deleted
+    state: deleted
+  register: result
+
+- assert:
+    that:
+      - "ansible_facts.network_resources.lag_interfaces|symmetric_difference(result.before)|length == 0"
+
+- name: Gather LAG interfaces post facts
+  nxos_facts: *facts
+
+- assert:
+    that:
+      - "result.after|length == 0"
+      - "result.changed == true"
+
+- name: Idempotence - deleted
+  nxos_lag_interfaces: *deleted
+  register: result
+
+- assert:
+    that:
+      - "result.changed == false"
+
+- name: teardown
+  nxos_config:
+    lines:
+      - "no channel-group 10"
+    parents: "{{ item }}"
+  ignore_errors: yes
+  loop:
+    - "interface {{ test_int1 }}"
+    - "interface {{ test_int2 }}"
+
+- name: disable feature lacp
+  nxos_feature:
+    feature: lacp
+    state: disabled
diff --git a/test/integration/targets/nxos_lag_interfaces/tests/cli/merged.yaml b/test/integration/targets/nxos_lag_interfaces/tests/cli/merged.yaml
new file mode 100644
index 00000000000..cd2ec4f8d52
--- /dev/null
+++ b/test/integration/targets/nxos_lag_interfaces/tests/cli/merged.yaml
@@ -0,0 +1,69 @@
+---
+- debug:
+    msg: "Start nxos_lag_interfaces merged integration tests connection={{ ansible_connection }}"
+
+- set_fact: test_int1="{{ nxos_int1 }}"
+- set_fact: test_int2="{{ nxos_int2 }}"
+
+- name: enable feature lacp
+  nxos_feature:
+    feature: lacp
+
+- name: setup
+  nxos_config:
+    lines:
+      - "no channel-group"
+    parents: "{{ item }}"
+  ignore_errors: yes
+  loop:
+    - "interface {{ test_int1 }}"
+    - "interface {{ test_int2 }}"
+
+- name: Merged
+  nxos_lag_interfaces: &merged
+    config:
+      - name: port-channel10
+        members:
+          - member: "{{ test_int1 }}"
+          - member: "{{ test_int2 }}"
+    state: merged
+  register: result
+
+- assert:
+    that:
+      - "result.before|length == 0"
+      - "result.changed == true"
+
+- name: Gather LAG interfaces facts
+  nxos_facts:
+    gather_subset:
+      - '!all'
+      - '!min'
+    gather_network_resources: lag_interfaces
+
+- assert:
+    that:
+      - "ansible_facts.network_resources.lag_interfaces|symmetric_difference(result.after)|length == 0"
+
+- name: Idempotence - Merged
+  nxos_lag_interfaces: *merged
+  register: result
+
+- assert:
+    that:
+      - "result.changed == false"
+
+- name: teardown
+  nxos_config:
+    lines:
+      - "no channel-group"
+    parents: "{{ item }}"
+  ignore_errors: yes
+  loop:
+    - "interface {{ test_int1 }}"
+    - "interface {{ test_int2 }}"
+
+- name: disable feature lacp
+  nxos_feature:
+    feature: lacp
+    state: disabled
diff --git a/test/integration/targets/nxos_lag_interfaces/tests/cli/overridden.yaml b/test/integration/targets/nxos_lag_interfaces/tests/cli/overridden.yaml
new file mode 100644
index 00000000000..6deed5ecd5f
--- /dev/null
+++ b/test/integration/targets/nxos_lag_interfaces/tests/cli/overridden.yaml
@@ -0,0 +1,78 @@
+---
+- debug:
+    msg: "Start nxos_lag_interfaces merged integration tests connection={{ ansible_connection }}"
+
+- set_fact: test_int1="{{ nxos_int1 }}"
+- set_fact: test_int2="{{ nxos_int2 }}"
+- set_fact: test_int3="{{ nxos_int3 }}"
+
+- name: enable feature lacp
+  nxos_feature:
+    feature: lacp
+
+- name: setup
+  nxos_config:
+    lines:
+      - "channel-group 10"
+    parents: "{{ item }}"
+  ignore_errors: yes
+  loop:
+    - "interface {{ test_int1 }}"
+    - "interface {{ test_int2 }}"
+
+- name: Gather LAG interfaces facts
+  nxos_facts: &facts
+    gather_subset:
+      - '!all'
+      - '!min'
+    gather_network_resources: lag_interfaces
+
+- name: Merged
+  nxos_lag_interfaces: &overridden
+    config:
+      - name: port-channel19
+        members:
+          - member: "{{ test_int3 }}"
+    state: merged
+  register: result
+
+- assert:
+    that:
+      - "ansible_facts.network_resources.lag_interfaces|symmetric_difference(result.before)|length == 0"
+      - "result.changed == true"
+
+- name: Gather LAG interfaces post facts
+  nxos_facts: *facts
+
+- assert:
+    that:
+      - "ansible_facts.network_resources.lag_interfaces|symmetric_difference(result.after)|length == 0"
+
+- name: Idempotence - Overridden
+  nxos_lag_interfaces: *overridden
+  register: result
+
+- assert:
+    that:
+      - "result.changed == false"
+
+- name: teardown1
+  nxos_config:
+    lines:
+      - "no channel-group 10"
+    parents: "{{ item }}"
+  ignore_errors: yes
+  loop:
+    - "interface {{ test_int1 }}"
+    - "interface {{ test_int2 }}"
+
+- name: teardown2
+  nxos_config:
+    lines:
+      - "no channel-group 19"
+    parents: "interface {{ test_int3 }}"
+
+- name: disable feature lacp
+  nxos_feature:
+    feature: lacp
+    state: disabled
diff --git a/test/integration/targets/nxos_lag_interfaces/tests/cli/replaced.yaml b/test/integration/targets/nxos_lag_interfaces/tests/cli/replaced.yaml
new file mode 100644
index 00000000000..5f740f861e6
--- /dev/null
+++ b/test/integration/targets/nxos_lag_interfaces/tests/cli/replaced.yaml
@@ -0,0 +1,73 @@
+---
+- debug:
+    msg: "Start nxos_lag_interfaces merged integration tests connection={{ ansible_connection }}"
+
+- set_fact: test_int1="{{ nxos_int1 }}"
+- set_fact: test_int2="{{ nxos_int2 }}"
+
+- name: enable feature lacp
+  nxos_feature:
+    feature: lacp
+
+- name: setup
+  nxos_config:
+    lines:
+      - "channel-group 10"
+    parents: "{{ item }}"
+  ignore_errors: yes
+  loop:
+    - "interface {{ test_int1 }}"
+    - "interface {{ test_int2 }}"
+
+- name: Gather LAG interfaces facts
+  nxos_facts: &facts
+    gather_subset:
+      - '!all'
+      - '!min'
+    gather_network_resources: lag_interfaces
+
+- name: Replaced
+  nxos_lag_interfaces: &replaced
+    config:
+      - name: port-channel11
+        members:
+          - member: "{{ test_int2 }}"
+            mode: active
+    state: replaced
+  register: result
+
+- assert:
+    that:
+      - "ansible_facts.network_resources.lag_interfaces|symmetric_difference(result.before)|length == 0"
+
+- name: Gather LAG interfaces post facts
+  nxos_facts: *facts
+
+- assert:
+    that:
+      - "ansible_facts.network_resources.lag_interfaces|symmetric_difference(result.after)|length == 0"
+
+- name: Idempotence - Replaced
+  nxos_lag_interfaces: *replaced
+  register: result
+
+- assert:
+    that:
+      - "result.changed == false"
+
+- name: teardown1
+  nxos_config:
+    lines:
+      - "no channel-group 10"
+    parents: "interface {{ test_int1 }}"
+
+- name: teardown2
+  nxos_config:
+    lines:
+      - "no channel-group 11"
+    parents: "interface {{ test_int2 }}"
+
+- name: disable feature lacp
+  nxos_feature:
+    feature: lacp
+    state: disabled
diff --git a/test/sanity/validate-modules/ignore.txt b/test/sanity/validate-modules/ignore.txt
index 662d6d44b1f..ecd9d8808e8 100644
--- a/test/sanity/validate-modules/ignore.txt
+++ b/test/sanity/validate-modules/ignore.txt
@@ -2649,8 +2649,6 @@ lib/ansible/modules/network/nxos/nxos_l2_interface.py E337
 lib/ansible/modules/network/nxos/nxos_l2_interface.py E338
 lib/ansible/modules/network/nxos/nxos_l3_interface.py E337
 lib/ansible/modules/network/nxos/nxos_l3_interface.py E338
-lib/ansible/modules/network/nxos/nxos_linkagg.py E337
-lib/ansible/modules/network/nxos/nxos_linkagg.py E338
 lib/ansible/modules/network/nxos/nxos_lldp.py E326
 lib/ansible/modules/network/nxos/nxos_lldp.py E338
 lib/ansible/modules/network/nxos/nxos_logging.py E337