CORD-469 added regex for include, exclude, and ignore for ifaces

Change-Id: I30f63ef222b7e54a131ea476fa68d352d0ee4573
diff --git a/build.gradle b/build.gradle
index 6533aa2..733ead5 100644
--- a/build.gradle
+++ b/build.gradle
@@ -317,7 +317,6 @@
             .p(config.seedServer.fabric_ip, "fabric_ip")
             .p(config.seedServer.fabric_network, "fabric_network")
             .p(config.seedServer.fabric_iface, "fabric_iface")
-            .p(config.seedServer.fabric_iface_spec, "fabric_iface_spec")
             .p(config.seedServer.domain, "domain")
 	    .p(config.seedServer.virtualbox_support, "virtualbox_support")
             .p(config.seedServer.power_helper_user, "power_helper_user")
@@ -376,7 +375,6 @@
 	    .p(config.seedServer.fabric_ip, "fabric_ip")
 	    .p(config.seedServer.fabric_network, "fabric_network")
 	    .p(config.seedServer.fabric_iface, "fabric_iface")
-            .p(config.seedServer.fabric_iface_spec, "fabric_iface_spec")
             .p(config.seedServer.domain, "domain")
             .p(config.seedServer.virtualbox_support, "virtualbox_support")
 	    .p(config.seedServer.power_helper_user, "power_helper_user")
diff --git a/library/netinfo.py b/library/netinfo.py
index 4811b3b..7247f3f 100644
--- a/library/netinfo.py
+++ b/library/netinfo.py
@@ -4,41 +4,150 @@
 import json
 import ethtool
 import shlex
+import re
+
+def match_list(value, list):
+    if len(list) == 0:
+        return False
+
+    for exp in list:
+        if re.match(exp, value):
+            return True
+
+    return False
+
+def has_match(name, module_type, bus_type, name_list, module_type_list, bus_type_list):
+    return match_list(name, name_list) or \
+                    match_list(module_type, module_type_list) or match_list(bus_type, bus_type_list)
 
 # read the argument string from the arguments file
 args_file = sys.argv[1]
 args_data = file(args_file).read()
 
-ignore=["tun", "bridge", "bonding", "veth"]
-bus_ignore=["", "N/A", "tap"]
+exclude_names=[]
+exclude_module_types=[]
+exclude_bus_types=[]
+include_names=[]
+include_module_types=[]
+include_bus_types=[]
+ignore_names=[]
+ignore_module_types=["tun", "bridge", "bonding", "veth"]
+ignore_bus_types=["^\W*$", "N/A", "tap"]
+debug_out = False
 
 # parse the task options
 arguments = shlex.split(args_data)
 for arg in arguments:
-    # ignore any arguments without an equals in it
+    # exclude any arguments without an equals in it
     if "=" in arg:
         (key, value) = arg.split("=")
-    # if setting the time, the key 'time'
-    # will contain the value we want to set the time to
 
-all = {}
+    if key == "exclude-names":
+        exclude_names = re.split("\W*,\W*", value)
+    elif key == "exclude-module-types":
+        exclude_module_types = re.split("\W*,\W*", value)
+    elif key == "exclude-bus-types":
+        exclude_bus_types = re.split("\W*,\W*", value)
+    elif key == "include-names":
+        include_names = re.split("\W*,\W*", value)
+    elif key == "include-module-types":
+        include_module_types = re.split("\W*,\W*", value)
+    elif key == "include-bus-types":
+        include_bus_types = re.split("\W*,\W*", value)
+    elif key == "ignore-names":
+        ignore_names = re.split("\W*,\W*", value)
+    elif key == "ignore-module-types":
+        ignore_module_types = re.split("\W*,\W*", value)
+    elif key == "ignore-bus-types":
+        ignore_bus_types = re.split("\W*,\W*", value)
+    elif key == "debug":
+        debug_out = value.lower() in ["true", "yes", "on", "t", "y", "1"]
+    elif key[0] != '_':
+        raise ValueError('Unknown option to task "%s"' % key)
+
+included = {}
+ignored = {}
+excluded = {}
+debug = []
+
+if debug_out:
+    debug.append("EXCLUDES: '%s', '%s', '%s'" % (exclude_names, exclude_module_types, exclude_bus_types))
+    debug.append("INCLUDE: '%s', '%s', '%s'" % (include_names, include_module_types, include_bus_types))
+    debug.append("IGNORE: '%s', '%s', '%s'" % (ignore_names, ignore_module_types, ignore_bus_types))
+
 for i in ethtool.get_devices():
     o = { "name": i }
     try:
         module = ethtool.get_module(i)
         businfo = ethtool.get_businfo(i)
-        if module in ignore or businfo in bus_ignore:
+
+        # If it matches an ignore pattern then just ignore it.
+        if has_match(i, module, businfo, ignore_names, ignore_module_types, ignore_bus_types):
+            if debug_out: debug.append("IGNORE '%s' on ignore match" % i)
+            ignored[i] = {
+                "name": i,
+                "module": module,
+            }
             continue
-        all[i] = {
+
+        # If no include specifications have been set and the interface is not ignored
+        # it needs to be considered for inclusion
+        if len(include_names) + len(include_module_types) + len(include_bus_types) == 0:
+            # If it matches exclude list then exclude it, else include it
+            if has_match(i, module, businfo, exclude_names, exclude_module_types, exclude_bus_types):
+                if debug_out: debug.append("EXCLUDE '%s' with no include specifiers, but with exclude match" %i)
+                excluded[i] = {
+                    "name": i,
+                    "module": module,
+                }
+                continue
+            if debug_out: debug.append("INCLUDE '%s' with no include specifiers, but with no exclude match" % i)
+            included[i] = {
+                "name": i,
+                "module": module,
+            }
+            continue
+
+        # If any of the include specifications are set then the interface must match at least one
+        # to be added to the mached list.
+        if has_match(i, module, businfo, include_names, include_module_types, include_bus_types):
+            if debug_out: debug.append("MATCH '%s' has include match" % i)
+            # If it matches exclude list then exclude it, else include it
+            if has_match(i, module, businfo, exclude_names, exclude_module_types, exclude_bus_types):
+                if debug_out: debug.append("EXCLUDE '%s' with include match, but also with exclude match" % i)
+                excluded[i] = {
+                    "name": i,
+                    "module": module,
+                }
+                continue
+            if debug_out: debug.append("INCLUDE '%s' with include match and with no exclude match" % i)
+            included[i] = {
+                "name": i,
+                "module" : module,
+            }
+            continue
+
+        # Implicitly ignore
+        if debug_out: debug.append("IGNORE: '%s' implicitly" %i)
+        ignored[i] = {
             "name": i,
-            "module" : module,
+            "module": module,
         }
+
     except:
         pass
 
-print json.dumps({
+result = {
     "changed" : False,
     "ansible_facts" : {
-        "netinfo" : all,
+        "netinfo" : {
+            "included" : included,
+            "excluded" : excluded,
+            "ignored"  : ignored,
+        },
     },
-})
+}
+
+if debug_out: result["ansible_facts"]["netinfo"]["debug"] = debug
+
+print json.dumps(result)
diff --git a/roles/compute-node/tasks/networking.yml b/roles/compute-node/tasks/networking.yml
index 3820b23..7f977bd 100644
--- a/roles/compute-node/tasks/networking.yml
+++ b/roles/compute-node/tasks/networking.yml
@@ -6,31 +6,128 @@
   with_items:
     - python-ethtool=0.7*
 
-- name: Gather Interface Information
-  netinfo:
-
 - name: Establish Interface Lists
   set_fact:
     reboot_required: false
     fabric_iface_list: []
-    nonfabric_iface_list: []
-    search_list: "|{{ compute_node.fabric_iface_match }}|"
+    fabric_iface_excluded_list: []
+    management_iface_list: []
+    management_iface_excluded_list: []
   changed_when: false
 
+- name: Explicitly Ignore External and Specified Interfaces From Fabric
+  set_fact:
+    ignore_names_combined: "{{ compute_node.interfaces.external }},{{ compute_node.fabric.ignore.names }}"
+  when:
+    - compute_node.interfaces.external
+    - compute_node.fabric.ignore.names is defined and compute_node.fabric.ignore.names != omit
+  changed_when: false
+
+- name: Ignore External Interface From Fabric
+  set_fact:
+    ignore_names_combined: "{{ compute_node.interfaces.external }}"
+  when:
+    - compute_node.interfaces.external
+    - compute_node.fabric.ignore.names is not defined or compute_node.fabric.ignore.names == omit
+  changed_when: false
+
+- name: Explicity Ingnore Specified Interfaces From Fabric
+  set_fact:
+    ignore_names_combined: "{{ compute_node.fabric.ignore.names }}"
+  when:
+    - not compute_node.interfaces.external
+    - compute_node.fabric.ignore.names is defined and compute_node.fabric.ignore.names != omit
+  changed_when: false
+
+- name: Discovery Fabric Interfaces
+  netinfo:
+    include-names: "{{ compute_node.fabric.include.names }}"
+    include-module-types: "{{ compute_node.fabric.include.module_types }}"
+    include-bus-types: "{{ compute_node.fabric.include.bus_types }}"
+    ignore-names: "{{ ignore_names_combined | default(omit) }}"
+    ignore-module-types: "{{ compute_node.fabric.ignore.module_types }}"
+    ignore-bus-types: "{{ compute_node.fabric.ignore.bus_types }}"
+    exclude-names: "{{ compute_node.fabric.exclude.names }}"
+    exclude-module-types: "{{ compute_node.fabric.exclude.module_types }}"
+    exclude-bus-types: "{{ compute_node.fabric.exclude.bus_types }}"
+    debug: on
+
 - name: Gather Fabric Interfaces
   set_fact:
     fabric_iface_list: "{{ fabric_iface_list + [item] }}"
   with_items:
-    - "{{ netinfo.keys() | sort }}"
-  when: netinfo[item]['module'] is defined and search_list.find('|' + netinfo[item]['module'] + '|') != -1 and ( not compute_node.interfaces.external or item != compute_node.interfaces.external  )
+    - "{{ netinfo.included.keys() | sort }}"
   changed_when: false
 
-- name: Gather Non-Fabric Interfaces
+- name: Gather Excluded Fabric Interfaces
   set_fact:
-    nonfabric_iface_list: "{{ nonfabric_iface_list + [item] }}"
+    fabric_iface_excluded_list: "{{ fabric_iface_excluded_list + [item] }}"
   with_items:
-    - "{{ netinfo.keys() | sort }}"
-  when: netinfo[item]['module'] is defined and search_list.find('|' + netinfo[item]['module'] + '|') == -1 and ( not compute_node.interfaces.external or item != compute_node.interfaces.external )
+    - "{{ netinfo.excluded.keys() | sort }}"
+  changed_when: false
+
+- name: Initialize Modules Ignored for Management Bridge
+  set_fact:
+    ignore_module_types_combined: "{{ compute_node.management.ignore.module_types }}"
+  changed_when: false
+
+- name: Ignore Fabric Modules for Management Bridge
+  set_fact:
+    ignore_module_types_combined: "{{ compute_node.fabric.include.module_types }}"
+  when:
+    - compute_node.management.ignore.module_types is not defined or compute_node.management.ignore.module_types == omit
+    - compute_node.fabric.include.module_types is defined and compute_node.fabric.include.module_types != omit
+  changed_when: false
+
+- name: Explicitly Ignore External and Specified Interfaces From Management Bridge
+  set_fact:
+    ignore_names_combined: "{{ compute_node.interfaces.external }},{{ compute_node.management.ignore.names }}"
+  when:
+    - compute_node.interfaces.external
+    - compute_node.management.ignore.names is defined and compute_node.management.ignore.names != omit
+  changed_when: false
+
+- name: Ignore External Interface From Management Bridge
+  set_fact:
+    ignore_names_combined: "{{ compute_node.interfaces.external }}"
+  when:
+    - compute_node.interfaces.external
+    - compute_node.management.ignore.names is not defined or compute_node.management.ignore.names == omit
+  changed_when: false
+
+- name: Explicity Ingnore Specified Interfaces From Management Bridge
+  set_fact:
+    exclude_names_combined: "{{ compute_node.management.ignore.names }}"
+  when:
+    - not compute_node.interfaces.external
+    - compute_node.management.ignore.names is defined and compute_node.management.ignore.names != omit
+  changed_when: false
+
+- name: Discover Management Interfaces
+  netinfo:
+    include-names: "{{ compute_node.management.include.names }}"
+    include-module-types: "{{ compute_node.management.include.module_types }}"
+    include-bus-types: "{{ compute_node.management.include.bus_types }}"
+    ignore-names: "{{ ignore_names_combined | default(omit) }}"
+    ignore-module-types: "{{ ignore_module_types_combined | default(omit) }}"
+    ignore-bus-types: "{{ compute_node.management.ignore.bus_types }}"
+    exclude-names: "{{ compute_node.management.exclude.names }}"
+    exclude-module-types: "{{ compute_node.management.exclude.module_types }}"
+    exclude-bus-types: "{{ compute_node.management.exclude.bus_types }}"
+    debug: on
+
+- name: Gather Management Interfaces
+  set_fact:
+    management_iface_list: "{{ management_iface_list + [item] }}"
+  with_items:
+    - "{{ netinfo.included.keys() | sort }}"
+  changed_when: false
+
+- name: Gather Excluded Management Interfaces
+  set_fact:
+    management_iface_excluded_list: "{{ management_iface_excluded_list + [item] }}"
+  with_items:
+    - "{{ netinfo.excluded.keys() | sort }}"
   changed_when: false
 
 #- name: Ensure Loopback
@@ -107,6 +204,22 @@
   set_fact:
     reboot_required: "{{ reboot_required }} or {{ net_changed.changed }}"
 
+- name: Mark Explicitly Excluded Fabric Interfaces as Manual
+  netfile:
+    src: "{{ compute_node.interfaces.file }}"
+    state: present
+    auto: false
+    name: "{{ item }}"
+    config: manual
+    description: "Explicitly Excluded Fabric Interface"
+  register: net_changed
+  with_items:
+    - "{{ fabric_iface_excluded_list | sort }}"
+
+- name: Verify Fabric Interfaces Changed
+  set_fact:
+    reboot_required: "{{ reboot_required }} or {{ net_changed.changed }}"
+
 - name: Ensure Management Bridge DHCP
   netfile:
     src: "{{ compute_node.interfaces.file }}"
@@ -114,7 +227,7 @@
     name: mgmtbr
     config: "dhcp"
     auto: true
-    bridge_ports: "{{ nonfabric_iface_list | join(' ') }}"
+    bridge_ports: "{{ management_iface_list | join(' ') }}"
     description: "Internal POD management interface"
   register: net_changed
   when: compute_node.addresses.management == "dhcp"
@@ -131,9 +244,9 @@
     config: static
     auto: true
     address: "{{ compute_node.addresses.management }}"
-    gateway: "{{ compute_node.gateway.management | default(omit) }}"
-    broadcast: "{{ compute_node.broadcast.management | default(omit) }}"
-    bridge_ports: "{{ nonfabric_iface_list | join(' ') }}"
+    gateway: "{{ compute_node.gateway.management }}"
+    broadcast: "{{ compute_node.broadcast.management }}"
+    bridge_ports: "{{ management_iface_list | join(' ') }}"
     description: "Internal POD management interface"
   register: net_changed
   when: compute_node.addresses.management != "dhcp"
@@ -152,7 +265,7 @@
     description: "Management interface"
   register: net_changed
   with_items:
-    - "{{ nonfabric_iface_list | sort }}"
+    - "{{ management_iface_list | sort }}"
 
 - name: Verify Management Bridge Interfaces Changed
   set_fact:
@@ -173,6 +286,22 @@
   set_fact:
     reboot_required: "{{ reboot_required }} or {{ net_changed.changed }}"
 
+- name: Mark Explicitly Excluded Management Bridge Interfaces as Manual
+  netfile:
+    src: "{{ compute_node.interfaces.file }}"
+    state: present
+    auto: false
+    name: "{{ item }}"
+    config: manual
+    description: "Explicitly Excluded Management Bridge Interface"
+  register: net_changed
+  with_items:
+    - "{{ management_iface_excluded_list | sort }}"
+
+- name: Verify Explicitly Excluded Management Interfaces Changed
+  set_fact:
+    reboot_required: "{{ reboot_required }} or {{ net_changed.changed }}"
+
 - name: Ensure External Interface STATIC
   netfile:
     src: "{{ compute_node.interfaces.file }}"
@@ -181,8 +310,8 @@
     name: "{{ compute_node.interfaces.external }}"
     config: static
     address: "{{ compute_node.addresses.external }}"
-    gateway: "{{ compute_node.gateway.external | default(omit) }}"
-    broadcast: "{{ compute_node.broadcast.external | default(omit) }}"
+    gateway: "{{ compute_node.gateway.external }}"
+    broadcast: "{{ compute_node.broadcast.external }}"
     description: "External interface from POD to Internet (uplink)"
   register: net_changed
   when: compute_node.interfaces.external and compute_node.addresses.external != "dhcp"
diff --git a/roles/compute-node/tasks/vars.yml b/roles/compute-node/tasks/vars.yml
deleted file mode 100644
index e0bba8b..0000000
--- a/roles/compute-node/tasks/vars.yml
+++ /dev/null
@@ -1,19 +0,0 @@
-pub_ssh_key: "{{ lookup('file', 'files/id_rsa.pub') }}"
-
-compute_node:
-    fabric_iface_match: "{{ fabric_iface_spec | default('i40e|mlx4_en') }}"
-    interfaces:
-        fabric: "{{ fabric_iface | default('fabric') }}"
-        management: "{{ management_iface | default('mgmtbr') }}"
-        external: "{{ external_iface | default(None) }}"
-        file: "{{ iface_file | default('/etc/network/interfaces') }}"
-    addresses:
-        fabric: "{{ fabric_ip | mandatory }}"
-        management: "{{ management_ip | default('dhcp') }}"
-        external: "{{ external_ip | default('manual') }}"
-    gateway:
-        external: "{{ external_gw | default(omit) }}"
-        management: "{{ management_gw | default(omit) }}"
-    broadcast:
-        external: "{{ external_bc | default(omit) }}"
-        management: "{{ management_bc | default(omit) }}"
diff --git a/roles/compute-node/vars/main.yml b/roles/compute-node/vars/main.yml
index 01323a2..7e6ff5e 100644
--- a/roles/compute-node/vars/main.yml
+++ b/roles/compute-node/vars/main.yml
@@ -1,7 +1,33 @@
 pub_ssh_key: "{{ lookup('file', 'files/id_rsa.pub') }}"
 
 compute_node:
-    fabric_iface_match: "{{ fabric_iface_spec | default('i40e|mlx4_en') }}"
+    fabric:
+        include:
+            names: "{{ fabric_include_names | default(omit) }}"
+            module_types: "{{ fabric_include_module_types | default('i40e, mlx4_en') }}"
+            bus_types: "{{ fabric_include_bus_types | default(omit) }}"
+        exclude:
+            names: "{{ fabric_exclude_names | default(omit) }}"
+            module_types: "{{ fabric_exclude_module_types | default(omit) }}"
+            bus_types: "{{ fabric_exclude_bus_types | default(omit) }}"
+        ignore:
+            names: "{{ fabric_ignore_names | default(omit) }}"
+            module_types: "{{ fabric_ignore_module_types | default(omit) }}"
+            bus_types: "{{ fabric_ignore_bus_types | default(omit) }}"
+    management:
+        include:
+            names: "{{ management_include_names | default(omit) }}"
+            module_types: "{{ management_include_module_types | default(omit) }}"
+            bus_types: "{{ management_include_bus_types | default(omit) }}"
+        exclude:
+            names: "{{ management_exclude_names | default(omit) }}"
+            module_types: "{{ management_exclude_module_types | default(omit) }}"
+            bus_types: "{{ management_exclude_bus_types | default(omit) }}"
+        ignore:
+            names: "{{ management_ignore_names | default(omit) }}"
+            module_types: "{{ management_ignore_module_types | default(omit) }}"
+            bus_types: "{{ management_ignore_bus_types | default(omit) }}"
+
     interfaces:
         fabric: "{{ fabric_iface | default('fabric') }}"
         management: "{{ management_iface | default('mgmtbr') }}"
@@ -13,7 +39,7 @@
         external: "{{ external_ip | default('manual') }}"
     gateway:
         external: "{{ external_gw | default(omit) }}"
-        management: "{{ management_gw | default('omit') }}"
+        management: "{{ management_gw | default(omit) }}"
     broadcast:
         external: "{{ external_bc | default(omit) }}"
-        management: "{{ management_bc | default('omit') }}"
+        management: "{{ management_bc | default(omit) }}"
diff --git a/roles/head-node/files/compute-node-vars.yml b/roles/head-node/files/compute-node-vars.yml
index 4991bbb..7ded468 100644
--- a/roles/head-node/files/compute-node-vars.yml
+++ b/roles/head-node/files/compute-node-vars.yml
@@ -1 +1,54 @@
-fabric_iface_spec: '{{ compute_node.fabric_iface_match }}'
+{% if compute_node.fabric.include.names is defined and compute_node.fabric.include.names != omit %}
+fabric_include_names: '{{ compute_node.fabric.include.names }}'
+{% endif %}
+{% if compute_node.fabric.include.module_types is defined and compute_node.fabric.include.module_types != omit %}
+fabric_include_module_types: '{{ compute_node.fabric.include.module_types }}'
+{% endif %}
+{% if compute_node.fabric.include.bus_types is defined and compute_node.fabric.include.bus_types != omit %}
+fabric_include_bus_types: '{{ compute_node.fabric.include.bus_types }}'
+{% endif %}
+{% if compute_node.fabric.exclude.names is defined and compute_node.fabric.exclude.names != omit %}
+fabric_exclude_names: '{{ compute_node.fabric.exclude.names }}'
+{% endif %}
+{% if compute_node.fabric.exclude.module_types is defined and compute_node.fabric.exclude.module_types != omit %}
+fabric_exclude_module_types: '{{ compute_node.fabric.exclude.module_types }}'
+{% endif %}
+{% if compute_node.fabric.exclude.bus_types is defined and compute_node.fabric.exclude.bus_types != omit %}
+fabric_exclude_bus_types: '{{ compute_node.fabric.exclude.bus_types }}'
+{% endif %}
+{% if compute_node.fabric.ignore.names is defined and compute_node.fabric.ignore.names != omit %}
+fabric_ignore_names: '{{ compute_node.fabric.ignore.names }}'
+{% endif %}
+{% if compute_node.fabric.ignore.module_types is defined and compute_node.fabric.ignore.module_types != omit %}
+fabric_ignore_module_types: '{{ compute_node.fabric.ignore.module_types }}'
+{% endif %}
+{% if compute_node.fabric.ignore.bus_types is defined and compute_node.fabric.ignore.bus_types != omit %}
+fabric_ignore_bus_types: '{{ compute_node.fabric.ignore.bus_types }}'
+{% endif %}
+{% if compute_node.management.include.names is defined and compute_node.management.include.names != omit %}
+management_include_names: '{{ compute_node.management.include.names }}'
+{% endif %}
+{% if compute_node.management.include.module_types is defined and compute_node.management.include.module_types != omit %}
+management_include_module_types: '{{ compute_node.management.include.module_types }}'
+{% endif %}
+{% if compute_node.management.include.bus_types is defined and compute_node.management.include.bus_types != omit %}
+management_include_bus_types: '{{ compute_node.management.include.bus_types }}'
+{% endif %}
+{% if compute_node.management.exclude.names is defined and compute_node.management.exclude.names != omit %}
+management_exclude_names: '{{ compute_node.management.exclude.names }}'
+{% endif %}
+{% if compute_node.management.exclude.module_types is defined and compute_node.management.exclude.module_types != omit %}
+management_exclude_module_types: '{{ compute_node.management.exclude.module_types }}'
+{% endif %}
+{% if compute_node.management.exclude.bus_types is defined and compute_node.management.exclude.bus_types != omit %}
+management_exclude_bus_types: '{{ compute_node.management.exclude.bus_types }}'
+{% endif %}
+{% if compute_node.management.ignore.names is defined and compute_node.management.ignore.names != omit %}
+management_ignore_names: '{{ compute_node.management.ignore.names }}'
+{% endif %}
+{% if compute_node.management.ignore.module_types is defined and compute_node.management.ignore.module_types != omit %}
+management_ignore_module_types: '{{ compute_node.management.ignore.module_types }}'
+{% endif %}
+{% if compute_node.management.ignore.bus_types is defined and compute_node.management.ignore.bus_types != omit %}
+management_ignore_bus_types: '{{ compute_node.management.ignore.bus_types }}'
+{% endif %}