Merge lp:~allenap/maas/parallel-tests-reliability into lp:~maas-committers/maas/trunk

Proposed by Gavin Panella
Status: Merged
Approved by: Gavin Panella
Approved revision: no longer in the source branch.
Merged at revision: 5664
Proposed branch: lp:~allenap/maas/parallel-tests-reliability
Merge into: lp:~maas-committers/maas/trunk
Diff against target: 694 lines (+157/-94)
21 files modified
Makefile (+2/-7)
media/README (+4/-4)
required-packages/forbidden (+3/-0)
src/maasserver/api/tests/test_discoveries.py (+5/-1)
src/maasserver/djangosettings/development.py (+3/-1)
src/maasserver/regiondservices/tests/test_ntp.py (+0/-6)
src/maasserver/testing/resources.py (+6/-1)
src/maasserver/utils/tests/test_dblocks.py (+30/-15)
src/maasserver/utils/tests/test_dbtasks.py (+1/-1)
src/maasserver/websockets/handlers/tests/test_subnet.py (+11/-7)
src/maastesting/fixtures.py (+21/-7)
src/maastesting/testcase.py (+8/-1)
src/maastesting/tests/test_conflict_markers.py (+20/-17)
src/maastesting/tests/test_fixtures.py (+11/-0)
src/metadataserver/models/scriptset.py (+4/-0)
src/provisioningserver/dhcp/__init__.py (+5/-5)
src/provisioningserver/rpc/tests/test_clusterservice.py (+2/-0)
src/provisioningserver/tests/test_config.py (+4/-2)
src/provisioningserver/utils/fs.py (+5/-4)
src/provisioningserver/utils/tests/test_fs.py (+4/-3)
src/provisioningserver/utils/tests/test_utils.py (+8/-12)
To merge this branch: bzr merge lp:~allenap/maas/parallel-tests-reliability
Reviewer Review Type Date Requested Status
Blake Rouse (community) Approve
Review via email: mp+315553@code.launchpad.net

Commit message

Make bin/test.parallel reliable.

Description of the change

I have run bin/test.parallel 10 times with only one failure. This turned out to be a broken test; see the fix in https://code.launchpad.net/~allenap/maas/finding-something-that-should-not-be-there--bug-1659244/+merge/315552.

I haven't swapped bin/test.parallel into the `make test` slot because it does not yet produce XUnit/JUnit output which Jenkins consumes. I also would like to get feedback from other team members to see if it is working reliably for them in different environments to my own.

To post a comment you must log in.
Revision history for this message
Blake Rouse (blake-rouse) wrote :

Looks good. Just a couple of questions.

review: Needs Information
Revision history for this message
Gavin Panella (allenap) wrote :

Thanks. I fixed a couple of problems you encountered when running bin/test.parallel.

I'm not sure how to fix the "No address found for host localhost" problem you hit (http://paste.ubuntu.com/23863538/). I've seen this happen too.

I would like to get test.js working again, locally, but there's no obvious reason why it breaks.

Revision history for this message
Blake Rouse (blake-rouse) wrote :

Well test.js works locally for me. It also must work on the lander as its a requirement for branches to land. As long as this branch doesn't remove that requirement I am good with landing it, once that test failure I am getting is fixed.

review: Approve
Revision history for this message
Gavin Panella (allenap) wrote :

Re-enabled test.js, and test failure fixed. Thanks!

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
=== modified file 'Makefile'
--- Makefile 2017-01-20 11:30:38 +0000
+++ Makefile 2017-01-25 16:51:52 +0000
@@ -90,10 +90,6 @@
90 echo '/src/**/*.pyc' >> $@90 echo '/src/**/*.pyc' >> $@
91 echo '/etc/**/*.pyc' >> $@91 echo '/etc/**/*.pyc' >> $@
9292
93run/etc/ntp.conf: templates/ntp.conf
94 @mkdir -p $(@D)
95 @cp templates/ntp.conf $@
96
97configure-buildout:93configure-buildout:
98 utilities/configure-buildout94 utilities/configure-buildout
9995
@@ -163,8 +159,7 @@
163 @touch --no-create $@159 @touch --no-create $@
164160
165bin/test.rack: \161bin/test.rack: \
166 bin/buildout buildout.cfg versions.cfg setup.py bin/maas-rack \162 bin/buildout buildout.cfg versions.cfg setup.py bin/maas-rack
167 run/etc/ntp.conf
168 $(buildout) install rack-test163 $(buildout) install rack-test
169 @touch --no-create $@164 @touch --no-create $@
170165
@@ -397,7 +392,7 @@
397 find . -type d -name '__pycache__' -print0 | xargs -r0 $(RM) -r392 find . -type d -name '__pycache__' -print0 | xargs -r0 $(RM) -r
398 find . -type f -name '*~' -print0 | xargs -r0 $(RM)393 find . -type f -name '*~' -print0 | xargs -r0 $(RM)
399 find . -type f -name dropin.cache -print0 | xargs -r0 $(RM)394 find . -type f -name dropin.cache -print0 | xargs -r0 $(RM)
400 $(RM) -r media/demo/* media/development395 $(RM) -r media/demo/* media/development media/development.*
401 $(RM) $(js_enums) $(js_enums).tmp396 $(RM) $(js_enums) $(js_enums).tmp
402 $(RM) src/maasserver/data/templates.py397 $(RM) src/maasserver/data/templates.py
403 $(RM) *.log398 $(RM) *.log
404399
=== modified file 'media/README'
--- media/README 2016-12-07 12:46:14 +0000
+++ media/README 2017-01-25 16:51:52 +0000
@@ -1,5 +1,5 @@
1This folder contains somewhat ephemeral things: subfolders serve as1This folder contains somewhat ephemeral things: subfolders serve as
2MEDIA_ROOT for maasserver.djangosettings.demo and .development2MEDIA_ROOT for `demo` and `development` environments. The media/demo
3environments. The media/demo directory should always exist and not be3directory should always exist and not be deleted, though its contents
4deleted, though its contents can be. The media/development directory4can be. The media/development.$$ (where $$ is the PID of the running
5should be created and destroyed by tests, as needed.5process) directories are created and destroyed by tests, as needed.
66
=== modified file 'required-packages/forbidden'
--- required-packages/forbidden 2014-09-24 13:05:22 +0000
+++ required-packages/forbidden 2017-01-25 16:51:52 +0000
@@ -0,0 +1,3 @@
1python3-testresources
2python3-testscenarios
3python3-testtools
04
=== renamed file 'templates/ntp.conf' => 'run/etc/ntp.conf'
=== modified file 'src/maasserver/api/tests/test_discoveries.py'
--- src/maasserver/api/tests/test_discoveries.py 2016-12-12 14:17:23 +0000
+++ src/maasserver/api/tests/test_discoveries.py 2017-01-25 16:51:52 +0000
@@ -9,6 +9,7 @@
9import http.client9import http.client
10import json10import json
11import random11import random
12from unittest.mock import ANY
1213
13from django.conf import settings14from django.conf import settings
14from django.core.urlresolvers import reverse15from django.core.urlresolvers import reverse
@@ -519,8 +520,11 @@
519 scan_all_rack_networks(cidrs=cidrs)520 scan_all_rack_networks(cidrs=cidrs)
520 self.assertThat(521 self.assertThat(
521 self.call_racks_sync_mock, MockCalledOnceWith(522 self.call_racks_sync_mock, MockCalledOnceWith(
522 cluster.ScanNetworks, controllers=self.started,523 cluster.ScanNetworks, controllers=ANY,
523 kwargs={'cidrs': cidrs}))524 kwargs={'cidrs': cidrs}))
525 # Check `controllers` separately because its order may vary.
526 controllers = self.call_racks_sync_mock.call_args[1]["controllers"]
527 self.assertItemsEqual(self.started, controllers)
524528
525 def test__calls_racks_synchronously_with_force_ping(self):529 def test__calls_racks_synchronously_with_force_ping(self):
526 scan_all_rack_networks(ping=True)530 scan_all_rack_networks(ping=True)
527531
=== modified file 'src/maasserver/djangosettings/development.py'
--- src/maasserver/djangosettings/development.py 2017-01-20 08:22:34 +0000
+++ src/maasserver/djangosettings/development.py 2017-01-25 16:51:52 +0000
@@ -63,7 +63,9 @@
6363
64# Absolute filesystem path to the directory that will hold user-uploaded files.64# Absolute filesystem path to the directory that will hold user-uploaded files.
65# Example: "/home/media/media.lawrence.com/media/"65# Example: "/home/media/media.lawrence.com/media/"
66MEDIA_ROOT = abspath("media/development")66# To allow tests to run in parallel the current process's PID is part of this
67# path. See MediaRootFixture for details.
68MEDIA_ROOT = abspath("media/development.%d" % os.getpid())
6769
68INTERNAL_IPS = ('127.0.0.1', '::1')70INTERNAL_IPS = ('127.0.0.1', '::1')
6971
7072
=== modified file 'src/maasserver/regiondservices/tests/test_ntp.py'
--- src/maasserver/regiondservices/tests/test_ntp.py 2016-12-12 14:17:23 +0000
+++ src/maasserver/regiondservices/tests/test_ntp.py 2017-01-25 16:51:52 +0000
@@ -5,8 +5,6 @@
55
6__all__ = []6__all__ = []
77
8from os.path import join
9
10from crochet import wait_for8from crochet import wait_for
11from maasserver.models.config import Config9from maasserver.models.config import Config
12from maasserver.regiondservices import ntp10from maasserver.regiondservices import ntp
@@ -123,10 +121,6 @@
123 # Ensure that we never actually execute against systemd.121 # Ensure that we never actually execute against systemd.
124 self.patch_autospec(service_monitor, "restartService")122 self.patch_autospec(service_monitor, "restartService")
125123
126 maasroot = self.useFixture(MAASRootFixture()).path
127 with open(join(maasroot, "etc", "ntp.conf"), "w") as ntp_conf:
128 ntp_conf.write("# Placeholder NTP configuration file.\n")
129
130 with TwistedLoggerFixture() as logger:124 with TwistedLoggerFixture() as logger:
131 yield service._tryUpdate()125 yield service._tryUpdate()
132126
133127
=== modified file 'src/maasserver/testing/resources.py'
--- src/maasserver/testing/resources.py 2017-01-20 08:21:22 +0000
+++ src/maasserver/testing/resources.py 2017-01-25 16:51:52 +0000
@@ -217,6 +217,7 @@
217217
218 def make(self, dependencies):218 def make(self, dependencies):
219 databases = dependencies["templates"]219 databases = dependencies["templates"]
220 clusterlock = databases.cluster.lock
220 with databases.cluster.connect() as conn:221 with databases.cluster.connect() as conn:
221 with conn.cursor() as cursor:222 with conn.cursor() as cursor:
222 for database in databases:223 for database in databases:
@@ -226,7 +227,11 @@
226 stmt = (227 stmt = (
227 "CREATE DATABASE %s WITH TEMPLATE %s"228 "CREATE DATABASE %s WITH TEMPLATE %s"
228 % (dbname, template))229 % (dbname, template))
229 cursor.execute(stmt)230 # Create the database with a shared lock to the cluster to
231 # avoid racing a DjangoPristineDatabaseManager.make in a
232 # concurrently running test process.
233 with clusterlock.shared:
234 cursor.execute(stmt)
230 debug(235 debug(
231 "Created {dbname}; statement: {stmt}",236 "Created {dbname}; statement: {stmt}",
232 dbname=dbname, stmt=stmt)237 dbname=dbname, stmt=stmt)
233238
=== modified file 'src/maasserver/utils/tests/test_dblocks.py'
--- src/maasserver/utils/tests/test_dblocks.py 2015-12-01 18:12:59 +0000
+++ src/maasserver/utils/tests/test_dblocks.py 2017-01-25 16:51:52 +0000
@@ -9,7 +9,7 @@
9 closing,9 closing,
10 contextmanager,10 contextmanager,
11)11)
12from random import randint12from random import randrange
13import sys13import sys
1414
15from django.db import (15from django.db import (
@@ -25,18 +25,24 @@
25from maasserver.utils import dblocks25from maasserver.utils import dblocks
26from testtools.matchers import Equals26from testtools.matchers import Equals
2727
28# Use "high" objid numbers to avoid conflicts with predeclared locks.
29objid_min = 2 << 10
30objid_max = 2 << 16
31
2832
29def get_locks():33def get_locks():
30 """Return the set of locks held."""34 """Return the set of locks held between `objid_min` and `objid_max`."""
31 stmt = "SELECT objid FROM pg_locks WHERE classid = %s"
32 with closing(connection.cursor()) as cursor:35 with closing(connection.cursor()) as cursor:
33 cursor.execute(stmt, [dblocks.classid])36 cursor.execute(
37 "SELECT objid FROM pg_locks "
38 "WHERE classid = %s AND objid >= %s AND objid < %s",
39 [dblocks.classid, objid_min, objid_max])
34 return {result[0] for result in cursor.fetchall()}40 return {result[0] for result in cursor.fetchall()}
3541
3642
37def random_objid():43def random_objid():
38 """Return a 'high' objid that's won't coincide with predeclared locks."""44 """Return a 'high' objid that's won't coincide with predeclared locks."""
39 return randint(2 << 10, 2 << 16)45 return randrange(objid_min, objid_max)
4046
4147
42@transaction.atomic48@transaction.atomic
@@ -99,10 +105,15 @@
99 objid = random_objid()105 objid = random_objid()
100 lock = self.make_lock(objid)106 lock = self.make_lock(objid)
101107
102 locks_held_before = get_locks()108 # Take an exclusive lock on the database cluster to prevent this
103 with lock:109 # section from running concurrently with any other thusly delimited
104 locks_held = get_locks()110 # section. Note that `self.databases` is the test resource for the
105 locks_held_after = get_locks()111 # database(s), which then has a reference to the cluster resource.
112 with self.databases.cluster.lock.exclusive:
113 locks_held_before = get_locks()
114 with lock:
115 locks_held = get_locks()
116 locks_held_after = get_locks()
106117
107 locks_obtained = locks_held - locks_held_before118 locks_obtained = locks_held - locks_held_before
108 self.assertEqual({objid}, locks_obtained)119 self.assertEqual({objid}, locks_obtained)
@@ -266,12 +277,16 @@
266 objid = random_objid()277 objid = random_objid()
267 lock = self.make_lock(objid)278 lock = self.make_lock(objid)
268279
269 with transaction.atomic():280 # Take an exclusive lock on the database cluster to prevent this
270 locks_held_before = get_locks()281 # section from running concurrently with any other thusly delimited
271 with lock:282 # section.
272 locks_held = get_locks()283 with self.databases.cluster.lock.exclusive:
273 locks_held_after = get_locks()284 with transaction.atomic():
274 locks_held_after_txn = get_locks()285 locks_held_before = get_locks()
286 with lock:
287 locks_held = get_locks()
288 locks_held_after = get_locks()
289 locks_held_after_txn = get_locks()
275290
276 locks_obtained = locks_held - locks_held_before291 locks_obtained = locks_held - locks_held_before
277 self.assertEqual({objid}, locks_obtained)292 self.assertEqual({objid}, locks_obtained)
278293
=== modified file 'src/maasserver/utils/tests/test_dbtasks.py'
--- src/maasserver/utils/tests/test_dbtasks.py 2016-06-22 17:03:02 +0000
+++ src/maasserver/utils/tests/test_dbtasks.py 2017-01-25 16:51:52 +0000
@@ -291,7 +291,7 @@
291291
292 self.assertDocTestMatches(292 self.assertDocTestMatches(
293 """\293 """\
294 Unhandled failure in database task.294 ...Unhandled failure in database task.
295 Traceback (most recent call last):295 Traceback (most recent call last):
296 ...296 ...
297 builtins.ZeroDivisionError: ...297 builtins.ZeroDivisionError: ...
298298
=== modified file 'src/maasserver/websockets/handlers/tests/test_subnet.py'
--- src/maasserver/websockets/handlers/tests/test_subnet.py 2016-12-20 06:52:13 +0000
+++ src/maasserver/websockets/handlers/tests/test_subnet.py 2017-01-25 16:51:52 +0000
@@ -5,6 +5,7 @@
55
6__all__ = []6__all__ = []
77
8import re
8from unittest.mock import sentinel9from unittest.mock import sentinel
910
10from fixtures import FakeLogger11from fixtures import FakeLogger
@@ -15,14 +16,14 @@
15from maasserver.utils.orm import reload_object16from maasserver.utils.orm import reload_object
16from maasserver.websockets.base import dehydrate_datetime17from maasserver.websockets.base import dehydrate_datetime
17from maasserver.websockets.handlers.subnet import SubnetHandler18from maasserver.websockets.handlers.subnet import SubnetHandler
18from maastesting.matchers import (19from maastesting.matchers import MockCalledOnceWith
19 DocTestMatches,
20 MockCalledOnceWith,
21)
22from netaddr import IPNetwork20from netaddr import IPNetwork
23from provisioningserver.utils.network import IPRangeStatistics21from provisioningserver.utils.network import IPRangeStatistics
24from testtools import ExpectedException22from testtools import ExpectedException
25from testtools.matchers import Equals23from testtools.matchers import (
24 Equals,
25 MatchesRegex,
26)
2627
2728
28class TestSubnetHandler(MAASServerTestCase):29class TestSubnetHandler(MAASServerTestCase):
@@ -160,8 +161,11 @@
160 handler.scan({161 handler.scan({
161 "id": subnet.id,162 "id": subnet.id,
162 })163 })
163 self.assertThat(logger.output, DocTestMatches(164 # Use MatchesRegex here rather than DocTestMatches because usernames
164 "User...%s...scan...%s" % (user.username, cidr)))165 # can contain characters that confuse DocTestMatches (e.g. periods).
166 self.assertThat(logger.output, MatchesRegex(
167 "User '%s' initiated a neighbour discovery scan against subnet: %s"
168 % (re.escape(user.username), re.escape(str(cidr)))))
165169
166 def test__scan_ipv6_fails(self):170 def test__scan_ipv6_fails(self):
167 user = factory.make_admin()171 user = factory.make_admin()
168172
=== modified file 'src/maastesting/fixtures.py'
--- src/maastesting/fixtures.py 2016-08-09 00:14:06 +0000
+++ src/maastesting/fixtures.py 2017-01-25 16:51:52 +0000
@@ -22,10 +22,9 @@
22)22)
23import logging23import logging
24import os24import os
25from pathlib import Path
25from subprocess import (26from subprocess import (
26 CalledProcessError,27 CalledProcessError,
27 check_output,
28 DEVNULL,
29 PIPE,28 PIPE,
30 Popen,29 Popen,
31)30)
@@ -404,7 +403,7 @@
404403
405404
406class MAASRootFixture(fixtures.Fixture):405class MAASRootFixture(fixtures.Fixture):
407 """Copy an existing `MAAS_ROOT` to a new temporary location.406 """Create a new, pristine, `MAAS_ROOT` in a temporary location.
408407
409 Also updates `MAAS_ROOT` in the environment to point to this new location.408 Also updates `MAAS_ROOT` in the environment to point to this new location.
410 """409 """
@@ -416,10 +415,25 @@
416 raise NotADirectoryError("MAAS_ROOT is not defined.")415 raise NotADirectoryError("MAAS_ROOT is not defined.")
417 elif os.path.isdir(maasroot):416 elif os.path.isdir(maasroot):
418 self.path = self.useFixture(TempDirectory()).join("run")417 self.path = self.useFixture(TempDirectory()).join("run")
419 # Neither shutil.copytree nor /bin/cp deal with relative symlinks,418 # Work only in $MAAS_ROOT/run; reference the old $MAAS_ROOT.
420 # but /bin/cp is much faster at copying dereferenced files.419 etc = Path(self.path).joinpath("etc")
421 command = "/bin/cp", "-r", "--dereference", maasroot, self.path420 src = Path(maasroot)
422 check_output(command, stdin=DEVNULL, stderr=PIPE)421 # Create and populate $MAAS_ROOT/run/etc/{ntp,ntp.conf}. The
422 # `.keep` file is not strictly necessary, but it's created for
423 # consistency with the source tree's `run` directory.
424 ntp = etc.joinpath("ntp")
425 ntp.mkdir(parents=True)
426 ntp.joinpath(".keep").touch()
427 ntp_conf = etc.joinpath("ntp.conf")
428 ntp_conf.write_bytes(src.joinpath("etc", "ntp.conf").read_bytes())
429 # Create and populate $MAAS_ROOT/run/etc/maas.
430 maas = etc.joinpath("maas")
431 maas.mkdir(parents=True)
432 maas.joinpath("drivers.yaml").symlink_to(
433 src.joinpath("etc", "maas", "drivers.yaml").resolve())
434 maas.joinpath("templates").symlink_to(
435 src.joinpath("etc", "maas", "templates").resolve())
436 # Update the environment.
423 self.useFixture(EnvironmentVariable("MAAS_ROOT", self.path))437 self.useFixture(EnvironmentVariable("MAAS_ROOT", self.path))
424 else:438 else:
425 raise NotADirectoryError(439 raise NotADirectoryError(
426440
=== modified file 'src/maastesting/testcase.py'
--- src/maastesting/testcase.py 2017-01-11 09:19:28 +0000
+++ src/maastesting/testcase.py 2017-01-25 16:51:52 +0000
@@ -24,7 +24,10 @@
24import crochet24import crochet
25from maastesting.crochet import EventualResultCatchingMixin25from maastesting.crochet import EventualResultCatchingMixin
26from maastesting.factory import factory26from maastesting.factory import factory
27from maastesting.fixtures import TempDirectory27from maastesting.fixtures import (
28 MAASRootFixture,
29 TempDirectory,
30)
28from maastesting.matchers import DocTestMatches31from maastesting.matchers import DocTestMatches
29from maastesting.runtest import (32from maastesting.runtest import (
30 MAASCrochetRunTest,33 MAASCrochetRunTest,
@@ -154,6 +157,10 @@
154 return MAASRunTest157 return MAASRunTest
155158
156 def setUp(self):159 def setUp(self):
160 # Every test gets a pristine MAAS_ROOT, when it is defined.
161 if "MAAS_ROOT" in os.environ:
162 self.useFixture(MAASRootFixture())
163
157 # Capture Twisted logs and add them as a test detail.164 # Capture Twisted logs and add them as a test detail.
158 twistedLog = self.useFixture(TwistedLoggerFixture())165 twistedLog = self.useFixture(TwistedLoggerFixture())
159 self.addDetail("Twisted logs", twistedLog.getContent())166 self.addDetail("Twisted logs", twistedLog.getContent())
160167
=== modified file 'src/maastesting/tests/test_conflict_markers.py'
--- src/maastesting/tests/test_conflict_markers.py 2015-12-01 18:12:59 +0000
+++ src/maastesting/tests/test_conflict_markers.py 2017-01-25 16:51:52 +0000
@@ -5,12 +5,16 @@
55
6__all__ = []6__all__ = []
77
8from pipes import quote8from os.path import (
9 isdir,
10 join,
11)
9from subprocess import (12from subprocess import (
10 PIPE,13 PIPE,
11 Popen,14 Popen,
12 STDOUT,15 STDOUT,
13)16)
17from unittest import skipIf
1418
15from maastesting import root19from maastesting import root
16from maastesting.testcase import MAASTestCase20from maastesting.testcase import MAASTestCase
@@ -19,25 +23,24 @@
19 UTF8_TEXT,23 UTF8_TEXT,
20)24)
2125
22# Do not use '=======' as a conflict marker since it's
23# used in docstrings.
24# Express the conflict markers so that this very file won't contain
25# them.
26CONFLICT_MARKERS = "<" * 7, ">" * 7
27
2826
29class TestConflictMarkers(MAASTestCase):27class TestConflictMarkers(MAASTestCase):
28 """Search for merge conflict markers if this is a branch."""
3029
31 def execute(self, *command):30 @skipIf(not isdir(join(root, ".bzr")), "Not a branch.")
32 process = Popen(command, stdout=PIPE, stderr=STDOUT, cwd=root)31 def test_no_conflict_markers(self):
32 # Do not search for '=======' as a conflict marker since it's used in
33 # docstrings, search for angle brackets instead. Express the conflict
34 # markers as a regular expression so that this very file won't match.
35 command = (
36 "bzr ls --kind=file --recursive --versioned --null | "
37 "xargs -r0 egrep -I '[<]{7}|[>]{7}' -C 3")
38 process = Popen(
39 command, shell=True, stdout=PIPE, stderr=STDOUT, cwd=root)
33 output, _ = process.communicate()40 output, _ = process.communicate()
34 if len(output) != 0:41 if len(output) != 0:
35 name = "stdout/err from `%s`" % " ".join(map(quote, command))42 name = "stdout/err from `%s`" % command
36 self.addDetail(name, Content(UTF8_TEXT, lambda: [output]))43 self.addDetail(name, Content(UTF8_TEXT, lambda: [output]))
37 self.assertEqual('', output, "Conflict markers present!")44 self.fail("Conflict markers present!")
38 self.assertEqual(1, process.wait(), "(return code is not one)")45 # Don't check the process's exit code because xargs muddles things.
3946 # Checking the output should suffice.
40 def test_no_conflict_markers(self):
41 command = ["egrep", "-rI", "--exclude=*~", "--exclude-dir=include"]
42 command.append("|".join(CONFLICT_MARKERS))
43 self.execute(*command)
4447
=== modified file 'src/maastesting/tests/test_fixtures.py'
--- src/maastesting/tests/test_fixtures.py 2016-08-09 00:14:06 +0000
+++ src/maastesting/tests/test_fixtures.py 2017-01-25 16:51:52 +0000
@@ -266,6 +266,17 @@
266 self.assertThat(fixture.path, Not(SamePath(self.maasroot)))266 self.assertThat(fixture.path, Not(SamePath(self.maasroot)))
267 files_expected = set(listdirs(self.maasroot))267 files_expected = set(listdirs(self.maasroot))
268 files_observed = set(listdirs(fixture.path))268 files_observed = set(listdirs(fixture.path))
269
270 # Some files, like regiond.conf and rackd.conf, may be created by
271 # concurrently running test suites. In particular, regiond.conf is
272 # created as a side-effect of loading Django's settings which
273 # takes place before tests are running. We ignore these files.
274 files_missing = files_expected - files_observed
275 if "etc/maas/regiond.conf" in files_missing:
276 files_expected.remove("etc/maas/regiond.conf")
277 if "etc/maas/rackd.conf" in files_missing:
278 files_expected.remove("etc/maas/rackd.conf")
279
269 self.assertThat(files_observed, Equals(files_expected))280 self.assertThat(files_observed, Equals(files_expected))
270 self.assertThat(fixture.path, Not(PathExists()))281 self.assertThat(fixture.path, Not(PathExists()))
271282
272283
=== modified file 'src/metadataserver/models/scriptset.py'
--- src/metadataserver/models/scriptset.py 2017-01-21 00:49:34 +0000
+++ src/metadataserver/models/scriptset.py 2017-01-25 16:51:52 +0000
@@ -16,6 +16,7 @@
16)16)
17from maasserver.models.cleansave import CleanSave17from maasserver.models.cleansave import CleanSave
18from maasserver.preseed import CURTIN_INSTALL_LOG18from maasserver.preseed import CURTIN_INSTALL_LOG
19from metadataserver import DefaultMeta
19from metadataserver.enum import (20from metadataserver.enum import (
20 RESULT_TYPE,21 RESULT_TYPE,
21 RESULT_TYPE_CHOICES,22 RESULT_TYPE_CHOICES,
@@ -91,6 +92,9 @@
9192
92class ScriptSet(CleanSave, Model):93class ScriptSet(CleanSave, Model):
9394
95 class Meta(DefaultMeta):
96 """Needed for South to recognize this model."""
97
94 objects = ScriptSetManager()98 objects = ScriptSetManager()
9599
96 last_ping = DateTimeField(blank=True, null=True)100 last_ping = DateTimeField(blank=True, null=True)
97101
=== modified file 'src/provisioningserver/dhcp/__init__.py'
--- src/provisioningserver/dhcp/__init__.py 2016-04-12 22:22:23 +0000
+++ src/provisioningserver/dhcp/__init__.py 2017-01-25 16:51:52 +0000
@@ -13,7 +13,7 @@
13 abstractproperty,13 abstractproperty,
14)14)
1515
16from provisioningserver.path import get_path16from provisioningserver.path import get_tentative_path
1717
18# Location of the DHCPv4 configuration file.18# Location of the DHCPv4 configuration file.
19DHCPv4_CONFIG_FILE = '/var/lib/maas/dhcpd.conf'19DHCPv4_CONFIG_FILE = '/var/lib/maas/dhcpd.conf'
@@ -65,8 +65,8 @@
6565
66 descriptive_name = "DHCPv4"66 descriptive_name = "DHCPv4"
67 template_basename = 'dhcpd.conf.template'67 template_basename = 'dhcpd.conf.template'
68 interfaces_filename = get_path(DHCPv4_INTERFACES_FILE)68 interfaces_filename = get_tentative_path(DHCPv4_INTERFACES_FILE)
69 config_filename = get_path(DHCPv4_CONFIG_FILE)69 config_filename = get_tentative_path(DHCPv4_CONFIG_FILE)
70 dhcp_service = "dhcpd"70 dhcp_service = "dhcpd"
71 ipv6 = False71 ipv6 = False
7272
@@ -79,7 +79,7 @@
7979
80 descriptive_name = "DHCPv6"80 descriptive_name = "DHCPv6"
81 template_basename = 'dhcpd6.conf.template'81 template_basename = 'dhcpd6.conf.template'
82 interfaces_filename = get_path(DHCPv6_INTERFACES_FILE)82 interfaces_filename = get_tentative_path(DHCPv6_INTERFACES_FILE)
83 config_filename = get_path(DHCPv6_CONFIG_FILE)83 config_filename = get_tentative_path(DHCPv6_CONFIG_FILE)
84 dhcp_service = "dhcpd6"84 dhcp_service = "dhcpd6"
85 ipv6 = True85 ipv6 = True
8686
=== modified file 'src/provisioningserver/rpc/tests/test_clusterservice.py'
--- src/provisioningserver/rpc/tests/test_clusterservice.py 2017-01-24 09:24:36 +0000
+++ src/provisioningserver/rpc/tests/test_clusterservice.py 2017-01-25 16:51:52 +0000
@@ -1229,6 +1229,7 @@
1229 # self.assertTrue(client.isSecure())1229 # self.assertTrue(client.isSecure())
12301230
1231 def test_authenticateRegion_accepts_matching_digests(self):1231 def test_authenticateRegion_accepts_matching_digests(self):
1232 set_shared_secret_on_filesystem(factory.make_bytes())
1232 client = self.make_running_client()1233 client = self.make_running_client()
12331234
1234 def calculate_digest(_, message):1235 def calculate_digest(_, message):
@@ -1243,6 +1244,7 @@
1243 self.assertTrue(extract_result(d))1244 self.assertTrue(extract_result(d))
12441245
1245 def test_authenticateRegion_rejects_non_matching_digests(self):1246 def test_authenticateRegion_rejects_non_matching_digests(self):
1247 set_shared_secret_on_filesystem(factory.make_bytes())
1246 client = self.make_running_client()1248 client = self.make_running_client()
12471249
1248 def calculate_digest(_, message):1250 def calculate_digest(_, message):
12491251
=== modified file 'src/provisioningserver/tests/test_config.py'
--- src/provisioningserver/tests/test_config.py 2016-08-11 01:50:21 +0000
+++ src/provisioningserver/tests/test_config.py 2017-01-25 16:51:52 +0000
@@ -19,6 +19,7 @@
19from fixtures import EnvironmentVariableFixture19from fixtures import EnvironmentVariableFixture
20import formencode20import formencode
21import formencode.validators21import formencode.validators
22import maastesting
22from maastesting.factory import factory23from maastesting.factory import factory
23from maastesting.fixtures import ImportErrorFixture24from maastesting.fixtures import ImportErrorFixture
24from maastesting.matchers import (25from maastesting.matchers import (
@@ -638,8 +639,9 @@
638 self.assertEqual({"tftp_port": example_port}, config.store)639 self.assertEqual({"tftp_port": example_port}, config.store)
639640
640 def test_default_tftp_root(self):641 def test_default_tftp_root(self):
641 maas_root = os.getenv("MAAS_ROOT")642 # The default tftp_root is calculated relative to MAAS_ROOT at module
642 self.assertIsNotNone(maas_root)643 # import time, so we need to recreate that value.
644 maas_root = os.path.join(maastesting.root, "run")
643 config = ClusterConfiguration({})645 config = ClusterConfiguration({})
644 self.assertEqual(646 self.assertEqual(
645 os.path.join(maas_root, "var/lib/maas/boot-resources/current"),647 os.path.join(maas_root, "var/lib/maas/boot-resources/current"),
646648
=== modified file 'src/provisioningserver/utils/fs.py'
--- src/provisioningserver/utils/fs.py 2016-12-14 08:43:09 +0000
+++ src/provisioningserver/utils/fs.py 2017-01-25 16:51:52 +0000
@@ -45,6 +45,7 @@
45import threading45import threading
46from time import sleep46from time import sleep
4747
48from provisioningserver.path import get_path
48from provisioningserver.utils import sudo49from provisioningserver.utils import sudo
49from provisioningserver.utils.shell import ExternalProcessError50from provisioningserver.utils.shell import ExternalProcessError
50from provisioningserver.utils.twisted import retries51from provisioningserver.utils.twisted import retries
@@ -485,7 +486,7 @@
485486
486487
487class RunLock(SystemLock):488class RunLock(SystemLock):
488 """Always create a lock file at ``/run/lock/maas@${path,modified}``.489 """Lock file at ``${MAAS_ROOT}/run/lock/maas@${path,modified}``.
489490
490 This implements an advisory file lock, by proxy, on the given file-system491 This implements an advisory file lock, by proxy, on the given file-system
491 path. This is especially useful if you do not have permissions to the492 path. This is especially useful if you do not have permissions to the
@@ -498,12 +499,12 @@
498 def __init__(self, path):499 def __init__(self, path):
499 abspath = FilePath(path).asTextMode().path.lstrip("/")500 abspath = FilePath(path).asTextMode().path.lstrip("/")
500 discriminator = abspath.replace(":", "::").replace("/", ":")501 discriminator = abspath.replace(":", "::").replace("/", ":")
501 lockpath = "/run/lock/maas@%s" % discriminator502 lockpath = get_path("run", "lock", "maas@%s" % discriminator)
502 super(RunLock, self).__init__(lockpath)503 super(RunLock, self).__init__(lockpath)
503504
504505
505class NamedLock(SystemLock):506class NamedLock(SystemLock):
506 """Always create a lock file at ``/run/lock/maas.${name}``.507 """Lock file at ``${MAAS_ROOT}/run/lock/maas.${name}``.
507508
508 This implements an advisory lock, by proxy, for an abstract name. The name509 This implements an advisory lock, by proxy, for an abstract name. The name
509 must be a string and can contain only numerical digits, hyphens, and ASCII510 must be a string and can contain only numerical digits, hyphens, and ASCII
@@ -524,5 +525,5 @@
524 "Lock name contains illegal characters: %s"525 "Lock name contains illegal characters: %s"
525 % "".join(sorted(illegal)))526 % "".join(sorted(illegal)))
526 else:527 else:
527 lockpath = "/run/lock/maas:%s" % name528 lockpath = get_path("run", "lock", "maas:%s" % name)
528 super(NamedLock, self).__init__(lockpath)529 super(NamedLock, self).__init__(lockpath)
529530
=== modified file 'src/provisioningserver/utils/tests/test_fs.py'
--- src/provisioningserver/utils/tests/test_fs.py 2017-01-06 09:50:17 +0000
+++ src/provisioningserver/utils/tests/test_fs.py 2017-01-25 16:51:52 +0000
@@ -33,6 +33,7 @@
33from maastesting.testcase import MAASTestCase33from maastesting.testcase import MAASTestCase
34from maastesting.utils import age_file34from maastesting.utils import age_file
35import provisioningserver.config35import provisioningserver.config
36from provisioningserver.path import get_tentative_path
36from provisioningserver.utils.fs import (37from provisioningserver.utils.fs import (
37 atomic_copy,38 atomic_copy,
38 atomic_delete,39 atomic_delete,
@@ -773,13 +774,13 @@
773774
774 def test__string_path(self):775 def test__string_path(self):
775 filename = '/foo/bar/123:456.txt'776 filename = '/foo/bar/123:456.txt'
776 expected = '/run/lock/maas@foo:bar:123::456.txt'777 expected = get_tentative_path('/run/lock/maas@foo:bar:123::456.txt')
777 observed = RunLock(filename).path778 observed = RunLock(filename).path
778 self.assertEqual(expected, observed)779 self.assertEqual(expected, observed)
779780
780 def test__byte_path(self):781 def test__byte_path(self):
781 filename = b'/foo/bar/123:456.txt'782 filename = b'/foo/bar/123:456.txt'
782 expected = '/run/lock/maas@foo:bar:123::456.txt'783 expected = get_tentative_path('/run/lock/maas@foo:bar:123::456.txt')
783 observed = RunLock(filename).path784 observed = RunLock(filename).path
784 self.assertEqual(expected, observed)785 self.assertEqual(expected, observed)
785786
@@ -789,7 +790,7 @@
789790
790 def test__string_name(self):791 def test__string_name(self):
791 name = factory.make_name("lock")792 name = factory.make_name("lock")
792 expected = '/run/lock/maas:' + name793 expected = get_tentative_path('/run/lock/maas:' + name)
793 observed = NamedLock(name).path794 observed = NamedLock(name).path
794 self.assertEqual(expected, observed)795 self.assertEqual(expected, observed)
795796
796797
=== modified file 'src/provisioningserver/utils/tests/test_utils.py'
--- src/provisioningserver/utils/tests/test_utils.py 2016-09-22 02:53:33 +0000
+++ src/provisioningserver/utils/tests/test_utils.py 2017-01-25 16:51:52 +0000
@@ -13,7 +13,6 @@
13from unittest.mock import sentinel13from unittest.mock import sentinel
1414
15from fixtures import EnvironmentVariableFixture15from fixtures import EnvironmentVariableFixture
16from maastesting import root
17from maastesting.factory import factory16from maastesting.factory import factory
18from maastesting.testcase import MAASTestCase17from maastesting.testcase import MAASTestCase
19import provisioningserver18import provisioningserver
@@ -40,20 +39,17 @@
40)39)
4140
4241
43def get_branch_dir(*path):42def get_run_path(*path):
44 """Locate a file or directory relative to ``MAAS_ROOT``.43 """Locate a file or directory relative to ``MAAS_ROOT``."""
4544 maas_root = os.environ["MAAS_ROOT"]
46 This function assumes that ``MAAS_ROOT`` has been set to a ``run``45 return os.path.abspath(os.path.join(maas_root, *path))
47 subdirectory of this working-tree's root.
48 """
49 return os.path.abspath(os.path.join(root, "run", *path))
5046
5147
52class TestLocateConfig(MAASTestCase):48class TestLocateConfig(MAASTestCase):
53 """Tests for `locate_config`."""49 """Tests for `locate_config`."""
5450
55 def test_returns_branch_etc_maas(self):51 def test_returns_branch_etc_maas(self):
56 self.assertEqual(get_branch_dir('etc/maas'), locate_config())52 self.assertEqual(get_run_path('etc/maas'), locate_config())
57 self.assertThat(locate_config(), DirExists())53 self.assertThat(locate_config(), DirExists())
5854
59 def test_defaults_to_global_etc_maas_if_variable_is_unset(self):55 def test_defaults_to_global_etc_maas_if_variable_is_unset(self):
@@ -71,18 +67,18 @@
71 def test_locates_config_file(self):67 def test_locates_config_file(self):
72 filename = factory.make_string()68 filename = factory.make_string()
73 self.assertEqual(69 self.assertEqual(
74 get_branch_dir('etc/maas/', filename),70 get_run_path('etc/maas/', filename),
75 locate_config(filename))71 locate_config(filename))
7672
77 def test_locates_full_path(self):73 def test_locates_full_path(self):
78 path = [factory.make_string() for counter in range(3)]74 path = [factory.make_string() for counter in range(3)]
79 self.assertEqual(75 self.assertEqual(
80 get_branch_dir('etc/maas/', *path),76 get_run_path('etc/maas/', *path),
81 locate_config(*path))77 locate_config(*path))
8278
83 def test_normalizes_path(self):79 def test_normalizes_path(self):
84 self.assertEqual(80 self.assertEqual(
85 get_branch_dir('etc/maas/bar/szot'),81 get_run_path('etc/maas/bar/szot'),
86 locate_config('foo/.././bar///szot'))82 locate_config('foo/.././bar///szot'))
8783
8884