Merge lp:~newell-jensen/maas/ui-power-parameter-errors into lp:~maas-committers/maas/trunk

Proposed by Newell Jensen
Status: Merged
Approved by: Newell Jensen
Approved revision: no longer in the source branch.
Merged at revision: 4958
Proposed branch: lp:~newell-jensen/maas/ui-power-parameter-errors
Merge into: lp:~maas-committers/maas/trunk
Diff against target: 346 lines (+160/-30)
8 files modified
src/maasserver/static/js/angular/controllers/node_details.js (+40/-2)
src/maasserver/static/js/angular/controllers/tests/test_node_details.js (+73/-0)
src/maasserver/static/partials/node-details.html (+3/-0)
src/provisioningserver/events.py (+5/-0)
src/provisioningserver/power/query.py (+5/-1)
src/provisioningserver/power/tests/test_query.py (+21/-8)
src/provisioningserver/rpc/clusterservice.py (+5/-1)
src/provisioningserver/rpc/tests/test_clusterservice.py (+8/-18)
To merge this branch: bzr merge lp:~newell-jensen/maas/ui-power-parameter-errors
Reviewer Review Type Date Requested Status
Blake Rouse (community) Approve
Review via email: mp+292602@code.launchpad.net

Commit message

This branch adds a power query when the power parameters are saved in the UI and if there is an error, it will be displayed for the user. Power querying has also been added to the event log.

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

Backend code looks good. The frontend code could use an improvement on how you find the error.

review: Needs Fixing
Revision history for this message
Newell Jensen (newell-jensen) wrote :

> Backend code looks good. The frontend code could use an improvement on how you
> find the error.

I added review fixes and updated tests. Ready for another review.

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

Thanks for the fixes. Looks good.

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

The attempt to merge lp:~newell-jensen/maas/ui-power-parameter-errors into lp:maas failed. Below is the output from the failed tests.

Get:1 http://security.ubuntu.com/ubuntu xenial-security InRelease [92.2 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 [92.2 kB]
Hit:4 http://prodstack-zone-2.clouds.archive.ubuntu.com/ubuntu xenial-backports InRelease
Fetched 184 kB in 0s (403 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-mock python3-netaddr python3-netifaces 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).
bind9 is already the newest version (1:9.10.3.dfsg.P4-8).
bind9utils is already the newest version (1:9.10.3.dfsg.P4-8).
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).
dnsutils is already the newest version (1:9.10.3.dfsg.P4-8).
firefox is already the newest version (45.0.2+build1-0ubuntu1).
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).
libj...

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

The attempt to merge lp:~newell-jensen/maas/ui-power-parameter-errors into lp:maas failed. Below is the output from the failed tests.

Get:1 http://security.ubuntu.com/ubuntu xenial-security InRelease [92.2 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 [92.2 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 [5,400 B]
Get:6 http://prodstack-zone-2.clouds.archive.ubuntu.com/ubuntu xenial-updates/universe amd64 Packages [3,796 B]
Fetched 194 kB in 0s (411 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-mock python3-netaddr python3-netifaces 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).
bind9 is already the newest version (1:9.10.3.dfsg.P4-8).
bind9utils is already the newest version (1:9.10.3.dfsg.P4-8).
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).
dnsutils is already the newest version (1:9.10.3.dfsg.P4-8).
firefox is already the newest version (45.0.2+build1-0ubuntu1).
freeipmi-tools is already the newest version (1.4.11-1ubuntu1).
git is already the newest ve...

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
=== modified file 'src/maasserver/static/js/angular/controllers/node_details.js'
--- src/maasserver/static/js/angular/controllers/node_details.js 2016-04-11 16:23:26 +0000
+++ src/maasserver/static/js/angular/controllers/node_details.js 2016-04-25 23:23:52 +0000
@@ -405,10 +405,16 @@
405 }405 }
406406
407 // Update the node with new data on the region.407 // Update the node with new data on the region.
408 $scope.updateNode = function(node) {408 $scope.updateNode = function(node, queryPower) {
409 if(angular.isUndefined(queryPower)) {
410 queryPower = false;
411 }
409 return $scope.nodesManager.updateItem(node).then(function(node) {412 return $scope.nodesManager.updateItem(node).then(function(node) {
410 updateHeader();413 updateHeader();
411 updateSummary();414 updateSummary();
415 if(queryPower) {
416 $scope.checkPowerState();
417 }
412 }, function(error) {418 }, function(error) {
413 console.log(error);419 console.log(error);
414 updateHeader();420 updateHeader();
@@ -804,7 +810,7 @@
804 node.power_parameters = angular.copy($scope.power.parameters);810 node.power_parameters = angular.copy($scope.power.parameters);
805811
806 // Update the node.812 // Update the node.
807 $scope.updateNode(node);813 $scope.updateNode(node, true);
808 };814 };
809815
810 // Return true if the "load more" events button should be available.816 // Return true if the "load more" events button should be available.
@@ -836,6 +842,38 @@
836 return text;842 return text;
837 };843 };
838844
845 $scope.getPowerEventError = function() {
846 for(i=0;i<$scope.node.events.length;i++) {
847 var event = $scope.node.events[i];
848 if(event.type.level === "warning" &&
849 event.type.description === "Failed to query node's BMC") {
850 // Latest power event is an error
851 return event;
852 } else if(event.type.level === "info" &&
853 event.type.description === "Queried node's BMC") {
854 // Latest power event is not an error
855 return;
856 }
857 }
858 // No power event found, thus no error
859 return;
860 };
861
862 $scope.hasPowerEventError = function() {
863 var event = $scope.getPowerEventError();
864 return angular.isObject(event);
865 };
866
867 $scope.getPowerEventErrorText = function() {
868 var event = $scope.getPowerEventError();
869 if(angular.isObject(event)) {
870 // Return text
871 return event.description;
872 } else {
873 return "";
874 }
875 };
876
839 // Called when the machine output view has changed.877 // Called when the machine output view has changed.
840 $scope.machineOutputViewChanged = function() {878 $scope.machineOutputViewChanged = function() {
841 if(angular.isObject($scope.machine_output.selectedView) &&879 if(angular.isObject($scope.machine_output.selectedView) &&
842880
=== modified file 'src/maasserver/static/js/angular/controllers/tests/test_node_details.js'
--- src/maasserver/static/js/angular/controllers/tests/test_node_details.js 2016-04-11 16:23:26 +0000
+++ src/maasserver/static/js/angular/controllers/tests/test_node_details.js 2016-04-25 23:23:52 +0000
@@ -1881,6 +1881,79 @@
1881 });1881 });
1882 });1882 });
18831883
1884 describe("getPowerEventError", function() {
1885
1886 it("returns event if there is a power event error", function() {
1887 var controller = makeController();
1888 var evt = makeEvent();
1889 evt.type.level = "warning";
1890 evt.type.description = "Failed to query node's BMC";
1891 $scope.node = node;
1892 $scope.node.events = [
1893 makeEvent(),
1894 evt
1895 ];
1896 expect($scope.getPowerEventError()).toBe(evt);
1897 });
1898
1899 it("returns nothing if there is no power event error", function() {
1900 var controller = makeController();
1901 var evt_info = makeEvent();
1902 var evt_error = makeEvent();
1903 evt_info.type.level = "info";
1904 evt_info.type.description = "Queried node's BMC";
1905 evt_error.type.level = "warning";
1906 evt_error.type.description = "Failed to query node's BMC";
1907 $scope.node = node;
1908 $scope.node.events = [
1909 makeEvent(),
1910 evt_info,
1911 evt_error
1912 ];
1913 expect($scope.getPowerEventError()).toBe();
1914 });
1915 });
1916
1917 describe("hasPowerEventError", function() {
1918
1919 it("returns true if last event is an error", function() {
1920 var controller = makeController();
1921 var evt = makeEvent();
1922 evt.type.level = "warning";
1923 evt.type.description = "Failed to query node's BMC";
1924 $scope.node = node;
1925 $scope.node.events = [evt];
1926 expect($scope.hasPowerEventError()).toBe(true);
1927 });
1928
1929 it("returns false if last event is not an error", function() {
1930 var controller = makeController();
1931 $scope.node = node;
1932 $scope.node.events = [makeEvent()];
1933 expect($scope.hasPowerEventError()).toBe(false);
1934 });
1935 });
1936
1937 describe("getPowerEventErrorText", function() {
1938
1939 it("returns just empty string", function() {
1940 var controller = makeController();
1941 $scope.node = node;
1942 $scope.node.events = [makeEvent()];
1943 expect($scope.getPowerEventErrorText()).toBe("");
1944 });
1945
1946 it("returns event description", function() {
1947 var controller = makeController();
1948 var evt = makeEvent();
1949 evt.type.level = "warning";
1950 evt.type.description = "Failed to query node's BMC";
1951 $scope.node = node;
1952 $scope.node.events = [evt];
1953 expect($scope.getPowerEventErrorText()).toBe(evt.description);
1954 });
1955 });
1956
1884 describe("machineOutputViewChanged", function() {1957 describe("machineOutputViewChanged", function() {
18851958
1886 it("sets showSummaryToggle to false if no selectedView", function() {1959 it("sets showSummaryToggle to false if no selectedView", function() {
18871960
=== modified file 'src/maasserver/static/partials/node-details.html'
--- src/maasserver/static/partials/node-details.html 2016-04-12 17:36:35 +0000
+++ src/maasserver/static/partials/node-details.html 2016-04-25 23:23:52 +0000
@@ -353,6 +353,9 @@
353 rack controller. To proceed, install the {$ getPowerErrors() $}353 rack controller. To proceed, install the {$ getPowerErrors() $}
354 on the rack controller.354 on the rack controller.
355 </li>355 </li>
356 <li class="flash-messages__item error" data-ng-if="hasPowerEventError()">
357 {$ getPowerEventErrorText() $}
358 </li>
356 <li class="flash-messages__item info" data-ng-if="power.type.name == 'manual'">359 <li class="flash-messages__item info" data-ng-if="power.type.name == 'manual'">
357 Power control for this power type will need to be handled manually.360 Power control for this power type will need to be handled manually.
358 </li>361 </li>
359362
=== modified file 'src/provisioningserver/events.py'
--- src/provisioningserver/events.py 2016-03-28 13:54:47 +0000
+++ src/provisioningserver/events.py 2016-04-25 23:23:52 +0000
@@ -52,6 +52,7 @@
52 NODE_POWERED_OFF = 'NODE_POWERED_OFF'52 NODE_POWERED_OFF = 'NODE_POWERED_OFF'
53 NODE_POWER_ON_FAILED = 'NODE_POWER_ON_FAILED'53 NODE_POWER_ON_FAILED = 'NODE_POWER_ON_FAILED'
54 NODE_POWER_OFF_FAILED = 'NODE_POWER_OFF_FAILED'54 NODE_POWER_OFF_FAILED = 'NODE_POWER_OFF_FAILED'
55 NODE_POWER_QUERIED = 'NODE_POWER_QUERIED'
55 NODE_POWER_QUERY_FAILED = 'NODE_POWER_QUERY_FAILED'56 NODE_POWER_QUERY_FAILED = 'NODE_POWER_QUERY_FAILED'
56 # PXE request event.57 # PXE request event.
57 NODE_PXE_REQUEST = 'NODE_PXE_REQUEST'58 NODE_PXE_REQUEST = 'NODE_PXE_REQUEST'
@@ -113,6 +114,10 @@
113 description="Failed to power off node",114 description="Failed to power off node",
114 level=ERROR,115 level=ERROR,
115 ),116 ),
117 EVENT_TYPES.NODE_POWER_QUERIED: EventDetail(
118 description="Queried node's BMC",
119 level=INFO,
120 ),
116 EVENT_TYPES.NODE_POWER_QUERY_FAILED: EventDetail(121 EVENT_TYPES.NODE_POWER_QUERY_FAILED: EventDetail(
117 description="Failed to query node's BMC",122 description="Failed to query node's BMC",
118 level=WARN,123 level=WARN,
119124
=== modified file 'src/provisioningserver/power/query.py'
--- src/provisioningserver/power/query.py 2016-03-28 13:54:47 +0000
+++ src/provisioningserver/power/query.py 2016-04-25 23:23:52 +0000
@@ -106,7 +106,11 @@
106@inlineCallbacks106@inlineCallbacks
107def power_query_success(system_id, hostname, state):107def power_query_success(system_id, hostname, state):
108 """Report a node that for which power querying has succeeded."""108 """Report a node that for which power querying has succeeded."""
109 message = "Power state queried: %s" % state
109 yield power.power_state_update(system_id, state)110 yield power.power_state_update(system_id, state)
111 yield send_event_node(
112 EVENT_TYPES.NODE_POWER_QUERIED,
113 system_id, hostname, message)
110114
111115
112@inlineCallbacks116@inlineCallbacks
@@ -118,7 +122,7 @@
118 yield power.power_state_update(system_id, 'error')122 yield power.power_state_update(system_id, 'error')
119 yield send_event_node(123 yield send_event_node(
120 EVENT_TYPES.NODE_POWER_QUERY_FAILED,124 EVENT_TYPES.NODE_POWER_QUERY_FAILED,
121 system_id, hostname, message)125 system_id, hostname, failure.getErrorMessage())
122126
123127
124@asynchronous128@asynchronous
125129
=== modified file 'src/provisioningserver/power/tests/test_query.py'
--- src/provisioningserver/power/tests/test_query.py 2016-03-28 13:54:47 +0000
+++ src/provisioningserver/power/tests/test_query.py 2016-04-25 23:23:52 +0000
@@ -90,9 +90,24 @@
90 protocol.SendEvent,90 protocol.SendEvent,
91 MockCalledOnceWith(91 MockCalledOnceWith(
92 ANY, type_name=EVENT_TYPES.NODE_POWER_QUERY_FAILED,92 ANY, type_name=EVENT_TYPES.NODE_POWER_QUERY_FAILED,
93 system_id=system_id, description=(93 system_id=system_id, description=message))
94 "Power state could not be queried: " + message),94
95 ))95 def test_power_query_success_emits_event(self):
96 system_id = factory.make_name('system_id')
97 hostname = factory.make_name('hostname')
98 state = factory.make_name('state')
99 message = "Power state queried: %s" % state
100 protocol, io = self.patch_rpc_methods()
101 d = power.query.power_query_success(
102 system_id, hostname, state)
103 # This blocks until the deferred is complete.
104 io.flush()
105 self.assertIsNone(extract_result(d))
106 self.assertThat(
107 protocol.SendEvent,
108 MockCalledOnceWith(
109 ANY, type_name=EVENT_TYPES.NODE_POWER_QUERIED,
110 system_id=system_id, description=message))
96111
97112
98class TestPowerQuery(MAASTestCase):113class TestPowerQuery(MAASTestCase):
@@ -249,10 +264,8 @@
249 (power_type, {264 (power_type, {
250 "power_type": power_type,265 "power_type": power_type,
251 "power_driver": power_drivers_by_name.get(power_type),266 "power_driver": power_drivers_by_name.get(power_type),
252 "func": ( # Function to invoke driver.267 "func": ( # Function to invoke power driver.
253 "perform_power_driver_query"268 "perform_power_driver_query"),
254 if power_type in PowerDriverRegistry
255 else "perform_power_query"),
256 "waits": ( # Pauses between retries.269 "waits": ( # Pauses between retries.
257 [] if power_type in PowerDriverRegistry270 [] if power_type in PowerDriverRegistry
258 else DEFAULT_WAITING_POLICY),271 else DEFAULT_WAITING_POLICY),
@@ -322,7 +335,7 @@
322 self.assertThat(335 self.assertThat(
323 send_event_node, MockCalledOnceWith(336 send_event_node, MockCalledOnceWith(
324 EVENT_TYPES.NODE_POWER_QUERY_FAILED,337 EVENT_TYPES.NODE_POWER_QUERY_FAILED,
325 system_id, hostname, expected_message))338 system_id, hostname, exception_message))
326339
327 # Nothing was logged to the Twisted log.340 # Nothing was logged to the Twisted log.
328 self.assertEqual("", logger_twisted.output)341 self.assertEqual("", logger_twisted.output)
329342
=== modified file 'src/provisioningserver/rpc/clusterservice.py'
--- src/provisioningserver/rpc/clusterservice.py 2016-04-14 19:54:36 +0000
+++ src/provisioningserver/rpc/clusterservice.py 2016-04-25 23:23:52 +0000
@@ -35,7 +35,10 @@
35from provisioningserver.logger.log import get_maas_logger35from provisioningserver.logger.log import get_maas_logger
36from provisioningserver.networks import get_interfaces_definition36from provisioningserver.networks import get_interfaces_definition
37from provisioningserver.power.change import maybe_change_power_state37from provisioningserver.power.change import maybe_change_power_state
38from provisioningserver.power.query import get_power_state38from provisioningserver.power.query import (
39 get_power_state,
40 report_power_state,
41)
39from provisioningserver.refresh import (42from provisioningserver.refresh import (
40 get_architecture,43 get_architecture,
41 get_os_release,44 get_os_release,
@@ -267,6 +270,7 @@
267 def power_query(self, system_id, hostname, power_type, context):270 def power_query(self, system_id, hostname, power_type, context):
268 d = get_power_state(271 d = get_power_state(
269 system_id, hostname, power_type, context=context)272 system_id, hostname, power_type, context=context)
273 d = report_power_state(d, system_id, hostname)
270 d.addCallback(lambda x: {'state': x})274 d.addCallback(lambda x: {'state': x})
271 return d275 return d
272276
273277
=== modified file 'src/provisioningserver/rpc/tests/test_clusterservice.py'
--- src/provisioningserver/rpc/tests/test_clusterservice.py 2016-04-14 19:54:36 +0000
+++ src/provisioningserver/rpc/tests/test_clusterservice.py 2016-04-25 23:23:52 +0000
@@ -1629,19 +1629,12 @@
1629 @inlineCallbacks1629 @inlineCallbacks
1630 def test_returns_power_state(self):1630 def test_returns_power_state(self):
1631 state = random.choice(['on', 'off'])1631 state = random.choice(['on', 'off'])
1632 perform_power_query = self.patch(1632 perform_power_driver_query = self.patch(
1633 power_module.query, "perform_power_query")1633 power_module.query, "perform_power_driver_query")
1634 perform_power_query.return_value = state1634 perform_power_driver_query.return_value = state
1635
1636 # During the transition from template-based power drivers to Python
1637 # drivers, alias perform_power_driver_query to perform_power_query.
1638 self.patch(
1639 power_module.query, "perform_power_driver_query",
1640 perform_power_query)
1641
1642 # Intercept calls to report the status.
1643 report_power_state = self.patch(1635 report_power_state = self.patch(
1644 power_module.query, "report_power_state")1636 clusterservice, "report_power_state")
1637 report_power_state.return_value = succeed(state)
16451638
1646 power_type = random.choice(QUERY_POWER_TYPES)1639 power_type = random.choice(QUERY_POWER_TYPES)
1647 arguments = {1640 arguments = {
@@ -1653,20 +1646,17 @@
16531646
1654 # Make sure power driver doesn't check for installed packages.1647 # Make sure power driver doesn't check for installed packages.
1655 power_driver = power_drivers_by_name.get(power_type)1648 power_driver = power_drivers_by_name.get(power_type)
1656 if power_driver:1649 self.patch_autospec(
1657 self.patch_autospec(1650 power_driver, "detect_missing_packages").return_value = []
1658 power_driver, "detect_missing_packages").return_value = []
16591651
1660 observed = yield call_responder(1652 observed = yield call_responder(
1661 Cluster(), cluster.PowerQuery, arguments)1653 Cluster(), cluster.PowerQuery, arguments)
1662 self.assertEqual({'state': state}, observed)1654 self.assertEqual({'state': state}, observed)
1663 self.assertThat(1655 self.assertThat(
1664 perform_power_query,1656 perform_power_driver_query,
1665 MockCalledOnceWith(1657 MockCalledOnceWith(
1666 arguments['system_id'], arguments['hostname'],1658 arguments['system_id'], arguments['hostname'],
1667 arguments['power_type'], arguments['context']))1659 arguments['power_type'], arguments['context']))
1668 # The region is NOT told about the change.
1669 self.assertThat(report_power_state, MockNotCalled())
16701660
16711661
1672class TestClusterProtocol_ConfigureDHCP(MAASTestCase):1662class TestClusterProtocol_ConfigureDHCP(MAASTestCase):