~brendan-donegan/maas/qa-lab-tests_bmc_ip_range_extend

« back to all changes in this revision

Viewing changes to maas-integration.py

  • Committer: MAAS Lander
  • Author(s): Brendan Donegan
  • Date: 2016-11-03 14:46:19 UTC
  • mfrom: (449.2.24 maas-image-ci)
  • Revision ID: maas_lander-20161103144619-rms8tg0mtf2g8fdr
[r=allenap][bug=][author=brendan-donegan] Refactor tests into separate suites, one for integration testing and one for CI testing. Add tests which perform actions on deployed machines over SSH

Show diffs side-by-side

added added

removed removed

Lines of Context:
1
 
from __future__ import (
2
 
    absolute_import,
3
 
    print_function,
4
 
    unicode_literals,
5
 
    )
6
 
 
7
 
__metaclass__ = type
8
 
__all__ = []
9
 
 
10
 
import os
11
 
import pipes
12
 
import platform
13
 
import pwd
14
 
import re
15
 
import sys
16
 
import tempfile
17
 
from contextlib import closing
18
 
from distutils.version import StrictVersion
19
 
from itertools import count
20
 
from textwrap import dedent
21
 
from time import sleep
22
 
from unittest import (
23
 
    skipIf,
24
 
    skipUnless,
25
 
    SkipTest,
26
 
    TestLoader,
27
 
)
28
 
try:
29
 
    from urllib2 import (
30
 
        urlopen,
31
 
        HTTPError,
32
 
    )
33
 
except ImportError:
34
 
    from urllib.request import urlopen
35
 
    from urllib.error import HTTPError
36
 
 
37
 
try:
38
 
    from urllib.parse import urlparse
39
 
except ImportError:
40
 
    from urlparse import urlparse
41
 
 
42
 
from netaddr import (
43
 
    IPAddress,
44
 
    IPRange,
45
 
)
46
 
from simplejson import loads
47
 
from testtools import TestCase
48
 
from testtools.content import (
49
 
    content_from_file,
50
 
    content_from_stream,
51
 
    text_content,
52
 
)
53
 
from testtools.matchers import (
54
 
    Contains,
55
 
    ContainsAll,
56
 
    Equals,
57
 
    MatchesRegex,
58
 
    Not,
59
 
    ContainsDict,
60
 
    MatchesDict,
61
 
    MatchesListwise,
62
 
)
63
 
from timeout import timeout
64
 
# Import `pause_until_released` if you need to block execution at a given state
65
 
# so you can examine the machines in the testing pool.
66
 
from utils import (
67
 
    assertCommandReturnCode,
68
 
    assertStartedUpstartService,
69
 
    change_logs_permissions,
70
 
    filter_dict,
71
 
    get_maas_revision,
72
 
    get_maas_version,
73
 
    get_ubuntu_version,
74
 
    retries,
75
 
    run_command,
76
 
    pause_until_released  # If this is not used below, linters will complain.
77
 
)
78
 
import yaml
79
 
import zmq
80
 
 
81
 
 
82
 
# Needed by the imports below - linters will complain.
83
 
sys.path.insert(0, "/usr/share/maas")
84
 
os.environ['DJANGO_SETTINGS_MODULE'] = 'maas.settings'
85
 
 
86
 
import django
87
 
django.setup()
88
 
 
89
 
from django.core.exceptions import AppRegistryNotReady
90
 
from django.core.management import call_command
91
 
from django.contrib.auth.models import User
92
 
 
93
 
from maasserver.enum import NODE_STATUS
94
 
from maasserver.models.user import get_creds_tuple
95
 
from maasserver.utils import get_local_cluster_UUID
96
 
from apiclient.creds import convert_tuple_to_string
97
 
 
98
 
from config import (
99
 
    ADMIN_USER,
100
 
    ARM_LAB,
101
 
    BMC_START_IP,
102
 
    BMC_END_IP,
103
 
    CLUSTER_CONTROLLER_IP,
104
 
    DO_NOT_USE_ARM_NODES,
105
 
    TEST_JUJU,
106
 
    TEST_CUSTOM_IMAGES,
107
 
    HTTP_PROXY,
108
 
    LAB_DNS_CONFIG,
109
 
    LENOVO_LAB,
110
 
    MAAS_URL,
111
 
    MAIN_ARCHIVE,
112
 
    NODE_SERIES,
113
 
    PASSWORD,
114
 
    PORTS_ARCHIVE,
115
 
    POWER_PASS,
116
 
    POWER_USER,
117
 
    PPC_SYSTEMS,
118
 
    SLAVE_HOSTNAME,
119
 
    SLAVE_IP,
120
 
    SLAVE_MAC,
121
 
    SQUID_DEB_PROXY_URL,
122
 
    USER_DATA_URL,
123
 
    USE_ARM_NODES,
124
 
    USE_CC_NODES,
125
 
    USE_PPC_NODES,
126
 
)
127
 
 
128
 
 
129
 
def setup_juju_cloud(server, cloud_yaml):
130
 
    cloud_yaml.write("""
131
 
clouds:
132
 
    testmaas:
133
 
        type: maas
134
 
        auth-types: [oauth1]
135
 
        endpoint: {server}""".format(server=server).encode('utf8'))
136
 
    cloud_yaml.file.flush()
137
 
 
138
 
 
139
 
def setup_juju_authentication(credentials_yaml, oauth):
140
 
    credentials_yaml.write("""
141
 
credentials:
142
 
    testmaas:
143
 
        test:
144
 
            auth-type: oauth1
145
 
            maas-oauth: {oauth}""".format(oauth=oauth))
146
 
    credentials_yaml.flush()
147
 
 
148
 
def get_token_str():
149
 
    admin = User.objects.get(username=ADMIN_USER)
150
 
    token = admin.tokens.all()[0]
151
 
    return convert_tuple_to_string(get_creds_tuple(token))
152
 
 
153
 
 
154
 
def setup_ssh():
155
 
    home_dir = os.path.expanduser('~/')
156
 
    ssh_dir = os.path.join(home_dir, '.ssh')
157
 
    # Setup ssh keys.
158
 
    ssh_key = os.path.join(ssh_dir, 'id_rsa')
159
 
    if not os.path.exists(ssh_key):
160
 
        try:
161
 
            os.mkdir(ssh_dir)
162
 
        except OSError:
163
 
            pass
164
 
        run_command(['ssh-keygen', '-t', 'rsa', '-N', '', '-f', ssh_key])
165
 
    # Setup ssh config.
166
 
    ssh_config = os.path.join(ssh_dir, 'config')
167
 
    with open(ssh_config, 'w') as f:
168
 
        f.write('StrictHostKeyChecking no')
169
 
 
170
 
 
171
 
def setup_local_dns():
172
 
    with open('/etc/resolv.conf', encoding='ascii') as fd:
173
 
        content = fd.read()
174
 
    content = 'nameserver 127.0.0.1\n' + content
175
 
    with open('/etc/resolv.conf', mode='w', encoding='ascii') as f:
176
 
        f.write(content)
177
 
 
178
 
 
179
 
DEB_PROXY_CONFIG = """
180
 
cache_peer %s parent %s 0 no-query no-digest
181
 
never_direct allow all
182
 
""" % (urlparse(SQUID_DEB_PROXY_URL).hostname,
183
 
       urlparse(SQUID_DEB_PROXY_URL).port)
184
 
 
185
 
 
186
 
tests_order = [
187
 
    "test_create_admin",
188
 
    "test_restart_dbus_avahi",
189
 
    "test_update_maas_url",
190
 
    "test_update_maas_url_rack",
191
 
    "test_check_initial_services_systemctl",
192
 
    "test_check_rpc_info",
193
 
    "test_update_preseed_arm",
194
 
    "test_login_api",
195
 
    "test_maas_logged_in",
196
 
    "test_set_main_archive",
197
 
    "test_main_archive_in_enlist_userdata_package_mirrors_config",
198
 
    "test_main_archive_in_enlist_userdata_apt_config",
199
 
    "test_set_http_proxy",
200
 
    "test_add_ssh_key",  # This is needed to deploy nodes.
201
 
    "test_region_rack_connected",
202
 
    "test_set_custom_boot_source",
203
 
    "test_stop_image_import", # Stop before updating boot sources
204
 
    "test_add_boot_source_selection_ppc64el",
205
 
    # From this point on, we can connect to the external world.
206
 
    "test_start_image_import", # Start importing now, check later.
207
 
    "test_create_dynamic_range",
208
 
    "test_reserve_bmc_range",
209
 
    "test_create_slave_device_and_link_subnet",
210
 
    "test_slave_device_interface_linked",
211
 
    "test_set_up_dhcp_vlan",
212
 
    "test_check_dhcp_service_systemctl",
213
 
    "test_update_dns_config_systemctl",
214
 
    "test_add_new_zones",
215
 
    "test_list_zones",
216
 
    "test_delete_zone",
217
 
    "test_add_new_spaces",
218
 
    "test_add_subnet_to_space",
219
 
    "test_list_spaces",
220
 
    "test_list_subnets",
221
 
    "test_delete_subnet",
222
 
    "test_delete_space",
223
 
    "test_add_new_fabrics",
224
 
    "test_add_vlan_to_fabric",
225
 
    "test_list_fabrics",
226
 
    "test_list_vlans",
227
 
    "test_delete_vlan",
228
 
    "test_delete_fabric",
229
 
    "test_region_imported_images", # Check the images imported before.
230
 
    "test_rack_imported_images", # Check rack imported images
231
 
    # From this point on, we can enlist and deploy nodes.
232
 
    "test_poweron_nodes_to_enlist",
233
 
    "test_check_machines_new",
234
 
    "test_assign_machines_to_test_zone",
235
 
    "test_set_machines_ipmi_config",
236
 
    "test_start_commissioning_machines",
237
 
    "test_check_nodes_ready",
238
 
    # At this point, we can start working with Juju...
239
 
    "test_configure_juju",
240
 
    "test_juju_bootstrap",
241
 
    "test_apply_tag_to_all_machines",
242
 
    "test_check_tag_applied_to_all_machines",
243
 
    "test_juju_deploy_postgresql",
244
 
    # Or, alternatively deploy manually
245
 
    "test_allocate_machines",
246
 
    "test_deploy_machines",
247
 
    "test_machines_deployed",
248
 
    "test_ip_addresses_not_in_dynamic_range",
249
 
    "test_ip_addresses_not_in_bmc_range",
250
 
]
 
1
from unittest import TestLoader
 
2
 
 
3
from common import TestMAASIntegration
 
4
 
 
5
def get_tests_order():
 
6
    suite = open('integration-suite')
 
7
    tests = [line.strip() for line in suite.readlines()]
 
8
 
 
9
tests_order = get_tests_order()
 
10
 
251
11
 
252
12
def sorting_method(ignored, first_test, second_test):
253
13
    return tests_order.index(first_test) - tests_order.index(second_test)
254
 
 
255
 
 
256
 
TestLoader.sortTestMethodsUsing = sorting_method
257
 
 
258
 
# The 'maas-cli' has been renamed to 'maas' in version 1931.
259
 
maascli = 'maas' if get_maas_revision() >= 1931 else 'maas-cli'
260
 
 
261
 
# The 'maas-region-admin' has been renamed to 'maas-region' in version .
262
 
maas_region_admin = (
263
 
    'maas-region' if get_maas_revision() >= 4738 else 'maas-region-admin')
264
 
 
265
 
# Uses the pre-1.6 API, where a cluster interface had no "name" field.
266
 
# MAAS will take the name from the "interface" field, as long as it's
267
 
# a unique name within the cluster.  Newer clients would pass a name.
268
 
REGION_DHCP_CONFIG = {
269
 
    "ip": "10.245.136.6",
270
 
    "interface": "eth1",
271
 
    "subnet_mask": "255.255.248.0",
272
 
    "broadcast_ip": "10.245.143.255",
273
 
    "router_ip": "10.245.136.1",
274
 
    "management": 2,
275
 
    "ip_range_low": "10.245.136.10",
276
 
    "ip_range_high": "10.245.136.200",
277
 
    # add cidr as it is now default in new networking configs.
278
 
    "cidr": "10.245.136.0/21"
279
 
}
280
 
 
281
 
 
282
 
class TestMAASIntegration(TestCase):
283
 
 
284
 
    def get_node_count(self):
285
 
        """The number of available nodes."""
286
 
        count = len(LENOVO_LAB)
287
 
        if USE_ARM_NODES:
288
 
            count += len(ARM_LAB)
289
 
        if USE_PPC_NODES:
290
 
            count += len(PPC_SYSTEMS)
291
 
        return count
292
 
 
293
 
    def _wait_maas_running(self):
294
 
        for tries in range(10):
295
 
            try:
296
 
                urlopen(MAAS_URL).code
297
 
            except HTTPError as e:
298
 
                sleep(30)
299
 
                continue
300
 
            return True
301
 
        return False
302
 
 
303
 
    def _run_maas_cli(self, args):
304
 
        retcode, output, err = run_command([maascli, "maas"] + args)
305
 
 
306
 
        self.addDetail(
307
 
            'retcode for %s maas %s' % (maascli, args),
308
 
            text_content('%d' % retcode))
309
 
        self.addDetail(
310
 
            'stdout for %s maas %s' % (maascli, args),
311
 
            text_content(output))
312
 
        self.addDetail(
313
 
            'stderr for %s maas %s' % (maascli, args),
314
 
            text_content(err))
315
 
        return (output, err)
316
 
 
317
 
    def _update_dhcpd_apparmor_profile(self):
318
 
        """Workaround for raring due to bug 1107686."""
319
 
        with open("/etc/apparmor.d/usr.sbin.dhcpd", "r+") as dhcpd_fd:
320
 
            dhcpd_file = dhcpd_fd.read().decode('ascii')
321
 
            dhcpd_file = dhcpd_file.replace(
322
 
                'network packet packet,',
323
 
                'network packet packet,\n  network packet raw,')
324
 
            dhcpd_fd.seek(0)
325
 
            dhcpd_fd.write(dhcpd_file.encode('utf-8'))
326
 
        cmd = ["service", "apparmor", "reload"]
327
 
        expected_output = '* Reloading AppArmor profiles'
328
 
        assertCommandReturnCode(self, cmd, expected_output)
329
 
 
330
 
    def _get_rack_interface_link(self, primary_rack_system_id, ip):
331
 
        rack, err = self._run_maas_cli(
332
 
            ['rack-controller', 'read', primary_rack_system_id])
333
 
        rack_controller = loads(rack)
334
 
 
335
 
        for interface in rack_controller['interface_set']:
336
 
            for link in interface['links']:
337
 
                if ip == link['ip_address']:
338
 
                    return link
339
 
        return None
340
 
 
341
 
    def _set_up_dhcp(self, primary_rack_system_id, ip):
342
 
        # Support the two following cases:
343
 
        #  1. eth1 is auto-detected
344
 
        #  2. eth1  not auto-dectected. in 2.0 this would be a bug.
345
 
        rack_interface_link = self._get_rack_interface_link(
346
 
            primary_rack_system_id, ip
347
 
        )
348
 
        rack_fabric = rack_interface_link['subnet']['vlan']['fabric']
349
 
        rack_vlan = rack_interface_link['subnet']['vlan']['name']
350
 
 
351
 
        maas_dhcp_cmd = [
352
 
             'vlan', 'update', rack_fabric, rack_vlan, 'dhcp_on=True',
353
 
             'primary_rack=%s' % primary_rack_system_id]
354
 
 
355
 
        output, err = self._run_maas_cli(maas_dhcp_cmd)
356
 
 
357
 
        try:
358
 
            vlan_result = loads(output)
359
 
        except:
360
 
            pass
361
 
            #from maas.provisioningserver.service_monitor import get_service_state
362
 
            pause_until_released(ng_list.__repr__())
363
 
        # The JSON object returned by MAAS doesn't include the router_ip
364
 
        # address, so let's remove it from the dhcp_config before comparing.
365
 
        self.assertEqual(True, vlan_result['dhcp_on'])
366
 
 
367
 
    def _boot_nodes(self):
368
 
        # Run ipmipower to boot up nodes.
369
 
        for ipmi_address in LENOVO_LAB.values():
370
 
            self.power_off(ipmi_address, user=POWER_USER, password=POWER_PASS)
371
 
            self.power_on(ipmi_address, user=POWER_USER, password=POWER_PASS)
372
 
        if USE_PPC_NODES:
373
 
            for ipmi_address in PPC_SYSTEMS.values():
374
 
                self.power_off(ipmi_address, password='admin', driver='LAN_2_0')
375
 
                self.power_on(ipmi_address, password='admin', driver='LAN_2_0')
376
 
        if USE_ARM_NODES:
377
 
            for ipmi_address in ARM_LAB.values():
378
 
                self.power_off(ipmi_address, user='admin', password='admin')
379
 
                self.power_on(ipmi_address, user='admin', password='admin')
380
 
 
381
 
    def power_on(self, ip, user=None, password=None, driver=None):
382
 
        cmd = ["ipmipower", "-h", ip]
383
 
        if driver is not None:
384
 
            cmd += ["-D", driver]
385
 
        if user is not None:
386
 
            cmd += ["-u", user]
387
 
        if password is not None:
388
 
            cmd += ["-p", password]
389
 
        cmd += ["--on"]
390
 
        expected_output = "%s: ok" % ip
391
 
        assertCommandReturnCode(self, cmd, expected_output)
392
 
 
393
 
    def power_off(self, ip, user=None, password=None, driver=None):
394
 
        cmd = ["ipmipower", "-h", ip]
395
 
        if user is not None:
396
 
            cmd += ["-u", user]
397
 
        if driver is not None:
398
 
            cmd += ["-D", driver]
399
 
        if password is not None:
400
 
            cmd += ["-p", password]
401
 
        cmd += ["--off"]
402
 
        expected_output = "%s: ok" % ip
403
 
        assertCommandReturnCode(self, cmd, expected_output)
404
 
 
405
 
    def _get_machines_with_status(self, status):
406
 
        """Retrieve from the API all nodes with `status` (or substatus)."""
407
 
        # Start by listing all nodes.  The output will be shown on failure, so
408
 
        # even if we can filter by status, doing it client-side may provide
409
 
        # more helpful debug output.
410
 
        output, err = self._run_maas_cli(["machines", "read"])
411
 
        machine_list = loads(output)
412
 
        # In 1.9 we used to look at the substatus for new status's. However 2.0+
413
 
        # we simply look at the status
414
 
        machines = [
415
 
            machine for machine in machine_list
416
 
            if status == machine['status']
417
 
        ]
418
 
        return machines
419
 
 
420
 
    def _wait_machines(self, status=None, num_expected=None):
421
 
        """Wait for `num_expected` nodes with status `status`."""
422
 
        if num_expected is None:
423
 
            num_expected = self.get_node_count()
424
 
        self.addDetail(
425
 
            'wait_%d_%d' % (num_expected, status),
426
 
            text_content(
427
 
                "Waiting for %d node(s) with status %d."
428
 
                % (num_expected, status)))
429
 
 
430
 
        filtered_list = self._get_machines_with_status(status)
431
 
        while len(filtered_list) != num_expected:
432
 
            sleep(5)
433
 
            filtered_list = self._get_machines_with_status(status)
434
 
        return filtered_list
435
 
 
436
 
    def _run_juju_command(self, args, env=None):
437
 
        command = ["juju", "--debug"]
438
 
        command.extend(args)
439
 
        retcode, output, err = run_command(command, env=env)
440
 
        command_name = " ".join(map(pipes.quote, command))
441
 
        self.addDetail(command_name, text_content(output))
442
 
        self.addDetail(command_name, text_content(err))
443
 
        return retcode, output, err
444
 
 
445
 
    def get_juju_status(self):
446
 
        # Juju2 status defaults to tabular so we need to force YAML
447
 
        _, status_output, _ = self._run_juju_command(["status", "--format=yaml"])
448
 
        status = yaml.safe_load(status_output)
449
 
        return status
450
 
 
451
 
    def _wait_machines_running(self, nb_machines):
452
 
        """Wait until at least `nb_machines` have their agent running."""
453
 
        while True:
454
 
            status = self.get_juju_status()
455
 
            if status is not None:
456
 
                machines = status['machines'].values()
457
 
                running_machines = [
458
 
                    machine for machine in machines
459
 
                    if machine.get('machine-status', {}).get('current', '') in [
460
 
                        'running', 'started']]
461
 
                if len(running_machines) >= nb_machines:
462
 
                    break
463
 
            sleep(20)
464
 
 
465
 
    def _wait_units_started(self, application, nb_units):
466
 
        """Wait until an application has at least `nb_units` units."""
467
 
        while True:
468
 
            status = self.get_juju_status()
469
 
            try:
470
 
                units = status['applications'][application]['units'].values()
471
 
            except KeyError:
472
 
                units = []
473
 
            started_units = [
474
 
                unit for unit in units
475
 
                if unit.get('workload-status', {}).get('current', '') == 'active']
476
 
            if len(started_units) >= nb_units:
477
 
                break
478
 
            sleep(20)
479
 
 
480
 
    def test_create_admin(self):
481
 
        """Run maas createsuperuser."""
482
 
        cmd_output = call_command(
483
 
            "createadmin", username=ADMIN_USER, password=PASSWORD,
484
 
            email="example@canonical.com", noinput=True)
485
 
        self.assertEqual(cmd_output, None)
486
 
 
487
 
    # Since revision 1828, MAAS doesn't use avahi/dbus anymore so
488
 
    # we do not need to restart these daemons.
489
 
    @skipIf(
490
 
        get_maas_revision() >= 1828, "Avahi/DBUS are not used anymore")
491
 
    def test_restart_dbus_avahi(self):
492
 
        cmd = ["service", "dbus", "restart"]
493
 
        expected_output = 'dbus start/running'
494
 
        assertCommandReturnCode(self, cmd, expected_output)
495
 
        cmd = ["service", "avahi-daemon", "restart"]
496
 
        expected_output = 'avahi-daemon start/running'
497
 
        assertCommandReturnCode(self, cmd, expected_output)
498
 
 
499
 
    @skipIf(
500
 
        os.path.exists("/etc/maas/maas_local_settings.py"),
501
 
        "local_config_set is not available")
502
 
    def test_update_maas_url(self):
503
 
        # XXX: allenap: Using a debconf file to set the initial value of the
504
 
        # MAAS URL should not work; see test_update_maas_url_old(), but I'm
505
 
        # not sure if that's true.
506
 
        cmd = [
507
 
            "maas-region", "local_config_set", "--maas-url",
508
 
            "http://10.245.136.6/MAAS",
509
 
        ]
510
 
        assertCommandReturnCode(self, cmd, "")
511
 
        # Restart apache2.
512
 
        cmd = ["systemctl", "restart", "maas-regiond"]
513
 
        for retry in retries(delay=10, timeout=60):
514
 
            retcode, output, err = run_command(cmd)
515
 
            if retcode == 0:
516
 
                break
517
 
        else:
518
 
            self.fail("maas-regiond cannot be restarted")
519
 
 
520
 
    def test_update_maas_url_rack(self):
521
 
        cmd = [
522
 
            "maas-rack", "config", "--region-url",
523
 
            "http://10.245.136.6:5240/MAAS",
524
 
        ]
525
 
        assertCommandReturnCode(self, cmd, "")
526
 
        # Restart apache2.
527
 
        cmd = ["systemctl", "restart", "maas-rackd"]
528
 
        for retry in retries(delay=10, timeout=60):
529
 
            retcode, output, err = run_command(cmd)
530
 
            if retcode == 0:
531
 
                break
532
 
        else:
533
 
            self.fail("maas-rackd could not be restarted")
534
 
 
535
 
    @skipIf(
536
 
        StrictVersion(get_ubuntu_version()) < StrictVersion('15.10'),
537
 
        'From 15.10 onwards, we use systemd')
538
 
    def test_check_initial_services_systemctl(self):
539
 
        cmd = ["systemctl", "status", "maas-rackd"]
540
 
        retcode, output, err = run_command(cmd)
541
 
        self.assertEqual(0, retcode)
542
 
        self.assertIn('Active: active (running)', output)
543
 
        self.assertEqual('', err)
544
 
 
545
 
    def test_check_rpc_info(self):
546
 
        # Ensure that the region is publishing RPC info to the clusters. This
547
 
        # is a reasonable indication that the region is running correctly.
548
 
        url = "%s/rpc/" % MAAS_URL.rstrip("/")
549
 
        self.addDetail('url', text_content(url))
550
 
        errors = []
551
 
        attempts = count(1)
552
 
        for elapsed, remaining in retries(timeout=300, delay=10):
553
 
            attempt = next(attempts)
554
 
            try:
555
 
                with closing(urlopen(url)) as response:
556
 
                    details = content_from_stream(response, buffer_now=True)
557
 
            except HTTPError as error:
558
 
                errors.append(error)
559
 
            else:
560
 
                self.addDetail('info', details)
561
 
                break
562
 
        else:
563
 
            self.addDetail('errors', text_content(
564
 
                "\n--\n".join([error for error in errors])))
565
 
            self.fail(
566
 
                "RPC info not published after %d attempts over %d seconds."
567
 
                % (attempt, elapsed))
568
 
 
569
 
    @skipIf(DO_NOT_USE_ARM_NODES, "Don't test ARM nodes")
570
 
    def test_update_preseed_arm(self):
571
 
        # XXX: matsubara add workaround to boot arm nodes.
572
 
        try:
573
 
            # Try the old location first, the one used before the templates
574
 
            # were moved to /etc/maas/.
575
 
            userdata_fd = open(
576
 
                "/usr/share/maas/preseeds/enlist_userdata", "rb+")
577
 
        except IOError:
578
 
            userdata_fd = open("/etc/maas/preseeds/enlist_userdata", "rb+")
579
 
        userdata = userdata_fd.read().decode('utf-8')
580
 
        url = 'http://ports.ubuntu.com/ubuntu-ports'
581
 
        userdata += '\n' + dedent("""\
582
 
            apt_sources:
583
 
            - source: "deb %s precise-proposed main"
584
 
            """) % url
585
 
        userdata_fd.seek(0)
586
 
        userdata_fd.write(userdata.encode('utf-8'))
587
 
        userdata_fd.close()
588
 
 
589
 
    def test_login_api(self):
590
 
        assert(self._wait_maas_running())
591
 
        token_str = get_token_str()
592
 
        api_url = MAAS_URL + "/api/2.0/"
593
 
        cmd = [maascli, "login", "maas", api_url, token_str]
594
 
        expected_output = "\nYou are now logged in to the MAAS server"
595
 
        for _ in range(30):
596
 
            retcode, output, err = run_command(cmd, env=os.environ.copy())
597
 
            if retcode == 0:
598
 
                break
599
 
            elif retcode == 1 and 'Expected application/json' in err:
600
 
                # The cli may error with 'Expected application/json, got:
601
 
                # text/html; charset=utf-8\n' with retcode 1 before all pieces
602
 
                # are up and running.
603
 
                sleep(10)  # We need to give MAAS some time.
604
 
        self.assertIn(expected_output, output)
605
 
 
606
 
    def test_maas_logged_in(self):
607
 
        """Make sure MAAS is successfully logged in. Also, it signals a handy
608
 
        place to put a pause."""
609
 
        # pause_until_released('test_maas_logged_in')
610
 
        cmd = ["maas", "list"]
611
 
        retcode, output, err = run_command(cmd)
612
 
        self.assertEqual(0, retcode)
613
 
        self.assertEqual('', err)
614
 
 
615
 
    def test_set_main_archive(self):
616
 
        output, err = self._run_maas_cli([
617
 
            "maas", "set-config", "name=main_archive",
618
 
            "value=%s" % MAIN_ARCHIVE])
619
 
        self.assertThat(output, Contains("OK"))
620
 
        output, err = self._run_maas_cli([
621
 
            "maas", "get-config", "name=main_archive"])
622
 
        self.assertThat(output, Contains(MAIN_ARCHIVE))
623
 
 
624
 
    @skipIf(
625
 
        StrictVersion(get_maas_version()[:3]) > StrictVersion('2.0'),
626
 
        'Cloud-init "package_mirror" config is used in MAAS 2.0 and earlier')
627
 
    def test_main_archive_in_enlist_userdata_package_mirrors_config(self):
628
 
        # Check that the user-data passed on to nodes have
629
 
        # the 'primary' mirror address.
630
 
        output = urlopen(USER_DATA_URL).read().decode('utf-8')
631
 
        self.assertThat(output, Contains(
632
 
            'primary:  ["%s"]' % MAIN_ARCHIVE))
633
 
 
634
 
    @skipIf(
635
 
        StrictVersion(get_maas_version()[:3]) < StrictVersion('2.1'),
636
 
        'Cloud-init "apt" config is used solely in MAAS 2.1+')
637
 
    def test_main_archive_in_enlist_userdata_apt_config(self):
638
 
        output = urlopen(USER_DATA_URL).read().decode('utf-8')
639
 
        # Old APT configuration.
640
 
        self.assertThat(yaml.safe_load(output), ContainsDict({
641
 
            'system_info': ContainsDict({
642
 
                'package_mirrors': MatchesListwise([
643
 
                    MatchesDict({
644
 
                        "arches": Equals(["i386", "amd64"]),
645
 
                        "search": MatchesDict({
646
 
                            "primary": Equals([MAIN_ARCHIVE]),
647
 
                            "security": Equals([MAIN_ARCHIVE]),
648
 
                            }),
649
 
                        "failsafe": MatchesDict({
650
 
                            "primary": Equals(
651
 
                                "http://archive.ubuntu.com/ubuntu"),
652
 
                            "security": Equals(
653
 
                                "http://security.ubuntu.com/ubuntu"),
654
 
                            })
655
 
                        }),
656
 
                    MatchesDict({
657
 
                        "arches": Equals(["default"]),
658
 
                        "search": MatchesDict({
659
 
                            "primary": Equals([PORTS_ARCHIVE]),
660
 
                            "security": Equals([PORTS_ARCHIVE]),
661
 
                            }),
662
 
                        "failsafe": MatchesDict({
663
 
                            "primary": Equals(
664
 
                                "http://ports.ubuntu.com/ubuntu-ports"),
665
 
                            "security": Equals(
666
 
                                "http://ports.ubuntu.com/ubuntu-ports"),
667
 
                            })
668
 
                        }),
669
 
                    ]),
670
 
                }),
671
 
            }))
672
 
        # New APT configuration.
673
 
        self.assertThat(yaml.safe_load(output), ContainsDict({
674
 
            'apt': ContainsDict({
675
 
                'preserve_sources_list': Equals(False),
676
 
                'primary': MatchesListwise([
677
 
                    MatchesDict({
678
 
                        "arches": Equals(["amd64", "i386"]),
679
 
                        "uri": Equals(MAIN_ARCHIVE),
680
 
                    }),
681
 
                    MatchesDict({
682
 
                        "arches": Equals(["default"]),
683
 
                        "uri": Equals(PORTS_ARCHIVE),
684
 
                    }),
685
 
                ]),
686
 
            })
687
 
        }))
688
 
 
689
 
    def test_set_http_proxy(self):
690
 
        output, err = self._run_maas_cli([
691
 
            "maas", "set-config", "name=http_proxy",
692
 
            "value=%s" % HTTP_PROXY])
693
 
        self.assertThat(output, Contains("OK"))
694
 
        output, err = self._run_maas_cli([
695
 
            "maas", "get-config", "name=http_proxy"])
696
 
        self.assertThat(output, Contains(HTTP_PROXY))
697
 
 
698
 
        # Check that the user-data passed on to nodes have the proxy address.
699
 
        output = urlopen(USER_DATA_URL).read().decode('utf-8')
700
 
        self.assertThat(output, Contains(
701
 
            "apt_proxy: %s" % HTTP_PROXY))
702
 
 
703
 
        # XXX: Brendan Donegan - 13-07-2016:
704
 
        # https://bugs.launchpad.net/maas/+bug/1603156
705
 
        # Restart regiond as otherwise the import of boot resources
706
 
        # does not start succesfully in the CI
707
 
        cmd = ["systemctl", "restart", "maas-regiond"]
708
 
        retcode, output, err = run_command(cmd)
709
 
        self.assertEqual(0, retcode)
710
 
        self.assertEqual('', output)  # No news is good news
711
 
        self.assertEqual('', err)
712
 
 
713
 
        assert(self._wait_maas_running())
714
 
 
715
 
    def get_master_rack(self):
716
 
        output, err = self._run_maas_cli(["rack-controllers", "read"])
717
 
        rack_controllers = loads(output)
718
 
        if len(rack_controllers) > 0:
719
 
            return rack_controllers[0]
720
 
        return None
721
 
 
722
 
    def get_rack_service_status(self, rack_id, service_name):
723
 
        output, err = self._run_maas_cli(["rack-controller", "read", rack_id])
724
 
        rack_info = loads(output)
725
 
        for service in rack_info['service_set']:
726
 
            if service['name'] == service_name:
727
 
                return service['status']
728
 
        return ''
729
 
 
730
 
    @timeout(5 * 60)
731
 
    def test_region_rack_connected(self):
732
 
        # The master cluster is connected and changed the uuid field of the
733
 
        # nodegroup object from 'master' to its UUID.
734
 
        while self.get_master_rack() is None:
735
 
            sleep(10)
736
 
        rack_id = self.get_master_rack()['system_id']
737
 
        while self.get_rack_service_status(rack_id, 'rackd') != 'running':
738
 
            sleep(10)
739
 
 
740
 
    def test_create_dynamic_range(self):
741
 
        output, err = self._run_maas_cli(
742
 
            ['ipranges', 'create', 'type=dynamic',
743
 
             'start_ip=%s' % REGION_DHCP_CONFIG['ip_range_low'],
744
 
             'end_ip=%s' % REGION_DHCP_CONFIG['ip_range_high']])
745
 
        # If the range was configured correct, it should return the subnet where
746
 
        # the range belogs to. As such, we check the output to ensure it is there.
747
 
        self.assertThat(output, Contains('"cidr": "%s"' % REGION_DHCP_CONFIG['cidr']))
748
 
        # TODO: We need to read all IP ranges for the subnet and check that the range
749
 
        # has actually been created regardless whether we checked the output above.
750
 
        output, err = self._run_maas_cli(
751
 
            ['ipranges', 'read'])
752
 
 
753
 
    def _get_rack_systemid_on_region(self):
754
 
        output, err = self._run_maas_cli(["rack-controllers", "read"])
755
 
        rack_controllers = loads(output)
756
 
        region_rack = 4
757
 
        for rack in rack_controllers:
758
 
            if rack['node_type'] == region_rack:
759
 
                return rack['system_id']
760
 
 
761
 
    def test_create_slave_device_and_link_subnet(self):
762
 
        # Create a device with the same IP as the MAAS CI slave so
763
 
        # that the IP address cannot be 'stolen' by another machine
764
 
        output, _ = self._run_maas_cli([
765
 
            "devices", "create", "hostname={}".format(SLAVE_HOSTNAME),
766
 
            "mac_addresses={}".format(SLAVE_MAC)
767
 
        ])
768
 
        device = loads(output)
769
 
        device_id = device["system_id"]
770
 
        interface_id = "{}".format(device["interface_set"][0]["id"])
771
 
 
772
 
        # Update the vlan on the interface so we can link the subnet
773
 
        rack_system_id = self._get_rack_systemid_on_region()
774
 
        rack_interface_link = self._get_rack_interface_link(
775
 
            rack_system_id, REGION_DHCP_CONFIG['ip']
776
 
        )
777
 
        subnet = rack_interface_link['subnet']['name']
778
 
        vlan_id = rack_interface_link['subnet']['vlan']['id']
779
 
        output, _ = self._run_maas_cli([
780
 
            "interface", "update", device_id,
781
 
            interface_id, "vlan={}".format(vlan_id),
782
 
        ])
783
 
        updated_interface = loads(output)
784
 
        self.assertThat(updated_interface['vlan']['id'], Equals(vlan_id))
785
 
        output, _ = self._run_maas_cli([
786
 
            "interface", "link-subnet", device_id, interface_id,
787
 
            "mode=STATIC", "subnet={}".format(subnet),
788
 
            "ip_address={}".format(SLAVE_IP),
789
 
        ])
790
 
        linked_interface = loads(output)
791
 
        self.assertThat(
792
 
            linked_interface['links'][0]['ip_address'],
793
 
            Equals(SLAVE_IP)
794
 
        )
795
 
 
796
 
    def test_slave_device_interface_linked(self):
797
 
        """
798
 
        Check that after we created the slave device, that the
799
 
        representation in MAAS looks sensible.
800
 
        """
801
 
        slave_device = None
802
 
        output, _ = self._run_maas_cli(["devices", "read"])
803
 
        devices = loads(output)
804
 
        for device in devices:
805
 
            if device['hostname'] == SLAVE_HOSTNAME:
806
 
                slave_device = device
807
 
        self.assertIsNotNone(
808
 
            slave_device, "Could not find {} in devices".format(SLAVE_HOSTNAME)
809
 
        )
810
 
        self.assertThat(slave_device['ip_addresses'], Contains(SLAVE_IP))
811
 
        mac_addresses = [
812
 
            iface['mac_address'] for iface in slave_device['interface_set']
813
 
        ]
814
 
        self.assertThat(mac_addresses, Contains(SLAVE_MAC.lower()))
815
 
 
816
 
    @timeout(10 * 60)
817
 
    def test_set_up_dhcp_vlan(self):
818
 
        rack_system_id = self._get_rack_systemid_on_region()
819
 
        self._set_up_dhcp(rack_system_id, REGION_DHCP_CONFIG['ip'])
820
 
 
821
 
        # Wait for the task to complete and create the dhcpd.conf file.
822
 
        while os.path.exists("/var/lib/maas/dhcpd.conf") is False:
823
 
            self.addDetail(
824
 
                "Waiting task create dhcpd.conf file.",
825
 
                content_from_file("/var/log/maas/maas.log"))
826
 
            sleep(2)
827
 
 
828
 
    @skipIf(
829
 
        StrictVersion(get_ubuntu_version()) < StrictVersion('15.10'),
830
 
        'From 15.10 onwards, we use systemd')
831
 
    # Timeout after 2 minutes, provided that it may take a while for the
832
 
    # rack controller to write dhcp config and start maas-dhcpd
833
 
    @timeout(2 * 60)
834
 
    def test_check_dhcp_service_systemctl(self):
835
 
        cmd = ["systemctl", "status", "maas-dhcpd"]
836
 
        # systemd will return 3 if a 'condition failed. This typically means that
837
 
        # /var/lib/maas/dhcpd.conf is not there yet, and we should wait for a bit
838
 
        # to see if the config is written and maas-dhcp is brought up by the rack.
839
 
        retcode, output, err = run_command(cmd)
840
 
        while retcode != 0:
841
 
            # query systemd every 3 seconds to see if maas-dhcpd us running
842
 
            sleep(3)
843
 
            retcode, output, err = run_command(cmd)
844
 
        #pause_until_released((retcode, output, err).__repr__())
845
 
        self.assertEqual(0, retcode)
846
 
        self.assertIn('Active: active (running)', output)
847
 
        self.assertEqual('', err)
848
 
 
849
 
    @skipIf(
850
 
        StrictVersion(get_ubuntu_version()) < StrictVersion('15.10'),
851
 
        'From 15.10 onwards, we use systemd')
852
 
    def test_update_dns_config_systemctl(self):
853
 
        dns_config = open("/etc/bind/named.conf.options", 'w')
854
 
        dns_config.write(LAB_DNS_CONFIG)
855
 
        dns_config.close()
856
 
        cmd = ["systemctl", "restart", "bind9"]
857
 
        retcode, output, err = run_command(cmd)
858
 
        self.assertEqual(0, retcode)
859
 
        self.assertEqual('', output)  # No news is good news
860
 
        self.assertEqual('', err)
861
 
 
862
 
    # Zones
863
 
    @skipIf(
864
 
        StrictVersion(get_maas_version()[:3]) < StrictVersion('1.5'),
865
 
        "Zone feature only available after 1.5")
866
 
    def test_add_new_zones(self):
867
 
        # Create 2 new zones.
868
 
        output, err = self._run_maas_cli(
869
 
            ["zones", "create", "name=test-zone", "description='A test zone'"])
870
 
        zone = loads(output)
871
 
        self.assertEqual('test-zone', zone['name'])
872
 
        output, err = self._run_maas_cli(
873
 
            ["zones", "create", "name=delete-zone",
874
 
             "description='A test zone to be deleted'"])
875
 
        zone = loads(output)
876
 
        self.assertEqual('delete-zone', zone['name'])
877
 
 
878
 
    @skipIf(
879
 
        StrictVersion(get_maas_version()[:3]) < StrictVersion('1.5'),
880
 
        "Zone feature only available after 1.5")
881
 
    def test_list_zones(self):
882
 
        output, err = self._run_maas_cli(["zones", "read"])
883
 
        zones = loads(output)
884
 
        expected = [u'default', u'delete-zone', u'test-zone']
885
 
        self.assertEqual(expected, sorted([zone['name'] for zone in zones]))
886
 
 
887
 
    @skipIf(
888
 
        StrictVersion(get_maas_version()[:3]) < StrictVersion('1.5'),
889
 
        "Zone feature only available after 1.5")
890
 
    def test_delete_zone(self):
891
 
        _, err = self._run_maas_cli(["zone", "delete", "delete-zone"])
892
 
        # List the remaining zones after the delete command.
893
 
        output, err = self._run_maas_cli(["zones", "read"])
894
 
        zones = loads(output)
895
 
        self.assertNotIn(
896
 
            'delete-zone', sorted([zone['name'] for zone in zones]))
897
 
 
898
 
    # Spaces and subnets
899
 
    @skipIf(
900
 
        StrictVersion(get_maas_version()[:3]) < StrictVersion('1.9'),
901
 
        'Space feature only available after 1.9')
902
 
    def test_add_new_spaces(self):
903
 
        # Create 2 new spaces.
904
 
        output, err = self._run_maas_cli(
905
 
            ['spaces', 'create', 'name=test-space'])
906
 
        out_dict = loads(output)
907
 
        self.assertEqual('test-space', out_dict['name'])
908
 
        output, err = self._run_maas_cli(
909
 
            ['spaces', 'create', 'name=delete-space'])
910
 
        out_dict = loads(output)
911
 
        self.assertEqual('delete-space', out_dict['name'])
912
 
 
913
 
    @skipIf(
914
 
        StrictVersion(get_maas_version()[:3]) < StrictVersion('1.9'),
915
 
        'Subnet feature only available after 1.9')
916
 
    def test_add_subnet_to_space(self):
917
 
        output, err = self._run_maas_cli(
918
 
            ['subnets', 'create', 'space=0', 'name=test-subnet',
919
 
             'cidr=192.168.200.0/24'])
920
 
        out_dict = loads(output)
921
 
        self.assertEqual('test-subnet', out_dict['name'])
922
 
        self.assertEqual('192.168.200.0/24', out_dict['cidr'])
923
 
        self.assertEqual('space-0', out_dict['space'])
924
 
 
925
 
    @skipIf(
926
 
        StrictVersion(get_maas_version()[:3]) < StrictVersion('1.9'),
927
 
        'Space feature only available after 1.9')
928
 
    def test_list_spaces(self):
929
 
        output, err = self._run_maas_cli(['spaces', 'read'])
930
 
        out_dict = loads(output)
931
 
        expected = ['delete-space', 'space-0', 'test-space']
932
 
        self.assertItemsEqual(
933
 
            expected, [item['name'] for item in out_dict])
934
 
 
935
 
    @skipIf(
936
 
        StrictVersion(get_maas_version()[:3]) < StrictVersion('1.9'),
937
 
        'Subnet feature only available after 1.9')
938
 
    def test_list_subnets(self):
939
 
        output, err = self._run_maas_cli(['subnets', 'read'])
940
 
        out_dict = loads(output)
941
 
        self.assertEqual(2, len(out_dict), 'space-0 should now have 2 subnets')
942
 
 
943
 
    @skipIf(
944
 
        StrictVersion(get_maas_version()[:3]) < StrictVersion('1.9'),
945
 
        'Subnet feature only available after 1.9')
946
 
    def test_delete_subnet(self):
947
 
        _, err = self._run_maas_cli(['subnet', 'delete', 'test-subnet'])
948
 
        output, err = self._run_maas_cli(['subnets', 'read'])
949
 
        out = loads(output)
950
 
        self.assertEqual(1, len(out), 'space-0 should now have 1 subnet')
951
 
        self.assertNotIn('test-subnet', [n['name'] for n in out])
952
 
        self.assertEqual(
953
 
            out[0]['name'], out[0]['cidr'],
954
 
            'Name and CIDR should be equal for the default subnet.')
955
 
 
956
 
    @skipIf(
957
 
        StrictVersion(get_maas_version()[:3]) < StrictVersion('1.9'),
958
 
        'Space feature only available after 1.9')
959
 
    def test_delete_space(self):
960
 
        _, err = self._run_maas_cli(['space', 'delete', 'delete-space'])
961
 
        # List the remaining zones after the delete command.
962
 
        output, err = self._run_maas_cli(['spaces', 'read'])
963
 
        out_dict = loads(output)
964
 
        self.assertNotIn(
965
 
            'delete-space', [item['name'] for item in out_dict])
966
 
 
967
 
    # Fabrics and VLANs
968
 
    @skipIf(
969
 
        StrictVersion(get_maas_version()[:3]) < StrictVersion('1.9'),
970
 
        'Fabric feature only available after 1.9')
971
 
    def test_add_new_fabrics(self):
972
 
        # Create 2 new fabrics.
973
 
        output, err = self._run_maas_cli(
974
 
            ['fabrics', 'create', 'name=test-fabric'])
975
 
        out_dict = loads(output)
976
 
        self.assertEqual('test-fabric', out_dict['name'])
977
 
        output, err = self._run_maas_cli(
978
 
            ['fabrics', 'create', 'name=delete-fabric'])
979
 
        out_dict = loads(output)
980
 
        self.assertEqual('delete-fabric', out_dict['name'])
981
 
 
982
 
    @skipIf(
983
 
        StrictVersion(get_maas_version()[:3]) < StrictVersion('1.9'),
984
 
        'VLAN feature only available after 1.9')
985
 
    def test_add_vlan_to_fabric(self):
986
 
        output, err = self._run_maas_cli(
987
 
            ['vlans', 'create', 'test-fabric', 'name=test-vlan', 'vid=2'])
988
 
        out_dict = loads(output)
989
 
        self.assertEqual('test-vlan', out_dict['name'])
990
 
        self.assertEqual('test-fabric', out_dict['fabric'])
991
 
        self.assertEqual(2, out_dict['vid'])
992
 
 
993
 
    @skipIf(
994
 
        StrictVersion(get_maas_version()[:3]) < StrictVersion('1.9'),
995
 
        'Fabric feature only available after 1.9')
996
 
    def test_list_fabrics(self):
997
 
        output, err = self._run_maas_cli(['fabrics', 'read'])
998
 
        out_dict = loads(output)
999
 
        fabric_names = [item['name'] for item in out_dict]
1000
 
        self.assertThat(fabric_names, Contains('test-fabric'))
1001
 
        fabric_names.remove('test-fabric')
1002
 
        self.assertThat(fabric_names, Contains('delete-fabric'))
1003
 
        fabric_names.remove('delete-fabric')
1004
 
        for fabric_name in fabric_names:
1005
 
            self.assertThat(fabric_name, MatchesRegex('fabric-\d$'))
1006
 
        self.assertIn(
1007
 
            'test-vlan',
1008
 
            [[v['name'] for v in f['vlans']]
1009
 
             for f in out_dict if f['name'] == 'test-fabric'][0])
1010
 
 
1011
 
    @skipIf(
1012
 
        StrictVersion(get_maas_version()[:3]) < StrictVersion('1.9'),
1013
 
        'VLAN feature only available after 1.9')
1014
 
    def test_list_vlans(self):
1015
 
        output, err = self._run_maas_cli(['vlans', 'read', 'test-fabric'])
1016
 
        out_dict = loads(output)
1017
 
        expected = ['untagged', 'test-vlan']
1018
 
        self.assertItemsEqual(expected, [v['name'] for v in out_dict])
1019
 
 
1020
 
    @skipIf(
1021
 
        StrictVersion(get_maas_version()[:3]) < StrictVersion('1.9'),
1022
 
        'VLAN feature only available after 1.9')
1023
 
    def test_delete_vlan(self):
1024
 
        _, err = self._run_maas_cli(
1025
 
            ['vlan', 'delete', 'test-fabric', 'test-vlan'])
1026
 
        output, err = self._run_maas_cli(['vlans', 'read', 'test-fabric'])
1027
 
        out = loads(output)
1028
 
        self.assertNotIn('test-vlan', [v['name'] for v in out])
1029
 
        self.assertEqual(1, len(out), 'Fabric should have only one VLAN now.')
1030
 
 
1031
 
    def test_reserve_bmc_range(self):
1032
 
        _, err = self._run_maas_cli(
1033
 
            ['ipranges', 'create', 'type=reserved',
1034
 
             'start_ip=' + BMC_START_IP, 'end_ip=' + BMC_END_IP,
1035
 
             'comment=BMCs']
1036
 
        )
1037
 
        output, err = self._run_maas_cli(
1038
 
            ['ipranges', 'read']
1039
 
        )
1040
 
        out = loads(output)
1041
 
        # TODO: assert that newly created range is there
1042
 
        bmc_range = None
1043
 
        for range in out:
1044
 
            if range['comment'] == 'BMCs':
1045
 
                bmc_range = range
1046
 
        self.assertIsNotNone(bmc_range, 'BMC range not found')
1047
 
        self.assertThat(bmc_range['start_ip'], Equals(BMC_START_IP))
1048
 
        self.assertThat(bmc_range['end_ip'], Equals(BMC_END_IP))
1049
 
        self.assertThat(bmc_range['type'], Equals('reserved'))
1050
 
 
1051
 
    @skipIf(
1052
 
        StrictVersion(get_maas_version()[:3]) < StrictVersion('1.9'),
1053
 
        "Fabric feature only available after 1.9")
1054
 
    def test_delete_fabric(self):
1055
 
        _, err = self._run_maas_cli(["fabric", "delete", "delete-fabric"])
1056
 
        # List the remaining zones after the delete command.
1057
 
        output, err = self._run_maas_cli(["fabrics", "read"])
1058
 
        out_dict = loads(output)
1059
 
        self.assertNotIn(
1060
 
            'delete-fabric', sorted([item['name'] for item in out_dict]))
1061
 
 
1062
 
    @skipUnless(TEST_CUSTOM_IMAGES, "Not testing custom images")
1063
 
    def test_set_custom_boot_source(self):
1064
 
        boot_source_url = "http://10.55.32.203/proposed/streams/v1/index.json"
1065
 
        output, _ = self._run_maas_cli([
1066
 
            "boot-source", "update", "1",
1067
 
            "url=" + boot_source_url,
1068
 
        ])
1069
 
        boot_source = loads(output)
1070
 
        self.assertThat(boot_source['url'], Equals(boot_source_url))
1071
 
 
1072
 
    def test_stop_image_import(self):
1073
 
        output, err = self._run_maas_cli(
1074
 
            ["boot-resources", "stop-import"])
1075
 
        self.assertThat(
1076
 
            output, Contains("Import of boot resources is being stopped")
1077
 
        )
1078
 
 
1079
 
    @skipUnless(USE_PPC_NODES, "Not testing PPC systems")
1080
 
    def test_add_boot_source_selection_ppc64el(self):
1081
 
        # Add the ppc64el boot source selection to all boot sources
1082
 
        output, err = self._run_maas_cli(["boot-sources", "read"])
1083
 
        boot_sources = loads(output)
1084
 
        for source in boot_sources:
1085
 
            output, err = self._run_maas_cli(
1086
 
                ["boot-source-selections", "read", str(source["id"])]
1087
 
            )
1088
 
            selections = loads(output)
1089
 
            # To add a new arch we need to specify the arches= parameter
1090
 
            # multiple times, e.g. arches=amd64 arches=ppc64el
1091
 
            for selection in selections:
1092
 
                new_arches = selection['arches'] + ['ppc64el']
1093
 
                selection_update_params = [
1094
 
                    'arches={arch}'.format(arch=arch) for arch in new_arches
1095
 
                ]
1096
 
                output, err = self._run_maas_cli([
1097
 
                    "boot-source-selection",
1098
 
                    "update",
1099
 
                    str(source["id"]),
1100
 
                    str(selection["id"]),
1101
 
                ] + selection_update_params)
1102
 
                updated_selection = loads(output)
1103
 
                self.assertThat(updated_selection["arches"], Equals(new_arches))
1104
 
 
1105
 
    def test_start_image_import(self):
1106
 
        output, err = self._run_maas_cli(
1107
 
            ["boot-resources", "import"])
1108
 
        self.assertThat(
1109
 
            output, Contains("Import of boot resources started")
1110
 
        )
1111
 
 
1112
 
    @timeout(60 * 60)  # Allow for up to one hour
1113
 
    def test_region_imported_images(self):
1114
 
        # MAAS 2.1 and up uses new kernel naming scheme
1115
 
        if StrictVersion(get_maas_version()[:3]) > StrictVersion('2.0'):
1116
 
            expected_resources = set([
1117
 
                (u'ubuntu/xenial', 'amd64/hwe-16.04'),
1118
 
                (u'ubuntu/xenial', 'amd64/hwe-16.04-lowlatency'),
1119
 
                (u'ubuntu/xenial', 'amd64/ga-16.04'),
1120
 
                (u'ubuntu/xenial', 'amd64/ga-16.04-lowlatency'),
1121
 
            ])
1122
 
            if USE_PPC_NODES:
1123
 
                expected_resources.update([
1124
 
                    (u'ubuntu/xenial', 'ppc64el/hwe-16.04'),
1125
 
                    (u'ubuntu/xenial', 'ppc64el/ga-16.04'),
1126
 
                ])
1127
 
        else:
1128
 
            expected_resources = set([
1129
 
                (u'ubuntu/xenial', 'amd64/hwe-x'),
1130
 
            ])
1131
 
            if USE_PPC_NODES:
1132
 
                expected_resources.update([
1133
 
                    (u'ubuntu/xenial', 'ppc64el/hwe-x'),
1134
 
                ])
1135
 
 
1136
 
        complete_resources = set()
1137
 
        while not expected_resources.issubset(complete_resources):
1138
 
            resources_output, err = self._run_maas_cli(
1139
 
                ["boot-resources", "read"])
1140
 
            resources = loads(resources_output)
1141
 
            for resource in resources:
1142
 
                resource_id = resource['id']
1143
 
                resource_name = resource['name']
1144
 
                resource_arch = resource['architecture']
1145
 
                output, err = self._run_maas_cli(
1146
 
                    ['boot-resource', 'read', '%s' % resource_id])
1147
 
                resource_data = loads(output)
1148
 
                for _, resource_set in resource_data['sets'].items():
1149
 
                    if resource_set['complete']:
1150
 
                        complete_resources.add((resource_name, resource_arch))
1151
 
            self.addDetail(
1152
 
                "Waiting for region to import boot resources.",
1153
 
                text_content(resources_output))
1154
 
            sleep(5)
1155
 
 
1156
 
    @timeout(60 * 30)  # Allow for up to 30 mins
1157
 
    def test_rack_imported_images(self):
1158
 
        rack_system_id = self._get_rack_systemid_on_region()
1159
 
        while True:
1160
 
            output, err = self._run_maas_cli(
1161
 
                ["rack-controller", "list-boot-images", rack_system_id])
1162
 
            rack_images = loads(output)
1163
 
            if rack_images['status'] == 'synced':
1164
 
                break
1165
 
            self.addDetail(
1166
 
                "Waiting for rack to import boot resources.",
1167
 
                text_content(output))
1168
 
            sleep(5)
1169
 
 
1170
 
    def test_add_ssh_key(self):
1171
 
        """Add our ssh key to the admin account."""
1172
 
        setup_ssh()
1173
 
        home_dir = os.path.expanduser('~/')
1174
 
        ssh_dir = os.path.join(home_dir, '.ssh')
1175
 
        ssh_key = open(
1176
 
            os.path.join(ssh_dir, 'id_rsa.pub')).read()
1177
 
        out, err = self._run_maas_cli(["sshkeys", "create", "key=%s" % ssh_key])
1178
 
        out, err = self._run_maas_cli(["sshkeys", "read"])
1179
 
        keys = loads(out)
1180
 
        self.assertEqual(ssh_key, keys[0]['key'])
1181
 
 
1182
 
    def test_poweron_nodes_to_enlist(self):
1183
 
        self._boot_nodes()
1184
 
 
1185
 
    @timeout(15 * 60)
1186
 
    def test_check_machines_new(self):
1187
 
        self._wait_machines(NODE_STATUS.NEW)
1188
 
 
1189
 
    @skipIf(
1190
 
        StrictVersion(get_maas_version()[:3]) < StrictVersion('1.5'),
1191
 
        "Zone feature only available after 1.5")
1192
 
    def test_assign_machines_to_test_zone(self):
1193
 
        # Set two of the declared nodes to the test-zone created earlier.
1194
 
        output, err = self._run_maas_cli(["machines", "read"])
1195
 
        machines = loads(output)
1196
 
        for machine in machines[:2]:
1197
 
            self._run_maas_cli(
1198
 
                ["machine", "update", machine['system_id'], "zone=test-zone"])
1199
 
        # Check nodes are in the test-zone
1200
 
        output, err = self._run_maas_cli(["machines", "read", "zone=test-zone"])
1201
 
        machines = loads(output)
1202
 
        self.assertEqual(2, len(machines))
1203
 
 
1204
 
    def test_set_machines_ipmi_config(self):
1205
 
        """Set IPMI configuration for each node."""
1206
 
        all_machines = {}
1207
 
        all_machines.update(LENOVO_LAB)
1208
 
        all_machines.update(ARM_LAB)
1209
 
        all_machines.update(PPC_SYSTEMS)
1210
 
        for mac in all_machines.keys():
1211
 
            # run maas-cli command to search node by mac and return system_id
1212
 
            out, err = self._run_maas_cli(
1213
 
                ["machines", "read", "mac_address=%s" % mac])
1214
 
            machine_list = loads(out)
1215
 
            for machine in machine_list:
1216
 
                power_driver = ''
1217
 
                if mac in ARM_LAB:
1218
 
                    power_user = power_pass = 'admin'
1219
 
                elif mac in PPC_SYSTEMS:
1220
 
                    power_user = ''
1221
 
                    power_pass = 'admin'
1222
 
                    power_driver = 'LAN_2_0'
1223
 
                else:
1224
 
                    power_user = POWER_USER
1225
 
                    power_pass = POWER_PASS
1226
 
                self._run_maas_cli([
1227
 
                    "machine", "update", machine['system_id'], "power_type=ipmi",
1228
 
                    "power_parameters_power_address=" + all_machines[mac],
1229
 
                    "power_parameters_power_user=" + power_user,
1230
 
                    "power_parameters_power_pass=" + power_pass,
1231
 
                    "power_parameters_power_driver=" + power_driver,
1232
 
                ])
1233
 
 
1234
 
    def test_start_commissioning_machines(self):
1235
 
        # Use maas-cli to accept all nodes.
1236
 
        output, err = self._run_maas_cli(["machines", "accept-all"])
1237
 
        for node in loads(output):
1238
 
            self.assertEqual(node['status'], 1)
1239
 
 
1240
 
    @timeout(10 * 60)
1241
 
    def test_check_nodes_ready(self):
1242
 
        self._wait_machines(NODE_STATUS.READY)
1243
 
 
1244
 
    def test_apply_tag_to_all_machines(self):
1245
 
        # Use maas-cli to set a tag on all nodes.
1246
 
        output, err = self._run_maas_cli(
1247
 
            ["tags", "create", "name=all", "definition=true()",
1248
 
             "comment=A tag present on all nodes"])
1249
 
        tag = loads(output)
1250
 
        self.assertEqual(tag['name'], "all")
1251
 
 
1252
 
    @timeout(10 * 60)
1253
 
    def test_check_tag_applied_to_all_machines(self):
1254
 
        # Wait for all nodes to have new tag applied.
1255
 
        expected_node_count = self.get_node_count()
1256
 
        while True:
1257
 
            output, err = self._run_maas_cli(["tag", "machines", "all"])
1258
 
            nodes = loads(output)
1259
 
            if len(nodes) == expected_node_count:
1260
 
                break
1261
 
            sleep(5)
1262
 
 
1263
 
    @skipUnless(TEST_JUJU, "Not testing juju")
1264
 
    def test_configure_juju(self):
1265
 
        # Proxy is currently broken: disable using the lab's proxy
1266
 
        # as parent for now.
1267
 
        token_str = get_token_str()
1268
 
        # Workaround bug 972829 (in juju precise).
1269
 
        server_url = MAAS_URL.replace('/MAAS', ':80/MAAS')
1270
 
        with tempfile.NamedTemporaryFile() as cloud_yaml:
1271
 
            # Create cloud configuration
1272
 
            setup_juju_cloud(
1273
 
                server=server_url, cloud_yaml=cloud_yaml
1274
 
            )
1275
 
            # Add cloud to Juju
1276
 
            retcode, _, _ = self._run_juju_command(
1277
 
                ['add-cloud', 'testmaas', cloud_yaml.name]
1278
 
            )
1279
 
            self.assertThat(retcode, Equals(0))
1280
 
        retcode, list_clouds_output, _ = self._run_juju_command(
1281
 
            ['list-clouds']
1282
 
        )
1283
 
        self.assertThat(retcode, Equals(0))
1284
 
        self.assertThat(list_clouds_output, Contains("testmaas"))
1285
 
        # Setup credentials
1286
 
        with open(os.path.expanduser(
1287
 
            '~/.local/share/juju/credentials.yaml'), mode='w', encoding='utf8') as credentials:
1288
 
            setup_juju_authentication(oauth=token_str, credentials_yaml=credentials)
1289
 
        retcode, list_creds_output, _ = self._run_juju_command(
1290
 
            ['list-credentials']
1291
 
        )
1292
 
        self.assertThat(retcode, Equals(0))
1293
 
        self.assertThat(list_creds_output, Contains("testmaas"))
1294
 
        setup_local_dns()
1295
 
 
1296
 
    @timeout(60 * 60)
1297
 
    @skipUnless(TEST_JUJU, "Not testing juju")
1298
 
    def test_juju_bootstrap(self):
1299
 
        # Wait a bit to let all the nodes go down.
1300
 
        # XXX: rvb 2013-04-23 bug=1171418
1301
 
        sleep(30)
1302
 
        # Proxy has to be set while bootstrapping
1303
 
        with tempfile.NamedTemporaryFile() as bootstrap_config:
1304
 
            bootstrap_config.write("""
1305
 
http-proxy: {proxy}
1306
 
https-proxy: {proxy}""".format(proxy=HTTP_PROXY).encode('utf8'))
1307
 
            bootstrap_config.file.flush()
1308
 
            env = os.environ.copy()
1309
 
            env['http_proxy'] = HTTP_PROXY
1310
 
            env['https_proxy'] = HTTP_PROXY
1311
 
            retcode, _, _ = self._run_juju_command([
1312
 
                'bootstrap', 'autopkgtest', 'testmaas',
1313
 
                '--config={config}'.format(config=bootstrap_config.name)
1314
 
            ], env=env)
1315
 
            self.assertThat(retcode, Equals(0))
1316
 
 
1317
 
    @skipUnless(TEST_JUJU, "Not testing juju")
1318
 
    def test_juju_deploy_postgresql(self):
1319
 
        # Deploy postgresql.
1320
 
        env = os.environ.copy()
1321
 
        env['http_proxy'] = HTTP_PROXY
1322
 
        env['https_proxy'] = HTTP_PROXY
1323
 
        retcode, _, _ = self._run_juju_command(["deploy", "postgresql"], env=env)
1324
 
        self.assertThat(retcode, Equals(0))
1325
 
        self._wait_machines_running(1)
1326
 
        self._wait_units_started('postgresql', 1)
1327
 
 
1328
 
    nb_deployed_machines = 2
1329
 
 
1330
 
    @timeout(5*60)
1331
 
    @skipIf(TEST_JUJU, "Tested allocation/deployment with Juju.")
1332
 
    def test_allocate_machines(self):
1333
 
        # Get all nodes that are Ready
1334
 
        ready_machines = self._wait_machines(NODE_STATUS.READY)
1335
 
        for machine in ready_machines:
1336
 
            system_id = machine['system_id']
1337
 
            self._run_maas_cli(
1338
 
                ['machines', 'allocate', 'system_id=' + system_id]
1339
 
            )
1340
 
 
1341
 
    @timeout(10*60)
1342
 
    @skipIf(TEST_JUJU, "Tested allocation/deployment with Juju.")
1343
 
    def test_deploy_machines(self):
1344
 
        # Get all nodes that are Allocated
1345
 
        allocated_machines = self._wait_machines(
1346
 
            NODE_STATUS.ALLOCATED,
1347
 
        )
1348
 
        for machine in allocated_machines:
1349
 
            system_id = machine['system_id']
1350
 
            self._run_maas_cli(['machine', 'deploy', system_id])
1351
 
 
1352
 
    def test_machines_deployed(self):
1353
 
        if TEST_JUJU:
1354
 
            deployed_machines = self._wait_machines(
1355
 
                NODE_STATUS.DEPLOYED,
1356
 
                num_expected=self.nb_deployed_machines,
1357
 
            )
1358
 
        else:
1359
 
            deployed_machines = self._wait_machines(NODE_STATUS.DEPLOYED)
1360
 
        for machine in deployed_machines:
1361
 
            self.assertThat(machine.get('status_name', ''), Equals('Deployed'))
1362
 
 
1363
 
    @timeout(10*60)
1364
 
    def test_ip_addresses_not_in_dynamic_range(self):
1365
 
        # Make sure the deployed machines have addresses in the dynamic range.
1366
 
        if TEST_JUJU:
1367
 
            deployed_machines = self._wait_machines(
1368
 
                NODE_STATUS.DEPLOYED, num_expected=self.nb_of_deployed_machines
1369
 
            )
1370
 
        else:
1371
 
            deployed_machines = self._wait_machines(NODE_STATUS.DEPLOYED)
1372
 
 
1373
 
        for deployed_machine in deployed_machines:
1374
 
            ips = [IPAddress(ip) for ip in deployed_machine['ip_addresses']]
1375
 
            ip_range = IPRange(
1376
 
                REGION_DHCP_CONFIG['ip_range_low'],
1377
 
                REGION_DHCP_CONFIG['ip_range_high'])
1378
 
            for ip in ips:
1379
 
                self.assertThat(ip_range, Not(Contains(ip)))
1380
 
 
1381
 
    @timeout(10*60)
1382
 
    def test_ip_addresses_not_in_bmc_range(self):
1383
 
        # Make sure that the deployed machines do not have addresses
1384
 
        # in the range reserved for the BMCs
1385
 
        if TEST_JUJU:
1386
 
            deployed_machines = self._wait_machines(
1387
 
                NODE_STATUS.DEPLOYED, num_expected=self.nb_of_deployed_machines
1388
 
            )
1389
 
        else:
1390
 
            deployed_machines = self._wait_machines(NODE_STATUS.DEPLOYED)
1391
 
        for deployed_machine in deployed_machines:
1392
 
            ips = [IPAddress(ip) for ip in deployed_machine['ip_addresses']]
1393
 
            ip_range = IPRange(BMC_START_IP, BMC_END_IP)
1394
 
            for ip in ips:
1395
 
                self.assertThat(ip_range, Not(Contains(ip)))
1396
 
 
1397
 
 
1398
 
    @classmethod
1399
 
    def dump_database(cls):
1400
 
        """Dump the Django DB to /var/log/maas."""
1401
 
        ret, stdout, stderr = run_command([
1402
 
            "sudo", maas_region_admin, "dumpdata",
1403
 
            "--indent", "4",
1404
 
            "--exclude", "maasserver.candidatename",
1405
 
        ])
1406
 
        log_dir = "/var/log/maas"
1407
 
        stdout_path = os.path.join(log_dir, 'dumpdata.stdout')
1408
 
        with open(stdout_path, 'wb') as w_file:
1409
 
            w_file.write(stdout.encode('utf-8'))
1410
 
        stderr_path = os.path.join(log_dir, 'dumpdata.stderr')
1411
 
        with open(stderr_path, 'wb') as w_file:
1412
 
            w_file.write(stderr.encode('utf-8'))
1413
 
 
1414
 
    @classmethod
1415
 
    def finalize(cls):
1416
 
        # Signal to the cluster that the region controller tests finished.
1417
 
        if not USE_CC_NODES:
1418
 
            raise SkipTest("Not testing Cluster controller")
1419
 
        context = zmq.Context()
1420
 
        socket = context.socket(zmq.REQ)
1421
 
        socket.connect('tcp://%s:5555' % CLUSTER_CONTROLLER_IP)
1422
 
        socket.send("Region controller tests finished.")
1423
 
 
1424
 
    @classmethod
1425
 
    def copy_juju_log(cls):
1426
 
        # Use timeout because juju debug-log never returns (known juju bug).
1427
 
        ret, stdout, stderr = run_command(
1428
 
            ["timeout", "10", "juju", "debug-log", "--replay", "-l", "TRACE"])
1429
 
        log_dir = "/var/log/maas"
1430
 
        stdout_path = os.path.join(log_dir, 'juju-log-replay.stdout')
1431
 
        with open(stdout_path, 'wb') as w_file:
1432
 
            w_file.write(stdout.encode('utf-8'))
1433
 
        ret, stdout, stderr = run_command([
1434
 
            "juju", "scp", "0:/var/log/juju/*", log_dir])
1435
 
 
1436
 
    @classmethod
1437
 
    def tearDownClass(cls):
1438
 
        cls.dump_database()
1439
 
        cls.finalize()