Merge lp:~mpontillo/maas/fix-xenial-network-commissioning-bug-1542349-1.10 into lp:maas/1.10

Proposed by Mike Pontillo
Status: Merged
Approved by: Mike Pontillo
Approved revision: no longer in the source branch.
Merged at revision: 4581
Proposed branch: lp:~mpontillo/maas/fix-xenial-network-commissioning-bug-1542349-1.10
Merge into: lp:maas/1.10
Diff against target: 291 lines (+105/-80)
5 files modified
src/metadataserver/api.py (+4/-15)
src/metadataserver/models/tests/ip_addr_results_xenial.txt (+16/-0)
src/metadataserver/models/tests/test_commissioningscript.py (+49/-3)
src/metadataserver/tests/test_api_status.py (+1/-62)
src/provisioningserver/utils/tests/test_ipaddr.py (+35/-0)
To merge this branch: bzr merge lp:~mpontillo/maas/fix-xenial-network-commissioning-bug-1542349-1.10
Reviewer Review Type Date Requested Status
Mike Pontillo (community) Approve
Review via email: mp+286262@code.launchpad.net

Commit message

Merge revision 4666 from trunk to fix bug #1542349: try to ensure the activities that should occur when commissioning finishes happen just once.

Description of the change

Back-port of this fix to bug #1542349, recently landed on trunk:

https://code.launchpad.net/~mpontillo/maas/fix-xenial-network-commissioning-bug-1542349/+merge/285987

I was hoping for a trivial merge, but this branch may require some additional work due to the following code not being present on trunk:

    node.release_leases()
    node.stop_transition_monitor()

To post a comment you must log in.
Revision history for this message
Mike Pontillo (mpontillo) wrote :

Approving based on trunk approval.

After a local run, it doesn't look like tests need fixing, but we'll see what the lander says.

review: Approve
Revision history for this message
MAAS Lander (maas-lander) wrote :
Download full text (1.0 MiB)

The attempt to merge lp:~mpontillo/maas/fix-xenial-network-commissioning-bug-1542349-1.10 into lp:maas/1.10 failed. Below is the output from the failed tests.

Hit:1 http://security.ubuntu.com/ubuntu xenial-security InRelease
Get:2 http://prodstack-zone-1.clouds.archive.ubuntu.com/ubuntu xenial InRelease [95.8 kB]
Hit:3 http://prodstack-zone-1.clouds.archive.ubuntu.com/ubuntu xenial-updates InRelease
Hit:4 http://prodstack-zone-1.clouds.archive.ubuntu.com/ubuntu xenial-backports InRelease
Get:5 http://prodstack-zone-1.clouds.archive.ubuntu.com/ubuntu xenial/main Sources [1,132 kB]
Get:6 http://prodstack-zone-1.clouds.archive.ubuntu.com/ubuntu xenial/universe Sources [7,685 kB]
Get:7 http://prodstack-zone-1.clouds.archive.ubuntu.com/ubuntu xenial/main amd64 Packages [1,469 kB]
Get:8 http://prodstack-zone-1.clouds.archive.ubuntu.com/ubuntu xenial/universe amd64 Packages [7,281 kB]
Get:9 http://prodstack-zone-1.clouds.archive.ubuntu.com/ubuntu xenial/universe Translation-en [4,860 kB]
Fetched 22.5 MB in 13s (1,673 kB/s)
Reading package lists...
sudo DEBIAN_FRONTEND=noninteractive apt-get -y \
    --no-install-recommends install apache2 authbind bind9 bind9utils build-essential bzr-builddeb chromium-browser chromium-chromedriver curl daemontools debhelper dh-apport dh-systemd distro-info dnsutils firefox freeipmi-tools git gjs ipython isc-dhcp-common libjs-angularjs libjs-jquery libjs-jquery-hotkeys libjs-yui3-full libjs-yui3-min libpq-dev make nodejs-legacy npm postgresql pxelinux python3-apt python3-bson python3-convoy python3-crochet python3-cssselect python3-curtin python3-dev python3-distro-info python3-django python3-django-nose python3-django-piston3 python3-djorm-ext-pgarray python3-docutils python3-formencode python3-hivex python3-httplib2 python3-jinja2 python3-jsonschema python3-lxml python3-mock python3-netaddr python3-netifaces python3-oauth python3-openssl python3-paramiko python3-petname python3-pexpect python3-psycopg2 python3-pyinotify python3-pyparsing python3-seamicroclient python3-simplejson python3-simplestreams python3-sphinx python3-tempita python3-twisted python3-txtftp python3-tz python3-yaml python3-zope.interface python-bson python-crochet python-django python-django-piston python-djorm-ext-pgarray python-formencode python-lxml python-netaddr python-netifaces python-pocket-lint python-psycopg2 python-tempita python-twisted python-yaml socat syslinux-common tgt ubuntu-cloudimage-keyring wget xvfb
Reading package lists...
Building dependency tree...
Reading state information...
apache2 is already the newest version (2.4.18-1ubuntu1).
authbind is already the newest version (2.1.1+nmu1).
bind9 is already the newest version (1:9.9.5.dfsg-12.1ubuntu1).
bind9utils is already the newest version (1:9.9.5.dfsg-12.1ubuntu1).
build-essential is already the newest version (12.1ubuntu2).
curl is already the newest version (7.47.0-1ubuntu1).
debhelper is already the newest version (9.20160115ubuntu2).
distro-info is already the newest version (0.14build1).
dnsutils is already the newest version (1:9.9.5.dfsg-12.1ubuntu1).
freeipmi-tools is already the newest version (1.4.11-1ubuntu1).
git is already the newest version (1:2.7.0-1).
isc...

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
1=== modified file 'src/metadataserver/api.py'
2--- src/metadataserver/api.py 2015-12-11 20:33:25 +0000
3+++ src/metadataserver/api.py 2016-02-17 07:41:00 +0000
4@@ -348,21 +348,7 @@
5 # At the end of a top-level event, we change the node status.
6 if _is_top_level(activity_name) and event_type == 'finish':
7 if node.status == NODE_STATUS.COMMISSIONING:
8-
9- # Ensure that any IP lease are forcefully released in case
10- # the host didn't bother doing that.
11- node.release_leases()
12-
13- node.stop_transition_monitor()
14- if result == 'SUCCESS':
15- # Recalculate tags.
16- populate_tags_for_single_node(Tag.objects.all(), node)
17-
18- # Setup the default storage layout and the initial
19- # networking configuration for the node.
20- node.set_default_storage_layout()
21- node.set_initial_networking_configuration()
22- elif result in ['FAIL', 'FAILURE']:
23+ if result in ['FAIL', 'FAILURE']:
24 node.status = NODE_STATUS.FAILED_COMMISSIONING
25
26 elif node.status == NODE_STATUS.DEPLOYING:
27@@ -372,6 +358,9 @@
28 "Installation failed (refer to the "
29 "installation log for more information).")
30 elif node.status == NODE_STATUS.DISK_ERASING:
31+ # XXX mpontillo 2016-02-12 we need to check that
32+ # only one "top level" event is sent during disk
33+ # erasing.
34 if result == 'SUCCESS':
35 # disk erasing complete, release node.
36 node.release()
37
38=== added file 'src/metadataserver/models/tests/ip_addr_results_xenial.txt'
39--- src/metadataserver/models/tests/ip_addr_results_xenial.txt 1970-01-01 00:00:00 +0000
40+++ src/metadataserver/models/tests/ip_addr_results_xenial.txt 2016-02-17 07:41:00 +0000
41@@ -0,0 +1,16 @@
42+1: lo: <LOOPBACK,UP,LOWER_UP> mtu 65536 qdisc noqueue state UNKNOWN group default
43+ link/loopback 00:00:00:00:00:00 brd 00:00:00:00:00:00
44+ inet 127.0.0.1/8 scope host lo
45+ valid_lft forever preferred_lft forever
46+ inet6 ::1/128 scope host
47+ valid_lft forever preferred_lft forever
48+2: ens3: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc pfifo_fast state UP group default qlen 1000
49+ link/ether 52:54:00:2d:39:49 brd ff:ff:ff:ff:ff:ff
50+ inet 172.16.100.108/24 brd 172.16.100.255 scope global ens3
51+ valid_lft forever preferred_lft forever
52+ inet6 fe80::5054:ff:fe2d:3949/64 scope link
53+ valid_lft forever preferred_lft forever
54+3: ens10: <BROADCAST,MULTICAST> mtu 1500 qdisc noop state DOWN group default qlen 1000
55+ link/ether 52:54:00:e5:c6:6b brd ff:ff:ff:ff:ff:ff
56+4: ens11: <BROADCAST,MULTICAST> mtu 1500 qdisc noop state DOWN group default qlen 1000
57+ link/ether 52:54:00:ed:9f:9d brd ff:ff:ff:ff:ff:ff
58
59=== modified file 'src/metadataserver/models/tests/test_commissioningscript.py'
60--- src/metadataserver/models/tests/test_commissioningscript.py 2016-02-04 14:04:08 +0000
61+++ src/metadataserver/models/tests/test_commissioningscript.py 2016-02-17 07:41:00 +0000
62@@ -1460,24 +1460,36 @@
63 'eth2': MAC("00:00:00:00:00:03"),
64 }
65
66+ EXPECTED_INTERFACES_XENIAL = {
67+ 'ens3': MAC("52:54:00:2d:39:49"),
68+ 'ens10': MAC("52:54:00:e5:c6:6b"),
69+ 'ens11': MAC("52:54:00:ed:9f:9d"),
70+ }
71+
72 IP_ADDR_OUTPUT_FILE = os.path.join(
73 os.path.dirname(__file__), 'ip_addr_results.txt')
74 with open(IP_ADDR_OUTPUT_FILE, "rb") as fd:
75 IP_ADDR_OUTPUT = fd.read()
76+ IP_ADDR_OUTPUT_FILE = os.path.join(
77+ os.path.dirname(__file__), 'ip_addr_results_xenial.txt')
78+ with open(IP_ADDR_OUTPUT_FILE, "rb") as fd:
79+ IP_ADDR_OUTPUT_XENIAL = fd.read()
80
81 def assert_expected_interfaces_and_macs_exist(
82- self, node_interfaces, additional_interfaces={}):
83+ self, node_interfaces, additional_interfaces={},
84+ expected_interfaces=EXPECTED_INTERFACES):
85 """Asserts to ensure that the type, name, and MAC address are
86 appropriate, given Node's interfaces. (and an optional list of
87 additional interfaces which must exist)
88 """
89- expected_interfaces = self.EXPECTED_INTERFACES.copy()
90+ expected_interfaces = expected_interfaces.copy()
91 expected_interfaces.update(additional_interfaces)
92
93 self.assertThat(len(node_interfaces), Equals(len(expected_interfaces)))
94
95 for interface in node_interfaces:
96- if interface.name.startswith('eth'):
97+ if (interface.name.startswith('eth') or
98+ interface.name.startswith('ens')):
99 parts = interface.name.split('.')
100 if len(parts) == 2 and parts[1].isdigit():
101 iftype = INTERFACE_TYPE.VLAN
102@@ -1510,6 +1522,23 @@
103 node_interfaces = Interface.objects.filter(node=node)
104 self.assert_expected_interfaces_and_macs_exist(node_interfaces)
105
106+ def test__add_all_interfaces_xenial(self):
107+ """Test a node that has no previously known interfaces on which we
108+ need to add a series of interfaces.
109+ """
110+ node = factory.make_Node()
111+
112+ # Delete all Interfaces created by factory attached to this node.
113+ Interface.objects.filter(node_id=node.id).delete()
114+
115+ update_node_network_information(node, self.IP_ADDR_OUTPUT_XENIAL, 0)
116+
117+ # Makes sure all the test dataset MAC addresses were added to the node.
118+ node_interfaces = Interface.objects.filter(node=node)
119+ self.assert_expected_interfaces_and_macs_exist(
120+ node_interfaces,
121+ expected_interfaces=self.EXPECTED_INTERFACES_XENIAL)
122+
123 def test__one_mac_missing(self):
124 """Test whether we correctly detach a NIC that no longer appears to be
125 connected to the node.
126@@ -1728,3 +1757,20 @@
127 MatchesStructure.byEquality(
128 alloc_type=IPADDRESS_TYPE.DISCOVERED, subnet=subnet,
129 ip=address))
130+
131+ def test__creates_discovered_ip_address_on_xenial(self):
132+ node = factory.make_Node()
133+ cidr = '172.16.100.108/24'
134+ subnet = factory.make_Subnet(
135+ cidr=cidr, vlan=VLAN.objects.get_default_vlan())
136+
137+ update_node_network_information(node, self.IP_ADDR_OUTPUT_XENIAL, 0)
138+ eth0 = Interface.objects.get(node=node, name='ens3')
139+ address = str(IPNetwork(cidr).ip)
140+ ipv4_ip = eth0.ip_addresses.get(ip=address)
141+ self.assertThat(
142+ ipv4_ip,
143+ MatchesStructure.byEquality(
144+ alloc_type=IPADDRESS_TYPE.DISCOVERED, subnet=subnet,
145+ ip=address))
146+ self.assertThat(eth0.ip_addresses.count(), Equals(1))
147
148=== modified file 'src/metadataserver/tests/test_api_status.py'
149--- src/metadataserver/tests/test_api_status.py 2015-12-01 18:12:59 +0000
150+++ src/metadataserver/tests/test_api_status.py 2016-02-17 07:41:00 +0000
151@@ -18,24 +18,19 @@
152 from maasserver.enum import NODE_STATUS
153 from maasserver.models import (
154 Event,
155- Node,
156 Tag,
157 )
158 from maasserver.testing.factory import factory
159 from maasserver.testing.oauthclient import OAuthAuthenticatedClient
160 from maasserver.testing.orm import reload_object
161 from maasserver.testing.testcase import MAASServerTestCase
162-from maastesting.matchers import (
163- MockCalledOnceWith,
164- MockNotCalled,
165-)
166+from maastesting.matchers import MockNotCalled
167 from metadataserver import api
168 from metadataserver.models import (
169 NodeKey,
170 NodeResult,
171 )
172 from metadataserver.nodeinituser import get_node_init_user
173-from mock import ANY
174 from provisioningserver.utils import typed
175
176
177@@ -138,62 +133,6 @@
178 data=urllib.parse.urlencode(payload))
179 self.assertEqual(http.client.BAD_REQUEST, response.status_code)
180
181- def test_status_comissioning_success_populates_tags(self):
182- populate_tags_for_single_node = self.patch(
183- api, "populate_tags_for_single_node")
184- node = factory.make_Node(
185- interface=True, status=NODE_STATUS.COMMISSIONING)
186- client = make_node_client(node=node)
187- payload = {
188- 'event_type': 'finish',
189- 'result': 'SUCCESS',
190- 'origin': 'curtin',
191- 'name': 'cmd-install',
192- 'description': 'Command Install',
193- }
194- response = call_status(client, node, payload)
195- self.assertEqual(http.client.OK, response.status_code)
196- self.assertThat(
197- populate_tags_for_single_node,
198- MockCalledOnceWith(ANY, node))
199-
200- def test_status_comissioning_success_sets_default_storage_layout(self):
201- node = factory.make_Node(
202- interface=True, status=NODE_STATUS.COMMISSIONING)
203- self.patch_autospec(Node, "set_default_storage_layout")
204- client = make_node_client(node=node)
205- payload = {
206- 'event_type': 'finish',
207- 'result': 'SUCCESS',
208- 'origin': 'curtin',
209- 'name': 'cmd-install',
210- 'description': 'Command Install',
211- }
212- response = call_status(client, node, payload)
213- self.assertEqual(http.client.OK, response.status_code)
214- self.assertThat(
215- Node.set_default_storage_layout,
216- MockCalledOnceWith(node))
217-
218- def test_status_comissioning_success_sets_node_network_configuration(self):
219- node = factory.make_Node(
220- interface=True, status=NODE_STATUS.COMMISSIONING)
221- mock_set_initial_networking_configuration = self.patch_autospec(
222- Node, "set_initial_networking_configuration")
223- client = make_node_client(node=node)
224- payload = {
225- 'event_type': 'finish',
226- 'result': 'SUCCESS',
227- 'origin': 'curtin',
228- 'name': 'cmd-install',
229- 'description': 'Command Install',
230- }
231- response = call_status(client, node, payload)
232- self.assertEqual(http.client.OK, response.status_code)
233- self.assertThat(
234- mock_set_initial_networking_configuration,
235- MockCalledOnceWith(node))
236-
237 def test_status_commissioning_failure_leaves_node_failed(self):
238 node = factory.make_Node(
239 interface=True, status=NODE_STATUS.COMMISSIONING)
240
241=== modified file 'src/provisioningserver/utils/tests/test_ipaddr.py'
242--- src/provisioningserver/utils/tests/test_ipaddr.py 2015-12-01 18:12:59 +0000
243+++ src/provisioningserver/utils/tests/test_ipaddr.py 2016-02-17 07:41:00 +0000
244@@ -27,6 +27,7 @@
245 from testtools.matchers import (
246 Contains,
247 Equals,
248+ Not,
249 )
250
251
252@@ -305,6 +306,40 @@
253 '2620:1:260::1/64'],
254 ip_link['eth1']['inet6'])
255
256+ def test_parses_xenial_interfaces(self):
257+ testdata = dedent("""
258+1: lo: <LOOPBACK,UP,LOWER_UP> mtu 65536 qdisc noqueue state UNKNOWN \
259+group default
260+ link/loopback 00:00:00:00:00:00 brd 00:00:00:00:00:00
261+ inet 127.0.0.1/8 scope host lo
262+ valid_lft forever preferred_lft forever
263+ inet6 ::1/128 scope host
264+ valid_lft forever preferred_lft forever
265+2: ens3: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc pfifo_fast state UP \
266+group default qlen 1000
267+ link/ether 52:54:00:2d:39:49 brd ff:ff:ff:ff:ff:ff
268+ inet 172.16.100.108/24 brd 172.16.100.255 scope global ens3
269+ valid_lft forever preferred_lft forever
270+ inet6 fe80::5054:ff:fe2d:3949/64 scope link
271+ valid_lft forever preferred_lft forever
272+3: ens10: <BROADCAST,MULTICAST> mtu 1500 qdisc noop state DOWN \
273+group default qlen 1000
274+ link/ether 52:54:00:e5:c6:6b brd ff:ff:ff:ff:ff:ff
275+4: ens11: <BROADCAST,MULTICAST> mtu 1500 qdisc noop state DOWN \
276+group default qlen 1000
277+ link/ether 52:54:00:ed:9f:9d brd ff:ff:ff:ff:ff:ff
278+ """)
279+ ip_link = parse_ip_addr(testdata)
280+ self.assertEqual(2, ip_link['ens3']['index'])
281+ self.assertEqual('52:54:00:2d:39:49', ip_link['ens3']['mac'])
282+ self.assertEqual(['172.16.100.108/24'], ip_link['ens3']['inet'])
283+ self.assertEqual(3, ip_link['ens10']['index'])
284+ self.assertEqual('52:54:00:e5:c6:6b', ip_link['ens10']['mac'])
285+ self.assertThat(ip_link['ens10'], Not(Contains('inet')))
286+ self.assertEqual(4, ip_link['ens11']['index'])
287+ self.assertEqual('52:54:00:ed:9f:9d', ip_link['ens11']['mac'])
288+ self.assertThat(ip_link['ens11'], Not(Contains('inet')))
289+
290
291 class TestGetInterfaceType(MAASTestCase):
292

Subscribers

People subscribed via source and target branches

to all changes: