Merge lp:~newell-jensen/maas/fix-1553841 into lp:~maas-committers/maas/trunk
- fix-1553841
- Merge into trunk
Status: | Merged |
---|---|
Approved by: | Newell Jensen |
Approved revision: | no longer in the source branch. |
Merged at revision: | 4998 |
Proposed branch: | lp:~newell-jensen/maas/fix-1553841 |
Merge into: | lp:~maas-committers/maas/trunk |
Diff against target: |
221 lines (+54/-18) 2 files modified
src/provisioningserver/drivers/power/amt.py (+26/-5) src/provisioningserver/drivers/power/tests/test_amt.py (+28/-13) |
To merge this branch: | bzr merge lp:~newell-jensen/maas/fix-1553841 |
Related bugs: |
Reviewer | Review Type | Date Requested | Status |
---|---|---|---|
Mike Pontillo (community) | Approve | ||
Review via email: mp+293819@code.launchpad.net |
Commit message
Update AMT error messages to instruct the user if they have entered an incorrect BMC configuration (power parameters).
Description of the change
Newell Jensen (newell-jensen) wrote : | # |
> Code looks fine to me. Just a couple things I'm wondering:
>
> * Can you update the commit message so that it is more accurate? (it makes it
> sound like error propagation in general, but the branch seems to just fix up
> the AMT exceptions so that they are thrown correctly.)
>
> * Are we confident the AMT utilities will always print these particular
> strings? (I assume we're using a consistent locale, so that the strings don't
> show up translated into another language and surprise us.
The subprocesses are executed using select_
And I was wondering
> if using just the HTTP status code for matching might be more future-proof...
> assuming the output isn't too large, and we could match the number in multiple
> places in the string...?)
I am on the fence about changing what we are matching on. Here are the two errors that amttool gives back:
401 Unauthorized at /usr/bin/amttool line 126.
and
500 Can't connect to 172.24.
If you think we should match just on the error status code and don't think it will cause any future issues, that is fine with me. These messages are coming back from amttool, so how I have it now seems fine to me.
Mike Pontillo (mpontillo) wrote : | # |
Yeah. I suppose the other option would be to just match the beginning of the string, if it's always the first thing in the output.
OK, I'll approve this branch, but please update the commit message to be more accurate before you land it.
MAAS Lander (maas-lander) wrote : | # |
The attempt to merge lp:~newell-jensen/maas/fix-1553841 into lp:maas failed. Below is the output from the failed tests.
Hit:1 http://
Get:2 http://
Hit:3 http://
Get:4 http://
Fetched 185 kB in 0s (389 kB/s)
Reading package lists...
sudo DEBIAN_
--no-
Reading package lists...
Building dependency tree...
Reading state information...
apache2 is already the newest version (2.4.18-2ubuntu3).
archdetect-deb is already the newest version (1.117ubuntu2).
authbind is already the newest version (2.1.1+nmu1).
bash is already the newest version (4.3-14ubuntu1).
build-essential is already the newest version (12.1ubuntu2).
bzr is already the newest version (2.7.0-2ubuntu1).
curl is already the newest version (7.47.0-1ubuntu2).
debhelper is already the newest version (9.20160115ubun
distro-info is already the newest version (0.14build1).
freeipmi-tools is already the newest version (1.4.11-1ubuntu1).
git is already the newest version (1:2.7.4-0ubuntu1).
isc-dhcp-common is already the newest version (4.3.3-5ubuntu12).
libjs-angularjs is already the newest version (1.2.28-1ubuntu2).
libjs-jquery is already the newest version (1.11.3+dfsg-4).
libjs-yui3-full is already the newest version (3.5.1-1ubuntu3).
libjs-yui3-min is already the newest version (3.5.1-1ubuntu3).
libpq-dev is already the newest version (9.5.2-1).
make is already the newest version (4.1-6).
postgresql is already the newest version (...
Mike Pontillo (mpontillo) wrote : | # |
Looks like you need to "make format".
FAIL: maastesting.
=(
Preview Diff
1 | === modified file 'src/provisioningserver/drivers/power/amt.py' | |||
2 | --- src/provisioningserver/drivers/power/amt.py 2016-04-15 14:54:37 +0000 | |||
3 | +++ src/provisioningserver/drivers/power/amt.py 2016-05-05 20:22:05 +0000 | |||
4 | @@ -21,9 +21,10 @@ | |||
5 | 21 | from provisioningserver.drivers.power import ( | 21 | from provisioningserver.drivers.power import ( |
6 | 22 | is_power_parameter_set, | 22 | is_power_parameter_set, |
7 | 23 | PowerActionError, | 23 | PowerActionError, |
8 | 24 | PowerAuthError, | ||
9 | 24 | PowerConnError, | 25 | PowerConnError, |
10 | 25 | PowerDriver, | 26 | PowerDriver, |
12 | 26 | PowerFatalError, | 27 | PowerSettingError, |
13 | 27 | ) | 28 | ) |
14 | 28 | from provisioningserver.utils import ( | 29 | from provisioningserver.utils import ( |
15 | 29 | shell, | 30 | shell, |
16 | @@ -31,6 +32,20 @@ | |||
17 | 31 | ) | 32 | ) |
18 | 32 | 33 | ||
19 | 33 | 34 | ||
20 | 35 | AMT_ERRORS = { | ||
21 | 36 | '401 Unauthorized': { | ||
22 | 37 | 'message': ( | ||
23 | 38 | "Incorrect password. Check BMC configuration and try again."), | ||
24 | 39 | 'exception': PowerAuthError | ||
25 | 40 | }, | ||
26 | 41 | "500 Can't connect": { | ||
27 | 42 | 'message': ( | ||
28 | 43 | "Could not connect to BMC. " | ||
29 | 44 | "Check BMC configuration and try again."), | ||
30 | 45 | 'exception': PowerConnError | ||
31 | 46 | }, | ||
32 | 47 | } | ||
33 | 48 | |||
34 | 34 | REQUIRED_PACKAGES = [["amttool", "amtterm"], ["wsman", "wsmancli"]] | 49 | REQUIRED_PACKAGES = [["amttool", "amtterm"], ["wsman", "wsmancli"]] |
35 | 35 | 50 | ||
36 | 36 | 51 | ||
37 | @@ -176,7 +191,7 @@ | |||
38 | 176 | sleep(1) | 191 | sleep(1) |
39 | 177 | 192 | ||
40 | 178 | if output is None: | 193 | if output is None: |
42 | 179 | raise PowerFatalError("amttool power querying FAILED.") | 194 | raise PowerActionError("amttool power querying failed.") |
43 | 180 | 195 | ||
44 | 181 | # Ensure that from this point forward that output is a str. | 196 | # Ensure that from this point forward that output is a str. |
45 | 182 | output = output.decode("utf-8") | 197 | output = output.decode("utf-8") |
46 | @@ -228,7 +243,7 @@ | |||
47 | 228 | elif state in ('6', '8', '12', '13'): | 243 | elif state in ('6', '8', '12', '13'): |
48 | 229 | return 'off' | 244 | return 'off' |
49 | 230 | else: | 245 | else: |
51 | 231 | raise PowerFatalError( | 246 | raise PowerActionError( |
52 | 232 | "Got unknown power state from node: %s" % state) | 247 | "Got unknown power state from node: %s" % state) |
53 | 233 | 248 | ||
54 | 234 | def amttool_restart(self, ip_address, power_pass, amttool_boot_mode): | 249 | def amttool_restart(self, ip_address, power_pass, amttool_boot_mode): |
55 | @@ -306,12 +321,16 @@ | |||
56 | 306 | stdout = stdout.decode("utf-8") | 321 | stdout = stdout.decode("utf-8") |
57 | 307 | stderr = stderr.decode("utf-8") | 322 | stderr = stderr.decode("utf-8") |
58 | 308 | if stdout == "" or stdout.isspace(): | 323 | if stdout == "" or stdout.isspace(): |
59 | 324 | for error, error_info in AMT_ERRORS.items(): | ||
60 | 325 | if error in stderr: | ||
61 | 326 | raise error_info.get( | ||
62 | 327 | 'exception')(error_info.get('message')) | ||
63 | 309 | raise PowerConnError( | 328 | raise PowerConnError( |
64 | 310 | "Unable to retrieve AMT version: %s" % stderr) | 329 | "Unable to retrieve AMT version: %s" % stderr) |
65 | 311 | else: | 330 | else: |
66 | 312 | match = re.search("AMT version:\s*([0-9]+)", stdout) | 331 | match = re.search("AMT version:\s*([0-9]+)", stdout) |
67 | 313 | if match is None: | 332 | if match is None: |
69 | 314 | raise PowerFatalError( | 333 | raise PowerActionError( |
70 | 315 | "Unable to extract AMT version from " | 334 | "Unable to extract AMT version from " |
71 | 316 | "amttool output: %s" % stdout) | 335 | "amttool output: %s" % stdout) |
72 | 317 | else: | 336 | else: |
73 | @@ -341,7 +360,9 @@ | |||
74 | 341 | elif is_power_parameter_set(ip_address): | 360 | elif is_power_parameter_set(ip_address): |
75 | 342 | return ip_address | 361 | return ip_address |
76 | 343 | else: | 362 | else: |
78 | 344 | raise PowerFatalError('No host provided') | 363 | raise PowerSettingError( |
79 | 364 | "No IP address provided. " | ||
80 | 365 | "Please update BMC configuration and try again.") | ||
81 | 345 | 366 | ||
82 | 346 | def power_on(self, system_id, context): | 367 | def power_on(self, system_id, context): |
83 | 347 | """Power on AMT node.""" | 368 | """Power on AMT node.""" |
84 | 348 | 369 | ||
85 | === modified file 'src/provisioningserver/drivers/power/tests/test_amt.py' | |||
86 | --- src/provisioningserver/drivers/power/tests/test_amt.py 2016-04-15 14:54:37 +0000 | |||
87 | +++ src/provisioningserver/drivers/power/tests/test_amt.py 2016-05-05 20:22:05 +0000 | |||
88 | @@ -26,7 +26,10 @@ | |||
89 | 26 | PowerConnError, | 26 | PowerConnError, |
90 | 27 | PowerFatalError, | 27 | PowerFatalError, |
91 | 28 | ) | 28 | ) |
93 | 29 | from provisioningserver.drivers.power.amt import AMTPowerDriver | 29 | from provisioningserver.drivers.power.amt import ( |
94 | 30 | AMT_ERRORS, | ||
95 | 31 | AMTPowerDriver, | ||
96 | 32 | ) | ||
97 | 30 | from provisioningserver.utils.shell import has_command_available | 33 | from provisioningserver.utils.shell import has_command_available |
98 | 31 | from testtools.matchers import Equals | 34 | from testtools.matchers import Equals |
99 | 32 | 35 | ||
100 | @@ -58,7 +61,7 @@ | |||
101 | 58 | """).encode('utf-8') | 61 | """).encode('utf-8') |
102 | 59 | 62 | ||
103 | 60 | 63 | ||
105 | 61 | def make_parameters(): | 64 | def make_context(): |
106 | 62 | return { | 65 | return { |
107 | 63 | 'system_id': factory.make_name('system_id'), | 66 | 'system_id': factory.make_name('system_id'), |
108 | 64 | 'power_address': factory.make_name('power_address'), | 67 | 'power_address': factory.make_name('power_address'), |
109 | @@ -304,7 +307,7 @@ | |||
110 | 304 | _issue_amttool_command_mock.return_value = None | 307 | _issue_amttool_command_mock.return_value = None |
111 | 305 | 308 | ||
112 | 306 | self.assertRaises( | 309 | self.assertRaises( |
114 | 307 | PowerFatalError, amt_power_driver.amttool_query_state, | 310 | PowerActionError, amt_power_driver.amttool_query_state, |
115 | 308 | ip_address, power_pass) | 311 | ip_address, power_pass) |
116 | 309 | 312 | ||
117 | 310 | def test_amttool_restart_power_cycles(self): | 313 | def test_amttool_restart_power_cycles(self): |
118 | @@ -431,7 +434,7 @@ | |||
119 | 431 | _issue_wsman_command_mock.return_value = WSMAN_OUTPUT % b'error' | 434 | _issue_wsman_command_mock.return_value = WSMAN_OUTPUT % b'error' |
120 | 432 | 435 | ||
121 | 433 | self.assertRaises( | 436 | self.assertRaises( |
123 | 434 | PowerFatalError, amt_power_driver.wsman_query_state, | 437 | PowerActionError, amt_power_driver.wsman_query_state, |
124 | 435 | ip_address, power_pass) | 438 | ip_address, power_pass) |
125 | 436 | self.assertThat( | 439 | self.assertThat( |
126 | 437 | _issue_wsman_command_mock, MockCalledOnceWith( | 440 | _issue_wsman_command_mock, MockCalledOnceWith( |
127 | @@ -571,9 +574,21 @@ | |||
128 | 571 | amt_power_driver = AMTPowerDriver() | 574 | amt_power_driver = AMTPowerDriver() |
129 | 572 | self.patch_popen(return_value=(b'No match here', b'')) | 575 | self.patch_popen(return_value=(b'No match here', b'')) |
130 | 573 | self.assertRaises( | 576 | self.assertRaises( |
132 | 574 | PowerFatalError, amt_power_driver._get_amt_command, | 577 | PowerActionError, amt_power_driver._get_amt_command, |
133 | 575 | sentinel.ip_address, sentinel.power_pass) | 578 | sentinel.ip_address, sentinel.power_pass) |
134 | 576 | 579 | ||
135 | 580 | def test__get_amt_command_raises_power_error(self): | ||
136 | 581 | amt_power_driver = AMTPowerDriver() | ||
137 | 582 | for error, error_info in AMT_ERRORS.items(): | ||
138 | 583 | popen_mock = self.patch(amt_module, 'Popen') | ||
139 | 584 | process = popen_mock.return_value | ||
140 | 585 | process.communicate.return_value = (b'', error.encode('utf-8')) | ||
141 | 586 | self.assertRaises( | ||
142 | 587 | error_info.get('exception'), | ||
143 | 588 | amt_power_driver._get_amt_command, | ||
144 | 589 | factory.make_ipv4_address(), | ||
145 | 590 | factory.make_name('power_pass')) | ||
146 | 591 | |||
147 | 577 | def test__get_amttool_boot_mode_local_boot(self): | 592 | def test__get_amttool_boot_mode_local_boot(self): |
148 | 578 | amt_power_driver = AMTPowerDriver() | 593 | amt_power_driver = AMTPowerDriver() |
149 | 579 | result = amt_power_driver._get_amttool_boot_mode('local') | 594 | result = amt_power_driver._get_amttool_boot_mode('local') |
150 | @@ -605,7 +620,7 @@ | |||
151 | 605 | 620 | ||
152 | 606 | def test_power_on_powers_on_with_amttool_when_already_on(self): | 621 | def test_power_on_powers_on_with_amttool_when_already_on(self): |
153 | 607 | amt_power_driver = AMTPowerDriver() | 622 | amt_power_driver = AMTPowerDriver() |
155 | 608 | context = make_parameters() | 623 | context = make_context() |
156 | 609 | _get_amt_command_mock = self.patch( | 624 | _get_amt_command_mock = self.patch( |
157 | 610 | amt_power_driver, '_get_amt_command') | 625 | amt_power_driver, '_get_amt_command') |
158 | 611 | _get_amt_command_mock.return_value = 'amttool' | 626 | _get_amt_command_mock.return_value = 'amttool' |
159 | @@ -630,7 +645,7 @@ | |||
160 | 630 | 645 | ||
161 | 631 | def test_power_on_powers_on_with_amttool_when_already_off(self): | 646 | def test_power_on_powers_on_with_amttool_when_already_off(self): |
162 | 632 | amt_power_driver = AMTPowerDriver() | 647 | amt_power_driver = AMTPowerDriver() |
164 | 633 | context = make_parameters() | 648 | context = make_context() |
165 | 634 | _get_amt_command_mock = self.patch( | 649 | _get_amt_command_mock = self.patch( |
166 | 635 | amt_power_driver, '_get_amt_command') | 650 | amt_power_driver, '_get_amt_command') |
167 | 636 | _get_amt_command_mock.return_value = 'amttool' | 651 | _get_amt_command_mock.return_value = 'amttool' |
168 | @@ -655,7 +670,7 @@ | |||
169 | 655 | 670 | ||
170 | 656 | def test_power_on_powers_on_with_wsman_when_already_on(self): | 671 | def test_power_on_powers_on_with_wsman_when_already_on(self): |
171 | 657 | amt_power_driver = AMTPowerDriver() | 672 | amt_power_driver = AMTPowerDriver() |
173 | 658 | context = make_parameters() | 673 | context = make_context() |
174 | 659 | _get_amt_command_mock = self.patch( | 674 | _get_amt_command_mock = self.patch( |
175 | 660 | amt_power_driver, '_get_amt_command') | 675 | amt_power_driver, '_get_amt_command') |
176 | 661 | _get_amt_command_mock.return_value = 'wsman' | 676 | _get_amt_command_mock.return_value = 'wsman' |
177 | @@ -679,7 +694,7 @@ | |||
178 | 679 | 694 | ||
179 | 680 | def test_power_on_powers_on_with_wsman_when_already_off(self): | 695 | def test_power_on_powers_on_with_wsman_when_already_off(self): |
180 | 681 | amt_power_driver = AMTPowerDriver() | 696 | amt_power_driver = AMTPowerDriver() |
182 | 682 | context = make_parameters() | 697 | context = make_context() |
183 | 683 | _get_amt_command_mock = self.patch( | 698 | _get_amt_command_mock = self.patch( |
184 | 684 | amt_power_driver, '_get_amt_command') | 699 | amt_power_driver, '_get_amt_command') |
185 | 685 | _get_amt_command_mock.return_value = 'wsman' | 700 | _get_amt_command_mock.return_value = 'wsman' |
186 | @@ -703,7 +718,7 @@ | |||
187 | 703 | 718 | ||
188 | 704 | def test_power_off_powers_off_with_amttool(self): | 719 | def test_power_off_powers_off_with_amttool(self): |
189 | 705 | amt_power_driver = AMTPowerDriver() | 720 | amt_power_driver = AMTPowerDriver() |
191 | 706 | context = make_parameters() | 721 | context = make_context() |
192 | 707 | _get_amt_command_mock = self.patch( | 722 | _get_amt_command_mock = self.patch( |
193 | 708 | amt_power_driver, '_get_amt_command') | 723 | amt_power_driver, '_get_amt_command') |
194 | 709 | _get_amt_command_mock.return_value = 'amttool' | 724 | _get_amt_command_mock.return_value = 'amttool' |
195 | @@ -726,7 +741,7 @@ | |||
196 | 726 | 741 | ||
197 | 727 | def test_power_off_powers_off_with_wsman(self): | 742 | def test_power_off_powers_off_with_wsman(self): |
198 | 728 | amt_power_driver = AMTPowerDriver() | 743 | amt_power_driver = AMTPowerDriver() |
200 | 729 | context = make_parameters() | 744 | context = make_context() |
201 | 730 | _get_amt_command_mock = self.patch( | 745 | _get_amt_command_mock = self.patch( |
202 | 731 | amt_power_driver, '_get_amt_command') | 746 | amt_power_driver, '_get_amt_command') |
203 | 732 | _get_amt_command_mock.return_value = 'wsman' | 747 | _get_amt_command_mock.return_value = 'wsman' |
204 | @@ -749,7 +764,7 @@ | |||
205 | 749 | 764 | ||
206 | 750 | def test_power_query_queries_with_amttool(self): | 765 | def test_power_query_queries_with_amttool(self): |
207 | 751 | amt_power_driver = AMTPowerDriver() | 766 | amt_power_driver = AMTPowerDriver() |
209 | 752 | context = make_parameters() | 767 | context = make_context() |
210 | 753 | _get_amt_command_mock = self.patch( | 768 | _get_amt_command_mock = self.patch( |
211 | 754 | amt_power_driver, '_get_amt_command') | 769 | amt_power_driver, '_get_amt_command') |
212 | 755 | _get_amt_command_mock.return_value = 'amttool' | 770 | _get_amt_command_mock.return_value = 'amttool' |
213 | @@ -769,7 +784,7 @@ | |||
214 | 769 | 784 | ||
215 | 770 | def test_power_query_queries_with_wsman(self): | 785 | def test_power_query_queries_with_wsman(self): |
216 | 771 | amt_power_driver = AMTPowerDriver() | 786 | amt_power_driver = AMTPowerDriver() |
218 | 772 | context = make_parameters() | 787 | context = make_context() |
219 | 773 | _get_amt_command_mock = self.patch( | 788 | _get_amt_command_mock = self.patch( |
220 | 774 | amt_power_driver, '_get_amt_command') | 789 | amt_power_driver, '_get_amt_command') |
221 | 775 | _get_amt_command_mock.return_value = 'wsman' | 790 | _get_amt_command_mock.return_value = 'wsman' |
Code looks fine to me. Just a couple things I'm wondering:
* Can you update the commit message so that it is more accurate? (it makes it sound like error propagation in general, but the branch seems to just fix up the AMT exceptions so that they are thrown correctly.)
* Are we confident the AMT utilities will always print these particular strings? (I assume we're using a consistent locale, so that the strings don't show up translated into another language and surprise us. And I was wondering if using just the HTTP status code for matching might be more future-proof... assuming the output isn't too large, and we could match the number in multiple places in the string...?)