Merge ~blake-rouse/maas:pools-modified-tracking into maas:master

Proposed by Blake Rouse
Status: Merged
Approved by: Blake Rouse
Approved revision: b4b742a4529f03fbd2fd87a452cdd1d583222a6b
Merge reported by: MAAS Lander
Merged at revision: not available
Proposed branch: ~blake-rouse/maas:pools-modified-tracking
Merge into: maas:master
Diff against target: 661 lines (+558/-0)
9 files modified
src/maasserver/migrations/builtin/maasserver/0179_rbacsync.py (+30/-0)
src/maasserver/models/__init__.py (+2/-0)
src/maasserver/models/rbacsync.py (+95/-0)
src/maasserver/models/tests/test_rbacsync.py (+42/-0)
src/maasserver/triggers/system.py (+152/-0)
src/maasserver/triggers/testing.py (+45/-0)
src/maasserver/triggers/tests/test_init.py (+6/-0)
src/maasserver/triggers/tests/test_system.py (+5/-0)
src/maasserver/triggers/tests/test_system_listener.py (+181/-0)
Reviewer Review Type Date Requested Status
MAAS Lander Needs Fixing
Alberto Donato (community) Approve
Review via email: mp+356106@code.launchpad.net

Commit message

Add tracking of when resource pools are created/updated/deleted. Send a notification when this occurs.

Description of the change

In a following branch the regiond will listen for the sys_rpool notification to sync the resource pools to the RBAC micro-service.

To post a comment you must log in.
Revision history for this message
Alberto Donato (ack) wrote :

The mechanism implemented here seems quite generic.
Maybe we could add a "resource_type" field so that a single table would track all type of resources that need sync.

Manager methods would then filter on that type (and the pg notification would also carry the type).

This way we don't need a different table and notification handler for each resource we cover with rbac.

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

I was actually thinking the same thing that I didn't make it generic enough. Because I also need to get notifications when the settings change.

I have re-done the branch to make it generic just for syncing with RBAC in general. We can easily add on to it as we add more resources to RBAC.

Revision history for this message
Alberto Donato (ack) wrote :

+1, just minor comments inline

review: Approve
Revision history for this message
MAAS Lander (maas-lander) wrote :

UNIT TESTS
-b pools-modified-tracking lp:~blake-rouse/maas/+git/maas into -b master lp:~maas-committers/maas

STATUS: FAILED
LOG: http://maas-ci-jenkins.internal:8080/job/maas/job/branch-tester/4065/console
COMMIT: 1b10f2bc36346903128f4891f04f16aefe040a89

review: Needs Fixing
Revision history for this message
MAAS Lander (maas-lander) wrote :
Revision history for this message
MAAS Lander (maas-lander) wrote :

UNIT TESTS
-b pools-modified-tracking lp:~blake-rouse/maas/+git/maas into -b master lp:~maas-committers/maas

STATUS: FAILED
LOG: http://maas-ci-jenkins.internal:8080/job/maas/job/branch-tester/4072/console
COMMIT: aad64793fd2ef8f99aa345f42c1ff53a463b10ee

review: Needs Fixing
b4b742a... by Blake Rouse

Fix tests.

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
1diff --git a/src/maasserver/migrations/builtin/maasserver/0179_rbacsync.py b/src/maasserver/migrations/builtin/maasserver/0179_rbacsync.py
2new file mode 100644
3index 0000000..3422aed
4--- /dev/null
5+++ b/src/maasserver/migrations/builtin/maasserver/0179_rbacsync.py
6@@ -0,0 +1,30 @@
7+# -*- coding: utf-8 -*-
8+# Generated by Django 1.11.11 on 2018-10-04 14:25
9+from __future__ import unicode_literals
10+
11+from django.db import (
12+ migrations,
13+ models,
14+)
15+
16+
17+class Migration(migrations.Migration):
18+
19+ dependencies = [
20+ ('maasserver', '0178_break_apart_linked_bmcs'),
21+ ]
22+
23+ operations = [
24+ migrations.CreateModel(
25+ name='RBACSync',
26+ fields=[
27+ ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
28+ ('action', models.CharField(blank=True, choices=[('full', 'full'), ('add', 'add'), ('update', 'update'), ('remove', 'remove')], default='full', editable=False, help_text='Action that should occur on the RBAC service.', max_length=6)),
29+ ('resource_type', models.CharField(blank=True, editable=False, help_text='Resource type that as been added/updated/removed.', max_length=255)),
30+ ('resource_id', models.IntegerField(blank=True, editable=False, help_text='Resource ID that has been added/updated/removed.', null=True)),
31+ ('resource_name', models.CharField(blank=True, editable=False, help_text='Resource name that has been added/updated/removed.', max_length=255)),
32+ ('created', models.DateTimeField(auto_now_add=True)),
33+ ('source', models.CharField(blank=True, editable=False, help_text='A brief explanation what changed.', max_length=255)),
34+ ],
35+ ),
36+ ]
37diff --git a/src/maasserver/models/__init__.py b/src/maasserver/models/__init__.py
38index 9177321..fc75e92 100644
39--- a/src/maasserver/models/__init__.py
40+++ b/src/maasserver/models/__init__.py
41@@ -60,6 +60,7 @@ __all__ = [
42 'PodStoragePool',
43 'RackController',
44 'RAID',
45+ 'RBACSync',
46 'RDNS',
47 'RegionController',
48 'RegionControllerProcess',
49@@ -167,6 +168,7 @@ from maasserver.models.partitiontable import PartitionTable
50 from maasserver.models.physicalblockdevice import PhysicalBlockDevice
51 from maasserver.models.podhints import PodHints
52 from maasserver.models.podstoragepool import PodStoragePool
53+from maasserver.models.rbacsync import RBACSync
54 from maasserver.models.rdns import RDNS
55 from maasserver.models.regioncontrollerprocess import RegionControllerProcess
56 from maasserver.models.regioncontrollerprocessendpoint import (
57diff --git a/src/maasserver/models/rbacsync.py b/src/maasserver/models/rbacsync.py
58new file mode 100644
59index 0000000..40905db
60--- /dev/null
61+++ b/src/maasserver/models/rbacsync.py
62@@ -0,0 +1,95 @@
63+# Copyright 2018 Canonical Ltd. This software is licensed under the
64+# GNU Affero General Public License version 3 (see the file LICENSE).
65+
66+"""RBACSync objects."""
67+
68+__all__ = [
69+ "RBACSync",
70+]
71+
72+from django.db.models import (
73+ Manager,
74+ Model,
75+)
76+from django.db.models.fields import (
77+ CharField,
78+ DateTimeField,
79+ IntegerField,
80+)
81+from maasserver import DefaultMeta
82+
83+
84+class RBAC_ACTION:
85+ #: Perform a full sync.
86+ FULL = 'full'
87+ #: Add a new resource.
88+ ADD = 'add'
89+ #: Update a resource.
90+ UPDATE = 'update'
91+ #: Remove a resource.
92+ REMOVE = 'remove'
93+
94+
95+RBAC_ACTION_CHOICES = [
96+ (RBAC_ACTION.FULL, 'full'),
97+ (RBAC_ACTION.ADD, 'add'),
98+ (RBAC_ACTION.UPDATE, 'update'),
99+ (RBAC_ACTION.REMOVE, 'remove'),
100+]
101+
102+
103+class RBACSyncManager(Manager):
104+ """Manager for `RBACSync` records."""
105+
106+ def changes(self):
107+ """Returns the changes that have occurred."""
108+ return list(self.order_by('id'))
109+
110+ def clear(self):
111+ """Deletes all `RBACSync`."""
112+ self.all().delete()
113+
114+
115+class RBACSync(Model):
116+ """A row in this table denotes a change that requires information RBAC
117+ micro-service to be updated.
118+
119+ Typically this will be populated by a trigger within the database. A
120+ listeners in regiond will be notified and consult the un-synced records
121+ in this table. This way we can consistently publish RBAC information to the
122+ RBAC service in an HA environment.
123+ """
124+
125+ class Meta(DefaultMeta):
126+ """Default meta."""
127+
128+ objects = RBACSyncManager()
129+
130+ action = CharField(
131+ editable=False, max_length=6, null=False, blank=True,
132+ choices=RBAC_ACTION_CHOICES, default=RBAC_ACTION.FULL,
133+ help_text="Action that should occur on the RBAC service.")
134+
135+ # An '' string is used when action is 'full'.
136+ resource_type = CharField(
137+ editable=False, max_length=255, null=False, blank=True,
138+ help_text="Resource type that as been added/updated/removed.")
139+
140+ # A `None` is used when action is 'full'.
141+ resource_id = IntegerField(
142+ editable=False, null=True, blank=True,
143+ help_text="Resource ID that has been added/updated/removed.")
144+
145+ # A '' string is used when action is 'full'.
146+ resource_name = CharField(
147+ editable=False, max_length=255, null=False, blank=True,
148+ help_text="Resource name that has been added/updated/removed.")
149+
150+ # This field is informational.
151+ created = DateTimeField(
152+ editable=False, null=False, auto_now=False, auto_now_add=True)
153+
154+ # This field is informational.
155+ source = CharField(
156+ editable=False, max_length=255, null=False, blank=True,
157+ help_text="A brief explanation what changed.")
158diff --git a/src/maasserver/models/tests/test_rbacsync.py b/src/maasserver/models/tests/test_rbacsync.py
159new file mode 100644
160index 0000000..8d1f670
161--- /dev/null
162+++ b/src/maasserver/models/tests/test_rbacsync.py
163@@ -0,0 +1,42 @@
164+# Copyright 2018 Canonical Ltd. This software is licensed under the
165+# GNU Affero General Public License version 3 (see the file LICENSE).
166+
167+"""Tests for RBACSync models."""
168+
169+__all__ = []
170+
171+from maasserver.models.rbacsync import RBACSync
172+from maasserver.testing.testcase import MAASServerTestCase
173+from testtools.matchers import (
174+ Equals,
175+ HasLength,
176+)
177+
178+
179+class TestRBACSync(MAASServerTestCase):
180+ """Test `RBACSync`."""
181+
182+ def setUp(self):
183+ super(TestRBACSync, self).setUp()
184+ # These tests expect the RBACSync table to be empty.
185+ RBACSync.objects.all().delete()
186+
187+ def test_changes(self):
188+ synced = [
189+ RBACSync.objects.create()
190+ for _ in range(3)
191+ ]
192+ self.assertThat(
193+ RBACSync.objects.changes(), Equals(synced))
194+
195+ def test_clear_does_nothing_when_nothing(self):
196+ self.assertThat(RBACSync.objects.all(), HasLength(0))
197+ RBACSync.objects.clear()
198+ self.assertThat(RBACSync.objects.all(), HasLength(0))
199+
200+ def test_clear_removes_all(self):
201+ for _ in range(3):
202+ RBACSync.objects.create()
203+ self.assertThat(RBACSync.objects.all(), HasLength(3))
204+ RBACSync.objects.clear()
205+ self.assertThat(RBACSync.objects.all(), HasLength(0))
206diff --git a/src/maasserver/triggers/system.py b/src/maasserver/triggers/system.py
207index ef0f07e..f73d11e 100644
208--- a/src/maasserver/triggers/system.py
209+++ b/src/maasserver/triggers/system.py
210@@ -1654,6 +1654,130 @@ PEER_PROXY_CONFIG_UPDATE = dedent("""\
211 """)
212
213
214+# Triggered when RBAC need to be synced. In essense this means on
215+# insert into maasserver_rbacsync.
216+RBAC_SYNC = dedent("""\
217+ CREATE OR REPLACE FUNCTION sys_rbac_sync()
218+ RETURNS trigger AS $$
219+ BEGIN
220+ PERFORM pg_notify('sys_rbac', '');
221+ RETURN NEW;
222+ END;
223+ $$ LANGUAGE plpgsql;
224+ """)
225+
226+
227+# Procedure to mark RBAC as needing a sync.
228+RBAC_SYNC_UPDATE = dedent("""\
229+ CREATE OR REPLACE FUNCTION sys_rbac_sync_update(
230+ reason text,
231+ action text DEFAULT 'full',
232+ resource_type text DEFAULT '',
233+ resource_id int DEFAULT NULL,
234+ resource_name text DEFAULT '')
235+ RETURNS void as $$
236+ BEGIN
237+ INSERT INTO maasserver_rbacsync
238+ (created, source, action, resource_type, resource_id, resource_name)
239+ VALUES (
240+ now(), substring(reason FOR 255),
241+ action, resource_type, resource_id, resource_name);
242+ END;
243+ $$ LANGUAGE plpgsql;
244+ """)
245+
246+
247+# Triggered when a new resource pool is added. Notifies that RBAC needs
248+# to be synced.
249+RBAC_RPOOL_INSERT = dedent("""\
250+ CREATE OR REPLACE FUNCTION sys_rbac_rpool_insert()
251+ RETURNS trigger as $$
252+ BEGIN
253+ PERFORM sys_rbac_sync_update(
254+ 'added resource pool ' || NEW.name,
255+ 'add', 'resource-pool', NEW.id, NEW.name);
256+ RETURN NEW;
257+ END;
258+ $$ LANGUAGE plpgsql;
259+ """)
260+
261+
262+# Triggered when a resource pool is updated. Notifies that RBAC needs
263+# to be synced. Only watches name.
264+RBAC_RPOOL_UPDATE = dedent("""\
265+ CREATE OR REPLACE FUNCTION sys_rbac_rpool_update()
266+ RETURNS trigger as $$
267+ DECLARE
268+ changes text[];
269+ BEGIN
270+ IF OLD.name != NEW.name THEN
271+ PERFORM sys_rbac_sync_update(
272+ 'renamed resource pool ' || OLD.name || ' to ' || NEW.name,
273+ 'update', 'resource-pool', OLD.id, NEW.name);
274+ END IF;
275+ RETURN NEW;
276+ END;
277+ $$ LANGUAGE plpgsql;
278+ """)
279+
280+
281+# Triggered when a resource pool is deleted. Notifies that RBAC needs
282+# to be synced.
283+RBAC_RPOOL_DELETE = dedent("""\
284+ CREATE OR REPLACE FUNCTION sys_rbac_rpool_delete()
285+ RETURNS trigger as $$
286+ BEGIN
287+ PERFORM sys_rbac_sync_update(
288+ 'removed resource pool ' || OLD.name,
289+ 'remove', 'resource-pool', OLD.id, OLD.name);
290+ RETURN OLD;
291+ END;
292+ $$ LANGUAGE plpgsql;
293+ """)
294+
295+
296+# Triggered when the Candid/RBAC settings are inserted. Notifies that RBAC
297+# needs to be synced.
298+RBAC_CONFIG_INSERT = dedent("""\
299+ CREATE OR REPLACE FUNCTION sys_rbac_config_insert()
300+ RETURNS trigger as $$
301+ BEGIN
302+ IF (NEW.name = 'external_auth_url' OR
303+ NEW.name = 'external_auth_user' OR
304+ NEW.name = 'external_auth_key' OR
305+ NEW.name = 'rbac_url') THEN
306+ PERFORM sys_rbac_sync_update(
307+ 'configuration ' || NEW.name || ' set to ' ||
308+ COALESCE(NEW.value, 'NULL'));
309+ END IF;
310+ RETURN NEW;
311+ END;
312+ $$ LANGUAGE plpgsql;
313+ """)
314+
315+
316+# Triggered when the Candid/RBAC settings are updated. Notifies that RBAC
317+# needs to be synced. The external_auth_* keys are included that way if
318+# the agent account that MAAS uses to update RBAC is updated in MAAS, MAAS will
319+# ensure that a full sync of data is performed.
320+RBAC_CONFIG_UPDATE = dedent("""\
321+ CREATE OR REPLACE FUNCTION sys_rbac_config_update()
322+ RETURNS trigger as $$
323+ BEGIN
324+ IF (OLD.value != NEW.value AND (
325+ NEW.name = 'external_auth_url' OR
326+ NEW.name = 'external_auth_user' OR
327+ NEW.name = 'external_auth_key' OR
328+ NEW.name = 'rbac_url')) THEN
329+ PERFORM sys_rbac_sync_update(
330+ 'configuration ' || NEW.name || ' changed to ' || NEW.value);
331+ END IF;
332+ RETURN NEW;
333+ END;
334+ $$ LANGUAGE plpgsql;
335+ """)
336+
337+
338 def render_sys_proxy_procedure(proc_name, on_delete=False):
339 """Render a database procedure with name `proc_name` that notifies that a
340 proxy update is needed.
341@@ -1914,3 +2038,31 @@ def register_system_triggers():
342 register_trigger(
343 "maasserver_config", "sys_proxy_config_use_peer_proxy_update",
344 "update")
345+
346+ # - RBACSync
347+ register_procedure(RBAC_SYNC)
348+ register_trigger(
349+ "maasserver_rbacsync",
350+ "sys_rbac_sync", "insert")
351+ register_procedure(RBAC_SYNC_UPDATE)
352+
353+ # - ResourcePool
354+ register_procedure(RBAC_RPOOL_INSERT)
355+ register_trigger(
356+ "maasserver_resourcepool", "sys_rbac_rpool_insert", "insert")
357+ register_procedure(RBAC_RPOOL_UPDATE)
358+ register_trigger(
359+ "maasserver_resourcepool", "sys_rbac_rpool_update", "update")
360+ register_procedure(RBAC_RPOOL_DELETE)
361+ register_trigger(
362+ "maasserver_resourcepool", "sys_rbac_rpool_delete", "delete")
363+
364+ # - Config (Candid/RBAC)
365+ register_procedure(RBAC_CONFIG_INSERT)
366+ register_trigger(
367+ "maasserver_config", "sys_rbac_config_insert",
368+ "insert")
369+ register_procedure(RBAC_CONFIG_UPDATE)
370+ register_trigger(
371+ "maasserver_config", "sys_rbac_config_update",
372+ "update")
373diff --git a/src/maasserver/triggers/testing.py b/src/maasserver/triggers/testing.py
374index 73a6b48..0fec0ac 100644
375--- a/src/maasserver/triggers/testing.py
376+++ b/src/maasserver/triggers/testing.py
377@@ -41,6 +41,7 @@ from maasserver.models.packagerepository import PackageRepository
378 from maasserver.models.partition import Partition
379 from maasserver.models.partitiontable import PartitionTable
380 from maasserver.models.physicalblockdevice import PhysicalBlockDevice
381+from maasserver.models.rbacsync import RBACSync
382 from maasserver.models.regioncontrollerprocess import RegionControllerProcess
383 from maasserver.models.regioncontrollerprocessendpoint import (
384 RegionControllerProcessEndpoint,
385@@ -847,3 +848,47 @@ class DNSHelpersMixin:
386 self.assertThat(
387 new.serial, GreaterThan(old.serial),
388 "DNS has not been published again.")
389+
390+
391+class RBACHelpersMixin:
392+ """Helper to get the latest rbac sync."""
393+
394+ @transactional
395+ def getSynced(self):
396+ try:
397+ return RBACSync.objects.order_by('-id').first()
398+ except RBACSync.DoesNotExist:
399+ return None
400+
401+ @inlineCallbacks
402+ def captureSynced(self):
403+ """Capture the most recent `RBACSync` record."""
404+ self.__synced = yield deferToDatabase(self.getSynced)
405+ returnValue(self.__synced)
406+
407+ def getCapturedSynced(self):
408+ """Return the captured sync record."""
409+ try:
410+ return self.__synced
411+ except AttributeError:
412+ self.fail(
413+ "No reference modification has been captured; "
414+ "use `captureSynced` before calling "
415+ "`getCapturedSynced`.")
416+
417+ @inlineCallbacks
418+ def assertSynced(self):
419+ """Assert there's a newer `RBACSync` record.
420+
421+ Call `captureSynced` first to obtain a reference record.
422+ """
423+ old = self.getCapturedSynced()
424+ new = yield self.captureSynced()
425+ if old is None:
426+ self.assertThat(
427+ new, Not(Is(None)),
428+ "RBAC sync tracking has not been modified at all.")
429+ else:
430+ self.assertThat(
431+ new.id, GreaterThan(old.id),
432+ "RBAC sync tracking has not been modified again.")
433diff --git a/src/maasserver/triggers/tests/test_init.py b/src/maasserver/triggers/tests/test_init.py
434index e5bce9e..d03a833 100644
435--- a/src/maasserver/triggers/tests/test_init.py
436+++ b/src/maasserver/triggers/tests/test_init.py
437@@ -105,6 +105,12 @@ class TestTriggersUsed(MAASServerTestCase):
438 "subnet_sys_proxy_subnet_insert",
439 "subnet_sys_proxy_subnet_update",
440 "vlan_sys_dhcp_vlan_update",
441+ "rbacsync_sys_rbac_sync",
442+ "resourcepool_sys_rbac_rpool_insert",
443+ "resourcepool_sys_rbac_rpool_update",
444+ "resourcepool_sys_rbac_rpool_delete",
445+ "config_sys_rbac_config_insert",
446+ "config_sys_rbac_config_update",
447 }
448
449 triggers_websocket = {
450diff --git a/src/maasserver/triggers/tests/test_system.py b/src/maasserver/triggers/tests/test_system.py
451index 1fe4615..eeccb47 100644
452--- a/src/maasserver/triggers/tests/test_system.py
453+++ b/src/maasserver/triggers/tests/test_system.py
454@@ -61,6 +61,11 @@ class TestTriggers(MAASServerTestCase):
455 "subnet_sys_proxy_subnet_insert",
456 "subnet_sys_proxy_subnet_update",
457 "subnet_sys_proxy_subnet_delete",
458+ "resourcepool_sys_rbac_rpool_insert",
459+ "resourcepool_sys_rbac_rpool_update",
460+ "resourcepool_sys_rbac_rpool_delete",
461+ "config_sys_rbac_config_insert",
462+ "config_sys_rbac_config_update",
463 ]
464 sql, args = psql_array(triggers, sql_type="text")
465 with closing(connection.cursor()) as cursor:
466diff --git a/src/maasserver/triggers/tests/test_system_listener.py b/src/maasserver/triggers/tests/test_system_listener.py
467index d6e2adc..c543ca3 100644
468--- a/src/maasserver/triggers/tests/test_system_listener.py
469+++ b/src/maasserver/triggers/tests/test_system_listener.py
470@@ -36,6 +36,7 @@ from maasserver.testing.testcase import (
471 from maasserver.triggers.system import register_system_triggers
472 from maasserver.triggers.testing import (
473 DNSHelpersMixin,
474+ RBACHelpersMixin,
475 TransactionalHelpersMixin,
476 )
477 from maasserver.utils.orm import transactional
478@@ -4688,3 +4689,183 @@ class TestProxyListener(
479 yield dv.get(timeout=2)
480 finally:
481 yield listener.stopService()
482+
483+
484+class TestRBACResourcePoolListener(
485+ MAASTransactionServerTestCase, TransactionalHelpersMixin,
486+ RBACHelpersMixin):
487+ """End-to-end test for the resource pool RBAC triggers code."""
488+
489+ @wait_for_reactor
490+ @inlineCallbacks
491+ def test_sends_message_for_resource_pool_insert(self):
492+ yield deferToDatabase(register_system_triggers)
493+ yield self.captureSynced()
494+ name = factory.make_name('pool')
495+ dv = DeferredValue()
496+ listener = self.make_listener_without_delay()
497+ listener.register(
498+ "sys_rbac", lambda *args: dv.set(args))
499+ yield listener.startService()
500+ try:
501+ pool = yield deferToDatabase(
502+ self.create_resource_pool, {'name': name})
503+ yield dv.get(timeout=2)
504+ yield self.assertSynced()
505+ finally:
506+ yield listener.stopService()
507+ change = self.getCapturedSynced()
508+ self.assertThat(
509+ change.source,
510+ Equals("added resource pool %s" % name))
511+ self.assertThat(
512+ change.action, Equals("add"))
513+ self.assertThat(
514+ change.resource_type, Equals("resource-pool"))
515+ self.assertThat(
516+ change.resource_id, Equals(pool.id))
517+ self.assertThat(
518+ change.resource_name, Equals(name))
519+
520+ @wait_for_reactor
521+ @inlineCallbacks
522+ def test_sends_message_for_resource_pool_update(self):
523+ yield deferToDatabase(register_system_triggers)
524+ pool = yield deferToDatabase(
525+ self.create_resource_pool, {})
526+ pool_name = factory.make_name('pool')
527+ yield self.captureSynced()
528+ dv = DeferredValue()
529+ listener = self.make_listener_without_delay()
530+ listener.register(
531+ "sys_rbac", lambda *args: dv.set(args))
532+ yield listener.startService()
533+ try:
534+ yield deferToDatabase(self.update_resource_pool, pool.id, {
535+ "name": pool_name,
536+ })
537+ yield dv.get(timeout=2)
538+ yield self.assertSynced()
539+ finally:
540+ yield listener.stopService()
541+ change = self.getCapturedSynced()
542+ self.assertThat(
543+ change.source,
544+ Equals("renamed resource pool %s to %s" % (pool.name, pool_name)))
545+ self.assertThat(
546+ change.action, Equals("update"))
547+ self.assertThat(
548+ change.resource_type, Equals("resource-pool"))
549+ self.assertThat(
550+ change.resource_id, Equals(pool.id))
551+ self.assertThat(
552+ change.resource_name, Equals(pool_name))
553+
554+ @wait_for_reactor
555+ @inlineCallbacks
556+ def test_sends_message_for_resource_pool_delete(self):
557+ yield deferToDatabase(register_system_triggers)
558+ pool = yield deferToDatabase(self.create_resource_pool)
559+ yield self.captureSynced()
560+ dv = DeferredValue()
561+ listener = self.make_listener_without_delay()
562+ listener.register(
563+ "sys_rbac", lambda *args: dv.set(args))
564+ yield listener.startService()
565+ try:
566+ yield deferToDatabase(self.delete_resource_pool, pool.id)
567+ yield dv.get(timeout=2)
568+ yield self.assertSynced()
569+ finally:
570+ yield listener.stopService()
571+ change = self.getCapturedSynced()
572+ self.assertThat(
573+ change.source,
574+ Equals("removed resource pool %s" % pool.name))
575+ self.assertThat(
576+ change.action, Equals("remove"))
577+ self.assertThat(
578+ change.resource_type, Equals("resource-pool"))
579+ self.assertThat(
580+ change.resource_id, Equals(pool.id))
581+ self.assertThat(
582+ change.resource_name, Equals(pool.name))
583+
584+
585+class TestRBACConfigListener(
586+ MAASTransactionServerTestCase, TransactionalHelpersMixin,
587+ RBACHelpersMixin):
588+ """End-to-end test for the config RBAC triggers code."""
589+
590+ scenarios = (
591+ ('external_auth_url', {
592+ 'config': 'external_auth_url',
593+ }),
594+ ('external_auth_user', {
595+ 'config': 'external_auth_user',
596+ }),
597+ ('external_auth_key', {
598+ 'config': 'external_auth_key',
599+ }),
600+ ('rbac_url', {
601+ 'config': 'rbac_url',
602+ }),
603+ )
604+
605+ @wait_for_reactor
606+ @inlineCallbacks
607+ def test_sends_message_for_config_insert(self):
608+ new_value = factory.make_name("value")
609+ yield deferToDatabase(register_system_triggers)
610+ yield self.captureSynced()
611+ dv = DeferredValue()
612+ listener = self.make_listener_without_delay()
613+ listener.register(
614+ "sys_rbac", lambda *args: dv.set(args))
615+ yield listener.startService()
616+ try:
617+ yield deferToDatabase(
618+ Config.objects.set_config,
619+ self.config, new_value)
620+ yield dv.get(timeout=2)
621+ yield self.assertSynced()
622+ finally:
623+ yield listener.stopService()
624+ change = self.getCapturedSynced()
625+ self.assertThat(
626+ change.source, Equals(
627+ "configuration %s set to %s"
628+ % (self.config, json.dumps(new_value))))
629+ self.assertThat(
630+ change.action, Equals("full"))
631+
632+ @wait_for_reactor
633+ @inlineCallbacks
634+ def test_sends_message_for_config_update(self):
635+ old_value = factory.make_name("old")
636+ new_value = factory.make_name("new")
637+ yield deferToDatabase(register_system_triggers)
638+ yield deferToDatabase(
639+ Config.objects.set_config,
640+ self.config, old_value)
641+ yield self.captureSynced()
642+ dv = DeferredValue()
643+ listener = self.make_listener_without_delay()
644+ listener.register(
645+ "sys_rbac", lambda *args: dv.set(args))
646+ yield listener.startService()
647+ try:
648+ yield deferToDatabase(
649+ Config.objects.set_config,
650+ self.config, new_value)
651+ yield dv.get(timeout=2)
652+ yield self.assertSynced()
653+ finally:
654+ yield listener.stopService()
655+ change = self.getCapturedSynced()
656+ self.assertThat(
657+ change.source, Equals(
658+ "configuration %s changed to %s"
659+ % (self.config, json.dumps(new_value))))
660+ self.assertThat(
661+ change.action, Equals("full"))

Subscribers

People subscribed via source and target branches