← Back to team overview

launchpad-reviewers team mailing list archive

[Merge] lp:~matsubara/maas/enable-cluster-controller into lp:~maas-maintainers/maas/qa-lab-tests

 

Diogo Matsubara has proposed merging lp:~matsubara/maas/enable-cluster-controller into lp:~maas-maintainers/maas/qa-lab-tests.

Commit message:
enable cluster controller tests

Requested reviews:
  MAAS Maintainers (maas-maintainers)

For more details, see:
https://code.launchpad.net/~matsubara/maas/enable-cluster-controller/+merge/146202

This branch adds the necessary changes to test the cluster controller on a separate network. It enables the CC tests using an environment variable. I included some changes suggested by rvba to improve the code organization and remove some duplication.

A test run with this branch can be seen here: http://10.189.74.2:8080/view/CE/job/quantal-adt-maas-cluster-controller/ARCH=amd64,label=maas-lenovo-lab/143/console



-- 
https://code.launchpad.net/~matsubara/maas/enable-cluster-controller/+merge/146202
Your team MAAS Maintainers is requested to review the proposed merge of lp:~matsubara/maas/enable-cluster-controller into lp:~maas-maintainers/maas/qa-lab-tests.
=== added file 'cluster-controller-integration.py'
--- cluster-controller-integration.py	1970-01-01 00:00:00 +0000
+++ cluster-controller-integration.py	2013-02-14 13:55:23 +0000
@@ -0,0 +1,40 @@
+import os
+from testtools import TestCase
+from testtools.matchers import Contains
+from time import sleep
+
+from utils import run_command, update_pxe_config, SIGNAL_FILE
+
+
+class ClusterControllerIntegration(TestCase):
+
+    def test_01_preseed_updated_cluster_config(self):
+        # Make sure the cluster config was updated by the seed file.
+        maas_fd = open("/etc/maas/maas_cluster.conf" , "r+")
+        maas_file = maas_fd.read()
+        self.assertThat(maas_file, Contains(
+            'MAAS_URL=http://192.168.21.5/MAAS'))
+
+    def test_02_update_pxe_config(self):
+        update_pxe_config()
+
+    def test_03_import_pxe_files(self):
+        output, err = run_command(["maas-import-pxe-files"])
+        self.assertThat(output, Contains('Downloading to temporary location'))
+        # XXX: matsubara Bug=1076444
+        # maas-import-pxe-files outputs to stderr during normal operation.
+        #self.assertIs(err, '')
+
+    def test_04_wait_for_region_controller(self):
+        """Wait for the region controller to run the integration tests.
+
+        The region controller will create a file in the cluster controller
+        once all tests finish.
+        """
+        while os.path.exists(SIGNAL_FILE) is False:
+            sleep(10)
+
+    @classmethod
+    def tearDownClass(cls):
+        """Power off the cluster controller VM after the test run."""
+        run_command(["sudo", "poweroff"])

=== removed file 'control'
--- control	2012-12-06 01:14:46 +0000
+++ control	1970-01-01 00:00:00 +0000
@@ -1,4 +0,0 @@
-Tests: maas-package-test
-# XXX: matsubara on precise you need to be explicit as @ is not being expanded
-# correctly
-Depends: @, maas, maas-dhcp, maas-dns, maas-cli, python-nose, bzr, python-testtools, python-yaml, juju

=== added file 'maas-cluster-controller-control'
--- maas-cluster-controller-control	1970-01-01 00:00:00 +0000
+++ maas-cluster-controller-control	2013-02-14 13:55:23 +0000
@@ -0,0 +1,2 @@
+Tests: maas-cluster-controller-package-test
+Depends: maas-cluster-controller, maas-dhcp, maas-cli, python-nose, bzr, python-testtools, python-yaml, juju, python-pip

=== added file 'maas-cluster-controller-package-test'
--- maas-cluster-controller-package-test	1970-01-01 00:00:00 +0000
+++ maas-cluster-controller-package-test	2013-02-14 13:55:23 +0000
@@ -0,0 +1,5 @@
+#!/bin/bash
+set -e -u
+exec 2>&1
+pip install --no-index --find-links file:///home/ubuntu/ nose-timer
+nosetests --verbose --stop --with-timer debian/tests/cluster-controller-integration.py

=== added file 'maas-control'
--- maas-control	1970-01-01 00:00:00 +0000
+++ maas-control	2013-02-14 13:55:23 +0000
@@ -0,0 +1,2 @@
+Tests: maas-package-test
+Depends: maas, maas-dhcp, maas-dns, maas-cli, python-nose, bzr, python-testtools, python-yaml, juju, python-pip

=== modified file 'maas-integration.py'
--- maas-integration.py	2013-02-08 14:50:19 +0000
+++ maas-integration.py	2013-02-14 13:55:23 +0000
@@ -1,17 +1,16 @@
 import os
 from simplejson import loads
 import sys
-from subprocess import Popen, PIPE
 from time import sleep
 from testtools import TestCase
-from testtools.matchers import Contains, Equals, StartsWith
+from testtools.matchers import Contains, StartsWith
 from testtools.content import text_content
-from unittest import skipIf
+from unittest import skipIf, SkipTest
 import yaml
 import platform
 import urllib2
 
-from timeout import timeout
+from utils import run_command, timeout, update_pxe_config, SIGNAL_FILE
 
 sys.path.insert(0, "/usr/share/maas")
 os.environ['DJANGO_SETTINGS_MODULE'] = 'maas.settings'
@@ -19,9 +18,11 @@
 from django.contrib.auth.models import User
 
 from maasserver.models.user import get_creds_tuple
+from maasserver.models import BootImage
+from maasserver.utils import get_local_cluster_UUID
 from apiclient.creds import convert_tuple_to_string
 
-# Environment variables that can be used to configured
+# Environment variables that can be used to configure
 # what is tested.
 
 # Series to install on deployed nodes.
@@ -40,6 +41,8 @@
 DO_NOT_TEST_JUJU = bool(
     os.environ.get('DO_NOT_TEST_JUJU', False))
 
+# Whether or not the cluster controller nodes should be used.
+USE_CC_NODES = bool(os.environ.get('USE_CC_NODES', False))
 
 MAAS_URL = "http://192.168.21.5/MAAS";
 ADMIN_USER = "admin"
@@ -47,21 +50,28 @@
 POWER_USER = "root"
 POWER_PASS = "ubuntu"
 
-LENOVO_LAB = {
+REGION_CONTROLLER_NODES = {
     "00:E0:81:DD:D5:99" : "192.168.22.33",
     "00:E0:81:DD:D1:0B" : "192.168.22.34",
     "00:E0:81:DD:D4:11" : "192.168.22.35",
     "00:E0:81:D1:B1:47" : "192.168.22.36",
-# There are in a different network, enable them
-# when testing with a CC on a different network
-# is supported.
-#    "00:E0:81:DD:D1:1B" : "192.168.22.37",
-#    "00:E0:81:DD:D1:2B" : "192.168.22.38",
-#    "00:E0:81:DD:D1:A3" : "192.168.22.39",
-#    "00:E0:81:DC:38:6D" : "192.168.22.40",
-#    "00:E0:81:DD:D0:FF" : "192.168.22.41",
-#    "00:E0:81:DD:D4:F9" : "192.168.22.42"
-    }
+    }
+
+CLUSTER_CONTROLLER_NODES = {
+    "00:E0:81:DD:D1:1B" : "192.168.22.37",
+    "00:E0:81:DD:D1:2B" : "192.168.22.38",
+    "00:E0:81:DD:D1:A3" : "192.168.22.39",
+    "00:E0:81:DC:38:6D" : "192.168.22.40",
+    "00:E0:81:DD:D0:FF" : "192.168.22.41",
+    "00:E0:81:DD:D4:F9" : "192.168.22.42"
+    }
+
+CLUSTER_CONTROLLER_IP = '192.168.20.5'
+
+if USE_CC_NODES:
+    LENOVO_LAB = dict(REGION_CONTROLLER_NODES, **CLUSTER_CONTROLLER_NODES)
+else:
+    LENOVO_LAB = REGION_CONTROLLER_NODES
 
 ARM_LAB = {
     'fc:2f:40:d8:fb:1a': '192.168.21.50',
@@ -123,9 +133,7 @@
             os.mkdir(ssh_dir)
         except OSError:
             pass
-        Popen(
-            ['ssh-keygen', '-t', 'rsa', '-N', '', '-f', ssh_key],
-            stdout=PIPE, stderr=PIPE).communicate()
+        run_command(['ssh-keygen', '-t', 'rsa', '-N', '', '-f', ssh_key])
     # Setup ssh config.
     ssh_config = os.path.join(ssh_dir, 'config')
     with open(ssh_config, 'w') as f:
@@ -139,6 +147,7 @@
         f.write(content)
 
 
+
 DEB_PROXY_CONFIG = """
 cache_peer 10.98.0.13 parent 8000 0 no-query no-digest
 never_direct allow all
@@ -152,9 +161,7 @@
     content = content + DEB_PROXY_CONFIG
     with open(config, 'w') as f:
         f.write(content)
-    Popen(
-        ['sudo', 'service', 'squid-deb-proxy', 'restart'],
-        stdout=PIPE, stderr=PIPE).communicate()
+    run_command(['sudo', 'service', 'squid-deb-proxy', 'restart'])
 
 
 class TestMAASIntegration(TestCase):
@@ -166,13 +173,8 @@
             count += len(ARM_LAB)
         return count
 
-    def _run_command(self, args):
-        process = Popen(args, stdout=PIPE, stderr=PIPE, stdin=PIPE)
-        stdout, stderr = process.communicate()
-        return stdout, stderr
-
     def _run_maas_cli(self, args):
-        result = self._run_command(["maas-cli", "maas"] + args)
+        result = run_command(["maas-cli", "maas"] + args)
         self.addDetail(
             'maas-cli maas %s' % str(args), text_content(str(result)))
         return result
@@ -210,47 +212,22 @@
         maas_fd.seek(0)
         maas_fd.write(maas_file)
         maas_fd.close()
-        output, err = self._run_command(["service", "apache2", "restart"])
+        output, err = run_command(["service", "apache2", "restart"])
         self.assertThat(
             output, StartsWith(' * Restarting web server apache2'))
 
     def test_03_restart_dbus_avahi(self):
         """XXX: matsubara bug=1065775 """
-        output, err = self._run_command(["service", "dbus", "restart"])
+        output, err = run_command(["service", "dbus", "restart"])
         self.assertThat(output, Contains('dbus start/running'))
-        output, err = self._run_command(["service", "avahi-daemon", "restart"])
+        output, err = run_command(["service", "avahi-daemon", "restart"])
         self.assertThat(output, Contains('avahi-daemon start/running'))
 
     def test_04_update_pxe_config(self):
-        ephemerals_fd = open("/etc/maas/import_ephemerals", "r+")
-        ephemerals = ephemerals_fd.read()
-        # Update mirrors for both trunk and the quantal SRU.
-        ephemerals_snippet = (
-            '\n'
-            'CLOUD_IMAGES_ARCHIVE="http://10.98.0.13/mirrors/maas-ephemeral"\n'
-            'REMOTE_IMAGES_MIRROR="http://10.98.0.13/mirrors/maas-ephemeral"\n'
-        #    XXX: rvb 2013-01-18: Do not use the daily ephemeral images as
-        #    they seem to be broken (more precisely, the image from 20130107 is
-        #    and the one from 20121008 isn't);  investigation is underway.
-        #   'STREAM=daily\n'
-            )
-        ephemerals += ephemerals_snippet
-        ephemerals_fd.seek(0)
-        ephemerals_fd.write(ephemerals)
-        ephemerals_fd.close()
-        # XXX: matsubara Bug=1074167
-        # Update import_pxe_files to not download squashfs images
-        # and use a proxy.
-        pxe_fd = open('/etc/maas/import_pxe_files', "r+")
-        pxe_file = pxe_fd.read()
-        pxe_snippet = 'export http_proxy="http://10.98.0.13:3128"\n'
-        pxe_file = pxe_snippet + pxe_file
-        pxe_fd.seek(0)
-        pxe_fd.write(pxe_file)
-        pxe_fd.close()
+        update_pxe_config()
 
     def test_05_import_pxe_files(self):
-        output, err = self._run_command(["maas-import-pxe-files"])
+        output, err = run_command(["maas-import-pxe-files"])
         self.assertThat(output, Contains('Downloading to temporary location'))
         # XXX: matsubara Bug=1076444
         # maas-import-pxe-files outputs to stderr during normal operation.
@@ -268,7 +245,7 @@
     def test_07_login_api(self):
         token_str = get_token_str()
         api_url = MAAS_URL + "/api/1.0/"
-        output, err = self._run_command(
+        output, err = run_command(
             ["maas-cli", "login", "maas", api_url, token_str])
         self.assertThat(
             output, Contains(
@@ -302,31 +279,76 @@
         output, err = self._run_command(["service", "apparmor", "reload"])
         self.assertThat(output, Contains('* Reloading AppArmor profiles'))
 
-    @timeout(60)
+    def _set_up_dhcp(self, uuid, dhcp_config):
+        dhcp_cli_args = [
+            "%s=%s" % (key, value) for key, value in dhcp_config.items()]
+        maas_dhcp_cmd = [
+            "node-group-interface", "update", uuid, "eth1" ] + dhcp_cli_args
+        output, err = self._run_maas_cli(maas_dhcp_cmd)
+        node_group = loads(output)
+        # The JSON object returned by MAAS doesn't include the router_ip
+        # address, so let's remove it from the dhcp_config before comparing.
+        dhcp_config.pop('router_ip')
+        self.assertEquals(node_group, dhcp_config)
+
+    @timeout(5*60)
     def test_09_set_up_dhcp(self):
         if "raring" in platform.linux_distribution():
             self._update_dhcpd_apparmor_profile()
 
+        region_uuid = get_local_cluster_UUID()
+
         output, err = self._run_maas_cli(["node-groups", "list"])
         node_groups = loads(output)
-        output, err = self._run_maas_cli([
-            "node-group-interface", "update", node_groups[0]['uuid'],
-            "eth1", "ip=192.168.21.5", "interface=eth1", "management=2",
-            "subnet_mask=255.255.255.0",
-            "broadcast_ip=192.168.21.255",
-            "router_ip=192.168.21.1",
-            "ip_range_low=192.168.21.10",
-            "ip_range_high=192.168.21.30"])
-        node_group = loads(output)
-        self.assertThat(node_group['ip'], Equals('192.168.21.5'))
-        self.assertThat(node_group['interface'], Equals('eth1'))
-        self.assertThat(node_group['subnet_mask'], Equals('255.255.255.0'))
-        self.assertThat(node_group['broadcast_ip'], Equals('192.168.21.255'))
-        self.assertThat(node_group['ip_range_low'], Equals('192.168.21.10'))
-        self.assertThat(node_group['ip_range_high'], Equals('192.168.21.30'))
+
+        ng_uuids = [ng['uuid'] for ng in node_groups]
+        self.assertIn(region_uuid, ng_uuids)
+
+        region_dhcp_config = {
+            "ip": "192.168.21.5",
+            "interface": "eth1",
+            "subnet_mask": "255.255.255.0",
+            "broadcast_ip": "192.168.21.255",
+            "router_ip": "192.168.21.1",
+            "management": 2,
+            "ip_range_low": "192.168.21.10",
+            "ip_range_high": "192.168.21.30"}
+
+        self._set_up_dhcp(region_uuid, region_dhcp_config)
+
         # Wait for the task to complete and create the dhcpd.conf file.
         while os.path.exists("/etc/maas/dhcpd.conf") is False:
             sleep(2)
+        if USE_CC_NODES:
+            # Wait until we have two node_group since the cluster controller
+            # might take some time to finish its configuration and contact the
+            # region controller.
+            while not len(node_groups) == 2:
+                output, err = self._run_maas_cli(["node-groups", "list"])
+                node_groups = loads(output)
+
+            for ng in node_groups:
+                if ng['uuid'] != region_uuid:
+                    cluster_uuid = ng['uuid']
+                    break
+            # Configure the cluster controller DHCP server.
+            cluster_dhcp_config = {
+                "ip": "192.168.20.5",
+                "interface": "eth1",
+                "subnet_mask": "255.255.255.0",
+                "broadcast_ip": "192.168.20.255",
+                "router_ip": "192.168.20.1",
+                "management": 2,
+                "ip_range_low": "192.168.20.10",
+                "ip_range_high": "192.168.20.30",
+                }
+            self._set_up_dhcp(cluster_uuid, cluster_dhcp_config)
+
+            # Accept the cluster controller into the MAAS server.
+            output, err = self._run_maas_cli([
+                "node-groups", "accept", "uuid=%s" % cluster_uuid])
+            self.assertThat(
+                output, Contains("Nodegroup(s) accepted."))
 
     def test_10_update_dns_config(self):
         #XXX: matsubara Could be asked by maas-dns package and configurable
@@ -334,18 +356,18 @@
         dns_config = open("/etc/bind/named.conf.options", 'w')
         dns_config.write(LAB_DNS_CONFIG)
         dns_config.close()
-        output, err = self._run_command(["service", "bind9", "restart"])
+        output, err = run_command(["service", "bind9", "restart"])
         self.assertThat(output, Contains(
             '* Starting domain name service... bind9'))
 
     def power_on(self, ip, user, password):
-        output, err = self._run_command(
+        output, err = run_command(
             ["ipmipower", "-h", ip, "-u", user, "-p", password, "--on"])
         self.addDetail('IPMI power on %s' % ip, text_content(str(output)))
         self.assertThat(output, Contains("%s: ok" % ip))
 
     def power_off(self, ip, user, password):
-        output, err = self._run_command(
+        output, err = run_command(
             ["ipmipower", "-h", ip, "-u", user, "-p", password, "--off"])
         self.addDetail('IPMI power off %s' % ip, text_content(str(output)))
         self.assertThat(output, Contains("%s: ok" % ip))
@@ -360,7 +382,17 @@
                 self.power_off(ipmi_address, 'admin', 'admin')
                 self.power_on(ipmi_address, 'admin', 'admin')
 
+    # XXX: matsubara bug=1108319
+    # revert the timeout back to the original value once bug is fixed.
+    #@timeout(3*60)
+    @timeout(8*60)
     def test_11_boot_nodes_enlist(self):
+        if USE_CC_NODES:
+            # XXX: matsubara bug=1108319
+            #  The maas-cli doesn't have a way to tell if cluster controllers
+            #  are missing the boot image.
+            while not BootImage.objects.all().exists():
+                sleep(10)
         self._boot_nodes()
 
     def _wait_nodes(self, status, min_node=None):
@@ -379,7 +411,7 @@
             filtered_list = [
                 node for node in node_list if node['status'] == status]
 
-    @timeout(5*60)
+    @timeout(6*60)
     def test_12_check_nodes_declared(self):
         self._wait_nodes(0)
 
@@ -425,7 +457,7 @@
         setup_local_dns()
 
     def _run_juju_command(self, args):
-        output, err = self._run_command(["juju"] + args)
+        output, err = run_command(["juju"] + args)
         # For some reason, in the log, juju replaces '-' by '_'.
         command_name = args[0].replace('-', '_')
         self.assertIn(
@@ -525,3 +557,18 @@
     def test_20_juju_add_unit_mediawiki(self):
         self._wait_machines_running(4)
         self._wait_units_started('mediawiki', 2)
+
+    @classmethod
+    def tearDownClass(cls):
+        """Signal to the cluster that the region controller tests finished."""
+        if not USE_CC_NODES:
+            raise SkipTest("Not testing Cluster controller")
+        cluster_ssh_command = [
+            "ssh", "-o", "UserKnownHostsFile=/dev/null",
+            "-o", "StrictHostKeyChecking=no",
+            "-o", "CheckHostIP=no",
+            "-i", "/home/ubuntu/adtkey",
+            "-t", "-o", "BatchMode=yes", "-l", "ubuntu",
+            CLUSTER_CONTROLLER_IP]
+        signal_cmd = ["touch", SIGNAL_FILE]
+        run_command(cluster_ssh_command + signal_cmd)

=== modified file 'maas-package-test'
--- maas-package-test	2012-12-06 02:22:18 +0000
+++ maas-package-test	2013-02-14 13:55:23 +0000
@@ -1,4 +1,5 @@
-#!/bin/sh
+#!/bin/bash
 set -e -u
 exec 2>&1
-nosetests --verbose --stop debian/tests/maas-integration.py
+pip install --no-index --find-links file:///home/ubuntu/ nose-timer
+nosetests --verbose --stop --with-timer debian/tests/maas-integration.py:TestMAASIntegration

=== added file 'utils.py'
--- utils.py	1970-01-01 00:00:00 +0000
+++ utils.py	2013-02-14 13:55:23 +0000
@@ -0,0 +1,73 @@
+# Timeout decorator from
+# http://stackoverflow.com/questions/2281850/timeout-function-if-it-takes-too-long-to-finish
+from subprocess import Popen, PIPE
+from tempfile import mktemp
+from functools import wraps
+import errno
+import os
+import signal
+
+
+SIGNAL_FILE = mktemp("maas-sig-file")
+
+
+class TimeoutError(Exception):
+    pass
+
+
+def timeout(seconds=10, error_message=os.strerror(errno.ETIME)):
+    def decorator(func):
+        def _handle_timeout(signum, frame):
+            raise TimeoutError(error_message)
+
+        def wrapper(*args, **kwargs):
+            signal.signal(signal.SIGALRM, _handle_timeout)
+            signal.alarm(seconds)
+            try:
+                result = func(*args, **kwargs)
+            finally:
+                signal.alarm(0)
+            return result
+
+        return wraps(func)(wrapper)
+
+    return decorator
+
+
+def run_command(args):
+    """A wrapper to Popen to run commands in the command-line."""
+    process = Popen(args, stdout=PIPE, stderr=PIPE, stdin=PIPE)
+    stdout, stderr = process.communicate()
+    return stdout, stderr
+
+
+def update_pxe_config():
+    """Update the files necessary for PXE booting."""
+    ephemerals_fd = open("/etc/maas/import_ephemerals", "r+")
+    ephemerals = ephemerals_fd.read()
+    # Update mirrors for both trunk and the quantal SRU.
+    ephemerals_snippet = (
+        '\n'
+        'CLOUD_IMAGES_ARCHIVE="http://10.98.0.13/mirrors/maas-ephemeral"\n'
+        'REMOTE_IMAGES_MIRROR="http://10.98.0.13/mirrors/maas-ephemeral"\n'
+    #    XXX: rvb 2013-01-18: Do not use the daily ephemeral images as
+    #    they seem to be broken (more precisely, the image from 20130107 is
+    #    and the one from 20121008 isn't);  investigation is underway.
+    #   'STREAM=daily\n'
+        )
+    ephemerals += ephemerals_snippet
+    ephemerals_fd.seek(0)
+    ephemerals_fd.write(ephemerals)
+    ephemerals_fd.close()
+    # XXX: matsubara Bug=1074167
+    # Update import_pxe_files to not download squashfs images
+    # and use a proxy.
+    pxe_fd = open('/etc/maas/import_pxe_files', "r+")
+    pxe_file = pxe_fd.read()
+    pxe_snippet = 'export http_proxy="http://10.98.0.13:3128"\n'
+    pxe_file = pxe_snippet + pxe_file
+    pxe_fd.seek(0)
+    pxe_fd.write(pxe_file)
+    pxe_fd.close()
+
+