Merge lp:~ltrager/maas/rack_transitioning into lp:~maas-committers/maas/trunk

Proposed by Lee Trager
Status: Merged
Approved by: Lee Trager
Approved revision: no longer in the source branch.
Merged at revision: 5052
Proposed branch: lp:~ltrager/maas/rack_transitioning
Merge into: lp:~maas-committers/maas/trunk
Diff against target: 368 lines (+253/-3)
6 files modified
src/maasserver/models/node.py (+22/-0)
src/maasserver/models/tests/test_node.py (+34/-0)
src/maasserver/rpc/rackcontrollers.py (+1/-2)
src/maasserver/rpc/tests/test_rackcontrollers.py (+7/-1)
src/maasserver/triggers/tests/test_websocket_listener.py (+125/-0)
src/maasserver/triggers/websocket.py (+64/-0)
To merge this branch: bzr merge lp:~ltrager/maas/rack_transitioning
Reviewer Review Type Date Requested Status
Gavin Panella (community) Approve
Review via email: mp+295552@code.launchpad.net

Commit message

Allow a machine to transition to and from a rack controller

Description of the change

This allows you to deploy a machine, install a rack controller on it, then delete the rack controller converting it back to a usable but still deployed machine. I added a trigger so the transitions of any node type to any node type are shown in the UI instantly. During testing I realized that we were always setting the owner to the rack controller worker user. This only sets the user if one isn't defined.

To post a comment you must log in.
Revision history for this message
Gavin Panella (allenap) wrote :

This is good stuff. I really like the scenarios in TestNodeTypeChange. I have one big question though, and I think TestNodeTypeChange is actually broken right now (though it's not far off), so Needs Fixing.

review: Needs Fixing
Revision history for this message
Lee Trager (ltrager) wrote :

Thanks for the review. I've made the corrections you've suggested and answered your question about testing below. I put _probably_was_machine as a Controller method as I'll need it for regions in a coming branch.

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

Cool!

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

The attempt to merge lp:~ltrager/maas/rack_transitioning into lp:maas failed. Below is the output from the failed tests.

Hit:1 http://prodstack-zone-2.clouds.archive.ubuntu.com/ubuntu xenial InRelease
Get:2 http://prodstack-zone-2.clouds.archive.ubuntu.com/ubuntu xenial-updates InRelease [94.5 kB]
Get:3 http://security.ubuntu.com/ubuntu xenial-security InRelease [94.5 kB]
Hit:4 http://prodstack-zone-2.clouds.archive.ubuntu.com/ubuntu xenial-backports InRelease
Get:5 http://prodstack-zone-2.clouds.archive.ubuntu.com/ubuntu xenial-updates/main amd64 Packages [111 kB]
Get:6 http://prodstack-zone-2.clouds.archive.ubuntu.com/ubuntu xenial-updates/universe amd64 Packages [50.3 kB]
Fetched 350 kB in 0s (754 kB/s)
Reading package lists...
sudo DEBIAN_FRONTEND=noninteractive apt-get -y \
    --no-install-recommends install apache2 archdetect-deb authbind bash bind9 bind9utils build-essential bzr 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-all python3-apt python3-bson python3-convoy python3-coverage python3-crochet python3-cssselect python3-curtin python3-dev python3-distro-info python3-django python3-django-nose python3-django-piston3 python3-dnspython python3-docutils python3-formencode python3-hivex python3-httplib2 python3-jinja2 python3-jsonschema python3-lxml python3-netaddr python3-netifaces python3-novaclient python3-oauth python3-oauthlib python3-openssl python3-paramiko python3-petname python3-pexpect python3-psycopg2 python3-pyinotify python3-pyparsing python3-pyvmomi python3-requests python3-seamicroclient python3-setuptools 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-simplejson 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-2ubuntu3).
archdetect-deb is already the newest version (1.117ubuntu2).
authbind is already the newest version (2.1.1+nmu1).
bash is already the newest version (4.3-14ubuntu1).
build-essential is already the newest version (12.1ubuntu2).
bzr is already the newest version (2.7.0-2ubuntu1).
curl is already the newest version (7.47.0-1ubuntu2).
debhelper is already the newest version (9.20160115ubuntu3).
distro-info is already the newest version (0.14build1).
freeipmi-tools is already the newest version (1.4.11-1ubuntu1).
git is already the newest version (1:2.7.4-0ubuntu1).
isc-dhcp-common is already the newest version (4.3.3-5ubuntu12).
libjs-angularjs is already the newest version (1.2.28-1ubuntu2).
libjs-jquery is already the newest version (1.11.3+dfsg-4).
libjs-yui3-full is already the newest ...

Revision history for this message
MAAS Lander (maas-lander) wrote :
Download full text (31.2 KiB)

The attempt to merge lp:~ltrager/maas/rack_transitioning into lp:maas failed. Below is the output from the failed tests.

Hit:1 http://prodstack-zone-2.clouds.archive.ubuntu.com/ubuntu xenial InRelease
Hit:2 http://prodstack-zone-2.clouds.archive.ubuntu.com/ubuntu xenial-updates InRelease
Hit:3 http://prodstack-zone-2.clouds.archive.ubuntu.com/ubuntu xenial-backports InRelease
Get:4 http://security.ubuntu.com/ubuntu xenial-security InRelease [94.5 kB]
Fetched 94.5 kB in 0s (204 kB/s)
Reading package lists...
sudo DEBIAN_FRONTEND=noninteractive apt-get -y \
    --no-install-recommends install apache2 archdetect-deb authbind bash bind9 bind9utils build-essential bzr 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-all python3-apt python3-bson python3-convoy python3-coverage python3-crochet python3-cssselect python3-curtin python3-dev python3-distro-info python3-django python3-django-nose python3-django-piston3 python3-dnspython python3-docutils python3-formencode python3-hivex python3-httplib2 python3-jinja2 python3-jsonschema python3-lxml python3-netaddr python3-netifaces python3-novaclient python3-oauth python3-oauthlib python3-openssl python3-paramiko python3-petname python3-pexpect python3-psycopg2 python3-pyinotify python3-pyparsing python3-pyvmomi python3-requests python3-seamicroclient python3-setuptools 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-simplejson 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-2ubuntu3).
archdetect-deb is already the newest version (1.117ubuntu2).
authbind is already the newest version (2.1.1+nmu1).
bash is already the newest version (4.3-14ubuntu1).
build-essential is already the newest version (12.1ubuntu2).
bzr is already the newest version (2.7.0-2ubuntu1).
curl is already the newest version (7.47.0-1ubuntu2).
debhelper is already the newest version (9.20160115ubuntu3).
distro-info is already the newest version (0.14build1).
freeipmi-tools is already the newest version (1.4.11-1ubuntu1).
git is already the newest version (1:2.7.4-0ubuntu1).
isc-dhcp-common is already the newest version (4.3.3-5ubuntu12).
libjs-angularjs is already the newest version (1.2.28-1ubuntu2).
libjs-jquery is already the newest version (1.11.3+dfsg-4).
libjs-yui3-full is already the newest version (3.5.1-1ubuntu3).
libjs-yui3-min is already the newest version (3.5.1-1ubuntu3).
make is already the newest version (4.1-6).
postgresql is already the newest version (9.5+173).
pxelinux is already the newest version (3:6...

Revision history for this message
MAAS Lander (maas-lander) wrote :
Download full text (31.1 KiB)

The attempt to merge lp:~ltrager/maas/rack_transitioning into lp:maas failed. Below is the output from the failed tests.

Get:1 http://security.ubuntu.com/ubuntu xenial-security InRelease [94.5 kB]
Hit:2 http://prodstack-zone-2.clouds.archive.ubuntu.com/ubuntu xenial InRelease
Get:3 http://prodstack-zone-2.clouds.archive.ubuntu.com/ubuntu xenial-updates InRelease [94.5 kB]
Hit:4 http://prodstack-zone-2.clouds.archive.ubuntu.com/ubuntu xenial-backports InRelease
Fetched 189 kB in 0s (482 kB/s)
Reading package lists...
sudo DEBIAN_FRONTEND=noninteractive apt-get -y \
    --no-install-recommends install apache2 archdetect-deb authbind bash bind9 bind9utils build-essential bzr 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-all 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-dnspython python3-docutils python3-formencode python3-hivex python3-httplib2 python3-jinja2 python3-jsonschema python3-lxml python3-netaddr python3-netifaces python3-novaclient python3-oauth python3-oauthlib python3-openssl python3-paramiko python3-petname python3-pexpect python3-psycopg2 python3-pyinotify python3-pyparsing python3-pyvmomi python3-requests python3-seamicroclient python3-setuptools 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-simplejson 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-2ubuntu3).
archdetect-deb is already the newest version (1.117ubuntu2).
authbind is already the newest version (2.1.1+nmu1).
bash is already the newest version (4.3-14ubuntu1).
build-essential is already the newest version (12.1ubuntu2).
bzr is already the newest version (2.7.0-2ubuntu1).
curl is already the newest version (7.47.0-1ubuntu2).
debhelper is already the newest version (9.20160115ubuntu3).
distro-info is already the newest version (0.14build1).
freeipmi-tools is already the newest version (1.4.11-1ubuntu1).
git is already the newest version (1:2.7.4-0ubuntu1).
isc-dhcp-common is already the newest version (4.3.3-5ubuntu12).
libjs-angularjs is already the newest version (1.2.28-1ubuntu2).
libjs-jquery is already the newest version (1.11.3+dfsg-4).
libjs-yui3-full is already the newest version (3.5.1-1ubuntu3).
libjs-yui3-min is already the newest version (3.5.1-1ubuntu3).
make is already the newest version (4.1-6).
postgresql is already the newest version (9.5+173).
pxelinux is already the newest version (3:6.03+dfsg...

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
1=== modified file 'src/maasserver/models/node.py'
2--- src/maasserver/models/node.py 2016-05-26 06:47:45 +0000
3+++ src/maasserver/models/node.py 2016-05-26 16:01:00 +0000
4@@ -3097,6 +3097,16 @@
5 def __init__(self, *args, **kwargs):
6 super(Controller, self).__init__(*args, **kwargs)
7
8+ def _was_probably_machine(self):
9+ """Best guess if a rack was a machine.
10+
11+ MAAS doesn't track node transitions so we have to look at
12+ breadcrumbs. The first is the status. Only machines can have their
13+ status changed to something other than NEW and a rack controller should
14+ only be installable when the machine is in a deployed state. Second a
15+ machine must have power information."""
16+ return self.status == NODE_STATUS.DEPLOYED and self.bmc is not None
17+
18
19 class RackController(Controller):
20 """A node which is running rackd."""
21@@ -3659,6 +3669,9 @@
22
23 def delete(self):
24 """Delete this rack controller."""
25+ # Avoid circular imports
26+ from maasserver.models import RegionRackRPCConnection
27+
28 primary_vlans = VLAN.objects.filter(primary_rack=self)
29 if len(primary_vlans) != 0:
30 raise ValidationError(
31@@ -3667,6 +3680,10 @@
32 (self.hostname,
33 ', '.join([str(vlan) for vlan in primary_vlans])))
34
35+ # Disable and delete all services related to this node
36+ Service.objects.mark_dead(self, dead_rack=True)
37+ Service.objects.filter(node=self).delete()
38+
39 try:
40 client = getClientFor(self.system_id, timeout=1)
41 call = client(DisableAndShutoffRackd)
42@@ -3676,6 +3693,8 @@
43 # is currently disconnected or rackd was killed
44 pass
45
46+ RegionRackRPCConnection.objects.filter(rack_controller=self).delete()
47+
48 for vlan in VLAN.objects.filter(secondary_rack=self):
49 vlan.secondary_rack = None
50 vlan.save()
51@@ -3683,6 +3702,9 @@
52 if self.node_type == NODE_TYPE.REGION_AND_RACK_CONTROLLER:
53 self.node_type = NODE_TYPE.REGION_CONTROLLER
54 self.save()
55+ elif self._was_probably_machine():
56+ self.node_type = NODE_TYPE.MACHINE
57+ self.save()
58 else:
59 super().delete()
60
61
62=== modified file 'src/maasserver/models/tests/test_node.py'
63--- src/maasserver/models/tests/test_node.py 2016-05-26 06:47:45 +0000
64+++ src/maasserver/models/tests/test_node.py 2016-05-26 16:01:00 +0000
65@@ -4597,6 +4597,18 @@
66 routable_racks, none_routable_racks)
67
68
69+class TestController(MAASServerTestCase):
70+
71+ def test__was_probably_machine_true(self):
72+ rack = factory.make_RackController(status=NODE_STATUS.DEPLOYED)
73+ rack.bmc = factory.make_BMC()
74+ rack.save()
75+ self.assertTrue(rack._was_probably_machine())
76+
77+ def test__was_probably_machine_false(self):
78+ self.assertFalse(factory.make_RackController()._was_probably_machine())
79+
80+
81 class TestRackControllerUpdateInterfaces(MAASServerTestCase):
82
83 def create_empty_rack_controller(self):
84@@ -7022,6 +7034,18 @@
85 RackController.DoesNotExist,
86 RackController.objects.get, system_id=rackcontroller.system_id)
87
88+ def test_deletes_services(self):
89+ rack = factory.make_RackController()
90+ factory.make_Service(rack)
91+ rack.delete()
92+ self.assertItemsEqual([], Service.objects.all())
93+
94+ def test_deletes_region_rack_rpc_connections(self):
95+ rack = factory.make_RackController()
96+ factory.make_RegionRackRPCConnection(rack_controller=rack)
97+ rack.delete()
98+ self.assertItemsEqual([], RegionRackRPCConnection.objects.all())
99+
100 def test_delete_converts_region_and_rack_to_region(self):
101 region_and_rack = factory.make_Node(
102 node_type=NODE_TYPE.REGION_AND_RACK_CONTROLLER)
103@@ -7031,6 +7055,16 @@
104 NODE_TYPE.REGION_CONTROLLER,
105 Node.objects.get(system_id=system_id).node_type)
106
107+ def test_delete_converts_rack_to_machine(self):
108+ rack = factory.make_RackController(status=NODE_STATUS.DEPLOYED)
109+ rack.power_parameters = {
110+ 'power_address': 'qemu+ssh://user@host/system',
111+ }
112+ rack.delete()
113+ self.assertEquals(
114+ NODE_TYPE.MACHINE,
115+ Node.objects.get(system_id=rack.system_id).node_type)
116+
117 def test_update_rackd_status_calls_mark_dead_when_no_connections(self):
118 rack_controller = factory.make_RackController()
119 mock_mark_dead = self.patch(Service.objects, "mark_dead")
120
121=== modified file 'src/maasserver/rpc/rackcontrollers.py'
122--- src/maasserver/rpc/rackcontrollers.py 2016-05-13 15:14:05 +0000
123+++ src/maasserver/rpc/rackcontrollers.py 2016-05-26 16:01:00 +0000
124@@ -83,8 +83,7 @@
125 if rackcontroller.url != url.geturl():
126 rackcontroller.url = url.geturl()
127 update_fields.append("url")
128- work_user = worker_user.get_worker_user()
129- if rackcontroller.owner != work_user:
130+ if rackcontroller.owner is None:
131 rackcontroller.owner = worker_user.get_worker_user()
132 update_fields.append("owner")
133 rackcontroller.save(update_fields=update_fields)
134
135=== modified file 'src/maasserver/rpc/tests/test_rackcontrollers.py'
136--- src/maasserver/rpc/tests/test_rackcontrollers.py 2016-05-13 15:14:05 +0000
137+++ src/maasserver/rpc/tests/test_rackcontrollers.py 2016-05-26 16:01:00 +0000
138@@ -97,11 +97,17 @@
139
140 class TestRegisterRackController(MAASServerTestCase):
141
142- def test_sets_owner_to_worker(self):
143+ def test_sets_owner_to_worker_when_none(self):
144 node = factory.make_Node()
145 rack_registered = register_rackcontroller(system_id=node.system_id)
146 self.assertEqual(worker_user.get_worker_user(), rack_registered.owner)
147
148+ def test_leaves_owner_when_owned(self):
149+ user = factory.make_User()
150+ node = factory.make_Machine(owner=user)
151+ rack_registered = register_rackcontroller(system_id=node.system_id)
152+ self.assertEqual(user, rack_registered.owner)
153+
154 def test_finds_existing_node_by_system_id(self):
155 node = factory.make_Node()
156 rack_registered = register_rackcontroller(system_id=node.system_id)
157
158=== modified file 'src/maasserver/triggers/tests/test_websocket_listener.py'
159--- src/maasserver/triggers/tests/test_websocket_listener.py 2016-04-28 13:53:02 +0000
160+++ src/maasserver/triggers/tests/test_websocket_listener.py 2016-05-26 16:01:00 +0000
161@@ -2645,3 +2645,128 @@
162 self.assertEqual(('delete', '%s' % snippet.id), dv.value)
163 finally:
164 yield listener.stopService()
165+
166+
167+class TestNodeTypeChange(
168+ MAASTransactionServerTestCase, TransactionalHelpersMixin):
169+ """End-to-end test of node type change triggers code."""
170+
171+ scenarios = (
172+ ('machine_to_rack', {
173+ 'from_type': NODE_TYPE.MACHINE,
174+ 'from_listener': 'machine',
175+ 'to_type': NODE_TYPE.RACK_CONTROLLER,
176+ 'to_listener': 'controller',
177+ }),
178+ ('machine_to_region', {
179+ 'from_type': NODE_TYPE.MACHINE,
180+ 'from_listener': 'machine',
181+ 'to_type': NODE_TYPE.REGION_CONTROLLER,
182+ 'to_listener': 'controller',
183+ }),
184+ ('machine_to_rack_and_region', {
185+ 'from_type': NODE_TYPE.MACHINE,
186+ 'from_listener': 'machine',
187+ 'to_type': NODE_TYPE.REGION_AND_RACK_CONTROLLER,
188+ 'to_listener': 'controller',
189+ }),
190+ ('machine_to_device', {
191+ 'from_type': NODE_TYPE.MACHINE,
192+ 'from_listener': 'machine',
193+ 'to_type': NODE_TYPE.DEVICE,
194+ 'to_listener': 'device',
195+ }),
196+ ('rack_to_machine', {
197+ 'from_type': NODE_TYPE.RACK_CONTROLLER,
198+ 'from_listener': 'controller',
199+ 'to_type': NODE_TYPE.MACHINE,
200+ 'to_listener': 'machine',
201+ }),
202+ ('rack_to_device', {
203+ 'from_type': NODE_TYPE.RACK_CONTROLLER,
204+ 'from_listener': 'controller',
205+ 'to_type': NODE_TYPE.DEVICE,
206+ 'to_listener': 'device',
207+ }),
208+ ('region_to_machine', {
209+ 'from_type': NODE_TYPE.REGION_CONTROLLER,
210+ 'from_listener': 'controller',
211+ 'to_type': NODE_TYPE.MACHINE,
212+ 'to_listener': 'machine',
213+ }),
214+ ('region_to_device', {
215+ 'from_type': NODE_TYPE.REGION_CONTROLLER,
216+ 'from_listener': 'controller',
217+ 'to_type': NODE_TYPE.DEVICE,
218+ 'to_listener': 'device',
219+ }),
220+ ('region_and_rack_to_machine', {
221+ 'from_type': NODE_TYPE.REGION_AND_RACK_CONTROLLER,
222+ 'from_listener': 'controller',
223+ 'to_type': NODE_TYPE.MACHINE,
224+ 'to_listener': 'machine',
225+ }),
226+ ('region_and_rack_to_device', {
227+ 'from_type': NODE_TYPE.REGION_AND_RACK_CONTROLLER,
228+ 'from_listener': 'controller',
229+ 'to_type': NODE_TYPE.DEVICE,
230+ 'to_listener': 'device',
231+ }),
232+ ('device_to_rack', {
233+ 'from_type': NODE_TYPE.DEVICE,
234+ 'from_listener': 'device',
235+ 'to_type': NODE_TYPE.RACK_CONTROLLER,
236+ 'to_listener': 'controller',
237+ }),
238+ ('device_to_region', {
239+ 'from_type': NODE_TYPE.DEVICE,
240+ 'from_listener': 'device',
241+ 'to_type': NODE_TYPE.REGION_CONTROLLER,
242+ 'to_listener': 'controller',
243+ }),
244+ ('device_to_rack_and_region', {
245+ 'from_type': NODE_TYPE.DEVICE,
246+ 'from_listener': 'device',
247+ 'to_type': NODE_TYPE.REGION_AND_RACK_CONTROLLER,
248+ 'to_listener': 'controller',
249+ }),
250+ ('device_to_machine', {
251+ 'from_type': NODE_TYPE.DEVICE,
252+ 'from_listener': 'device',
253+ 'to_type': NODE_TYPE.MACHINE,
254+ 'to_listener': 'machine',
255+ }),
256+ )
257+
258+ @wait_for_reactor
259+ @inlineCallbacks
260+ def test__transition_notifies(self):
261+ yield deferToDatabase(register_websocket_triggers)
262+ listener1 = self.make_listener_without_delay()
263+ listener2 = self.make_listener_without_delay()
264+ node = yield deferToDatabase(
265+ self.create_node, {'node_type': self.from_type})
266+ q_from, q_to = DeferredQueue(), DeferredQueue()
267+ listener1.register(self.from_listener, lambda *args: q_from.put(args))
268+ listener2.register(self.to_listener, lambda *args: q_to.put(args))
269+ yield listener1.startService()
270+ yield listener2.startService()
271+ try:
272+ node.node_type = self.to_type
273+ yield deferToDatabase(node.save)
274+ self.assertEqual(
275+ ('delete', node.system_id),
276+ (yield deferWithTimeout(2, q_from.get)))
277+ self.assertEqual(
278+ {
279+ ('create', node.system_id),
280+ ('update', node.system_id),
281+ },
282+ {
283+ (yield deferWithTimeout(2, q_to.get)),
284+ (yield deferWithTimeout(2, q_to.get)),
285+ }
286+ )
287+ finally:
288+ yield listener1.stopService()
289+ yield listener2.stopService()
290
291=== modified file 'src/maasserver/triggers/websocket.py'
292--- src/maasserver/triggers/websocket.py 2016-04-01 22:54:51 +0000
293+++ src/maasserver/triggers/websocket.py 2016-05-26 16:01:00 +0000
294@@ -772,6 +772,66 @@
295 NODE_TYPE.REGION_AND_RACK_CONTROLLER))
296
297
298+def node_type_change():
299+ return dedent("""\
300+ CREATE OR REPLACE FUNCTION node_type_change_notify()
301+ RETURNS trigger AS $$
302+ BEGIN
303+ IF (OLD.node_type != NEW.node_type AND NOT (
304+ (
305+ OLD.node_type = {rack_controller} OR
306+ OLD.node_type = {region_controller} OR
307+ OLD.node_type = {region_and_rack_controller}
308+ ) AND (
309+ NEW.node_type = {rack_controller} OR
310+ NEW.node_type = {region_controller} OR
311+ NEW.node_type = {region_and_rack_controller}
312+ ))) THEN
313+ CASE OLD.node_type
314+ WHEN {machine} THEN
315+ PERFORM pg_notify('machine_delete',CAST(
316+ OLD.system_id AS TEXT));
317+ WHEN {device} THEN
318+ PERFORM pg_notify('device_delete',CAST(
319+ OLD.system_id AS TEXT));
320+ WHEN {rack_controller} THEN
321+ PERFORM pg_notify('controller_delete',CAST(
322+ OLD.system_id AS TEXT));
323+ WHEN {region_controller} THEN
324+ PERFORM pg_notify('controller_delete',CAST(
325+ OLD.system_id AS TEXT));
326+ WHEN {region_and_rack_controller} THEN
327+ PERFORM pg_notify('controller_delete',CAST(
328+ OLD.system_id AS TEXT));
329+ END CASE;
330+ CASE NEW.node_type
331+ WHEN {machine} THEN
332+ PERFORM pg_notify('machine_create',CAST(
333+ NEW.system_id AS TEXT));
334+ WHEN {device} THEN
335+ PERFORM pg_notify('device_create',CAST(
336+ NEW.system_id AS TEXT));
337+ WHEN {rack_controller} THEN
338+ PERFORM pg_notify('controller_create',CAST(
339+ NEW.system_id AS TEXT));
340+ WHEN {region_controller} THEN
341+ PERFORM pg_notify('controller_create',CAST(
342+ NEW.system_id AS TEXT));
343+ WHEN {region_and_rack_controller} THEN
344+ PERFORM pg_notify('controller_create',CAST(
345+ NEW.system_id AS TEXT));
346+ END CASE;
347+ END IF;
348+ RETURN NEW;
349+ END;
350+ $$ LANGUAGE plpgsql;
351+ """.format(
352+ machine=NODE_TYPE.MACHINE, device=NODE_TYPE.DEVICE,
353+ rack_controller=NODE_TYPE.RACK_CONTROLLER,
354+ region_controller=NODE_TYPE.REGION_CONTROLLER,
355+ region_and_rack_controller=NODE_TYPE.REGION_AND_RACK_CONTROLLER))
356+
357+
358 @transactional
359 def register_websocket_triggers():
360 """Register all websocket triggers into the database."""
361@@ -1414,3 +1474,7 @@
362 "maasserver_dhcpsnippet", "dhcpsnippet_update_notify", "update")
363 register_trigger(
364 "maasserver_dhcpsnippet", "dhcpsnippet_delete_notify", "delete")
365+
366+ register_procedure(node_type_change())
367+ register_trigger(
368+ "maasserver_node", "node_type_change_notify", "update")