Merge lp:~trapnine/maas/bug-1381000 into lp:~maas-committers/maas/trunk

Proposed by Jeffrey C Jones
Status: Merged
Approved by: Andres Rodriguez
Approved revision: no longer in the source branch.
Merged at revision: 4344
Proposed branch: lp:~trapnine/maas/bug-1381000
Merge into: lp:~maas-committers/maas/trunk
Diff against target: 1296 lines (+668/-32)
43 files modified
src/maasserver/clusterrpc/power_parameters.py (+7/-3)
src/maasserver/clusterrpc/tests/test_power_parameters.py (+7/-2)
src/maasserver/rpc/tests/test_nodes.py (+2/-2)
src/maasserver/static/js/angular/controllers/node_details.js (+18/-0)
src/maasserver/static/partials/node-details.html (+9/-0)
src/maasserver/tests/test_middleware.py (+1/-1)
src/maastesting/testcase.py (+1/-1)
src/provisioningserver/drivers/__init__.py (+9/-9)
src/provisioningserver/drivers/hardware/apc.py (+4/-0)
src/provisioningserver/drivers/hardware/hmc.py (+4/-0)
src/provisioningserver/drivers/power/__init__.py (+23/-2)
src/provisioningserver/drivers/power/amt.py (+40/-0)
src/provisioningserver/drivers/power/apc.py (+8/-0)
src/provisioningserver/drivers/power/dli.py (+32/-0)
src/provisioningserver/drivers/power/ether_wake.py (+34/-0)
src/provisioningserver/drivers/power/fence_cdu.py (+32/-0)
src/provisioningserver/drivers/power/hmc.py (+8/-0)
src/provisioningserver/drivers/power/ipmi.py (+6/-0)
src/provisioningserver/drivers/power/moonshot.py (+6/-0)
src/provisioningserver/drivers/power/mscm.py (+4/-0)
src/provisioningserver/drivers/power/msftocs.py (+4/-0)
src/provisioningserver/drivers/power/seamicro.py (+6/-0)
src/provisioningserver/drivers/power/tests/test_amt.py (+51/-0)
src/provisioningserver/drivers/power/tests/test_apc.py (+15/-0)
src/provisioningserver/drivers/power/tests/test_base.py (+3/-0)
src/provisioningserver/drivers/power/tests/test_dli.py (+51/-0)
src/provisioningserver/drivers/power/tests/test_ether_wake.py (+51/-0)
src/provisioningserver/drivers/power/tests/test_fence_cdu.py (+51/-0)
src/provisioningserver/drivers/power/tests/test_hmc.py (+15/-0)
src/provisioningserver/drivers/power/tests/test_ipmi.py (+18/-1)
src/provisioningserver/drivers/power/tests/test_moonshot.py (+18/-1)
src/provisioningserver/drivers/power/tests/test_mscm.py (+6/-0)
src/provisioningserver/drivers/power/tests/test_msftocs.py (+6/-0)
src/provisioningserver/drivers/power/tests/test_seamicro.py (+18/-1)
src/provisioningserver/drivers/power/tests/test_ucsm.py (+6/-0)
src/provisioningserver/drivers/power/tests/test_virsh.py (+15/-0)
src/provisioningserver/drivers/power/tests/test_vmware.py (+15/-0)
src/provisioningserver/drivers/power/ucsm.py (+4/-0)
src/provisioningserver/drivers/power/virsh.py (+12/-0)
src/provisioningserver/drivers/power/vmware.py (+6/-0)
src/provisioningserver/drivers/tests/test_base.py (+34/-7)
src/provisioningserver/power/schema.py (+6/-0)
src/provisioningserver/rpc/clusterservice.py (+2/-2)
To merge this branch: bzr merge lp:~trapnine/maas/bug-1381000
Reviewer Review Type Date Requested Status
Andres Rodriguez (community) Approve
Blake Rouse (community) Approve
Review via email: mp+272875@code.launchpad.net

Commit message

Checks selected node power types for missing system packages and warns user about them.
Checks are fast, and run on cluster every time power type info is loaded from cluster server (generally via 'command -v').
Messages are displayed dynamically when power type is changed/selected in the power dropdown.

Description of the change

See commit message.

Next step is to prevent deployment of nodes with uninstalled power drivers, but this commit satisfies the bug as reported.

I used the power driver architecture to implement the checks. The template based power types now have drivers, but they're not in the PowerDriverRegistry and are only used for the package check. The templates can be merged into these drivers later and then they can be in the Registry.

I also blew away the PowerTypeRegistry, as it was rendered superfluous.

REVIEWERS:
Please confirm these are the correct apps/packages to check for.
I built this by inspecting the power driver code and templates.

DRIVER: BINARY CHECKED / PACKAGE DISPLAYED
amt: amttool/amtterm and wsman/wsmancli (we will demand both, not just amttool! do most people have V8+ amt's?)
apc: snmpset/snmp
dli: wget/wget (hey, the template invokes it!)
etherwake: wakeonlan/wakeonlan or etherwake/etherwake
fence_cdu (sentry switch CDU): fence_cdu/fence-agents
hmc: chsysstate/'HMC Management Software' (no package AFAIK)
ipmi: ipmipower/freeipmi-tools (dep of maas)
moonshot (iLO IPMI): ipmipower/freeipmi-tools (both drivers use ipmipower?)
mscm (iLO4 chassis manager): uses paramiko, no check
microsoft ocs: uses urllib2, no check
seamicro: ipmitool/ipmitool (wasn't 100% sure on this one - can't use ipmipower?)
cisco ucsm: uses urllib2, no check
virsh: virsh/libvirt-bin, virt-login-shell/libvirt-bin
vmware: tests import of pyVmomi, pyVim.connect / python-pyvmomi package

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

The implementation looks sound. Really like how you did it. Got some comments, the biggest issue is the missing unit tests. You are missing unit tests for all of the power drivers. Please add unit tests on each to test the newly added methods functionality.

review: Needs Fixing
Revision history for this message
Jeffrey C Jones (trapnine) wrote :

> The implementation looks sound. Really like how you did it. Got some comments,
> the biggest issue is the missing unit tests. You are missing unit tests for
> all of the power drivers. Please add unit tests on each to test the newly
> added methods functionality.

thanks for the feedback. added & fixed, please test!

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

Thanks for all the fixes.

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

There are additional revisions which have not been approved in review. Please seek review and approval of these new revisions.

Revision history for this message
Andres Rodriguez (andreserl) :
review: Approve

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
1=== modified file 'src/maasserver/clusterrpc/power_parameters.py'
2--- src/maasserver/clusterrpc/power_parameters.py 2015-08-21 13:59:15 +0000
3+++ src/maasserver/clusterrpc/power_parameters.py 2015-10-01 23:31:26 +0000
4@@ -86,7 +86,8 @@
5 return form_field
6
7
8-def add_power_type_parameters(name, description, fields, parameters_set):
9+def add_power_type_parameters(
10+ name, description, fields, missing_packages, parameters_set):
11 """Add new power type parameters to the given parameters_set if it
12 does not already exist.
13
14@@ -113,7 +114,8 @@
15 }
16 validate(fields, field_set_schema)
17 parameters_set.append(
18- {'name': name, 'description': description, 'fields': fields})
19+ {'name': name, 'description': description, 'fields': fields,
20+ 'missing_packages': missing_packages})
21
22
23 def get_power_type_parameters_from_json(json_power_type_parameters):
24@@ -195,5 +197,7 @@
25 name = power_type['name']
26 fields = power_type['fields']
27 description = power_type['description']
28- add_power_type_parameters(name, description, fields, merged_types)
29+ missing_packages = power_type['missing_packages']
30+ add_power_type_parameters(
31+ name, description, fields, missing_packages, merged_types)
32 return sorted(merged_types, key=itemgetter("description"))
33
34=== modified file 'src/maasserver/clusterrpc/tests/test_power_parameters.py'
35--- src/maasserver/clusterrpc/tests/test_power_parameters.py 2015-08-21 16:28:40 +0000
36+++ src/maasserver/clusterrpc/tests/test_power_parameters.py 2015-10-01 23:31:26 +0000
37@@ -253,6 +253,7 @@
38 }]
39 add_power_type_parameters(
40 name='blah', description='baz', fields=[self.make_field()],
41+ missing_packages=[],
42 parameters_set=existing_parameters)
43 self.assertEqual(
44 [{'name': 'blah', 'description': 'baz', 'fields': {}}],
45@@ -261,24 +262,28 @@
46 def test_adds_new_power_type_parameters(self):
47 existing_parameters = []
48 fields = [self.make_field()]
49+ missing_packages = ['package1', 'package2']
50 add_power_type_parameters(
51 name='blah', description='baz', fields=fields,
52+ missing_packages=missing_packages,
53 parameters_set=existing_parameters)
54 self.assertEqual(
55- [{'name': 'blah', 'description': 'baz', 'fields': fields}],
56+ [{'name': 'blah', 'description': 'baz', 'fields': fields,
57+ 'missing_packages': missing_packages}],
58 existing_parameters)
59
60 def test_validates_new_parameters(self):
61 self.assertRaises(
62 jsonschema.ValidationError, add_power_type_parameters,
63 name='blah', description='baz', fields=[{}],
64- parameters_set=[])
65+ missing_packages=[], parameters_set=[])
66
67 def test_subsequent_parameters_set_is_valid(self):
68 parameters_set = []
69 fields = [self.make_field()]
70 add_power_type_parameters(
71 name='blah', description='baz', fields=fields,
72+ missing_packages=[],
73 parameters_set=parameters_set)
74 jsonschema.validate(
75 parameters_set, JSON_POWER_TYPE_SCHEMA)
76
77=== modified file 'src/maasserver/rpc/tests/test_nodes.py'
78--- src/maasserver/rpc/tests/test_nodes.py 2015-08-29 01:42:44 +0000
79+++ src/maasserver/rpc/tests/test_nodes.py 2015-10-01 23:31:26 +0000
80@@ -45,7 +45,7 @@
81 from maasserver.testing.testcase import MAASServerTestCase
82 from maasserver.utils.orm import post_commit_hooks
83 from maastesting.twisted import always_succeed_with
84-from provisioningserver.drivers import PowerTypeRegistry
85+from provisioningserver.drivers import gen_power_types
86 from provisioningserver.power import QUERY_POWER_TYPES
87 from provisioningserver.rpc.cluster import (
88 DescribePowerTypes,
89@@ -76,7 +76,7 @@
90
91 fixture = self.useFixture(MockLiveRegionToClusterRPCFixture())
92 protocol = fixture.makeCluster(cluster, DescribePowerTypes)
93- self.power_types = [item for name, item in PowerTypeRegistry]
94+ self.power_types = list(gen_power_types())
95 protocol.DescribePowerTypes.side_effect = always_succeed_with(
96 {'power_types': self.power_types})
97 return protocol
98
99=== modified file 'src/maasserver/static/js/angular/controllers/node_details.js'
100--- src/maasserver/static/js/angular/controllers/node_details.js 2015-09-27 20:01:05 +0000
101+++ src/maasserver/static/js/angular/controllers/node_details.js 2015-10-01 23:31:26 +0000
102@@ -903,6 +903,24 @@
103 }
104 };
105
106+ // Check to see if the power type has any missing system packages.
107+ $scope.hasPowerError = function() {
108+ if(angular.isObject($scope.power.type)) {
109+ return $scope.power.type.missing_packages.length > 0;
110+ } else {
111+ return false;
112+ }
113+ };
114+
115+ // Returns an array of missing system packages.
116+ $scope.getPowerError = function() {
117+ if(angular.isObject($scope.power.type)) {
118+ return $scope.power.type.missing_packages;
119+ } else {
120+ return false;
121+ }
122+ };
123+
124 // Load all the required managers.
125 ManagerHelperService.loadManagers([
126 NodesManager,
127
128=== modified file 'src/maasserver/static/partials/node-details.html'
129--- src/maasserver/static/partials/node-details.html 2015-10-01 14:07:46 +0000
130+++ src/maasserver/static/partials/node-details.html 2015-10-01 23:31:26 +0000
131@@ -267,6 +267,15 @@
132 <div class="twelve-col">
133 <h3 class="title">Power</h3>
134 </div>
135+ <div class="twelve-col ng-hide error" data-ng-show="hasPowerError()">
136+ <ul class="flash-messages">
137+ <li class="flash-messages__item error">
138+ To control power with this power type, install the
139+ '<span ng-repeat="error in getPowerError()">{$ error $}{$$last ? '' : ', '$}</span>'
140+ package(s) on cluster '{$ node.nodegroup.name $}'
141+ </li>
142+ </ul>
143+ </div>
144 <fieldset class="six-col"
145 data-ng-disabled="!power.editing"
146 data-maas-power-parameters="power.types"
147
148=== modified file 'src/maasserver/tests/test_middleware.py'
149--- src/maasserver/tests/test_middleware.py 2015-05-29 16:47:37 +0000
150+++ src/maasserver/tests/test_middleware.py 2015-10-01 23:31:26 +0000
151@@ -355,7 +355,7 @@
152 middleware = RPCErrorsMiddleware()
153 request = factory.make_fake_request(factory.make_string(), 'POST')
154 error_message = (
155- "No connections availble for cluster %s" %
156+ "No connections available for cluster %s" %
157 factory.make_name('cluster'))
158 error = NoConnectionsAvailable(error_message)
159 response = middleware.process_exception(request, error)
160
161=== modified file 'src/maastesting/testcase.py'
162--- src/maastesting/testcase.py 2015-09-24 16:22:12 +0000
163+++ src/maastesting/testcase.py 2015-10-01 23:31:26 +0000
164@@ -258,7 +258,7 @@
165 """
166 # If 'attribute' is None, assume 'obj' is a 'fully-qualified' object,
167 # and assume that its __module__ is what we want to patch. For more
168- # complex use cases, the two-paramerter 'patch' will still need to
169+ # complex use cases, the two-parameter 'patch' will still need to
170 # be used.
171 if attribute is None:
172 attribute = obj.__name__
173
174=== modified file 'src/provisioningserver/drivers/__init__.py'
175--- src/provisioningserver/drivers/__init__.py 2015-08-21 13:59:15 +0000
176+++ src/provisioningserver/drivers/__init__.py 2015-10-01 23:31:26 +0000
177@@ -133,6 +133,15 @@
178 validate(setting_fields, JSON_SETTING_SCHEMA)
179
180
181+def gen_power_types():
182+ from provisioningserver.drivers.power import power_drivers_by_name
183+ for power_type in JSON_POWER_TYPE_PARAMETERS:
184+ driver = power_drivers_by_name.get(power_type['name'])
185+ if driver is not None:
186+ power_type['missing_packages'] = driver.detect_missing_packages()
187+ yield power_type
188+
189+
190 class Architecture:
191
192 def __init__(self, name, description, pxealiases=None,
193@@ -232,10 +241,6 @@
194 """Registry for boot resource classes."""
195
196
197-class PowerTypeRegistry(Registry):
198- """Registry for power type classes."""
199-
200-
201 builtin_architectures = [
202 Architecture(name="i386/generic", description="i386"),
203 Architecture(name="amd64/generic", description="amd64"),
204@@ -268,8 +273,3 @@
205 ]
206 for arch in builtin_architectures:
207 ArchitectureRegistry.register_item(arch.name, arch)
208-
209-
210-builtin_power_types = JSON_POWER_TYPE_PARAMETERS
211-for power_type in builtin_power_types:
212- PowerTypeRegistry.register_item(power_type['name'], power_type)
213
214=== modified file 'src/provisioningserver/drivers/hardware/apc.py'
215--- src/provisioningserver/drivers/hardware/apc.py 2015-05-18 20:01:38 +0000
216+++ src/provisioningserver/drivers/hardware/apc.py 2015-10-01 23:31:26 +0000
217@@ -101,3 +101,7 @@
218 elif power_state == APCState.ON:
219 return 'on'
220 raise APCException('Unknown power state: %r' % power_state)
221+
222+
223+def required_package():
224+ return ['snmpset', 'snmp']
225
226=== modified file 'src/provisioningserver/drivers/hardware/hmc.py'
227--- src/provisioningserver/drivers/hardware/hmc.py 2015-07-07 21:23:57 +0000
228+++ src/provisioningserver/drivers/hardware/hmc.py 2015-10-01 23:31:26 +0000
229@@ -110,3 +110,7 @@
230 elif power_state in HMCState.ON:
231 return 'on'
232 raise HMCException('Unknown power state: %s' % power_state)
233+
234+
235+def required_package():
236+ return ['chsysstate', 'HMC Management Software']
237
238=== modified file 'src/provisioningserver/drivers/power/__init__.py'
239--- src/provisioningserver/drivers/power/__init__.py 2015-09-24 16:22:12 +0000
240+++ src/provisioningserver/drivers/power/__init__.py 2015-10-01 23:31:26 +0000
241@@ -195,6 +195,12 @@
242 self.clock = reactor
243
244 @abstractmethod
245+ def detect_missing_packages(self):
246+ """Implement this method for the actual implementation
247+ of the check for the driver's missing support packages.
248+ """
249+
250+ @abstractmethod
251 def power_on(self, system_id, **kwargs):
252 """Implement this method for the actual implementation
253 of the power on command.
254@@ -313,7 +319,11 @@
255 return schemas
256
257
258+from provisioningserver.drivers.power.amt import AMTPowerDriver
259 from provisioningserver.drivers.power.apc import APCPowerDriver
260+from provisioningserver.drivers.power.dli import DLIPowerDriver
261+from provisioningserver.drivers.power.ether_wake import EtherWakePowerDriver
262+from provisioningserver.drivers.power.fence_cdu import FenceCDUPowerDriver
263 from provisioningserver.drivers.power.hmc import HMCPowerDriver
264 from provisioningserver.drivers.power.ipmi import IPMIPowerDriver
265 from provisioningserver.drivers.power.msftocs import MicrosoftOCSPowerDriver
266@@ -324,7 +334,7 @@
267 from provisioningserver.drivers.power.virsh import VirshPowerDriver
268 from provisioningserver.drivers.power.vmware import VMwarePowerDriver
269
270-builtin_power_drivers = [
271+registered_power_drivers = [
272 APCPowerDriver(),
273 HMCPowerDriver(),
274 IPMIPowerDriver(),
275@@ -336,5 +346,16 @@
276 VirshPowerDriver(),
277 VMwarePowerDriver(),
278 ]
279-for driver in builtin_power_drivers:
280+for driver in registered_power_drivers:
281 PowerDriverRegistry.register_item(driver.name, driver)
282+
283+unregistered_power_drivers = [
284+ AMTPowerDriver(),
285+ DLIPowerDriver(),
286+ EtherWakePowerDriver(),
287+ FenceCDUPowerDriver(),
288+]
289+power_drivers_by_name = {
290+ d.name: d for d in
291+ registered_power_drivers + unregistered_power_drivers
292+}
293
294=== added file 'src/provisioningserver/drivers/power/amt.py'
295--- src/provisioningserver/drivers/power/amt.py 1970-01-01 00:00:00 +0000
296+++ src/provisioningserver/drivers/power/amt.py 2015-10-01 23:31:26 +0000
297@@ -0,0 +1,40 @@
298+# Copyright 2015 Canonical Ltd. This software is licensed under the
299+# GNU Affero General Public License version 3 (see the file LICENSE).
300+
301+"""Template-based AMT Power Driver."""
302+
303+str = None
304+
305+__metaclass__ = type
306+__all__ = []
307+
308+from provisioningserver.drivers.power import PowerDriver
309+from provisioningserver.utils import shell
310+
311+
312+REQUIRED_PACKAGES = [["amttool", "amtterm"], ["wsman", "wsmancli"]]
313+
314+
315+class AMTPowerDriver(PowerDriver):
316+ name = 'amt'
317+ description = "AMT Power Driver."
318+ settings = []
319+
320+ def detect_missing_packages(self):
321+ missing_packages = []
322+ # when this becomes a non-templated, registered power driver, we can
323+ # detect what version of AMT is on the Node to find out if wsman is
324+ # required (see amt.template). For now, we assume wsman is required
325+ for binary, package in REQUIRED_PACKAGES:
326+ if not shell.has_command_available(binary):
327+ missing_packages.append(package)
328+ return missing_packages
329+
330+ def power_on(self, system_id, **kwargs):
331+ raise NotImplementedError
332+
333+ def power_off(self, system_id, **kwargs):
334+ raise NotImplementedError
335+
336+ def power_query(self, system_id, **kwargs):
337+ raise NotImplementedError
338
339=== modified file 'src/provisioningserver/drivers/power/apc.py'
340--- src/provisioningserver/drivers/power/apc.py 2015-07-02 21:24:06 +0000
341+++ src/provisioningserver/drivers/power/apc.py 2015-10-01 23:31:26 +0000
342@@ -17,8 +17,10 @@
343 from provisioningserver.drivers.hardware.apc import (
344 power_control_apc,
345 power_state_apc,
346+ required_package,
347 )
348 from provisioningserver.drivers.power import PowerDriver
349+from provisioningserver.utils import shell
350
351
352 def extract_apc_parameters(params):
353@@ -34,6 +36,12 @@
354 description = "APC Power Driver."
355 settings = []
356
357+ def detect_missing_packages(self):
358+ binary, package = required_package()
359+ if not shell.has_command_available(binary):
360+ return [package]
361+ return []
362+
363 def power_on(self, system_id, **kwargs):
364 """Power on Apc outlet."""
365 power_change = 'on'
366
367=== added file 'src/provisioningserver/drivers/power/dli.py'
368--- src/provisioningserver/drivers/power/dli.py 1970-01-01 00:00:00 +0000
369+++ src/provisioningserver/drivers/power/dli.py 2015-10-01 23:31:26 +0000
370@@ -0,0 +1,32 @@
371+# Copyright 2015 Canonical Ltd. This software is licensed under the
372+# GNU Affero General Public License version 3 (see the file LICENSE).
373+
374+"""Template-based DLI Power Driver."""
375+
376+str = None
377+
378+__metaclass__ = type
379+__all__ = []
380+
381+from provisioningserver.drivers.power import PowerDriver
382+from provisioningserver.utils import shell
383+
384+
385+class DLIPowerDriver(PowerDriver):
386+ name = 'dli'
387+ description = "DLI Power Driver."
388+ settings = []
389+
390+ def detect_missing_packages(self):
391+ if not shell.has_command_available('wget'):
392+ return ['wget']
393+ return []
394+
395+ def power_on(self, system_id, **kwargs):
396+ raise NotImplementedError
397+
398+ def power_off(self, system_id, **kwargs):
399+ raise NotImplementedError
400+
401+ def power_query(self, system_id, **kwargs):
402+ raise NotImplementedError
403
404=== added file 'src/provisioningserver/drivers/power/ether_wake.py'
405--- src/provisioningserver/drivers/power/ether_wake.py 1970-01-01 00:00:00 +0000
406+++ src/provisioningserver/drivers/power/ether_wake.py 2015-10-01 23:31:26 +0000
407@@ -0,0 +1,34 @@
408+# Copyright 2015 Canonical Ltd. This software is licensed under the
409+# GNU Affero General Public License version 3 (see the file LICENSE).
410+
411+"""Template-based ether-wake Power Driver."""
412+
413+str = None
414+
415+__metaclass__ = type
416+__all__ = []
417+
418+from provisioningserver.drivers.power import PowerDriver
419+from provisioningserver.utils import shell
420+
421+
422+class EtherWakePowerDriver(PowerDriver):
423+ name = 'ether_wake'
424+ description = "Ether-wake Power Driver."
425+ settings = []
426+
427+ def detect_missing_packages(self):
428+ if shell.has_command_available('wakeonlan') or \
429+ shell.has_command_available('etherwake'):
430+ return []
431+ # you need one or the other, not both
432+ return ['wakeonlan or etherwake']
433+
434+ def power_on(self, system_id, **kwargs):
435+ raise NotImplementedError
436+
437+ def power_off(self, system_id, **kwargs):
438+ raise NotImplementedError
439+
440+ def power_query(self, system_id, **kwargs):
441+ raise NotImplementedError
442
443=== added file 'src/provisioningserver/drivers/power/fence_cdu.py'
444--- src/provisioningserver/drivers/power/fence_cdu.py 1970-01-01 00:00:00 +0000
445+++ src/provisioningserver/drivers/power/fence_cdu.py 2015-10-01 23:31:26 +0000
446@@ -0,0 +1,32 @@
447+# Copyright 2015 Canonical Ltd. This software is licensed under the
448+# GNU Affero General Public License version 3 (see the file LICENSE).
449+
450+"""Template-based Fence CDU Power Driver."""
451+
452+str = None
453+
454+__metaclass__ = type
455+__all__ = []
456+
457+from provisioningserver.drivers.power import PowerDriver
458+from provisioningserver.utils import shell
459+
460+
461+class FenceCDUPowerDriver(PowerDriver):
462+ name = 'fence_cdu'
463+ description = "Fence CDU Power Driver."
464+ settings = []
465+
466+ def detect_missing_packages(self):
467+ if not shell.has_command_available('fence_cdu'):
468+ return ['fence-agents']
469+ return []
470+
471+ def power_on(self, system_id, **kwargs):
472+ raise NotImplementedError
473+
474+ def power_off(self, system_id, **kwargs):
475+ raise NotImplementedError
476+
477+ def power_query(self, system_id, **kwargs):
478+ raise NotImplementedError
479
480=== modified file 'src/provisioningserver/drivers/power/hmc.py'
481--- src/provisioningserver/drivers/power/hmc.py 2015-06-25 20:39:09 +0000
482+++ src/provisioningserver/drivers/power/hmc.py 2015-10-01 23:31:26 +0000
483@@ -17,8 +17,10 @@
484 from provisioningserver.drivers.hardware.hmc import (
485 power_control_hmc,
486 power_state_hmc,
487+ required_package,
488 )
489 from provisioningserver.drivers.power import PowerDriver
490+from provisioningserver.utils import shell
491
492
493 def extract_hmc_parameters(params):
494@@ -36,6 +38,12 @@
495 description = "IBM Hardware Management Console Power Driver."
496 settings = []
497
498+ def detect_missing_packages(self):
499+ binary, package = required_package()
500+ if not shell.has_command_available(binary):
501+ return [package]
502+ return []
503+
504 def power_on(self, system_id, **kwargs):
505 ip, username, password, server_name, lpar = (
506 extract_hmc_parameters(kwargs))
507
508=== modified file 'src/provisioningserver/drivers/power/ipmi.py'
509--- src/provisioningserver/drivers/power/ipmi.py 2015-08-27 05:52:33 +0000
510+++ src/provisioningserver/drivers/power/ipmi.py 2015-10-01 23:31:26 +0000
511@@ -26,6 +26,7 @@
512 PowerDriver,
513 PowerFatalError,
514 )
515+from provisioningserver.utils import shell
516 from provisioningserver.utils.network import find_ip_via_arp
517 from provisioningserver.utils.shell import (
518 call_and_check,
519@@ -51,6 +52,11 @@
520 description = "IPMI Power Driver."
521 settings = []
522
523+ def detect_missing_packages(self):
524+ if not shell.has_command_available('ipmipower'):
525+ return ['freeipmi-tools']
526+ return []
527+
528 def get_c_environment(self):
529 env = os.environ.copy()
530 env['LC_ALL'] = 'C'
531
532=== modified file 'src/provisioningserver/drivers/power/moonshot.py'
533--- src/provisioningserver/drivers/power/moonshot.py 2015-08-26 17:26:37 +0000
534+++ src/provisioningserver/drivers/power/moonshot.py 2015-10-01 23:31:26 +0000
535@@ -20,6 +20,7 @@
536 PowerDriver,
537 PowerFatalError,
538 )
539+from provisioningserver.utils import shell
540 from provisioningserver.utils.shell import (
541 call_and_check,
542 ExternalProcessError,
543@@ -32,6 +33,11 @@
544 description = "Moonshot IPMI Power Driver."
545 settings = []
546
547+ def detect_missing_packages(self):
548+ if not shell.has_command_available('ipmipower'):
549+ return ['freeipmi-tools']
550+ return []
551+
552 def _issue_ipmitool_command(
553 self, power_change, power_hwaddress=None, power_address=None,
554 power_user=None, power_pass=None, ipmitool=None, **extra):
555
556=== modified file 'src/provisioningserver/drivers/power/mscm.py'
557--- src/provisioningserver/drivers/power/mscm.py 2015-06-05 21:19:20 +0000
558+++ src/provisioningserver/drivers/power/mscm.py 2015-10-01 23:31:26 +0000
559@@ -35,6 +35,10 @@
560 description = "Moonshot HP iLO Chassis Manager Power Driver."
561 settings = []
562
563+ def detect_missing_packages(self):
564+ # uses pure-python paramiko ssh client - nothing to look for!
565+ return []
566+
567 def power_on(self, system_id, **kwargs):
568 """Power on MSCM node."""
569 host, username, password, node_id = extract_mscm_parameters(kwargs)
570
571=== modified file 'src/provisioningserver/drivers/power/msftocs.py'
572--- src/provisioningserver/drivers/power/msftocs.py 2015-07-02 22:29:11 +0000
573+++ src/provisioningserver/drivers/power/msftocs.py 2015-10-01 23:31:26 +0000
574@@ -36,6 +36,10 @@
575 description = "MicrosoftOCS Power Driver."
576 settings = []
577
578+ def detect_missing_packages(self):
579+ # uses urllib2 http client - nothing to look for!
580+ return []
581+
582 def power_on(self, system_id, **kwargs):
583 """Power on MicrosoftOCS node."""
584 power_change = 'on'
585
586=== modified file 'src/provisioningserver/drivers/power/seamicro.py'
587--- src/provisioningserver/drivers/power/seamicro.py 2015-07-18 20:16:36 +0000
588+++ src/provisioningserver/drivers/power/seamicro.py 2015-10-01 23:31:26 +0000
589@@ -23,6 +23,7 @@
590 PowerDriver,
591 PowerFatalError,
592 )
593+from provisioningserver.utils import shell
594 from provisioningserver.utils.shell import (
595 call_and_check,
596 ExternalProcessError,
597@@ -44,6 +45,11 @@
598 description = "SeaMicro Power Driver."
599 settings = []
600
601+ def detect_missing_packages(self):
602+ if not shell.has_command_available('ipmitool'):
603+ return ['ipmitool']
604+ return []
605+
606 def _power_control_seamicro15k_ipmi(
607 self, ip, username, password, server_id, power_change):
608 """Power on/off SeaMicro node via ipmitool."""
609
610=== added file 'src/provisioningserver/drivers/power/tests/test_amt.py'
611--- src/provisioningserver/drivers/power/tests/test_amt.py 1970-01-01 00:00:00 +0000
612+++ src/provisioningserver/drivers/power/tests/test_amt.py 2015-10-01 23:31:26 +0000
613@@ -0,0 +1,51 @@
614+# Copyright 2015 Canonical Ltd. This software is licensed under the
615+# GNU Affero General Public License version 3 (see the file LICENSE).
616+
617+"""Tests for `provisioningserver.drivers.power.amt`."""
618+
619+from __future__ import (
620+ absolute_import,
621+ print_function,
622+ unicode_literals,
623+ )
624+
625+str = None
626+
627+__metaclass__ = type
628+__all__ = []
629+
630+from maastesting.testcase import MAASTestCase
631+from provisioningserver.drivers.power import amt as amt_module
632+from provisioningserver.utils.shell import has_command_available
633+
634+
635+class TestAMTPowerDriver(MAASTestCase):
636+
637+ def test_missing_packages(self):
638+ mock = self.patch(has_command_available)
639+ mock.return_value = False
640+ driver = amt_module.AMTPowerDriver()
641+ missing = driver.detect_missing_packages()
642+ self.assertItemsEqual(["amtterm", "wsmancli"], missing)
643+
644+ def test_no_missing_packages(self):
645+ mock = self.patch(has_command_available)
646+ mock.return_value = True
647+ driver = amt_module.AMTPowerDriver()
648+ missing = driver.detect_missing_packages()
649+ self.assertItemsEqual([], missing)
650+
651+ def test_power_on(self):
652+ driver = amt_module.AMTPowerDriver()
653+ self.assertRaises(
654+ NotImplementedError, driver.power_on, "fake_id")
655+
656+ def test_power_off(self):
657+ driver = amt_module.AMTPowerDriver()
658+ self.assertRaises(
659+ NotImplementedError, driver.power_off, "fake_id")
660+
661+ def test_power_query(self):
662+ driver = amt_module.AMTPowerDriver()
663+ self.assertRaises(
664+ NotImplementedError, driver.power_query, "fake_id")
665
666=== modified file 'src/provisioningserver/drivers/power/tests/test_apc.py'
667--- src/provisioningserver/drivers/power/tests/test_apc.py 2015-07-07 02:54:21 +0000
668+++ src/provisioningserver/drivers/power/tests/test_apc.py 2015-10-01 23:31:26 +0000
669@@ -24,11 +24,26 @@
670 APCPowerDriver,
671 extract_apc_parameters,
672 )
673+from provisioningserver.utils.shell import has_command_available
674 from testtools.matchers import Equals
675
676
677 class TestAPCPowerDriver(MAASTestCase):
678
679+ def test_missing_packages(self):
680+ mock = self.patch(has_command_available)
681+ mock.return_value = False
682+ driver = apc_module.APCPowerDriver()
683+ missing = driver.detect_missing_packages()
684+ self.assertItemsEqual(["snmp"], missing)
685+
686+ def test_no_missing_packages(self):
687+ mock = self.patch(has_command_available)
688+ mock.return_value = True
689+ driver = apc_module.APCPowerDriver()
690+ missing = driver.detect_missing_packages()
691+ self.assertItemsEqual([], missing)
692+
693 def make_parameters(self):
694 system_id = factory.make_name('system_id')
695 ip = factory.make_ipv4_address()
696
697=== modified file 'src/provisioningserver/drivers/power/tests/test_base.py'
698--- src/provisioningserver/drivers/power/tests/test_base.py 2015-08-21 17:50:25 +0000
699+++ src/provisioningserver/drivers/power/tests/test_base.py 2015-10-01 23:31:26 +0000
700@@ -253,6 +253,9 @@
701 self.wait_time = wait_time
702 super(FakePowerDriver, self).__init__(clock)
703
704+ def detect_missing_packages(self):
705+ raise NotImplementedError
706+
707 def power_on(self, system_id, **kwargs):
708 raise NotImplementedError
709
710
711=== added file 'src/provisioningserver/drivers/power/tests/test_dli.py'
712--- src/provisioningserver/drivers/power/tests/test_dli.py 1970-01-01 00:00:00 +0000
713+++ src/provisioningserver/drivers/power/tests/test_dli.py 2015-10-01 23:31:26 +0000
714@@ -0,0 +1,51 @@
715+# Copyright 2015 Canonical Ltd. This software is licensed under the
716+# GNU Affero General Public License version 3 (see the file LICENSE).
717+
718+"""Tests for `provisioningserver.drivers.power.dli`."""
719+
720+from __future__ import (
721+ absolute_import,
722+ print_function,
723+ unicode_literals,
724+ )
725+
726+str = None
727+
728+__metaclass__ = type
729+__all__ = []
730+
731+from maastesting.testcase import MAASTestCase
732+from provisioningserver.drivers.power import dli as dli_module
733+from provisioningserver.utils.shell import has_command_available
734+
735+
736+class TestDLIPowerDriver(MAASTestCase):
737+
738+ def test_missing_packages(self):
739+ mock = self.patch(has_command_available)
740+ mock.return_value = False
741+ driver = dli_module.DLIPowerDriver()
742+ missing = driver.detect_missing_packages()
743+ self.assertItemsEqual(["wget"], missing)
744+
745+ def test_no_missing_packages(self):
746+ mock = self.patch(has_command_available)
747+ mock.return_value = True
748+ driver = dli_module.DLIPowerDriver()
749+ missing = driver.detect_missing_packages()
750+ self.assertItemsEqual([], missing)
751+
752+ def test_power_on(self):
753+ driver = dli_module.DLIPowerDriver()
754+ self.assertRaises(
755+ NotImplementedError, driver.power_on, "fake_id")
756+
757+ def test_power_off(self):
758+ driver = dli_module.DLIPowerDriver()
759+ self.assertRaises(
760+ NotImplementedError, driver.power_off, "fake_id")
761+
762+ def test_power_query(self):
763+ driver = dli_module.DLIPowerDriver()
764+ self.assertRaises(
765+ NotImplementedError, driver.power_query, "fake_id")
766
767=== added file 'src/provisioningserver/drivers/power/tests/test_ether_wake.py'
768--- src/provisioningserver/drivers/power/tests/test_ether_wake.py 1970-01-01 00:00:00 +0000
769+++ src/provisioningserver/drivers/power/tests/test_ether_wake.py 2015-10-01 23:31:26 +0000
770@@ -0,0 +1,51 @@
771+# Copyright 2015 Canonical Ltd. This software is licensed under the
772+# GNU Affero General Public License version 3 (see the file LICENSE).
773+
774+"""Tests for `provisioningserver.drivers.power.ether_wake`."""
775+
776+from __future__ import (
777+ absolute_import,
778+ print_function,
779+ unicode_literals,
780+ )
781+
782+str = None
783+
784+__metaclass__ = type
785+__all__ = []
786+
787+from maastesting.testcase import MAASTestCase
788+from provisioningserver.drivers.power import ether_wake as ether_wake_module
789+from provisioningserver.utils.shell import has_command_available
790+
791+
792+class TestEtherWakePowerDriver(MAASTestCase):
793+
794+ def test_missing_packages(self):
795+ mock = self.patch(has_command_available)
796+ mock.return_value = False
797+ driver = ether_wake_module.EtherWakePowerDriver()
798+ missing = driver.detect_missing_packages()
799+ self.assertItemsEqual(["wakeonlan or etherwake"], missing)
800+
801+ def test_no_missing_packages(self):
802+ mock = self.patch(has_command_available)
803+ mock.return_value = True
804+ driver = ether_wake_module.EtherWakePowerDriver()
805+ missing = driver.detect_missing_packages()
806+ self.assertItemsEqual([], missing)
807+
808+ def test_power_on(self):
809+ driver = ether_wake_module.EtherWakePowerDriver()
810+ self.assertRaises(
811+ NotImplementedError, driver.power_on, "fake_id")
812+
813+ def test_power_off(self):
814+ driver = ether_wake_module.EtherWakePowerDriver()
815+ self.assertRaises(
816+ NotImplementedError, driver.power_off, "fake_id")
817+
818+ def test_power_query(self):
819+ driver = ether_wake_module.EtherWakePowerDriver()
820+ self.assertRaises(
821+ NotImplementedError, driver.power_query, "fake_id")
822
823=== added file 'src/provisioningserver/drivers/power/tests/test_fence_cdu.py'
824--- src/provisioningserver/drivers/power/tests/test_fence_cdu.py 1970-01-01 00:00:00 +0000
825+++ src/provisioningserver/drivers/power/tests/test_fence_cdu.py 2015-10-01 23:31:26 +0000
826@@ -0,0 +1,51 @@
827+# Copyright 2015 Canonical Ltd. This software is licensed under the
828+# GNU Affero General Public License version 3 (see the file LICENSE).
829+
830+"""Tests for `provisioningserver.drivers.power.fence_cdu`."""
831+
832+from __future__ import (
833+ absolute_import,
834+ print_function,
835+ unicode_literals,
836+ )
837+
838+str = None
839+
840+__metaclass__ = type
841+__all__ = []
842+
843+from maastesting.testcase import MAASTestCase
844+from provisioningserver.drivers.power import fence_cdu as fence_cdu_module
845+from provisioningserver.utils.shell import has_command_available
846+
847+
848+class TestFenceCDUPowerDriver(MAASTestCase):
849+
850+ def test_missing_packages(self):
851+ mock = self.patch(has_command_available)
852+ mock.return_value = False
853+ driver = fence_cdu_module.FenceCDUPowerDriver()
854+ missing = driver.detect_missing_packages()
855+ self.assertItemsEqual(["fence-agents"], missing)
856+
857+ def test_no_missing_packages(self):
858+ mock = self.patch(has_command_available)
859+ mock.return_value = True
860+ driver = fence_cdu_module.FenceCDUPowerDriver()
861+ missing = driver.detect_missing_packages()
862+ self.assertItemsEqual([], missing)
863+
864+ def test_power_on(self):
865+ driver = fence_cdu_module.FenceCDUPowerDriver()
866+ self.assertRaises(
867+ NotImplementedError, driver.power_on, "fake_id")
868+
869+ def test_power_off(self):
870+ driver = fence_cdu_module.FenceCDUPowerDriver()
871+ self.assertRaises(
872+ NotImplementedError, driver.power_off, "fake_id")
873+
874+ def test_power_query(self):
875+ driver = fence_cdu_module.FenceCDUPowerDriver()
876+ self.assertRaises(
877+ NotImplementedError, driver.power_query, "fake_id")
878
879=== modified file 'src/provisioningserver/drivers/power/tests/test_hmc.py'
880--- src/provisioningserver/drivers/power/tests/test_hmc.py 2015-07-07 01:39:59 +0000
881+++ src/provisioningserver/drivers/power/tests/test_hmc.py 2015-10-01 23:31:26 +0000
882@@ -22,11 +22,26 @@
883 extract_hmc_parameters,
884 HMCPowerDriver,
885 )
886+from provisioningserver.utils.shell import has_command_available
887 from testtools.matchers import Equals
888
889
890 class TestHMCPowerDriver(MAASTestCase):
891
892+ def test_missing_packages(self):
893+ mock = self.patch(has_command_available)
894+ mock.return_value = False
895+ driver = hmc_module.HMCPowerDriver()
896+ missing = driver.detect_missing_packages()
897+ self.assertItemsEqual(['HMC Management Software'], missing)
898+
899+ def test_no_missing_packages(self):
900+ mock = self.patch(has_command_available)
901+ mock.return_value = True
902+ driver = hmc_module.HMCPowerDriver()
903+ missing = driver.detect_missing_packages()
904+ self.assertItemsEqual([], missing)
905+
906 def make_parameters(self):
907 system_id = factory.make_name('system_id')
908 ip = factory.make_name('power_address')
909
910=== modified file 'src/provisioningserver/drivers/power/tests/test_ipmi.py'
911--- src/provisioningserver/drivers/power/tests/test_ipmi.py 2015-08-27 05:52:33 +0000
912+++ src/provisioningserver/drivers/power/tests/test_ipmi.py 2015-10-01 23:31:26 +0000
913@@ -36,7 +36,10 @@
914 IPMI_CONFIG,
915 IPMIPowerDriver,
916 )
917-from provisioningserver.utils.shell import ExternalProcessError
918+from provisioningserver.utils.shell import (
919+ ExternalProcessError,
920+ has_command_available,
921+)
922 from testtools.matchers import (
923 Contains,
924 Equals,
925@@ -89,6 +92,20 @@
926
927 class TestIPMIPowerDriver(MAASTestCase):
928
929+ def test_missing_packages(self):
930+ mock = self.patch(has_command_available)
931+ mock.return_value = False
932+ driver = ipmi_module.IPMIPowerDriver()
933+ missing = driver.detect_missing_packages()
934+ self.assertItemsEqual(['freeipmi-tools'], missing)
935+
936+ def test_no_missing_packages(self):
937+ mock = self.patch(has_command_available)
938+ mock.return_value = True
939+ driver = ipmi_module.IPMIPowerDriver()
940+ missing = driver.detect_missing_packages()
941+ self.assertItemsEqual([], missing)
942+
943 def test__finds_power_address_from_mac_address(self):
944 (power_address, power_user, power_pass, power_driver, power_off_mode,
945 ipmipower, ipmi_chassis_config, params) = make_parameters()
946
947=== modified file 'src/provisioningserver/drivers/power/tests/test_moonshot.py'
948--- src/provisioningserver/drivers/power/tests/test_moonshot.py 2015-08-26 17:26:37 +0000
949+++ src/provisioningserver/drivers/power/tests/test_moonshot.py 2015-10-01 23:31:26 +0000
950@@ -23,7 +23,10 @@
951 PowerFatalError,
952 )
953 from provisioningserver.drivers.power.moonshot import MoonshotIPMIPowerDriver
954-from provisioningserver.utils.shell import ExternalProcessError
955+from provisioningserver.utils.shell import (
956+ ExternalProcessError,
957+ has_command_available,
958+)
959 from testtools.matchers import Equals
960
961
962@@ -48,6 +51,20 @@
963
964 class TestMoonshotIPMIPowerDriver(MAASTestCase):
965
966+ def test_missing_packages(self):
967+ mock = self.patch(has_command_available)
968+ mock.return_value = False
969+ driver = moonshot_module.MoonshotIPMIPowerDriver()
970+ missing = driver.detect_missing_packages()
971+ self.assertItemsEqual(['freeipmi-tools'], missing)
972+
973+ def test_no_missing_packages(self):
974+ mock = self.patch(has_command_available)
975+ mock.return_value = True
976+ driver = moonshot_module.MoonshotIPMIPowerDriver()
977+ missing = driver.detect_missing_packages()
978+ self.assertItemsEqual([], missing)
979+
980 def test__issue_ipmitool_command_issues_power_on(self):
981 params = make_parameters()
982 power_change = 'on'
983
984=== modified file 'src/provisioningserver/drivers/power/tests/test_mscm.py'
985--- src/provisioningserver/drivers/power/tests/test_mscm.py 2015-07-13 18:15:20 +0000
986+++ src/provisioningserver/drivers/power/tests/test_mscm.py 2015-10-01 23:31:26 +0000
987@@ -28,6 +28,12 @@
988
989 class TestMSCMPowerDriver(MAASTestCase):
990
991+ def test_missing_packages(self):
992+ # there's nothing to check for, just confirm it returns []
993+ driver = mscm_module.MSCMPowerDriver()
994+ missing = driver.detect_missing_packages()
995+ self.assertItemsEqual([], missing)
996+
997 def make_parameters(self):
998 system_id = factory.make_name('system_id')
999 host = factory.make_name('power_address')
1000
1001=== modified file 'src/provisioningserver/drivers/power/tests/test_msftocs.py'
1002--- src/provisioningserver/drivers/power/tests/test_msftocs.py 2015-07-07 02:34:03 +0000
1003+++ src/provisioningserver/drivers/power/tests/test_msftocs.py 2015-10-01 23:31:26 +0000
1004@@ -27,6 +27,12 @@
1005
1006 class TestMicrosoftOCSPowerDriver(MAASTestCase):
1007
1008+ def test_missing_packages(self):
1009+ # there's nothing to check for, just confirm it returns []
1010+ driver = msftocs_module.MicrosoftOCSPowerDriver()
1011+ missing = driver.detect_missing_packages()
1012+ self.assertItemsEqual([], missing)
1013+
1014 def make_parameters(self):
1015 system_id = factory.make_name('system_id')
1016 ip = factory.make_name('power_address')
1017
1018=== modified file 'src/provisioningserver/drivers/power/tests/test_seamicro.py'
1019--- src/provisioningserver/drivers/power/tests/test_seamicro.py 2015-07-18 20:05:39 +0000
1020+++ src/provisioningserver/drivers/power/tests/test_seamicro.py 2015-10-01 23:31:26 +0000
1021@@ -27,12 +27,29 @@
1022 extract_seamicro_parameters,
1023 SeaMicroPowerDriver,
1024 )
1025-from provisioningserver.utils.shell import ExternalProcessError
1026+from provisioningserver.utils.shell import (
1027+ ExternalProcessError,
1028+ has_command_available,
1029+)
1030 from testtools.matchers import Equals
1031
1032
1033 class TestSeaMicroPowerDriver(MAASTestCase):
1034
1035+ def test_missing_packages(self):
1036+ mock = self.patch(has_command_available)
1037+ mock.return_value = False
1038+ driver = seamicro_module.SeaMicroPowerDriver()
1039+ missing = driver.detect_missing_packages()
1040+ self.assertItemsEqual(['ipmitool'], missing)
1041+
1042+ def test_no_missing_packages(self):
1043+ mock = self.patch(has_command_available)
1044+ mock.return_value = True
1045+ driver = seamicro_module.SeaMicroPowerDriver()
1046+ missing = driver.detect_missing_packages()
1047+ self.assertItemsEqual([], missing)
1048+
1049 def make_parameters(self):
1050 ip = factory.make_name('power_address')
1051 username = factory.make_name('power_user')
1052
1053=== modified file 'src/provisioningserver/drivers/power/tests/test_ucsm.py'
1054--- src/provisioningserver/drivers/power/tests/test_ucsm.py 2015-07-13 18:22:16 +0000
1055+++ src/provisioningserver/drivers/power/tests/test_ucsm.py 2015-10-01 23:31:26 +0000
1056@@ -27,6 +27,12 @@
1057
1058 class TestUCSMPowerDriver(MAASTestCase):
1059
1060+ def test_missing_packages(self):
1061+ # there's nothing to check for, just confirm it returns []
1062+ driver = ucsm_module.UCSMPowerDriver()
1063+ missing = driver.detect_missing_packages()
1064+ self.assertItemsEqual([], missing)
1065+
1066 def make_parameters(self):
1067 system_id = factory.make_name('system_id')
1068 url = factory.make_name('power_address')
1069
1070=== modified file 'src/provisioningserver/drivers/power/tests/test_virsh.py'
1071--- src/provisioningserver/drivers/power/tests/test_virsh.py 2015-07-09 01:11:44 +0000
1072+++ src/provisioningserver/drivers/power/tests/test_virsh.py 2015-10-01 23:31:26 +0000
1073@@ -22,11 +22,26 @@
1074 extract_virsh_parameters,
1075 VirshPowerDriver,
1076 )
1077+from provisioningserver.utils.shell import has_command_available
1078 from testtools.matchers import Equals
1079
1080
1081 class TestVirshPowerDriver(MAASTestCase):
1082
1083+ def test_missing_packages(self):
1084+ mock = self.patch(has_command_available)
1085+ mock.return_value = False
1086+ driver = virsh_module.VirshPowerDriver()
1087+ missing = driver.detect_missing_packages()
1088+ self.assertItemsEqual(['libvirt-bin'], missing)
1089+
1090+ def test_no_missing_packages(self):
1091+ mock = self.patch(has_command_available)
1092+ mock.return_value = True
1093+ driver = virsh_module.VirshPowerDriver()
1094+ missing = driver.detect_missing_packages()
1095+ self.assertItemsEqual([], missing)
1096+
1097 def make_parameters(self):
1098 system_id = factory.make_name('system_id')
1099 poweraddr = factory.make_name('power_address')
1100
1101=== modified file 'src/provisioningserver/drivers/power/tests/test_vmware.py'
1102--- src/provisioningserver/drivers/power/tests/test_vmware.py 2015-07-07 02:07:39 +0000
1103+++ src/provisioningserver/drivers/power/tests/test_vmware.py 2015-10-01 23:31:26 +0000
1104@@ -17,6 +17,7 @@
1105 from maastesting.factory import factory
1106 from maastesting.matchers import MockCalledOnceWith
1107 from maastesting.testcase import MAASTestCase
1108+from provisioningserver.drivers.hardware.vmware import try_pyvmomi_import
1109 from provisioningserver.drivers.power import vmware as vmware_module
1110 from provisioningserver.drivers.power.vmware import (
1111 extract_vmware_parameters,
1112@@ -27,6 +28,20 @@
1113
1114 class TestVMwarePowerDriver(MAASTestCase):
1115
1116+ def test_missing_packages(self):
1117+ mock = self.patch(try_pyvmomi_import)
1118+ mock.return_value = False
1119+ driver = vmware_module.VMwarePowerDriver()
1120+ missing = driver.detect_missing_packages()
1121+ self.assertItemsEqual(["python-pyvmomi"], missing)
1122+
1123+ def test_no_missing_packages(self):
1124+ mock = self.patch(try_pyvmomi_import)
1125+ mock.return_value = True
1126+ driver = vmware_module.VMwarePowerDriver()
1127+ missing = driver.detect_missing_packages()
1128+ self.assertItemsEqual([], missing)
1129+
1130 def make_parameters(self):
1131 system_id = factory.make_name('system_id')
1132 host = factory.make_name('power_address')
1133
1134=== modified file 'src/provisioningserver/drivers/power/ucsm.py'
1135--- src/provisioningserver/drivers/power/ucsm.py 2015-07-13 18:22:16 +0000
1136+++ src/provisioningserver/drivers/power/ucsm.py 2015-10-01 23:31:26 +0000
1137@@ -35,6 +35,10 @@
1138 description = "Cisco UCS Power Driver."
1139 settings = []
1140
1141+ def detect_missing_packages(self):
1142+ # uses urllib2 http client - nothing to look for!
1143+ return []
1144+
1145 def power_on(self, system_id, **kwargs):
1146 """Power on UCSM node."""
1147 url, username, password, uuid = extract_ucsm_parameters(kwargs)
1148
1149=== modified file 'src/provisioningserver/drivers/power/virsh.py'
1150--- src/provisioningserver/drivers/power/virsh.py 2015-06-23 16:45:25 +0000
1151+++ src/provisioningserver/drivers/power/virsh.py 2015-10-01 23:31:26 +0000
1152@@ -19,6 +19,11 @@
1153 power_state_virsh,
1154 )
1155 from provisioningserver.drivers.power import PowerDriver
1156+from provisioningserver.utils import shell
1157+
1158+
1159+REQUIRED_PACKAGES = [["virsh", "libvirt-bin"],
1160+ ["virt-login-shell", "libvirt-bin"]]
1161
1162
1163 def extract_virsh_parameters(params):
1164@@ -34,6 +39,13 @@
1165 description = "Virsh Power Driver."
1166 settings = []
1167
1168+ def detect_missing_packages(self):
1169+ missing_packages = set()
1170+ for binary, package in REQUIRED_PACKAGES:
1171+ if not shell.has_command_available(binary):
1172+ missing_packages.add(package)
1173+ return list(missing_packages)
1174+
1175 def power_on(self, system_id, **kwargs):
1176 """Power on Virsh node."""
1177 power_change = 'on'
1178
1179=== modified file 'src/provisioningserver/drivers/power/vmware.py'
1180--- src/provisioningserver/drivers/power/vmware.py 2015-06-30 22:28:33 +0000
1181+++ src/provisioningserver/drivers/power/vmware.py 2015-10-01 23:31:26 +0000
1182@@ -14,6 +14,7 @@
1183 __metaclass__ = type
1184 __all__ = []
1185
1186+from provisioningserver.drivers.hardware import vmware
1187 from provisioningserver.drivers.hardware.vmware import (
1188 power_control_vmware,
1189 power_query_vmware,
1190@@ -38,6 +39,11 @@
1191 description = "VMware Power Driver."
1192 settings = []
1193
1194+ def detect_missing_packages(self):
1195+ if not vmware.try_pyvmomi_import():
1196+ return ["python-pyvmomi"]
1197+ return []
1198+
1199 def power_on(self, system_id, **kwargs):
1200 """Power on VMware node."""
1201 power_change = 'on'
1202
1203=== modified file 'src/provisioningserver/drivers/tests/test_base.py'
1204--- src/provisioningserver/drivers/tests/test_base.py 2015-05-07 18:14:38 +0000
1205+++ src/provisioningserver/drivers/tests/test_base.py 2015-10-01 23:31:26 +0000
1206@@ -31,7 +31,6 @@
1207 BootResourceRegistry,
1208 JSON_SETTING_SCHEMA,
1209 make_setting_field,
1210- PowerTypeRegistry,
1211 SETTING_PARAMETER_FIELD_SCHEMA,
1212 validate_settings,
1213 )
1214@@ -153,9 +152,37 @@
1215 self.assertEqual(
1216 None, ArchitectureRegistry.get_by_pxealias("stinkywinky"))
1217
1218- def test_power_type_registry(self):
1219- self.assertItemsEqual([], PowerTypeRegistry)
1220- PowerTypeRegistry.register_item("resource", sentinel.resource)
1221- self.assertIn(
1222- sentinel.resource,
1223- (item for name, item in PowerTypeRegistry))
1224+ def test_gen_power_types(self):
1225+
1226+ from provisioningserver.drivers import power
1227+ from provisioningserver.power import schema
1228+
1229+ class TestGenPowerTypesPowerDriver(power.PowerDriver):
1230+ name = 'test_gen_power_types'
1231+ description = "test_gen_power_types Power Driver."
1232+ settings = []
1233+
1234+ def detect_missing_packages(self):
1235+ # these packages are forever missing
1236+ return ['fake-package-one', 'fake-package-two']
1237+
1238+ def power_on(self, system_id, **kwargs):
1239+ raise NotImplementedError
1240+
1241+ def power_off(self, system_id, **kwargs):
1242+ raise NotImplementedError
1243+
1244+ def power_query(self, system_id, **kwargs):
1245+ raise NotImplementedError
1246+
1247+ # add my fake driver
1248+ driver = TestGenPowerTypesPowerDriver()
1249+ power.power_drivers_by_name[driver.name] = driver
1250+ schema.JSON_POWER_TYPE_PARAMETERS += [{'name': "test_gen_power_types"}]
1251+
1252+ # make sure fake packages are reported missing
1253+ power_types = list(drivers.gen_power_types())
1254+ self.assertEqual(15, len(power_types))
1255+ self.assertItemsEqual(
1256+ ['fake-package-one', 'fake-package-two'],
1257+ power_types[-1].get('missing_packages'))
1258
1259=== modified file 'src/provisioningserver/power/schema.py'
1260--- src/provisioningserver/power/schema.py 2015-08-21 13:59:15 +0000
1261+++ src/provisioningserver/power/schema.py 2015-10-01 23:31:26 +0000
1262@@ -96,6 +96,12 @@
1263 'description': {
1264 'type': 'string',
1265 },
1266+ 'missing_packages': {
1267+ 'type': 'array',
1268+ 'items': {
1269+ 'type': 'string',
1270+ },
1271+ },
1272 'fields': {
1273 'type': 'array',
1274 'items': POWER_TYPE_PARAMETER_FIELD_SCHEMA,
1275
1276=== modified file 'src/provisioningserver/rpc/clusterservice.py'
1277--- src/provisioningserver/rpc/clusterservice.py 2015-09-24 16:22:12 +0000
1278+++ src/provisioningserver/rpc/clusterservice.py 2015-10-01 23:31:26 +0000
1279@@ -29,7 +29,7 @@
1280 from provisioningserver.config import ClusterConfiguration
1281 from provisioningserver.drivers import (
1282 ArchitectureRegistry,
1283- PowerTypeRegistry,
1284+ gen_power_types,
1285 )
1286 from provisioningserver.drivers.hardware.mscm import probe_and_enlist_mscm
1287 from provisioningserver.drivers.hardware.msftocs import (
1288@@ -193,7 +193,7 @@
1289 :py:class:`~provisioningserver.rpc.cluster.DescribePowerTypes`.
1290 """
1291 return {
1292- 'power_types': [item for name, item in PowerTypeRegistry],
1293+ 'power_types': list(gen_power_types()),
1294 }
1295
1296 @cluster.ListSupportedArchitectures.responder