c3f9da
From ec14b8ed9cb4264333b80b4361171b1b529c58f3 Mon Sep 17 00:00:00 2001
c3f9da
From: Eduardo Otubo <otubo@redhat.com>
c3f9da
Date: Tue, 3 Nov 2020 12:11:45 +0100
c3f9da
Subject: [PATCH 3/5] Add config modules for controlling IBM PowerVM RMC.
c3f9da
 (#584)
c3f9da
c3f9da
RH-Author: Eduardo Terrell Ferrari Otubo (eterrell)
c3f9da
RH-MergeRequest: 16: Add config modules for controlling IBM PowerVM RMC. (#584)
c3f9da
RH-Commit: [1/1] 734e2c48d323af31aa36abefae346ef62ba3ef5d (eterrell/cloud-init)
c3f9da
RH-Bugzilla: 1894014
c3f9da
c3f9da
commit f99d4f96b00a9cfec1c721d364cbfd728674e5dc
c3f9da
Author: Aman306 <45781773+Aman306@users.noreply.github.com>
c3f9da
Date:   Wed Oct 28 23:36:09 2020 +0530
c3f9da
c3f9da
    Add config modules for controlling IBM PowerVM RMC. (#584)
c3f9da
c3f9da
    Reliable Scalable Cluster Technology (RSCT) is a set of software
c3f9da
    components that together provide a comprehensive clustering
c3f9da
    environment(RAS features) for IBM PowerVM based virtual machines. RSCT
c3f9da
    includes the Resource Monitoring and Control (RMC) subsystem. RMC is a
c3f9da
    generalized framework used for managing, monitoring, and manipulating
c3f9da
    resources. RMC runs as a daemon process on individual machines and needs
c3f9da
    creation of unique node id and restarts during VM boot.
c3f9da
c3f9da
    LP: #1895979
c3f9da
c3f9da
    Co-authored-by: Scott Moser <smoser@brickies.net>
c3f9da
c3f9da
Conflicts:
c3f9da
* Calls to module subp.* are replaced by old calls to util.* since the
c3f9da
patch that groups subp.* calls into its own module are introduced after
c3f9da
19.4 release - and it's a huge reafctoring not worth the cherry-pick.
c3f9da
c3f9da
Signed-off-by: Eduardo Otubo <otubo@redhat.com>
c3f9da
---
c3f9da
 cloudinit/config/cc_refresh_rmc_and_interface.py   | 158 +++++++++++++++++++++
c3f9da
 cloudinit/config/cc_reset_rmc.py                   | 142 ++++++++++++++++++
c3f9da
 config/cloud.cfg.tmpl                              |   2 +
c3f9da
 .../test_handler_refresh_rmc_and_interface.py      | 109 ++++++++++++++
c3f9da
 4 files changed, 411 insertions(+)
c3f9da
 create mode 100644 cloudinit/config/cc_refresh_rmc_and_interface.py
c3f9da
 create mode 100644 cloudinit/config/cc_reset_rmc.py
c3f9da
 create mode 100644 tests/unittests/test_handler/test_handler_refresh_rmc_and_interface.py
c3f9da
c3f9da
diff --git a/cloudinit/config/cc_refresh_rmc_and_interface.py b/cloudinit/config/cc_refresh_rmc_and_interface.py
c3f9da
new file mode 100644
c3f9da
index 0000000..07050c4
c3f9da
--- /dev/null
c3f9da
+++ b/cloudinit/config/cc_refresh_rmc_and_interface.py
c3f9da
@@ -0,0 +1,158 @@
c3f9da
+# (c) Copyright IBM Corp. 2020 All Rights Reserved
c3f9da
+#
c3f9da
+# Author: Aman Kumar Sinha <amansi26@in.ibm.com>
c3f9da
+#
c3f9da
+# This file is part of cloud-init. See LICENSE file for license information.
c3f9da
+
c3f9da
+"""
c3f9da
+Refresh IPv6 interface and RMC
c3f9da
+------------------------------
c3f9da
+**Summary:** Ensure Network Manager is not managing IPv6 interface
c3f9da
+
c3f9da
+This module is IBM PowerVM Hypervisor specific
c3f9da
+
c3f9da
+Reliable Scalable Cluster Technology (RSCT) is a set of software components
c3f9da
+that together provide a comprehensive clustering environment(RAS features)
c3f9da
+for IBM PowerVM based virtual machines. RSCT includes the Resource
c3f9da
+Monitoring and Control (RMC) subsystem. RMC is a generalized framework used
c3f9da
+for managing, monitoring, and manipulating resources. RMC runs as a daemon
c3f9da
+process on individual machines and needs creation of unique node id and
c3f9da
+restarts during VM boot.
c3f9da
+More details refer
c3f9da
+https://www.ibm.com/support/knowledgecenter/en/SGVKBA_3.2/admin/bl503_ovrv.htm
c3f9da
+
c3f9da
+This module handles
c3f9da
+- Refreshing RMC
c3f9da
+- Disabling NetworkManager from handling IPv6 interface, as IPv6 interface
c3f9da
+  is used for communication between RMC daemon and PowerVM hypervisor.
c3f9da
+
c3f9da
+**Internal name:** ``cc_refresh_rmc_and_interface``
c3f9da
+
c3f9da
+**Module frequency:** per always
c3f9da
+
c3f9da
+**Supported distros:** RHEL
c3f9da
+
c3f9da
+"""
c3f9da
+
c3f9da
+from cloudinit import log as logging
c3f9da
+from cloudinit.settings import PER_ALWAYS
c3f9da
+from cloudinit import util
c3f9da
+from cloudinit import netinfo
c3f9da
+
c3f9da
+import errno
c3f9da
+
c3f9da
+frequency = PER_ALWAYS
c3f9da
+
c3f9da
+LOG = logging.getLogger(__name__)
c3f9da
+# Ensure that /opt/rsct/bin has been added to standard PATH of the
c3f9da
+# distro. The symlink to rmcctrl is /usr/sbin/rsct/bin/rmcctrl .
c3f9da
+RMCCTRL = 'rmcctrl'
c3f9da
+
c3f9da
+
c3f9da
+def handle(name, _cfg, _cloud, _log, _args):
c3f9da
+    if not util.which(RMCCTRL):
c3f9da
+        LOG.debug("No '%s' in path, disabled", RMCCTRL)
c3f9da
+        return
c3f9da
+
c3f9da
+    LOG.debug(
c3f9da
+        'Making the IPv6 up explicitly. '
c3f9da
+        'Ensuring IPv6 interface is not being handled by NetworkManager '
c3f9da
+        'and it is  restarted to re-establish the communication with '
c3f9da
+        'the hypervisor')
c3f9da
+
c3f9da
+    ifaces = find_ipv6_ifaces()
c3f9da
+
c3f9da
+    # Setting NM_CONTROLLED=no for IPv6 interface
c3f9da
+    # making it down and up
c3f9da
+
c3f9da
+    if len(ifaces) == 0:
c3f9da
+        LOG.debug("Did not find any interfaces with ipv6 addresses.")
c3f9da
+    else:
c3f9da
+        for iface in ifaces:
c3f9da
+            refresh_ipv6(iface)
c3f9da
+            disable_ipv6(sysconfig_path(iface))
c3f9da
+        restart_network_manager()
c3f9da
+
c3f9da
+
c3f9da
+def find_ipv6_ifaces():
c3f9da
+    info = netinfo.netdev_info()
c3f9da
+    ifaces = []
c3f9da
+    for iface, data in info.items():
c3f9da
+        if iface == "lo":
c3f9da
+            LOG.debug('Skipping localhost interface')
c3f9da
+        if len(data.get("ipv4", [])) != 0:
c3f9da
+            # skip this interface, as it has ipv4 addrs
c3f9da
+            continue
c3f9da
+        ifaces.append(iface)
c3f9da
+    return ifaces
c3f9da
+
c3f9da
+
c3f9da
+def refresh_ipv6(interface):
c3f9da
+    # IPv6 interface is explicitly brought up, subsequent to which the
c3f9da
+    # RMC services are restarted to re-establish the communication with
c3f9da
+    # the hypervisor.
c3f9da
+    util.subp(['ip', 'link', 'set', interface, 'down'])
c3f9da
+    util.subp(['ip', 'link', 'set', interface, 'up'])
c3f9da
+
c3f9da
+
c3f9da
+def sysconfig_path(iface):
c3f9da
+    return '/etc/sysconfig/network-scripts/ifcfg-' + iface
c3f9da
+
c3f9da
+
c3f9da
+def restart_network_manager():
c3f9da
+    util.subp(['systemctl', 'restart', 'NetworkManager'])
c3f9da
+
c3f9da
+
c3f9da
+def disable_ipv6(iface_file):
c3f9da
+    # Ensuring that the communication b/w the hypervisor and VM is not
c3f9da
+    # interrupted due to NetworkManager. For this purpose, as part of
c3f9da
+    # this function, the NM_CONTROLLED is explicitly set to No for IPV6
c3f9da
+    # interface and NetworkManager is restarted.
c3f9da
+    try:
c3f9da
+        contents = util.load_file(iface_file)
c3f9da
+    except IOError as e:
c3f9da
+        if e.errno == errno.ENOENT:
c3f9da
+            LOG.debug("IPv6 interface file %s does not exist\n",
c3f9da
+                      iface_file)
c3f9da
+        else:
c3f9da
+            raise e
c3f9da
+
c3f9da
+    if 'IPV6INIT' not in contents:
c3f9da
+        LOG.debug("Interface file %s did not have IPV6INIT", iface_file)
c3f9da
+        return
c3f9da
+
c3f9da
+    LOG.debug("Editing interface file %s ", iface_file)
c3f9da
+
c3f9da
+    # Dropping any NM_CONTROLLED or IPV6 lines from IPv6 interface file.
c3f9da
+    lines = contents.splitlines()
c3f9da
+    lines = [line for line in lines if not search(line)]
c3f9da
+    lines.append("NM_CONTROLLED=no")
c3f9da
+
c3f9da
+    with open(iface_file, "w") as fp:
c3f9da
+        fp.write("\n".join(lines) + "\n")
c3f9da
+
c3f9da
+
c3f9da
+def search(contents):
c3f9da
+    # Search for any NM_CONTROLLED or IPV6 lines in IPv6 interface file.
c3f9da
+    return(
c3f9da
+        contents.startswith("IPV6ADDR") or
c3f9da
+        contents.startswith("IPADDR6") or
c3f9da
+        contents.startswith("IPV6INIT") or
c3f9da
+        contents.startswith("NM_CONTROLLED"))
c3f9da
+
c3f9da
+
c3f9da
+def refresh_rmc():
c3f9da
+    # To make a healthy connection between RMC daemon and hypervisor we
c3f9da
+    # refresh RMC. With refreshing RMC we are ensuring that making IPv6
c3f9da
+    # down and up shouldn't impact communication between RMC daemon and
c3f9da
+    # hypervisor.
c3f9da
+    # -z : stop Resource Monitoring & Control subsystem and all resource
c3f9da
+    # managers, but the command does not return control to the user
c3f9da
+    # until the subsystem and all resource managers are stopped.
c3f9da
+    # -s : start Resource Monitoring & Control subsystem.
c3f9da
+    try:
c3f9da
+        util.subp([RMCCTRL, '-z'])
c3f9da
+        util.subp([RMCCTRL, '-s'])
c3f9da
+    except Exception:
c3f9da
+        util.logexc(LOG, 'Failed to refresh the RMC subsystem.')
c3f9da
+        raise
c3f9da
diff --git a/cloudinit/config/cc_reset_rmc.py b/cloudinit/config/cc_reset_rmc.py
c3f9da
new file mode 100644
c3f9da
index 0000000..68373ad
c3f9da
--- /dev/null
c3f9da
+++ b/cloudinit/config/cc_reset_rmc.py
c3f9da
@@ -0,0 +1,142 @@
c3f9da
+# (c) Copyright IBM Corp. 2020 All Rights Reserved
c3f9da
+#
c3f9da
+# Author: Aman Kumar Sinha <amansi26@in.ibm.com>
c3f9da
+#
c3f9da
+# This file is part of cloud-init. See LICENSE file for license information.
c3f9da
+
c3f9da
+
c3f9da
+"""
c3f9da
+Reset RMC
c3f9da
+------------
c3f9da
+**Summary:** reset rsct node id
c3f9da
+
c3f9da
+Reset RMC module is IBM PowerVM Hypervisor specific
c3f9da
+
c3f9da
+Reliable Scalable Cluster Technology (RSCT) is a set of software components,
c3f9da
+that  together provide a comprehensive clustering environment (RAS features)
c3f9da
+for IBM PowerVM based virtual machines. RSCT includes the Resource monitoring
c3f9da
+and control (RMC) subsystem. RMC is a generalized framework used for managing,
c3f9da
+monitoring, and manipulating resources. RMC runs as a daemon process on
c3f9da
+individual machines and needs creation of unique node id and restarts
c3f9da
+during VM boot.
c3f9da
+More details refer
c3f9da
+https://www.ibm.com/support/knowledgecenter/en/SGVKBA_3.2/admin/bl503_ovrv.htm
c3f9da
+
c3f9da
+This module handles
c3f9da
+- creation of the unique RSCT node id to every instance/virtual machine
c3f9da
+  and ensure once set, it isn't changed subsequently by cloud-init.
c3f9da
+  In order to do so, it restarts RSCT service.
c3f9da
+
c3f9da
+Prerequisite of using this module is to install RSCT packages.
c3f9da
+
c3f9da
+**Internal name:** ``cc_reset_rmc``
c3f9da
+
c3f9da
+**Module frequency:** per instance
c3f9da
+
c3f9da
+**Supported distros:** rhel, sles and ubuntu
c3f9da
+
c3f9da
+"""
c3f9da
+import os
c3f9da
+
c3f9da
+from cloudinit import log as logging
c3f9da
+from cloudinit.settings import PER_INSTANCE
c3f9da
+from cloudinit import util
c3f9da
+
c3f9da
+frequency = PER_INSTANCE
c3f9da
+
c3f9da
+# RMCCTRL is expected to be in system PATH (/opt/rsct/bin)
c3f9da
+# The symlink for RMCCTRL and RECFGCT are
c3f9da
+# /usr/sbin/rsct/bin/rmcctrl and
c3f9da
+# /usr/sbin/rsct/install/bin/recfgct respectively.
c3f9da
+RSCT_PATH = '/opt/rsct/install/bin'
c3f9da
+RMCCTRL = 'rmcctrl'
c3f9da
+RECFGCT = 'recfgct'
c3f9da
+
c3f9da
+LOG = logging.getLogger(__name__)
c3f9da
+
c3f9da
+NODE_ID_FILE = '/etc/ct_node_id'
c3f9da
+
c3f9da
+
c3f9da
+def handle(name, _cfg, cloud, _log, _args):
c3f9da
+    # Ensuring node id has to be generated only once during first boot
c3f9da
+    if cloud.datasource.platform_type == 'none':
c3f9da
+        LOG.debug('Skipping creation of new ct_node_id node')
c3f9da
+        return
c3f9da
+
c3f9da
+    if not os.path.isdir(RSCT_PATH):
c3f9da
+        LOG.debug("module disabled, RSCT_PATH not present")
c3f9da
+        return
c3f9da
+
c3f9da
+    orig_path = os.environ.get('PATH')
c3f9da
+    try:
c3f9da
+        add_path(orig_path)
c3f9da
+        reset_rmc()
c3f9da
+    finally:
c3f9da
+        if orig_path:
c3f9da
+            os.environ['PATH'] = orig_path
c3f9da
+        else:
c3f9da
+            del os.environ['PATH']
c3f9da
+
c3f9da
+
c3f9da
+def reconfigure_rsct_subsystems():
c3f9da
+    # Reconfigure the RSCT subsystems, which includes removing all RSCT data
c3f9da
+    # under the /var/ct directory, generating a new node ID, and making it
c3f9da
+    # appear as if the RSCT components were just installed
c3f9da
+    try:
c3f9da
+        out = util.subp([RECFGCT])[0]
c3f9da
+        LOG.debug(out.strip())
c3f9da
+        return out
c3f9da
+    except util.ProcessExecutionError:
c3f9da
+        util.logexc(LOG, 'Failed to reconfigure the RSCT subsystems.')
c3f9da
+        raise
c3f9da
+
c3f9da
+
c3f9da
+def get_node_id():
c3f9da
+    try:
c3f9da
+        fp = util.load_file(NODE_ID_FILE)
c3f9da
+        node_id = fp.split('\n')[0]
c3f9da
+        return node_id
c3f9da
+    except Exception:
c3f9da
+        util.logexc(LOG, 'Failed to get node ID from file %s.' % NODE_ID_FILE)
c3f9da
+        raise
c3f9da
+
c3f9da
+
c3f9da
+def add_path(orig_path):
c3f9da
+    # Adding the RSCT_PATH to env standard path
c3f9da
+    # So thet cloud init automatically find and
c3f9da
+    # run RECFGCT to create new node_id.
c3f9da
+    suff = ":" + orig_path if orig_path else ""
c3f9da
+    os.environ['PATH'] = RSCT_PATH + suff
c3f9da
+    return os.environ['PATH']
c3f9da
+
c3f9da
+
c3f9da
+def rmcctrl():
c3f9da
+    # Stop the RMC subsystem and all resource managers so that we can make
c3f9da
+    # some changes to it
c3f9da
+    try:
c3f9da
+        return util.subp([RMCCTRL, '-z'])
c3f9da
+    except Exception:
c3f9da
+        util.logexc(LOG, 'Failed to stop the RMC subsystem.')
c3f9da
+        raise
c3f9da
+
c3f9da
+
c3f9da
+def reset_rmc():
c3f9da
+    LOG.debug('Attempting to reset RMC.')
c3f9da
+
c3f9da
+    node_id_before = get_node_id()
c3f9da
+    LOG.debug('Node ID at beginning of module: %s', node_id_before)
c3f9da
+
c3f9da
+    # Stop the RMC subsystem and all resource managers so that we can make
c3f9da
+    # some changes to it
c3f9da
+    rmcctrl()
c3f9da
+    reconfigure_rsct_subsystems()
c3f9da
+
c3f9da
+    node_id_after = get_node_id()
c3f9da
+    LOG.debug('Node ID at end of module: %s', node_id_after)
c3f9da
+
c3f9da
+    # Check if new node ID is generated or not
c3f9da
+    # by comparing old and new node ID
c3f9da
+    if node_id_after == node_id_before:
c3f9da
+        msg = 'New node ID did not get generated.'
c3f9da
+        LOG.error(msg)
c3f9da
+        raise Exception(msg)
c3f9da
diff --git a/config/cloud.cfg.tmpl b/config/cloud.cfg.tmpl
c3f9da
index 87c37ba..52a259c 100644
c3f9da
--- a/config/cloud.cfg.tmpl
c3f9da
+++ b/config/cloud.cfg.tmpl
c3f9da
@@ -121,6 +121,8 @@ cloud_final_modules:
c3f9da
  - mcollective
c3f9da
 {% endif %}
c3f9da
  - salt-minion
c3f9da
+ - reset_rmc
c3f9da
+ - refresh_rmc_and_interface
c3f9da
  - rightscale_userdata
c3f9da
  - scripts-vendor
c3f9da
  - scripts-per-once
c3f9da
diff --git a/tests/unittests/test_handler/test_handler_refresh_rmc_and_interface.py b/tests/unittests/test_handler/test_handler_refresh_rmc_and_interface.py
c3f9da
new file mode 100644
c3f9da
index 0000000..0c35710
c3f9da
--- /dev/null
c3f9da
+++ b/tests/unittests/test_handler/test_handler_refresh_rmc_and_interface.py
c3f9da
@@ -0,0 +1,109 @@
c3f9da
+from cloudinit.config import cc_refresh_rmc_and_interface as ccrmci
c3f9da
+
c3f9da
+from cloudinit import util
c3f9da
+
c3f9da
+from cloudinit.tests import helpers as t_help
c3f9da
+from cloudinit.tests.helpers import mock
c3f9da
+
c3f9da
+from textwrap import dedent
c3f9da
+import logging
c3f9da
+
c3f9da
+LOG = logging.getLogger(__name__)
c3f9da
+MPATH = "cloudinit.config.cc_refresh_rmc_and_interface"
c3f9da
+NET_INFO = {
c3f9da
+    'lo': {'ipv4': [{'ip': '127.0.0.1',
c3f9da
+                    'bcast': '', 'mask': '255.0.0.0',
c3f9da
+                                 'scope': 'host'}],
c3f9da
+           'ipv6': [{'ip': '::1/128',
c3f9da
+                     'scope6': 'host'}], 'hwaddr': '',
c3f9da
+           'up': 'True'},
c3f9da
+    'env2': {'ipv4': [{'ip': '8.0.0.19',
c3f9da
+                       'bcast': '8.0.0.255', 'mask': '255.255.255.0',
c3f9da
+                                             'scope': 'global'}],
c3f9da
+             'ipv6': [{'ip': 'fe80::f896:c2ff:fe81:8220/64',
c3f9da
+                       'scope6': 'link'}], 'hwaddr': 'fa:96:c2:81:82:20',
c3f9da
+             'up': 'True'},
c3f9da
+    'env3': {'ipv4': [{'ip': '90.0.0.14',
c3f9da
+                       'bcast': '90.0.0.255', 'mask': '255.255.255.0',
c3f9da
+                                              'scope': 'global'}],
c3f9da
+             'ipv6': [{'ip': 'fe80::f896:c2ff:fe81:8221/64',
c3f9da
+                       'scope6': 'link'}], 'hwaddr': 'fa:96:c2:81:82:21',
c3f9da
+             'up': 'True'},
c3f9da
+    'env4': {'ipv4': [{'ip': '9.114.23.7',
c3f9da
+                       'bcast': '9.114.23.255', 'mask': '255.255.255.0',
c3f9da
+                                                'scope': 'global'}],
c3f9da
+             'ipv6': [{'ip': 'fe80::f896:c2ff:fe81:8222/64',
c3f9da
+                       'scope6': 'link'}], 'hwaddr': 'fa:96:c2:81:82:22',
c3f9da
+             'up': 'True'},
c3f9da
+    'env5': {'ipv4': [],
c3f9da
+             'ipv6': [{'ip': 'fe80::9c26:c3ff:fea4:62c8/64',
c3f9da
+                       'scope6': 'link'}], 'hwaddr': '42:20:86:df:fa:4c',
c3f9da
+             'up': 'True'}}
c3f9da
+
c3f9da
+
c3f9da
+class TestRsctNodeFile(t_help.CiTestCase):
c3f9da
+    def test_disable_ipv6_interface(self):
c3f9da
+        """test parsing of iface files."""
c3f9da
+        fname = self.tmp_path("iface-eth5")
c3f9da
+        util.write_file(fname, dedent("""\
c3f9da
+            BOOTPROTO=static
c3f9da
+            DEVICE=eth5
c3f9da
+            HWADDR=42:20:86:df:fa:4c
c3f9da
+            IPV6INIT=yes
c3f9da
+            IPADDR6=fe80::9c26:c3ff:fea4:62c8/64
c3f9da
+            IPV6ADDR=fe80::9c26:c3ff:fea4:62c8/64
c3f9da
+            NM_CONTROLLED=yes
c3f9da
+            ONBOOT=yes
c3f9da
+            STARTMODE=auto
c3f9da
+            TYPE=Ethernet
c3f9da
+            USERCTL=no
c3f9da
+            """))
c3f9da
+
c3f9da
+        ccrmci.disable_ipv6(fname)
c3f9da
+        self.assertEqual(dedent("""\
c3f9da
+            BOOTPROTO=static
c3f9da
+            DEVICE=eth5
c3f9da
+            HWADDR=42:20:86:df:fa:4c
c3f9da
+            ONBOOT=yes
c3f9da
+            STARTMODE=auto
c3f9da
+            TYPE=Ethernet
c3f9da
+            USERCTL=no
c3f9da
+            NM_CONTROLLED=no
c3f9da
+            """), util.load_file(fname))
c3f9da
+
c3f9da
+    @mock.patch(MPATH + '.refresh_rmc')
c3f9da
+    @mock.patch(MPATH + '.restart_network_manager')
c3f9da
+    @mock.patch(MPATH + '.disable_ipv6')
c3f9da
+    @mock.patch(MPATH + '.refresh_ipv6')
c3f9da
+    @mock.patch(MPATH + '.netinfo.netdev_info')
c3f9da
+    @mock.patch(MPATH + '.util.which')
c3f9da
+    def test_handle(self, m_refresh_rmc,
c3f9da
+                    m_netdev_info, m_refresh_ipv6, m_disable_ipv6,
c3f9da
+                    m_restart_nm, m_which):
c3f9da
+        """Basic test of handle."""
c3f9da
+        m_netdev_info.return_value = NET_INFO
c3f9da
+        m_which.return_value = '/opt/rsct/bin/rmcctrl'
c3f9da
+        ccrmci.handle(
c3f9da
+            "refresh_rmc_and_interface", None, None, None, None)
c3f9da
+        self.assertEqual(1, m_netdev_info.call_count)
c3f9da
+        m_refresh_ipv6.assert_called_with('env5')
c3f9da
+        m_disable_ipv6.assert_called_with(
c3f9da
+            '/etc/sysconfig/network-scripts/ifcfg-env5')
c3f9da
+        self.assertEqual(1, m_restart_nm.call_count)
c3f9da
+        self.assertEqual(1, m_refresh_rmc.call_count)
c3f9da
+
c3f9da
+    @mock.patch(MPATH + '.netinfo.netdev_info')
c3f9da
+    def test_find_ipv6(self, m_netdev_info):
c3f9da
+        """find_ipv6_ifaces parses netdev_info returning those with ipv6"""
c3f9da
+        m_netdev_info.return_value = NET_INFO
c3f9da
+        found = ccrmci.find_ipv6_ifaces()
c3f9da
+        self.assertEqual(['env5'], found)
c3f9da
+
c3f9da
+    @mock.patch(MPATH + '.util.subp')
c3f9da
+    def test_refresh_ipv6(self, m_subp):
c3f9da
+        """refresh_ipv6 should ip down and up the interface."""
c3f9da
+        iface = "myeth0"
c3f9da
+        ccrmci.refresh_ipv6(iface)
c3f9da
+        m_subp.assert_has_calls([
c3f9da
+            mock.call(['ip', 'link', 'set', iface, 'down']),
c3f9da
+            mock.call(['ip', 'link', 'set', iface, 'up'])])
c3f9da
-- 
c3f9da
1.8.3.1
c3f9da