Merge ~ltrager/maas:s390x-power-driver into maas:master
- Git
- lp:~ltrager/maas
- s390x-power-driver
- Merge into master
Status: | Rejected |
---|---|
Rejected by: | Blake Rouse |
Proposed branch: | ~ltrager/maas:s390x-power-driver |
Merge into: | maas:master |
Prerequisite: | ~ltrager/maas:s390x-dpm-boot |
Diff against target: |
551 lines (+356/-4) 14 files modified
snap/snapcraft.yaml (+2/-0) src/maasserver/clusterrpc/driver_parameters.py (+2/-0) src/maasserver/clusterrpc/tests/test_driver_parameters.py (+21/-1) src/maasserver/forms/__init__.py (+23/-2) src/maasserver/forms/tests/test_machine.py (+17/-0) src/maasserver/forms/tests/test_machinewithmacaddresses.py (+11/-0) src/provisioningserver/drivers/__init__.py (+2/-1) src/provisioningserver/drivers/hardware/s390x.py (+61/-0) src/provisioningserver/drivers/hardware/tests/test_s390x.py (+83/-0) src/provisioningserver/drivers/power/registry.py (+2/-0) src/provisioningserver/drivers/power/s390x.py (+70/-0) src/provisioningserver/drivers/power/tests/test_s390x.py (+60/-0) src/provisioningserver/drivers/tests/test_base.py (+1/-0) utilities/check-imports (+1/-0) |
Related bugs: |
Reviewer | Review Type | Date Requested | Status |
---|---|---|---|
MAAS Lander | Needs Fixing | ||
Mike Pontillo (community) | Approve | ||
Review via email: mp+361231@code.launchpad.net |
Commit message
Add S390X power driver.
LPARs on the S390X are identified by both the boot and power driver using the
same UUID. The power form mixin has been modified to allow power fields to be
copied into node fields on form save. As MAC addresses are not consistent and
the S390X power driver can only be used on the S390X architecture neither
field is required when adding a machine.
Description of the change
TODO: Add power driver dependencies - pending LP:1805367
Known: The HMC takes awhile to respond. It can take ~3minutes to perform a power action.
When a Linux host shuts itself down the LPAR goes into a paused state. A paused LPAR is not running but it cannot be started until its stopped first. When an LPAR starts it goes Stopped -> Starting -> Paused -> Active. Stopping a paused LPAR is the solution but checking, stopping, then starting will be a long delay due to the HMC.
- de19895... by Lee Trager
-
Merge branch 'master' into node-uuid
- 7a47669... by Lee Trager
-
Merge branch 'node-uuid' into s390x-dpm-boot
- ea1e980... by Lee Trager
-
Fix lint
- bbb853a... by Lee Trager
-
Filter out ip= on the kernel command line for s390x
- 86b4d6c... by Lee Trager
-
Merge branch 's390x-dpm-boot' into s390x-power-driver
- 89490c3... by Lee Trager
-
Fix missing import
MAAS Lander (maas-lander) wrote : | # |
UNIT TESTS
-b s390x-power-driver lp:~ltrager/maas/+git/maas into -b master lp:~maas-committers/maas
STATUS: FAILED
LOG: http://
COMMIT: 89490c377b249c1
Mike Pontillo (mpontillo) wrote : | # |
You might plan to do this in a later branch, but I feel like if `zhmcclient` isn't installed, we should do something like what we do the VMware power driver.
See src/provisionin
The most confusing aspect of this branch is the usage of node_field; it isn't immediately clear what it means. (Some comments on that below; I feel this could be mitigated by improving the comments.)
What is the expected user experience for this change? Should the user be able to edit the UUID in both the power parameters /and/ the node model? It looks like the user is expected to input the UUID at machine creation time. Subsequently, can the user edit the hardware_uuid on the Machine? I don't see where we ever get the value of the node_field except when the form is submitted.
When you setattr() and the hardware_uuid field gets set on the Node model, will that always cause the Node to be saved, both in the case where a new Node is created, /and/ if the UUID is edited? (The setattr() seems to implicitly imply a save().)
- 83eda29... by Lee Trager
-
Merge branch 'master' into node-uuid
- e453feb... by Lee Trager
-
Merge branch 'node-uuid' into s390x-dpm-boot
- 760bf04... by Lee Trager
-
Merge branch 's390x-dpm-boot' into s390x-power-driver
- ec3a671... by Lee Trager
-
mpontillo fixes
Lee Trager (ltrager) wrote : | # |
Thanks for the review. I've added support for detecting if zhmcclient is missing from the system and gracefully ignoring it.
My node-uuid branch[1] adds support to MAAS to detect the hardware_uuid in lshw output during commissioning and allows booting using the hardware_uuid as an identifier. A user can't edit this value it must be discovered during commissioning. On S390X the hardware_uuid is used to identify which LPAR to power control and is the only way to identify a system while booting. Because MAAS needs to be able to identify the system node_field allows the user defined hardware_uuid from the power driver to be automatically copied to the node model. This allows us to avoid having to do an extra database query into the BMC model when systems are booting.
I did this generically using the name node_field as I could see this being useful for other power drivers in the future. For example virsh hosts normally use the same name for the VM ID and hostname.
[1] https:/
MAAS Lander (maas-lander) wrote : | # |
UNIT TESTS
-b s390x-power-driver lp:~ltrager/maas/+git/maas into -b master lp:~maas-committers/maas
STATUS: FAILED
LOG: http://
COMMIT: ec3a6713fc35afb
Mike Pontillo (mpontillo) wrote : | # |
Thanks for the updates; I'm happy with this branch now. I think Newell should also take a look, since he's got a lot of experience with the power drivers.
My one nit is "what happens if the client can't be imported" - I see you have a try/except in there, but I wonder what the user experience will be if the import doesn't succeed. (Maybe some logging should be added?)
I suggest you work with Newell to ensure this is consistent with other power drivers before landing.
- 620073f... by Lee Trager
-
Merge branch 'master' into s390x-dpm-boot
- c1a87c0... by Lee Trager
-
Merge branch 's390x-dpm-boot' into s390x-power-driver
MAAS Lander (maas-lander) wrote : | # |
UNIT TESTS
-b s390x-power-driver lp:~ltrager/maas/+git/maas into -b master lp:~maas-committers/maas
STATUS: FAILED
LOG: http://
COMMIT: c1a87c09768d53b
- dbc1cd5... by Lee Trager
-
Merge branch 'master' into s390x-dpm-boot
- a8702ba... by Lee Trager
-
Merge branch 's390x-dpm-boot' into s390x-power-driver
MAAS Lander (maas-lander) wrote : | # |
UNIT TESTS
-b s390x-power-driver lp:~ltrager/maas/+git/maas into -b master lp:~maas-committers/maas
STATUS: SUCCESS
COMMIT: a8702baa04c0be9
- 58651ef... by Lee Trager
-
Merge branch 'master' into s390x-dpm-boot
- 698cefe... by Lee Trager
-
Merge branch 's390x-dpm-boot' into s390x-power-driver
MAAS Lander (maas-lander) wrote : | # |
UNIT TESTS
-b s390x-power-driver lp:~ltrager/maas/+git/maas into -b master lp:~maas-committers/maas
STATUS: SUCCESS
COMMIT: 698cefe90d09aea
- c7af01e... by Lee Trager
-
Merge branch 'master' into s390x-dpm-boot
- ed525e9... by Lee Trager
-
Fix merge mistakes
- 5e0a738... by Lee Trager
-
Track transfer time for s390x DPM boot configuration files.
- ce9269d... by Lee Trager
-
Merge branch 's390x-dpm-boot' into s390x-power-driver
- 616d0b5... by Lee Trager
-
Merge branch 'master' into s390x-dpm-boot
- b64c34b... by Lee Trager
-
Merge branch 's390x-dpm-boot' into s390x-power-driver
- 77306d0... by Lee Trager
-
Fix broken tests
- 166980c... by Lee Trager
-
Merge branch 's390x-dpm-boot' into s390x-power-driver
- af170f4... by Lee Trager
-
Merge branch 'master' into s390x-dpm-boot
- 107f430... by Lee Trager
-
Merge branch 's390x-dpm-boot' into s390x-power-driver
- 1e2a32c... by Lee Trager
-
Merge branch 'master' into s390x-dpm-boot
- f395212... by Lee Trager
-
Merge branch 's390x-dpm-boot' into s390x-power-driver
- 9dbaef8... by Lee Trager
-
Stop partition before starting it.
- 01a0d47... by Lee Trager
-
Merge branch 'master' into s390x-dpm-boot
- 3377f4e... by Lee Trager
-
Merge branch 's390x-dpm-boot' into s390x-power-driver
- 9a3e330... by Lee Trager
-
Merge branch 'master' into s390x-dpm-boot
- 0cad56b... by Lee Trager
-
Merge branch 's390x-dpm-boot' into s390x-power-driver
MAAS Lander (maas-lander) wrote : | # |
UNIT TESTS
-b s390x-power-driver lp:~ltrager/maas/+git/maas into -b master lp:~maas-committers/maas
STATUS: FAILED
LOG: http://
COMMIT: 0cad56b1a682511
Blake Rouse (blake-rouse) wrote : | # |
Rejecting due to inactivity.
Unmerged commits
- 0cad56b... by Lee Trager
-
Merge branch 's390x-dpm-boot' into s390x-power-driver
- 9a3e330... by Lee Trager
-
Merge branch 'master' into s390x-dpm-boot
- 3377f4e... by Lee Trager
-
Merge branch 's390x-dpm-boot' into s390x-power-driver
- 01a0d47... by Lee Trager
-
Merge branch 'master' into s390x-dpm-boot
- 9dbaef8... by Lee Trager
-
Stop partition before starting it.
- f395212... by Lee Trager
-
Merge branch 's390x-dpm-boot' into s390x-power-driver
- 1e2a32c... by Lee Trager
-
Merge branch 'master' into s390x-dpm-boot
- 107f430... by Lee Trager
-
Merge branch 's390x-dpm-boot' into s390x-power-driver
- af170f4... by Lee Trager
-
Merge branch 'master' into s390x-dpm-boot
- 166980c... by Lee Trager
-
Merge branch 's390x-dpm-boot' into s390x-power-driver
Preview Diff
1 | diff --git a/snap/snapcraft.yaml b/snap/snapcraft.yaml |
2 | index e1a40bb..1355808 100644 |
3 | --- a/snap/snapcraft.yaml |
4 | +++ b/snap/snapcraft.yaml |
5 | @@ -96,6 +96,8 @@ parts: |
6 | - squid |
7 | - tcpdump |
8 | - ubuntu-keyring |
9 | + python-packages: |
10 | + - zhmcclient |
11 | organize: |
12 | lib/python3.*/site-packages/etc/*: etc |
13 | lib/python3.*/site-packages/usr/bin/*: usr/bin |
14 | diff --git a/src/maasserver/clusterrpc/driver_parameters.py b/src/maasserver/clusterrpc/driver_parameters.py |
15 | index 3502cd3..dc1746e 100644 |
16 | --- a/src/maasserver/clusterrpc/driver_parameters.py |
17 | +++ b/src/maasserver/clusterrpc/driver_parameters.py |
18 | @@ -83,6 +83,8 @@ def make_form_field(json_field): |
19 | form_field = field_class( |
20 | label=json_field['label'], required=json_field['required'], |
21 | **extra_parameters) |
22 | + form_field.name = json_field['name'] |
23 | + form_field.node_field = json_field.get('node_field') |
24 | return form_field |
25 | |
26 | |
27 | diff --git a/src/maasserver/clusterrpc/tests/test_driver_parameters.py b/src/maasserver/clusterrpc/tests/test_driver_parameters.py |
28 | index f03c20d..467831e 100644 |
29 | --- a/src/maasserver/clusterrpc/tests/test_driver_parameters.py |
30 | +++ b/src/maasserver/clusterrpc/tests/test_driver_parameters.py |
31 | @@ -1,4 +1,4 @@ |
32 | -# Copyright 2012-2016 Canonical Ltd. This software is licensed under the |
33 | +# Copyright 2012-2018 Canonical Ltd. This software is licensed under the |
34 | # GNU Affero General Public License version 3 (see the file LICENSE). |
35 | |
36 | """Tests for power parameters.""" |
37 | @@ -191,6 +191,23 @@ class TestMakeFormField(MAASServerTestCase): |
38 | django_field = make_form_field(json_field) |
39 | self.assertEquals(json_field['default'], django_field.initial) |
40 | |
41 | + def test__sets_node_field(self): |
42 | + # Tests that if a power drive specifies a field should be mapped to |
43 | + # the node model the copy happens. Currently only the s390x power |
44 | + # driver uses this for the hardware_uuid field. |
45 | + name = factory.make_name('name') |
46 | + node_field = factory.make_name('node_field') |
47 | + json_field = { |
48 | + 'name': name, |
49 | + 'label': 'Some Field', |
50 | + 'field_type': 'string', |
51 | + 'required': False, |
52 | + 'node_field': node_field, |
53 | + } |
54 | + django_field = make_form_field(json_field) |
55 | + self.assertEquals(json_field['name'], django_field.name) |
56 | + self.assertEquals(json_field['node_field'], django_field.node_field) |
57 | + |
58 | |
59 | class TestMakeSettingField(MAASServerTestCase): |
60 | """Test that make_setting_field() creates JSON-verifiable fields.""" |
61 | @@ -209,6 +226,7 @@ class TestMakeSettingField(MAASServerTestCase): |
62 | 'choices': [], |
63 | 'default': '', |
64 | 'scope': 'bmc', |
65 | + 'node_field': None, |
66 | } |
67 | self.assertEqual(expected_field, json_field) |
68 | |
69 | @@ -224,6 +242,7 @@ class TestMakeSettingField(MAASServerTestCase): |
70 | ], |
71 | 'default': 'spam', |
72 | 'scope': 'bmc', |
73 | + 'node_field': None, |
74 | } |
75 | json_field = make_setting_field(**expected_field) |
76 | self.assertEqual(expected_field, json_field) |
77 | @@ -244,6 +263,7 @@ class TestMakeSettingField(MAASServerTestCase): |
78 | 'choices': [], |
79 | 'default': '', |
80 | 'scope': 'bmc', |
81 | + 'node_field': None, |
82 | } |
83 | self.assertEqual(expected_field, json_field) |
84 | |
85 | diff --git a/src/maasserver/forms/__init__.py b/src/maasserver/forms/__init__.py |
86 | index 021acdd..0347ab1 100644 |
87 | --- a/src/maasserver/forms/__init__.py |
88 | +++ b/src/maasserver/forms/__init__.py |
89 | @@ -379,6 +379,17 @@ class WithPowerTypeMixin: |
90 | if type_changed or len(initial_parameters) > 0: |
91 | machine.power_parameters = form.cleaned_data.get( |
92 | params_field_name) |
93 | + # Set the specified node fields to BMC field values. This allows for |
94 | + # a power driver field to be copied onto a node model field. s390x |
95 | + # uses the hardware_uuid field to identify the LPAR to the power driver |
96 | + # and boots by hardware_uuid, not MAC. This allows a user to set both |
97 | + # using the power field as a user normally can't set the hardware_uuid |
98 | + # field. |
99 | + for field in form.fields['power_parameters'].fields: |
100 | + if getattr(field, 'node_field', None): |
101 | + setattr( |
102 | + machine, field.node_field, |
103 | + form.cleaned_data['power_parameters'][field.name]) |
104 | |
105 | def clean(self): |
106 | cleaned_data = super(WithPowerTypeMixin, self).clean() |
107 | @@ -738,8 +749,11 @@ class MachineForm(NodeForm): |
108 | invalid_arch_message = compose_invalid_choice_text( |
109 | 'architecture', choices) |
110 | power_type = self.data.get('power_type', None) |
111 | + if power_type == 's390x': |
112 | + default_arch = 's390x/generic' |
113 | self.fields['architecture'] = forms.ChoiceField( |
114 | - choices=choices, required=(power_type != 'ipmi' or requires_arch), |
115 | + choices=choices, required=( |
116 | + power_type not in ['ipmi', 's390x'] or requires_arch), |
117 | initial=default_arch, error_messages={ |
118 | 'invalid_choice': invalid_arch_message}) |
119 | |
120 | @@ -801,6 +815,12 @@ class MachineForm(NodeForm): |
121 | distro_series) |
122 | except ValidationError as e: |
123 | set_form_error(self, 'hwe_kernel', e.message) |
124 | + |
125 | + # The s390x power driver can only be used on s390x. |
126 | + if (not cleaned_data.get('architecture') and |
127 | + self.data.get('power_type') == 's390x'): |
128 | + cleaned_data['architecture'] = 's390x/generic' |
129 | + |
130 | return cleaned_data |
131 | |
132 | def is_valid(self): |
133 | @@ -1286,7 +1306,8 @@ class WithMACAddressesMixin: |
134 | def set_up_mac_addresses_field(self): |
135 | macs = [mac for mac in self.data.getlist('mac_addresses') if mac] |
136 | self.fields['mac_addresses'] = MultipleMACAddressField( |
137 | - len(macs), required=(self.data.get('power_type') != 'ipmi')) |
138 | + len(macs), required=( |
139 | + self.data.get('power_type') not in ['ipmi', 's390x'])) |
140 | self.data = self.data.copy() |
141 | self.data['mac_addresses'] = macs |
142 | |
143 | diff --git a/src/maasserver/forms/tests/test_machine.py b/src/maasserver/forms/tests/test_machine.py |
144 | index 9a79406..664f0de 100644 |
145 | --- a/src/maasserver/forms/tests/test_machine.py |
146 | +++ b/src/maasserver/forms/tests/test_machine.py |
147 | @@ -461,3 +461,20 @@ class TestAdminMachineForm(MAASServerTestCase): |
148 | instance=node) |
149 | node = form.save() |
150 | self.assertEqual(power_type, node.power_type) |
151 | + |
152 | + def test_AdminMachineForm_updates_node_fields(self): |
153 | + arch = make_usable_architecture(self) |
154 | + uuid = factory.make_UUID() |
155 | + form = AdminMachineForm(data={ |
156 | + 'architecture': arch, |
157 | + 'power_type': 's390x', |
158 | + 'power_parameters': { |
159 | + 'power_address': factory.make_ip_address(), |
160 | + 'power_user': factory.make_name('user'), |
161 | + 'power_pass': factory.make_name('pass'), |
162 | + 'power_uuid': uuid, |
163 | + }, |
164 | + }) |
165 | + self.assertTrue(form.is_valid(), form.errors) |
166 | + node = form.save() |
167 | + self.assertEqual(uuid, node.hardware_uuid) |
168 | diff --git a/src/maasserver/forms/tests/test_machinewithmacaddresses.py b/src/maasserver/forms/tests/test_machinewithmacaddresses.py |
169 | index 8bc5f98..0b51072 100644 |
170 | --- a/src/maasserver/forms/tests/test_machinewithmacaddresses.py |
171 | +++ b/src/maasserver/forms/tests/test_machinewithmacaddresses.py |
172 | @@ -159,6 +159,17 @@ class MachineWithMACAddressesFormTest(MAASServerTestCase): |
173 | form = MachineWithMACAddressesForm(data=params) |
174 | self.assertTrue(form.is_valid()) |
175 | |
176 | + def test__no_architecture_or_mac_addresses_is_ok_for_s390x(self): |
177 | + # S390X hosts are identified by hardware_uuid |
178 | + params = self.make_params( |
179 | + mac_addresses=[], architecture='s390x/generic') |
180 | + params['architecture'] = None |
181 | + params['power_type'] = 's390x' |
182 | + form = MachineWithMACAddressesForm(data=params) |
183 | + self.assertTrue(form.is_valid(), form.errors) |
184 | + node = form.save() |
185 | + self.assertEquals('s390x/generic', node.architecture) |
186 | + |
187 | def test__save(self): |
188 | macs = ['aa:bb:cc:dd:ee:ff', '9a:bb:c3:33:e5:7f'] |
189 | form = MachineWithMACAddressesForm( |
190 | diff --git a/src/provisioningserver/drivers/__init__.py b/src/provisioningserver/drivers/__init__.py |
191 | index 2770870..3fde00a 100644 |
192 | --- a/src/provisioningserver/drivers/__init__.py |
193 | +++ b/src/provisioningserver/drivers/__init__.py |
194 | @@ -126,7 +126,7 @@ class SETTING_SCOPE: |
195 | |
196 | def make_setting_field( |
197 | name, label, field_type=None, choices=None, default=None, |
198 | - required=False, scope=SETTING_SCOPE.BMC): |
199 | + required=False, scope=SETTING_SCOPE.BMC, node_field=None): |
200 | """Helper function for building a JSON setting parameters field. |
201 | |
202 | :param name: The name of the field. |
203 | @@ -165,6 +165,7 @@ def make_setting_field( |
204 | 'choices': choices, |
205 | 'default': default, |
206 | 'scope': scope, |
207 | + 'node_field': node_field, |
208 | } |
209 | |
210 | |
211 | diff --git a/src/provisioningserver/drivers/hardware/s390x.py b/src/provisioningserver/drivers/hardware/s390x.py |
212 | new file mode 100644 |
213 | index 0000000..3677f65 |
214 | --- /dev/null |
215 | +++ b/src/provisioningserver/drivers/hardware/s390x.py |
216 | @@ -0,0 +1,61 @@ |
217 | +# Copyright 2019 Canonical Ltd. This software is licensed under the |
218 | +# GNU Affero General Public License version 3 (see the file LICENSE). |
219 | + |
220 | +from provisioningserver.logger import get_maas_logger |
221 | + |
222 | + |
223 | +maaslog = get_maas_logger('drivers.s390x') |
224 | + |
225 | + |
226 | +class S390XHMCClient: |
227 | + |
228 | + def __init__(self, host, user, password): |
229 | + # Attempt to import zhmcclient |
230 | + try: |
231 | + import zhmcclient |
232 | + except ImportError: |
233 | + self.session = None |
234 | + self.client = None |
235 | + else: |
236 | + self.session = zhmcclient.Session(host, user, password) |
237 | + self.client = zhmcclient.Client(self.session) |
238 | + |
239 | + def get_power_state(self, uuid): |
240 | + maaslog.info('Checking power state for {}'.format(uuid)) |
241 | + partition = self._get_partition(uuid) |
242 | + if partition: |
243 | + return partition.properties['status'] |
244 | + else: |
245 | + return 'unknown' |
246 | + |
247 | + def start_partition(self, uuid): |
248 | + partition = self._get_partition(uuid) |
249 | + if partition: |
250 | + if partition.properties['status'] == 'active': |
251 | + # Partition is already running, nothing to do. |
252 | + return |
253 | + elif partition.properties['status'] != 'stopped': |
254 | + # Partition cannot be started unless it is in a 'stopped' |
255 | + # state. LPAR's which are shutdown from within Linux go |
256 | + # into a 'paused' state which cannot be started without |
257 | + # being stopped first. |
258 | + partition.stop(wait_for_completion=True) |
259 | + partition.start(wait_for_completion=False) |
260 | + |
261 | + def stop_partition(self, uuid): |
262 | + partition = self._get_partition(uuid) |
263 | + if partition: |
264 | + partition.stop(wait_for_completion=False) |
265 | + |
266 | + def _get_partition(self, uuid): |
267 | + if None in (self.session, self.client): |
268 | + return None |
269 | + for cpc in self.client.cpcs.list(): |
270 | + if not cpc.dpm_enabled: |
271 | + maaslog.info('DPM is not enabled') |
272 | + continue |
273 | + partitions = cpc.partitions.list( |
274 | + full_properties=True, filter_args={'object-id': uuid}) |
275 | + if partitions: |
276 | + maaslog.info('Found partition') |
277 | + return partitions[0] |
278 | diff --git a/src/provisioningserver/drivers/hardware/tests/test_s390x.py b/src/provisioningserver/drivers/hardware/tests/test_s390x.py |
279 | new file mode 100644 |
280 | index 0000000..327f1ae |
281 | --- /dev/null |
282 | +++ b/src/provisioningserver/drivers/hardware/tests/test_s390x.py |
283 | @@ -0,0 +1,83 @@ |
284 | +# Copyright 2019 Canonical Ltd. This software is licensed under the |
285 | +# GNU Affero General Public License version 3 (see the file LICENSE). |
286 | + |
287 | +"""Tests for `provisioningserver.drivers.hardware.s390x`. |
288 | +""" |
289 | + |
290 | +__all__ = [] |
291 | + |
292 | +from unittest.mock import MagicMock |
293 | + |
294 | +from maastesting.factory import factory |
295 | +from maastesting.matchers import ( |
296 | + MockCalledOnceWith, |
297 | + MockNotCalled, |
298 | +) |
299 | +from maastesting.testcase import MAASTestCase |
300 | +from provisioningserver.drivers.hardware import s390x |
301 | + |
302 | + |
303 | +class TestS390XHMCClient(MAASTestCase): |
304 | + """Tests for `S390XHMCClient`.""" |
305 | + |
306 | + def test_get_power_state(self): |
307 | + client = s390x.S390XHMCClient('foo', 'bar', 'baz') |
308 | + mock_get_partition = self.patch(client, '_get_partition') |
309 | + part = MagicMock() |
310 | + mock_get_partition.return_value = part |
311 | + self.assertEquals( |
312 | + part.properties['status'], |
313 | + client.get_power_state(factory.make_UUID())) |
314 | + |
315 | + def test_get_power_state_unknown_with_no_driver(self): |
316 | + client = s390x.S390XHMCClient('foo', 'bar', 'baz') |
317 | + client.session = client.client = None |
318 | + self.assertEquals( |
319 | + 'unknown', client.get_power_state(factory.make_UUID())) |
320 | + |
321 | + def test_start_partition(self): |
322 | + client = s390x.S390XHMCClient('foo', 'bar', 'baz') |
323 | + uuid = factory.make_UUID() |
324 | + mock_get_partition = self.patch(client, '_get_partition') |
325 | + mock_get_partition.return_value = MagicMock() |
326 | + client.start_partition(uuid) |
327 | + self.assertThat(mock_get_partition, MockCalledOnceWith(uuid)) |
328 | + self.assertThat( |
329 | + mock_get_partition.return_value.start, |
330 | + MockCalledOnceWith(wait_for_completion=False)) |
331 | + |
332 | + def test_start_partition_does_nothing_if_on(self): |
333 | + client = s390x.S390XHMCClient('foo', 'bar', 'baz') |
334 | + uuid = factory.make_UUID() |
335 | + partition = MagicMock() |
336 | + partition.properties = {'status': 'active'} |
337 | + mock_get_partition = self.patch(client, '_get_partition') |
338 | + mock_get_partition.return_value = partition |
339 | + client.start_partition(uuid) |
340 | + self.assertThat(mock_get_partition, MockCalledOnceWith(uuid)) |
341 | + self.assertThat(partition.start, MockNotCalled()) |
342 | + |
343 | + def test_start_partition_stops_if_needed(self): |
344 | + client = s390x.S390XHMCClient('foo', 'bar', 'baz') |
345 | + uuid = factory.make_UUID() |
346 | + partition = MagicMock() |
347 | + partition.properties = {'status': factory.make_name('status')} |
348 | + mock_get_partition = self.patch(client, '_get_partition') |
349 | + mock_get_partition.return_value = partition |
350 | + client.start_partition(uuid) |
351 | + self.assertThat(mock_get_partition, MockCalledOnceWith(uuid)) |
352 | + self.assertThat( |
353 | + partition.stop, MockCalledOnceWith(wait_for_completion=True)) |
354 | + self.assertThat( |
355 | + partition.start, MockCalledOnceWith(wait_for_completion=False)) |
356 | + |
357 | + def test_stop_partition(self): |
358 | + client = s390x.S390XHMCClient('foo', 'bar', 'baz') |
359 | + uuid = factory.make_UUID() |
360 | + mock_get_partition = self.patch(client, '_get_partition') |
361 | + mock_get_partition.return_value = MagicMock() |
362 | + client.stop_partition(uuid) |
363 | + self.assertThat(mock_get_partition, MockCalledOnceWith(uuid)) |
364 | + self.assertThat( |
365 | + mock_get_partition.return_value.stop, |
366 | + MockCalledOnceWith(wait_for_completion=False)) |
367 | diff --git a/src/provisioningserver/drivers/power/registry.py b/src/provisioningserver/drivers/power/registry.py |
368 | index ce228bd..98dcc74 100644 |
369 | --- a/src/provisioningserver/drivers/power/registry.py |
370 | +++ b/src/provisioningserver/drivers/power/registry.py |
371 | @@ -24,6 +24,7 @@ from provisioningserver.drivers.power.nova import NovaPowerDriver |
372 | from provisioningserver.drivers.power.openbmc import OpenBMCPowerDriver |
373 | from provisioningserver.drivers.power.recs import RECSPowerDriver |
374 | from provisioningserver.drivers.power.redfish import RedfishPowerDriver |
375 | +from provisioningserver.drivers.power.s390x import S390XPowerDriver |
376 | from provisioningserver.drivers.power.seamicro import SeaMicroPowerDriver |
377 | from provisioningserver.drivers.power.ucsm import UCSMPowerDriver |
378 | from provisioningserver.drivers.power.virsh import VirshPowerDriver |
379 | @@ -65,6 +66,7 @@ power_drivers = [ |
380 | OpenBMCPowerDriver(), |
381 | RECSPowerDriver(), |
382 | RedfishPowerDriver(), |
383 | + S390XPowerDriver(), |
384 | SeaMicroPowerDriver(), |
385 | UCSMPowerDriver(), |
386 | VirshPowerDriver(), |
387 | diff --git a/src/provisioningserver/drivers/power/s390x.py b/src/provisioningserver/drivers/power/s390x.py |
388 | new file mode 100644 |
389 | index 0000000..287d6af |
390 | --- /dev/null |
391 | +++ b/src/provisioningserver/drivers/power/s390x.py |
392 | @@ -0,0 +1,70 @@ |
393 | +# Copyright 2018 Canonical Ltd. This software is licensed under the |
394 | +# GNU Affero General Public License version 3 (see the file LICENSE). |
395 | + |
396 | +"""s390x Power Driver.""" |
397 | + |
398 | +__all__ = [] |
399 | + |
400 | +from importlib import import_module |
401 | + |
402 | +from provisioningserver.drivers import ( |
403 | + make_ip_extractor, |
404 | + make_setting_field, |
405 | + SETTING_SCOPE, |
406 | +) |
407 | +from provisioningserver.drivers.hardware.s390x import S390XHMCClient |
408 | +from provisioningserver.drivers.power import PowerDriver |
409 | + |
410 | + |
411 | +def get_client(context): |
412 | + return S390XHMCClient( |
413 | + host=context['power_address'], user=context['power_user'], |
414 | + password=context['power_pass']) |
415 | + |
416 | + |
417 | +class S390XPowerDriver(PowerDriver): |
418 | + |
419 | + name = 's390x' |
420 | + chassis = False |
421 | + description = 'IBM Z (s390x)' |
422 | + settings = [ |
423 | + make_setting_field('power_address', 'HMC host', required=True), |
424 | + make_setting_field('power_user', 'HMC user', required=True), |
425 | + make_setting_field( |
426 | + 'power_pass', 'HMC password', |
427 | + required=True, field_type='password'), |
428 | + make_setting_field( |
429 | + 'power_uuid', 'Partition UUID', scope=SETTING_SCOPE.NODE, |
430 | + required=True, node_field='hardware_uuid'), |
431 | + ] |
432 | + |
433 | + ip_extractor = make_ip_extractor('power_address') |
434 | + |
435 | + def detect_missing_packages(self): |
436 | + try: |
437 | + import_module('zhmcclient') |
438 | + except ImportError: |
439 | + return ['python3-zhmcclient'] |
440 | + else: |
441 | + return [] |
442 | + |
443 | + def power_on(self, system_id, context): |
444 | + """Power on S390X partition.""" |
445 | + client = get_client(context) |
446 | + client.start_partition(context['power_uuid']) |
447 | + |
448 | + def power_off(self, system_id, context): |
449 | + """Power off Virsh node.""" |
450 | + client = get_client(context) |
451 | + client.stop_partition(context['power_uuid']) |
452 | + |
453 | + def power_query(self, system_id, context): |
454 | + """Power query Virsh node.""" |
455 | + client = get_client(context) |
456 | + state = client.get_power_state(context['power_uuid']) |
457 | + if state in ['starting', 'active', 'stopping', 'degraded']: |
458 | + return 'on' |
459 | + elif state == 'unknown': |
460 | + return 'unknown' |
461 | + else: |
462 | + return 'off' |
463 | diff --git a/src/provisioningserver/drivers/power/tests/test_s390x.py b/src/provisioningserver/drivers/power/tests/test_s390x.py |
464 | new file mode 100644 |
465 | index 0000000..3fccceb |
466 | --- /dev/null |
467 | +++ b/src/provisioningserver/drivers/power/tests/test_s390x.py |
468 | @@ -0,0 +1,60 @@ |
469 | +# Copyright 2018 Canonical Ltd. This software is licensed under the |
470 | +# GNU Affero General Public License version 3 (see the file LICENSE). |
471 | + |
472 | +"""Tests for `provisioningserver.drivers.power.s390x`.""" |
473 | + |
474 | +__all__ = [] |
475 | + |
476 | +import random |
477 | +from unittest.mock import MagicMock |
478 | + |
479 | +from maastesting.factory import factory |
480 | +from maastesting.matchers import MockCalledOnceWith |
481 | +from maastesting.testcase import MAASTestCase |
482 | +from provisioningserver.drivers.power import s390x as s390x_module |
483 | +from provisioningserver.drivers.power.s390x import S390XPowerDriver |
484 | + |
485 | + |
486 | +class TestS390XPowerDriver(MAASTestCase): |
487 | + |
488 | + def setUp(self): |
489 | + super().setUp() |
490 | + self.driver = S390XPowerDriver() |
491 | + self.mock_get_client = self.patch(s390x_module, 'get_client') |
492 | + self.system_id = factory.make_name('system_id') |
493 | + self.context = { |
494 | + 'power_uuid': factory.make_UUID(), |
495 | + } |
496 | + |
497 | + def test_power_on(self): |
498 | + self.driver.power_on(self.system_id, self.context) |
499 | + self.assertThat( |
500 | + self.mock_get_client.return_value.start_partition, |
501 | + MockCalledOnceWith(self.context['power_uuid'])) |
502 | + |
503 | + def test_power_off(self): |
504 | + self.driver.power_off(self.system_id, self.context) |
505 | + self.assertThat( |
506 | + self.mock_get_client.return_value.stop_partition, |
507 | + MockCalledOnceWith(self.context['power_uuid'])) |
508 | + |
509 | + def test_power_query_on(self): |
510 | + mock_client = MagicMock() |
511 | + self.mock_get_client.return_value = mock_client |
512 | + mock_client.get_power_state.return_value = random.choice([ |
513 | + 'starting', 'active', 'stopping', 'degraded']) |
514 | + self.assertEquals( |
515 | + 'on', self.driver.power_query(self.system_id, self.context)) |
516 | + self.assertThat( |
517 | + mock_client.get_power_state, |
518 | + MockCalledOnceWith(self.context['power_uuid'])) |
519 | + |
520 | + def test_power_query_off(self): |
521 | + mock_client = MagicMock() |
522 | + self.mock_get_client.return_value = mock_client |
523 | + mock_client.get_power_state.return_value = 'off' |
524 | + self.assertEquals( |
525 | + 'off', self.driver.power_query(self.system_id, self.context)) |
526 | + self.assertThat( |
527 | + mock_client.get_power_state, |
528 | + MockCalledOnceWith(self.context['power_uuid'])) |
529 | diff --git a/src/provisioningserver/drivers/tests/test_base.py b/src/provisioningserver/drivers/tests/test_base.py |
530 | index 250fa09..726b727 100644 |
531 | --- a/src/provisioningserver/drivers/tests/test_base.py |
532 | +++ b/src/provisioningserver/drivers/tests/test_base.py |
533 | @@ -229,6 +229,7 @@ class TestMakeSettingField(MAASTestCase): |
534 | 'default': default, |
535 | 'required': True, |
536 | 'scope': SETTING_SCOPE.NODE, |
537 | + 'node_field': None, |
538 | }, setting) |
539 | |
540 | |
541 | diff --git a/utilities/check-imports b/utilities/check-imports |
542 | index 6344767..d75e720 100755 |
543 | --- a/utilities/check-imports |
544 | +++ b/utilities/check-imports |
545 | @@ -232,6 +232,7 @@ RackControllerRule = Rule( |
546 | Allow("uvloop"), |
547 | Allow("yaml"), |
548 | Allow("zope.interface|zope.interface.**"), |
549 | + Allow("zhmcclient|zhmcclient.**|zhmcclient_mock.**"), |
550 | Allow(StandardLibraries), |
551 | ) |
552 |
UNIT TESTS
-b s390x-power-driver lp:~ltrager/maas/+git/maas into -b master lp:~maas-committers/maas
STATUS: FAILED maas-ci- jenkins. internal: 8080/job/ maas/job/ branch- tester/ 4812/console f36175880b9bce2 e9cbd8f9e8
LOG: http://
COMMIT: dce33319b74388d