Merge lp:~julian-edwards/maas/1.7-race-for-staticip-bug-1387262 into lp:~maas-committers/maas/trunk
- 1.7-race-for-staticip-bug-1387262
- Merge into trunk
Proposed by
Julian Edwards
Status: | Superseded |
---|---|
Proposed branch: | lp:~julian-edwards/maas/1.7-race-for-staticip-bug-1387262 |
Merge into: | lp:~maas-committers/maas/trunk |
Diff against target: |
2560 lines (+1719/-477) (has conflicts) 17 files modified
Makefile (+2/-2) docs/changelog.rst (+937/-466) src/maasserver/api/tests/test_node.py (+6/-1) src/maasserver/forms_settings.py (+10/-0) src/maasserver/models/config.py (+1/-0) src/maasserver/models/node.py (+162/-0) src/maasserver/models/tests/test_bootsource.py (+4/-0) src/maasserver/models/tests/test_node.py (+417/-0) src/maasserver/tests/test_bootresources.py (+19/-0) src/maasserver/views/nodes.py (+5/-0) src/maasserver/views/tests/test_nodes.py (+54/-0) src/metadataserver/api.py (+17/-3) src/metadataserver/models/commissioningscript.py (+22/-5) src/metadataserver/models/tests/test_noderesults.py (+27/-0) src/metadataserver/tests/test_api.py (+15/-0) src/provisioningserver/rpc/boot_images.py (+14/-0) src/provisioningserver/rpc/tests/test_boot_images.py (+7/-0) Text conflict in docs/changelog.rst Text conflict in src/maasserver/api/tests/test_node.py Text conflict in src/maasserver/models/node.py Text conflict in src/maasserver/models/tests/test_node.py Text conflict in src/maasserver/tests/test_bootresources.py Text conflict in src/maasserver/views/nodes.py Text conflict in src/maasserver/views/tests/test_nodes.py Text conflict in src/metadataserver/api.py Text conflict in src/provisioningserver/rpc/boot_images.py |
To merge this branch: | bzr merge lp:~julian-edwards/maas/1.7-race-for-staticip-bug-1387262 |
Related bugs: |
Reviewer | Review Type | Date Requested | Status |
---|---|---|---|
Julian Edwards (community) | Approve | ||
Review via email: mp+240799@code.launchpad.net |
Commit message
Backport trunk r3337: Use a lock in the critical section of the code that finds and records a new static IP address in the database. Some users have reported errors in the PG log file complaining about inserting duplicates, so there must be a race.
Description of the change
To post a comment you must log in.
Preview Diff
[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
1 | === modified file 'Makefile' |
2 | --- Makefile 2014-10-01 07:22:16 +0000 |
3 | +++ Makefile 2014-11-06 01:24:10 +0000 |
4 | @@ -351,8 +351,8 @@ |
5 | # has a bug and always considers apt-source tarballs before the specified |
6 | # branch. So instead, export to a local tarball which is always found. |
7 | # Make sure debhelper and dh-apport packages are installed before using this. |
8 | -PACKAGING := $(CURDIR)/../packaging.trunk |
9 | -PACKAGING_BRANCH := lp:~maas-maintainers/maas/packaging |
10 | +PACKAGING := $(CURDIR)/../packaging.utopic |
11 | +PACKAGING_BRANCH := lp:~maas-maintainers/maas/packaging.utopic |
12 | |
13 | package_branch: |
14 | @echo Downloading/refreshing packaging branch... |
15 | |
16 | === modified file 'docs/changelog.rst' |
17 | --- docs/changelog.rst 2014-11-05 03:36:42 +0000 |
18 | +++ docs/changelog.rst 2014-11-06 01:24:10 +0000 |
19 | @@ -2,472 +2,943 @@ |
20 | Changelog |
21 | ========= |
22 | |
23 | -1.7.0 |
24 | -===== |
25 | - |
26 | -Important announcements |
27 | ------------------------ |
28 | - |
29 | -**Re-import your boot images** |
30 | - You must re-import your boot images, see below for details. |
31 | - |
32 | -**Update Curtin preseed files** |
33 | - Two changes were made to Curtin preseed files that need your attention |
34 | - if you made any customisations: |
35 | - |
36 | - * The OS name must now appear in the filename. The new schema is shown |
37 | - here, each file pattern is tried in turn until a match is found:: |
38 | - |
39 | - {prefix}_{osystem}_{node_arch}_{node_subarch}_{release}_{node_name} |
40 | - {prefix}_{osystem}_{node_arch}_{node_subarch}_{release} |
41 | - {prefix}_{osystem}_{node_arch}_{node_subarch} |
42 | - {prefix}_{osystem}_{node_arch} |
43 | - {prefix}_{osystem} |
44 | - {prefix} |
45 | - |
46 | - * If you are modifying ``/etc/network/interfaces`` in the preseed, it must be |
47 | - moved so it is processed last in ``late_commands`` since MAAS now writes |
48 | - to this file itself as part of IPv6 setup. For example:: |
49 | - |
50 | - late_commands: |
51 | - bonding_02: ["curtin", "in-target", "--", "wget", "-O", "/etc/network/interfaces", "http://[...snip...]"] |
52 | - |
53 | - must now look like this:: |
54 | - |
55 | - late_commands: |
56 | - zz_write_ifaces: ["curtin", "in-target", "--", "wget", "-O", "/etc/network/interfaces", "http://[...snip...]"] |
57 | - |
58 | - The leading ``zz`` ensures the command sorts to the end of the |
59 | - ``late_commands`` list. |
60 | - |
61 | - |
62 | -Major new features |
63 | ------------------- |
64 | - |
65 | -**Improved image downloading and reporting.** |
66 | - MAAS boot images are now downloaded centrally by the region controller |
67 | - and disseminated to all registered cluster controllers. This change includes |
68 | - a new web UI under the `Images` tab that allows the admin to select |
69 | - which images to import and shows the progress of the ongoing download. |
70 | - This completely replaces any file-based configuration that used to take |
71 | - place on cluster controllers. The cluster page now shows whether it has |
72 | - synchronised all the images from the region controller. |
73 | - |
74 | - This process is also completely controllable using the API. |
75 | - |
76 | -.. Note:: |
77 | - Unfortunately due to a format change in the way images are stored, it |
78 | - was not possible to migrate previously downloaded images to the new region |
79 | - storage. The cluster(s) will still be able to use the existing images, |
80 | - however the region controller will be unaware of them until an import |
81 | - is initiated. When the import is finished, the cluster(s) will remove |
82 | - older image resources. |
83 | - |
84 | - This means that the first thing to do after upgrading to 1.7 is go to the |
85 | - `Images` tab and re-import the images. |
86 | - |
87 | -**Increased robustness.** |
88 | - A large amount of effort has been given to ensuring that MAAS remains |
89 | - robust in the face of adversity. An updated node state model has been |
90 | - implemented that takes into account more of the situations in which a |
91 | - node can be found including any failures at each stage. |
92 | - |
93 | - When a node is getting deployed, it is now monitored to check that each |
94 | - stage is reached in a timely fashion; if it does not then it is marked |
95 | - as failed. |
96 | - |
97 | - The core power driver was updated to check the state of the power on each |
98 | - node and is reported in the web UI and API. The core driver now also |
99 | - handles retries when changing the power state of hardware, removing the |
100 | - requirement that each power template handle it individually. |
101 | - |
102 | -**RPC security.** |
103 | - As a step towards mutually verified TLS connections between MAAS's |
104 | - components, 1.7 introduces a simple shared-secret mechanism to |
105 | - authenticate the region with the clusters and vice-versa. For those |
106 | - clusters that run on the same machine as the region controller (which |
107 | - will account for most people), everything will continue to work |
108 | - without intervention. However, if you're running a cluster on a |
109 | - separate machine, you must install the secret: |
110 | - |
111 | - 1. After upgrading the region controller, view /var/lib/maas/secret |
112 | - (it's text) and copy it. |
113 | - |
114 | - 2. On each cluster, run: |
115 | - |
116 | - sudo -u maas maas-provision install-shared-secret |
117 | - |
118 | - You'll be prompted for the secret; paste it in and press enter. It |
119 | - is a password prompt, so the secret will not be echoed back to you. |
120 | - |
121 | - That's it; the upgraded cluster controller will find the secret |
122 | - without needing to be told. |
123 | - |
124 | -**RPC connections.** |
125 | - Each cluster maintains a persistent connection to each region |
126 | - controller process that's running. The ports on which the region is |
127 | - listening are all high-numbered, and they are allocated randomly by |
128 | - the OS. In a future release of MAAS we will narrow this down. For now, |
129 | - each cluster controller needs unfiltered access to each machine in the |
130 | - region on all high-numbered TCP ports. |
131 | - |
132 | -**Node event log.** |
133 | - For every major event on nodes, it is now logged in a node-specific log. |
134 | - This includes events such as power changes, deployments and any failures. |
135 | - |
136 | -**IPv6.** |
137 | - It is now possible to deploy Ubuntu nodes that have IPv6 enabled. |
138 | - See :doc:`ipv6` for more details. |
139 | - |
140 | -**Removal of Celery and RabbitMQ.** |
141 | - While Celery was found to be very reliable it ultimately did not suit |
142 | - the project's requirements as it is a largely fire-and-forget mechanism. |
143 | - Additionally it was another moving part that caused some headaches for |
144 | - users and admins alike, so the decision was taken to remove it and implement |
145 | - a custom communications mechanism between the region controller and cluster |
146 | - controllers. The new mechanism is bidirectional and allowed the complex |
147 | - interactions to take place that are required as part of the robustness |
148 | - improvements. |
149 | - |
150 | - Since a constant connection is maintained, as a side effect the web UI now |
151 | - shows whether each cluster is connected or not. |
152 | - |
153 | -**Support for other OSes.** |
154 | - Non-Ubuntu OSes are fully supported now. This includes: |
155 | - - Windows |
156 | - - Centos |
157 | - - SuSE |
158 | - |
159 | -**Custom Images.** |
160 | - MAAS now supports the deployment of Custom Images. Custom images can be |
161 | - uploaded via the API. The usage of custom images allows the deployment of |
162 | - other Ubuntu Flavors, such as Ubuntu Desktop. |
163 | - |
164 | -**maas-proxy.** |
165 | - MAAS now uses maas-proxy as the default proxy solution instead of |
166 | - squid-deb-proxy. On a fresh install, MAAS will use maas-proxy by default. |
167 | - On upgrades from previous releases, MAAS will install maas-proxy instead of |
168 | - squid-deb-proxy. |
169 | - |
170 | -Minor notable changes |
171 | ---------------------- |
172 | - |
173 | -**Better handling of networks.** |
174 | - All networks referred to by cluster interfaces are now automatically |
175 | - registered on the Network page. Any node network interfaces are |
176 | - automatically linked to the relevant Network. |
177 | - |
178 | -.. Note:: |
179 | - Commissioning currently requires an IP address to be available for each |
180 | - network interface on a network that MAAS manages; this allows MAAS to |
181 | - auto-populate its networks database. In general you should use a |
182 | - well-sized network (/16 recommended if you will be using containers and |
183 | - VMs) and dynamic pool. If this feature risks causing IP exhaustion for |
184 | - your deployment and you do not need the auto-populate functionality, you |
185 | - can disable it by running the following command on your region controller:: |
186 | - |
187 | - sudo maas <profile> maas set-config name=enable_dhcp_discovery_on_unconfigured_interfaces value=False |
188 | - |
189 | -**Improved logging.** |
190 | - A total overhaul of where logging is produced was undertaken, and now |
191 | - all the main events in MAAS are selectively reported to syslog with the |
192 | - "maas" prefix from both the region and cluster controllers alike. If MAAS |
193 | - is installed using the standard Ubuntu packaging, its syslog entries are |
194 | - redirected to /var/log/maas/maas.log. |
195 | - |
196 | - On the clusters, pserv.log is now less chatty and contains only errors. |
197 | - On the region controller appservers, maas-django.log contains only appserver |
198 | - errors. |
199 | - |
200 | -**Static IP selection.** |
201 | - The API was extended so that specific IPs can be pre-allocated for network |
202 | - interfaces on nodes and for user-allocated IPs. |
203 | - |
204 | -**Pronounceable random hostnames.** |
205 | - The old auto-generated 5-letter names were replaced with a pseudo-random |
206 | - name that is produced from a dictionary giving names of the form |
207 | - 'adjective-noun'. |
208 | - |
209 | - |
210 | -Known Problems & Workarounds |
211 | ----------------------------- |
212 | - |
213 | -**Upgrade issues** |
214 | - There may be upgrade issues for users currently on MAAS 1.5 and 1.6; while we |
215 | - have attempted to reproduce and address all the issues reported, some bugs |
216 | - remain inconclusive. We recommend a full, tested backup of the MAAS servers |
217 | - before attempting the upgrade to 1.7. If you do encounter issues, please file |
218 | - these and flag them to the attention of the MAAS team and we will address them |
219 | - in point-releases. See bugs `1381058`_, `1382266`_, `1379890`_, `1379532`_, |
220 | - and `1379144`_. |
221 | - |
222 | -.. _1381058: |
223 | - https://launchpad.net/bugs/1381058 |
224 | -.. _1382266: |
225 | - https://launchpad.net/bugs/1382266 |
226 | -.. _1379890: |
227 | - https://launchpad.net/bugs/1379890 |
228 | -.. _1379532: |
229 | - https://launchpad.net/bugs/1379532 |
230 | -.. _1379144: |
231 | - https://launchpad.net/bugs/1379144 |
232 | - |
233 | -**Split Region/Cluster set-ups** |
234 | - If you site your cluster on a separate host to the region, it needs a |
235 | - security key to be manually installed by running |
236 | - ``maas-provision install-shared-secret`` on the cluster host. |
237 | - |
238 | -**Private boot streams** |
239 | - If you had private boot image stream information configured in MAAS 1.5 or |
240 | - 1.6, upgrading to 1.7 will not take that into account and it will need to be |
241 | - manually entered on the settings page in the MAAS UI (bug `1379890`_) |
242 | - |
243 | -.. _1379890: |
244 | - https://launchpad.net/bugs/1379890 |
245 | - |
246 | -**Concurrency issues** |
247 | - Concurrency issues expose us to races when simultaneous operations are |
248 | - triggered. This is the source of many hard to reproduce issues which will |
249 | - require us to change the default database isolation level. We intend to address |
250 | - this in the first point release of 1.7. |
251 | - |
252 | -**Destroying a Juju environment** |
253 | - When attempting to "juju destroy" an environment, nodes must be in the DEPLOYED |
254 | - state; otherwise, the destroy will fail. You should wait for all in-progress |
255 | - actions on the MAAS cluster to conclude before issuing the command. (bug |
256 | - `1381619`_) |
257 | - |
258 | -.. _1381619: |
259 | - https://launchpad.net/bugs/_1381619 |
260 | - |
261 | -**AMT power control** |
262 | - A few AMT-related issues remain, with workarounds: |
263 | - |
264 | - * Commissioning NUC reboots instead of shutting down (bug `1368685`_). There |
265 | - is `a workaround in the power template`_ |
266 | - |
267 | - * MAAS (amttool) cannot control AMT version > 8. See `workaround described in |
268 | - bug 1331214`_ |
269 | - |
270 | - * AMT NUC stuck at boot prompt instead of powering down (no ACPI support in |
271 | - syslinux poweroff) (bug `1376716`_). See the `ACPI-only workaround`_ |
272 | - |
273 | -.. _1368685: |
274 | - https://bugs.launchpad.net/maas/+bug/1368685 |
275 | -.. _a workaround in the power template: |
276 | - https://bugs.launchpad.net/maas/+bug/1368685/comments/8 |
277 | -.. _workaround described in bug 1331214: |
278 | - https://bugs.launchpad.net/maas/+bug/1331214/comments/18 |
279 | -.. _1376716: |
280 | - https://bugs.launchpad.net/maas/+bug/1376716 |
281 | -.. _ACPI-only workaround: |
282 | - https://bugs.launchpad.net/maas/+bug/1376716/comments/12 |
283 | - |
284 | - |
285 | -**Disk wiping** |
286 | - If you enable disk wiping, juju destroy-environment may fail for you. The |
287 | - current workaround is to wait and re-issue the command. This will be fixed in |
288 | - future versions of MAAS & Juju. (bug `1386327`_) |
289 | - |
290 | -.. _1386327: |
291 | - https://bugs.launchpad.net/maas/+bug/1386327 |
292 | - |
293 | -**BIND with DNSSEC** |
294 | - If you are using BIND with a forwarder that uses DNSSEC and have not |
295 | - configured certificates, you will need to explicitly disable that feature in |
296 | - your BIND configuration (1384334) |
297 | - |
298 | -.. _1384334: |
299 | - https://bugs.launchpad.net/maas/+bug/1384334 |
300 | - |
301 | -**Boot source selections on the API** |
302 | - Use of API to change image selections can leave DB in a bad state |
303 | - (bug `1376812`_). It can be fixed by issuing direct database updates. |
304 | - |
305 | -.. _1376812: |
306 | - https://bugs.launchpad.net/maas/+bug/1376812 |
307 | - |
308 | -**Disabling DNS** |
309 | - Disabling DNS may not work (bug `1383768`_) |
310 | - |
311 | -.. _1383768: |
312 | - https://bugs.launchpad.net/maas/+bug/1383768 |
313 | - |
314 | -**Stale DNS zone files** |
315 | - Stale DNS zone files may be left behind if the MAAS domainname is changed |
316 | - (bug `1383329`_) |
317 | - |
318 | -.. _1383329: |
319 | - https://bugs.launchpad.net/maas/+bug/1383329 |
320 | - |
321 | - |
322 | - |
323 | -Major bugs fixed in this release |
324 | --------------------------------- |
325 | - |
326 | -See https://launchpad.net/maas/+milestone/1.7.0 for full details. |
327 | - |
328 | -#1081660 If maas-enlist fails to reach a DNS server, the node will be named ";; connection timed out; no servers could be reached" |
329 | - |
330 | -#1087183 MaaS cloud-init configuration specifies 'manage_etc_hosts: localhost' |
331 | - |
332 | -#1328351 ConstipationError: When the cluster runs the "import boot images" task it blocks other tasks |
333 | - |
334 | -#1342117 CLI command to set up node-group-interface fails with /usr/lib/python2.7/dist-packages/maascli/__main__.py: error: u'name' |
335 | - |
336 | -#1349254 Duplicate FQDN can be configured on MAAS via CLI or API |
337 | - |
338 | -#1352575 BMC password showing in the apache2 logs |
339 | - |
340 | -#1355534 UnknownPowerType traceback in appserver log |
341 | - |
342 | -#1363850 Auto-enlistment not reporting power parameters |
343 | - |
344 | -#1363900 Dev server errors while trying to write to '/var/lib/maas' |
345 | - |
346 | -#1363999 Not assigning static IP addresses |
347 | - |
348 | -#1364481 http 500 error doesn't contain a stack trace |
349 | - |
350 | -#1364993 500 error when trying to acquire a commissioned node (AddrFormatError: failed to detect a valid IP address from None) |
351 | - |
352 | -#1365130 django-admin prints spurious messages to stdout, breaking scripts |
353 | - |
354 | -#1365850 DHCP scan using cluster interface name as network interface? |
355 | - |
356 | -#1366172 NUC does not boot after power off/power on |
357 | - |
358 | -#1366212 Large dhcp leases file leads to tftp timeouts |
359 | - |
360 | -#1366652 Leaking temporary directories |
361 | - |
362 | -#1368269 internal server error when deleting a node |
363 | - |
364 | -#1368590 Power actions are not serialized. |
365 | - |
366 | -#1370534 Recurrent update of the power state of nodes crashes if the connection to the BMC fails. |
367 | - |
368 | -#1370958 excessive pserv logging |
369 | - |
370 | -#1372767 Twisted web client does not support IPv6 address |
371 | - |
372 | -#1372944 Twisted web client fails looking up IPv6 address hostname |
373 | - |
374 | -#1373031 Cannot register cluster |
375 | - |
376 | -#1373103 compose_curtin_network_preseed breaks installation of all other operating systems |
377 | - |
378 | -#1373368 Conflicting power actions being dropped on the floor can result in leaving a node in an inconsistent state |
379 | - |
380 | -#1373699 Cluster Listing Page lacks feedback about the images each cluster has |
381 | - |
382 | -#1374102 No retries for AMT power? |
383 | - |
384 | -#1375980 Nodes failed to transition out of "New" state on bulk commission |
385 | - |
386 | -#1376023 After performing bulk action on maas nodes, Internal Server Error |
387 | - |
388 | -#1376888 Nodes can't be deleted if DHCP management is off. |
389 | - |
390 | -#1377099 Bulk operation leaves nodes in inconsistent state |
391 | - |
392 | -#1379209 When a node has multiple interfaces on a network MAAS manages, MAAS assigns static IP addresses to all of them |
393 | - |
394 | -#1379744 Cluster registration is fragile and insecure |
395 | - |
396 | -#1380932 MAAS does not cope with changes of the dhcp daemons |
397 | - |
398 | -#1381605 Not all the DNS records are being added when deploying multiple nodes |
399 | - |
400 | -#1012954 If a power script fails, there is no UI feedback |
401 | - |
402 | -#1186196 "Starting a node" has different meanings in the UI and in the API. |
403 | - |
404 | -#1237215 maas and curtin do not indicate failure reasonably |
405 | - |
406 | -#1273222 MAAS doesn't check return values of power actions |
407 | - |
408 | -#1288502 archive and proxy settings not honoured for commissioning |
409 | - |
410 | -#1316919 Checks don't exist to confirm a node will actually boot |
411 | - |
412 | -#1321885 IPMI detection and automatic setting fail in ubuntu 14.04 maas |
413 | - |
414 | -#1325610 node marked "Ready" before poweroff complete |
415 | - |
416 | -#1340188 unallocated node started manually, causes AssertionError for purpose poweroff |
417 | - |
418 | -#1341118 No feedback when IPMI credentials fail |
419 | - |
420 | -#1341121 No feedback to user when cluster is not running |
421 | - |
422 | -#1341581 power state is not represented in api and ui |
423 | - |
424 | -#1341800 MAAS doesn't support soft power off through the API |
425 | - |
426 | -#1344177 hostnames can't be changed while a node is acquired |
427 | - |
428 | -#1347518 Confusing error message when API key is wrong |
429 | - |
430 | -#1349496 Unable to request a specific static IP on the API |
431 | - |
432 | -#1349736 MAAS logging is too verbose and not very useful |
433 | - |
434 | -#1349917 guess_server_address() can return IPAddress or hostname |
435 | - |
436 | -#1350103 No support for armhf/keystone architecture |
437 | - |
438 | -#1350856 Can't constrain acquisition of nodes by not having a tag |
439 | - |
440 | -#1356880 MAAS shouldn't allow changing the hostname of a deployed node |
441 | - |
442 | -#1357714 Virsh power driver does not seem to work at all |
443 | - |
444 | -#1358859 Commissioning output xml is hard to understand, would be nice to have yaml as an output option. |
445 | - |
446 | -#1359169 MAAS should handle invalid consumers gracefully |
447 | - |
448 | -#1359822 Gateway is missing in network definition |
449 | - |
450 | -#1363913 Impossible to remove last MAC from network in UI |
451 | - |
452 | -#1364228 Help text for node hostname is wrong |
453 | - |
454 | -#1364591 MAAS Archive Mirror does not respect non-default port |
455 | - |
456 | -#1365616 Non-admin access to cluster controller config |
457 | - |
458 | -#1365619 DNS should be an optional field in the network definition |
459 | - |
460 | -#1365776 commissioning results view for a node also shows installation results |
461 | - |
462 | -#1366812 Old boot resources are not being removed on clusters |
463 | - |
464 | -#1367455 MAC address for node's IPMI is reversed looked up to yield IP address using case sensitive comparison |
465 | - |
466 | -#1373580 [SRU] Glen m700 cartridge list as ARM64/generic after enlist |
467 | - |
468 | -#1373723 Releasing a node without power parameters ends up in not being able to release a node |
469 | - |
470 | -#1233158 no way to get power parameters in api |
471 | - |
472 | -#1319854 `maas login` tells you you're logged in successfully when you're not |
473 | - |
474 | -#1368480 Need API to gather image metadata across all of MAAS |
475 | - |
476 | -#1281406 Disk/memory space on Node edit page have no units |
477 | - |
478 | -#1299231 MAAS DHCP/DNS can't manage more than a /16 network |
479 | - |
480 | -#1357381 maas-region-admin createadmin shows error if not params given |
481 | - |
482 | -#1376393 powerkvm boot loader installs even when not needed |
483 | - |
484 | -#1287224 MAAS random generated hostnames are not pronounceable |
485 | - |
486 | -#1348364 non-maas managed subnets cannot query maas DNS |
487 | - |
488 | - |
489 | +<<<<<<< TREE |
490 | +1.7.0 |
491 | +===== |
492 | + |
493 | +Important announcements |
494 | +----------------------- |
495 | + |
496 | +**Re-import your boot images** |
497 | + You must re-import your boot images, see below for details. |
498 | + |
499 | +**Update Curtin preseed files** |
500 | + Two changes were made to Curtin preseed files that need your attention |
501 | + if you made any customisations: |
502 | + |
503 | + * The OS name must now appear in the filename. The new schema is shown |
504 | + here, each file pattern is tried in turn until a match is found:: |
505 | + |
506 | + {prefix}_{osystem}_{node_arch}_{node_subarch}_{release}_{node_name} |
507 | + {prefix}_{osystem}_{node_arch}_{node_subarch}_{release} |
508 | + {prefix}_{osystem}_{node_arch}_{node_subarch} |
509 | + {prefix}_{osystem}_{node_arch} |
510 | + {prefix}_{osystem} |
511 | + {prefix} |
512 | + |
513 | + * If you are modifying ``/etc/network/interfaces`` in the preseed, it must be |
514 | + moved so it is processed last in ``late_commands`` since MAAS now writes |
515 | + to this file itself as part of IPv6 setup. For example:: |
516 | + |
517 | + late_commands: |
518 | + bonding_02: ["curtin", "in-target", "--", "wget", "-O", "/etc/network/interfaces", "http://[...snip...]"] |
519 | + |
520 | + must now look like this:: |
521 | + |
522 | + late_commands: |
523 | + zz_write_ifaces: ["curtin", "in-target", "--", "wget", "-O", "/etc/network/interfaces", "http://[...snip...]"] |
524 | + |
525 | + The leading ``zz`` ensures the command sorts to the end of the |
526 | + ``late_commands`` list. |
527 | + |
528 | + |
529 | +Major new features |
530 | +------------------ |
531 | + |
532 | +**Improved image downloading and reporting.** |
533 | + MAAS boot images are now downloaded centrally by the region controller |
534 | + and disseminated to all registered cluster controllers. This change includes |
535 | + a new web UI under the `Images` tab that allows the admin to select |
536 | + which images to import and shows the progress of the ongoing download. |
537 | + This completely replaces any file-based configuration that used to take |
538 | + place on cluster controllers. The cluster page now shows whether it has |
539 | + synchronised all the images from the region controller. |
540 | + |
541 | + This process is also completely controllable using the API. |
542 | + |
543 | +.. Note:: |
544 | + Unfortunately due to a format change in the way images are stored, it |
545 | + was not possible to migrate previously downloaded images to the new region |
546 | + storage. The cluster(s) will still be able to use the existing images, |
547 | + however the region controller will be unaware of them until an import |
548 | + is initiated. When the import is finished, the cluster(s) will remove |
549 | + older image resources. |
550 | + |
551 | + This means that the first thing to do after upgrading to 1.7 is go to the |
552 | + `Images` tab and re-import the images. |
553 | + |
554 | +**Increased robustness.** |
555 | + A large amount of effort has been given to ensuring that MAAS remains |
556 | + robust in the face of adversity. An updated node state model has been |
557 | + implemented that takes into account more of the situations in which a |
558 | + node can be found including any failures at each stage. |
559 | + |
560 | + When a node is getting deployed, it is now monitored to check that each |
561 | + stage is reached in a timely fashion; if it does not then it is marked |
562 | + as failed. |
563 | + |
564 | + The core power driver was updated to check the state of the power on each |
565 | + node and is reported in the web UI and API. The core driver now also |
566 | + handles retries when changing the power state of hardware, removing the |
567 | + requirement that each power template handle it individually. |
568 | + |
569 | +**RPC security.** |
570 | + As a step towards mutually verified TLS connections between MAAS's |
571 | + components, 1.7 introduces a simple shared-secret mechanism to |
572 | + authenticate the region with the clusters and vice-versa. For those |
573 | + clusters that run on the same machine as the region controller (which |
574 | + will account for most people), everything will continue to work |
575 | + without intervention. However, if you're running a cluster on a |
576 | + separate machine, you must install the secret: |
577 | + |
578 | + 1. After upgrading the region controller, view /var/lib/maas/secret |
579 | + (it's text) and copy it. |
580 | + |
581 | + 2. On each cluster, run: |
582 | + |
583 | + sudo -u maas maas-provision install-shared-secret |
584 | + |
585 | + You'll be prompted for the secret; paste it in and press enter. It |
586 | + is a password prompt, so the secret will not be echoed back to you. |
587 | + |
588 | + That's it; the upgraded cluster controller will find the secret |
589 | + without needing to be told. |
590 | + |
591 | +**RPC connections.** |
592 | + Each cluster maintains a persistent connection to each region |
593 | + controller process that's running. The ports on which the region is |
594 | + listening are all high-numbered, and they are allocated randomly by |
595 | + the OS. In a future release of MAAS we will narrow this down. For now, |
596 | + each cluster controller needs unfiltered access to each machine in the |
597 | + region on all high-numbered TCP ports. |
598 | + |
599 | +**Node event log.** |
600 | + For every major event on nodes, it is now logged in a node-specific log. |
601 | + This includes events such as power changes, deployments and any failures. |
602 | + |
603 | +**IPv6.** |
604 | + It is now possible to deploy Ubuntu nodes that have IPv6 enabled. |
605 | + See :doc:`ipv6` for more details. |
606 | + |
607 | +**Removal of Celery and RabbitMQ.** |
608 | + While Celery was found to be very reliable it ultimately did not suit |
609 | + the project's requirements as it is a largely fire-and-forget mechanism. |
610 | + Additionally it was another moving part that caused some headaches for |
611 | + users and admins alike, so the decision was taken to remove it and implement |
612 | + a custom communications mechanism between the region controller and cluster |
613 | + controllers. The new mechanism is bidirectional and allowed the complex |
614 | + interactions to take place that are required as part of the robustness |
615 | + improvements. |
616 | + |
617 | + Since a constant connection is maintained, as a side effect the web UI now |
618 | + shows whether each cluster is connected or not. |
619 | + |
620 | +**Support for other OSes.** |
621 | + Non-Ubuntu OSes are fully supported now. This includes: |
622 | + - Windows |
623 | + - Centos |
624 | + - SuSE |
625 | + |
626 | +**Custom Images.** |
627 | + MAAS now supports the deployment of Custom Images. Custom images can be |
628 | + uploaded via the API. The usage of custom images allows the deployment of |
629 | + other Ubuntu Flavors, such as Ubuntu Desktop. |
630 | + |
631 | +**maas-proxy.** |
632 | + MAAS now uses maas-proxy as the default proxy solution instead of |
633 | + squid-deb-proxy. On a fresh install, MAAS will use maas-proxy by default. |
634 | + On upgrades from previous releases, MAAS will install maas-proxy instead of |
635 | + squid-deb-proxy. |
636 | + |
637 | +Minor notable changes |
638 | +--------------------- |
639 | + |
640 | +**Better handling of networks.** |
641 | + All networks referred to by cluster interfaces are now automatically |
642 | + registered on the Network page. Any node network interfaces are |
643 | + automatically linked to the relevant Network. |
644 | + |
645 | +.. Note:: |
646 | + Commissioning currently requires an IP address to be available for each |
647 | + network interface on a network that MAAS manages; this allows MAAS to |
648 | + auto-populate its networks database. In general you should use a |
649 | + well-sized network (/16 recommended if you will be using containers and |
650 | + VMs) and dynamic pool. If this feature risks causing IP exhaustion for |
651 | + your deployment and you do not need the auto-populate functionality, you |
652 | + can disable it by running the following command on your region controller:: |
653 | + |
654 | + sudo maas <profile> maas set-config name=enable_dhcp_discovery_on_unconfigured_interfaces value=False |
655 | + |
656 | +**Improved logging.** |
657 | + A total overhaul of where logging is produced was undertaken, and now |
658 | + all the main events in MAAS are selectively reported to syslog with the |
659 | + "maas" prefix from both the region and cluster controllers alike. If MAAS |
660 | + is installed using the standard Ubuntu packaging, its syslog entries are |
661 | + redirected to /var/log/maas/maas.log. |
662 | + |
663 | + On the clusters, pserv.log is now less chatty and contains only errors. |
664 | + On the region controller appservers, maas-django.log contains only appserver |
665 | + errors. |
666 | + |
667 | +**Static IP selection.** |
668 | + The API was extended so that specific IPs can be pre-allocated for network |
669 | + interfaces on nodes and for user-allocated IPs. |
670 | + |
671 | +**Pronounceable random hostnames.** |
672 | + The old auto-generated 5-letter names were replaced with a pseudo-random |
673 | + name that is produced from a dictionary giving names of the form |
674 | + 'adjective-noun'. |
675 | + |
676 | + |
677 | +Known Problems & Workarounds |
678 | +---------------------------- |
679 | + |
680 | +**Upgrade issues** |
681 | + There may be upgrade issues for users currently on MAAS 1.5 and 1.6; while we |
682 | + have attempted to reproduce and address all the issues reported, some bugs |
683 | + remain inconclusive. We recommend a full, tested backup of the MAAS servers |
684 | + before attempting the upgrade to 1.7. If you do encounter issues, please file |
685 | + these and flag them to the attention of the MAAS team and we will address them |
686 | + in point-releases. See bugs `1381058`_, `1382266`_, `1379890`_, `1379532`_, |
687 | + and `1379144`_. |
688 | + |
689 | +.. _1381058: |
690 | + https://launchpad.net/bugs/1381058 |
691 | +.. _1382266: |
692 | + https://launchpad.net/bugs/1382266 |
693 | +.. _1379890: |
694 | + https://launchpad.net/bugs/1379890 |
695 | +.. _1379532: |
696 | + https://launchpad.net/bugs/1379532 |
697 | +.. _1379144: |
698 | + https://launchpad.net/bugs/1379144 |
699 | + |
700 | +**Split Region/Cluster set-ups** |
701 | + If you site your cluster on a separate host to the region, it needs a |
702 | + security key to be manually installed by running |
703 | + ``maas-provision install-shared-secret`` on the cluster host. |
704 | + |
705 | +**Private boot streams** |
706 | + If you had private boot image stream information configured in MAAS 1.5 or |
707 | + 1.6, upgrading to 1.7 will not take that into account and it will need to be |
708 | + manually entered on the settings page in the MAAS UI (bug `1379890`_) |
709 | + |
710 | +.. _1379890: |
711 | + https://launchpad.net/bugs/1379890 |
712 | + |
713 | +**Concurrency issues** |
714 | + Concurrency issues expose us to races when simultaneous operations are |
715 | + triggered. This is the source of many hard to reproduce issues which will |
716 | + require us to change the default database isolation level. We intend to address |
717 | + this in the first point release of 1.7. |
718 | + |
719 | +**Destroying a Juju environment** |
720 | + When attempting to "juju destroy" an environment, nodes must be in the DEPLOYED |
721 | + state; otherwise, the destroy will fail. You should wait for all in-progress |
722 | + actions on the MAAS cluster to conclude before issuing the command. (bug |
723 | + `1381619`_) |
724 | + |
725 | +.. _1381619: |
726 | + https://launchpad.net/bugs/_1381619 |
727 | + |
728 | +**AMT power control** |
729 | + A few AMT-related issues remain, with workarounds: |
730 | + |
731 | + * Commissioning NUC reboots instead of shutting down (bug `1368685`_). There |
732 | + is `a workaround in the power template`_ |
733 | + |
734 | + * MAAS (amttool) cannot control AMT version > 8. See `workaround described in |
735 | + bug 1331214`_ |
736 | + |
737 | + * AMT NUC stuck at boot prompt instead of powering down (no ACPI support in |
738 | + syslinux poweroff) (bug `1376716`_). See the `ACPI-only workaround`_ |
739 | + |
740 | +.. _1368685: |
741 | + https://bugs.launchpad.net/maas/+bug/1368685 |
742 | +.. _a workaround in the power template: |
743 | + https://bugs.launchpad.net/maas/+bug/1368685/comments/8 |
744 | +.. _workaround described in bug 1331214: |
745 | + https://bugs.launchpad.net/maas/+bug/1331214/comments/18 |
746 | +.. _1376716: |
747 | + https://bugs.launchpad.net/maas/+bug/1376716 |
748 | +.. _ACPI-only workaround: |
749 | + https://bugs.launchpad.net/maas/+bug/1376716/comments/12 |
750 | + |
751 | + |
752 | +**Disk wiping** |
753 | + If you enable disk wiping, juju destroy-environment may fail for you. The |
754 | + current workaround is to wait and re-issue the command. This will be fixed in |
755 | + future versions of MAAS & Juju. (bug `1386327`_) |
756 | + |
757 | +.. _1386327: |
758 | + https://bugs.launchpad.net/maas/+bug/1386327 |
759 | + |
760 | +**BIND with DNSSEC** |
761 | + If you are using BIND with a forwarder that uses DNSSEC and have not |
762 | + configured certificates, you will need to explicitly disable that feature in |
763 | + your BIND configuration (1384334) |
764 | + |
765 | +.. _1384334: |
766 | + https://bugs.launchpad.net/maas/+bug/1384334 |
767 | + |
768 | +**Boot source selections on the API** |
769 | + Use of API to change image selections can leave DB in a bad state |
770 | + (bug `1376812`_). It can be fixed by issuing direct database updates. |
771 | + |
772 | +.. _1376812: |
773 | + https://bugs.launchpad.net/maas/+bug/1376812 |
774 | + |
775 | +**Disabling DNS** |
776 | + Disabling DNS may not work (bug `1383768`_) |
777 | + |
778 | +.. _1383768: |
779 | + https://bugs.launchpad.net/maas/+bug/1383768 |
780 | + |
781 | +**Stale DNS zone files** |
782 | + Stale DNS zone files may be left behind if the MAAS domainname is changed |
783 | + (bug `1383329`_) |
784 | + |
785 | +.. _1383329: |
786 | + https://bugs.launchpad.net/maas/+bug/1383329 |
787 | + |
788 | + |
789 | + |
790 | +Major bugs fixed in this release |
791 | +-------------------------------- |
792 | + |
793 | +See https://launchpad.net/maas/+milestone/1.7.0 for full details. |
794 | + |
795 | +#1081660 If maas-enlist fails to reach a DNS server, the node will be named ";; connection timed out; no servers could be reached" |
796 | + |
797 | +#1087183 MaaS cloud-init configuration specifies 'manage_etc_hosts: localhost' |
798 | + |
799 | +#1328351 ConstipationError: When the cluster runs the "import boot images" task it blocks other tasks |
800 | + |
801 | +#1342117 CLI command to set up node-group-interface fails with /usr/lib/python2.7/dist-packages/maascli/__main__.py: error: u'name' |
802 | + |
803 | +#1349254 Duplicate FQDN can be configured on MAAS via CLI or API |
804 | + |
805 | +#1352575 BMC password showing in the apache2 logs |
806 | + |
807 | +#1355534 UnknownPowerType traceback in appserver log |
808 | + |
809 | +#1363850 Auto-enlistment not reporting power parameters |
810 | + |
811 | +#1363900 Dev server errors while trying to write to '/var/lib/maas' |
812 | + |
813 | +#1363999 Not assigning static IP addresses |
814 | + |
815 | +#1364481 http 500 error doesn't contain a stack trace |
816 | + |
817 | +#1364993 500 error when trying to acquire a commissioned node (AddrFormatError: failed to detect a valid IP address from None) |
818 | + |
819 | +#1365130 django-admin prints spurious messages to stdout, breaking scripts |
820 | + |
821 | +#1365850 DHCP scan using cluster interface name as network interface? |
822 | + |
823 | +#1366172 NUC does not boot after power off/power on |
824 | + |
825 | +#1366212 Large dhcp leases file leads to tftp timeouts |
826 | + |
827 | +#1366652 Leaking temporary directories |
828 | + |
829 | +#1368269 internal server error when deleting a node |
830 | + |
831 | +#1368590 Power actions are not serialized. |
832 | + |
833 | +#1370534 Recurrent update of the power state of nodes crashes if the connection to the BMC fails. |
834 | + |
835 | +#1370958 excessive pserv logging |
836 | + |
837 | +#1372767 Twisted web client does not support IPv6 address |
838 | + |
839 | +#1372944 Twisted web client fails looking up IPv6 address hostname |
840 | + |
841 | +#1373031 Cannot register cluster |
842 | + |
843 | +#1373103 compose_curtin_network_preseed breaks installation of all other operating systems |
844 | + |
845 | +#1373368 Conflicting power actions being dropped on the floor can result in leaving a node in an inconsistent state |
846 | + |
847 | +#1373699 Cluster Listing Page lacks feedback about the images each cluster has |
848 | + |
849 | +#1374102 No retries for AMT power? |
850 | + |
851 | +#1375980 Nodes failed to transition out of "New" state on bulk commission |
852 | + |
853 | +#1376023 After performing bulk action on maas nodes, Internal Server Error |
854 | + |
855 | +#1376888 Nodes can't be deleted if DHCP management is off. |
856 | + |
857 | +#1377099 Bulk operation leaves nodes in inconsistent state |
858 | + |
859 | +#1379209 When a node has multiple interfaces on a network MAAS manages, MAAS assigns static IP addresses to all of them |
860 | + |
861 | +#1379744 Cluster registration is fragile and insecure |
862 | + |
863 | +#1380932 MAAS does not cope with changes of the dhcp daemons |
864 | + |
865 | +#1381605 Not all the DNS records are being added when deploying multiple nodes |
866 | + |
867 | +#1012954 If a power script fails, there is no UI feedback |
868 | + |
869 | +#1186196 "Starting a node" has different meanings in the UI and in the API. |
870 | + |
871 | +#1237215 maas and curtin do not indicate failure reasonably |
872 | + |
873 | +#1273222 MAAS doesn't check return values of power actions |
874 | + |
875 | +#1288502 archive and proxy settings not honoured for commissioning |
876 | + |
877 | +#1316919 Checks don't exist to confirm a node will actually boot |
878 | + |
879 | +#1321885 IPMI detection and automatic setting fail in ubuntu 14.04 maas |
880 | + |
881 | +#1325610 node marked "Ready" before poweroff complete |
882 | + |
883 | +#1340188 unallocated node started manually, causes AssertionError for purpose poweroff |
884 | + |
885 | +#1341118 No feedback when IPMI credentials fail |
886 | + |
887 | +#1341121 No feedback to user when cluster is not running |
888 | + |
889 | +#1341581 power state is not represented in api and ui |
890 | + |
891 | +#1341800 MAAS doesn't support soft power off through the API |
892 | + |
893 | +#1344177 hostnames can't be changed while a node is acquired |
894 | + |
895 | +#1347518 Confusing error message when API key is wrong |
896 | + |
897 | +#1349496 Unable to request a specific static IP on the API |
898 | + |
899 | +#1349736 MAAS logging is too verbose and not very useful |
900 | + |
901 | +#1349917 guess_server_address() can return IPAddress or hostname |
902 | + |
903 | +#1350103 No support for armhf/keystone architecture |
904 | + |
905 | +#1350856 Can't constrain acquisition of nodes by not having a tag |
906 | + |
907 | +#1356880 MAAS shouldn't allow changing the hostname of a deployed node |
908 | + |
909 | +#1357714 Virsh power driver does not seem to work at all |
910 | + |
911 | +#1358859 Commissioning output xml is hard to understand, would be nice to have yaml as an output option. |
912 | + |
913 | +#1359169 MAAS should handle invalid consumers gracefully |
914 | + |
915 | +#1359822 Gateway is missing in network definition |
916 | + |
917 | +#1363913 Impossible to remove last MAC from network in UI |
918 | + |
919 | +#1364228 Help text for node hostname is wrong |
920 | + |
921 | +#1364591 MAAS Archive Mirror does not respect non-default port |
922 | + |
923 | +#1365616 Non-admin access to cluster controller config |
924 | + |
925 | +#1365619 DNS should be an optional field in the network definition |
926 | + |
927 | +#1365776 commissioning results view for a node also shows installation results |
928 | + |
929 | +#1366812 Old boot resources are not being removed on clusters |
930 | + |
931 | +#1367455 MAC address for node's IPMI is reversed looked up to yield IP address using case sensitive comparison |
932 | + |
933 | +#1373580 [SRU] Glen m700 cartridge list as ARM64/generic after enlist |
934 | + |
935 | +#1373723 Releasing a node without power parameters ends up in not being able to release a node |
936 | + |
937 | +#1233158 no way to get power parameters in api |
938 | + |
939 | +#1319854 `maas login` tells you you're logged in successfully when you're not |
940 | + |
941 | +#1368480 Need API to gather image metadata across all of MAAS |
942 | + |
943 | +#1281406 Disk/memory space on Node edit page have no units |
944 | + |
945 | +#1299231 MAAS DHCP/DNS can't manage more than a /16 network |
946 | + |
947 | +#1357381 maas-region-admin createadmin shows error if not params given |
948 | + |
949 | +#1376393 powerkvm boot loader installs even when not needed |
950 | + |
951 | +#1287224 MAAS random generated hostnames are not pronounceable |
952 | + |
953 | +#1348364 non-maas managed subnets cannot query maas DNS |
954 | + |
955 | + |
956 | +======= |
957 | +1.7.0 |
958 | +===== |
959 | + |
960 | +Important announcements |
961 | +----------------------- |
962 | + |
963 | +**Re-import your boot images** |
964 | + You must re-import your boot images, see below for details. |
965 | + |
966 | +**Update Curtin preseed files** |
967 | + Two changes were made to Curtin preseed files that need your attention |
968 | + if you made any customisations: |
969 | + |
970 | + * The OS name must now appear in the filename. The new schema is shown |
971 | + here, each file pattern is tried in turn until a match is found:: |
972 | + |
973 | + {prefix}_{osystem}_{node_arch}_{node_subarch}_{release}_{node_name} |
974 | + {prefix}_{osystem}_{node_arch}_{node_subarch}_{release} |
975 | + {prefix}_{osystem}_{node_arch}_{node_subarch} |
976 | + {prefix}_{osystem}_{node_arch} |
977 | + {prefix}_{osystem} |
978 | + {prefix} |
979 | + |
980 | + * If you are modifying ``/etc/network/interfaces`` in the preseed, it must be |
981 | + moved so it is processed last in ``late_commands`` since MAAS now writes |
982 | + to this file itself as part of IPv6 setup. For example:: |
983 | + |
984 | + late_commands: |
985 | + bonding_02: ["curtin", "in-target", "--", "wget", "-O", "/etc/network/interfaces", "http://[...snip...]"] |
986 | + |
987 | + must now look like this:: |
988 | + |
989 | + late_commands: |
990 | + zz_write_ifaces: ["curtin", "in-target", "--", "wget", "-O", "/etc/network/interfaces", "http://[...snip...]"] |
991 | + |
992 | + The leading ``zz`` ensures the command sorts to the end of the |
993 | + ``late_commands`` list. |
994 | + |
995 | + |
996 | +Major new features |
997 | +------------------ |
998 | + |
999 | +**Improved image downloading and reporting.** |
1000 | + MAAS boot images are now downloaded centrally by the region controller |
1001 | + and disseminated to all registered cluster controllers. This change includes |
1002 | + a new web UI under the `Images` tab that allows the admin to select |
1003 | + which images to import and shows the progress of the ongoing download. |
1004 | + This completely replaces any file-based configuration that used to take |
1005 | + place on cluster controllers. The cluster page now shows whether it has |
1006 | + synchronised all the images from the region controller. |
1007 | + |
1008 | + This process is also completely controllable using the API. |
1009 | + |
1010 | +.. Note:: |
1011 | + Unfortunately due to a format change in the way images are stored, it |
1012 | + was not possible to migrate previously downloaded images to the new region |
1013 | + storage. The cluster(s) will still be able to use the existing images, |
1014 | + however the region controller will be unaware of them until an import |
1015 | + is initiated. When the import is finished, the cluster(s) will remove |
1016 | + older image resources. |
1017 | + |
1018 | + This means that the first thing to do after upgrading to 1.7 is go to the |
1019 | + `Images` tab and re-import the images. |
1020 | + |
1021 | +**Increased robustness.** |
1022 | + A large amount of effort has been given to ensuring that MAAS remains |
1023 | + robust in the face of adversity. An updated node state model has been |
1024 | + implemented that takes into account more of the situations in which a |
1025 | + node can be found including any failures at each stage. |
1026 | + |
1027 | + When a node is getting deployed, it is now monitored to check that each |
1028 | + stage is reached in a timely fashion; if it does not then it is marked |
1029 | + as failed. |
1030 | + |
1031 | + The core power driver was updated to check the state of the power on each |
1032 | + node and is reported in the web UI and API. The core driver now also |
1033 | + handles retries when changing the power state of hardware, removing the |
1034 | + requirement that each power template handle it individually. |
1035 | + |
1036 | +**RPC security.** |
1037 | + As a step towards mutually verified TLS connections between MAAS's |
1038 | + components, 1.7 introduces a simple shared-secret mechanism to |
1039 | + authenticate the region with the clusters and vice-versa. For those |
1040 | + clusters that run on the same machine as the region controller (which |
1041 | + will account for most people), everything will continue to work |
1042 | + without intervention. However, if you're running a cluster on a |
1043 | + separate machine, you must install the secret: |
1044 | + |
1045 | + 1. After upgrading the region controller, view /var/lib/maas/secret |
1046 | + (it's text) and copy it. |
1047 | + |
1048 | + 2. On each cluster, run: |
1049 | + |
1050 | + sudo -u maas maas-provision install-shared-secret |
1051 | + |
1052 | + You'll be prompted for the secret; paste it in and press enter. It |
1053 | + is a password prompt, so the secret will not be echoed back to you. |
1054 | + |
1055 | + That's it; the upgraded cluster controller will find the secret |
1056 | + without needing to be told. |
1057 | + |
1058 | +**RPC connections.** |
1059 | + Each cluster maintains a persistent connection to each region |
1060 | + controller process that's running. The ports on which the region is |
1061 | + listening are all high-numbered, and they are allocated randomly by |
1062 | + the OS. In a future release of MAAS we will narrow this down. For now, |
1063 | + each cluster controller needs unfiltered access to each machine in the |
1064 | + region on all high-numbered TCP ports. |
1065 | + |
1066 | +**Node event log.** |
1067 | + For every major event on nodes, it is now logged in a node-specific log. |
1068 | + This includes events such as power changes, deployments and any failures. |
1069 | + |
1070 | +**IPv6.** |
1071 | + It is now possible to deploy Ubuntu nodes that have IPv6 enabled. |
1072 | + See :doc:`ipv6` for more details. |
1073 | + |
1074 | +**Removal of Celery and RabbitMQ.** |
1075 | + While Celery was found to be very reliable it ultimately did not suit |
1076 | + the project's requirements as it is a largely fire-and-forget mechanism. |
1077 | + Additionally it was another moving part that caused some headaches for |
1078 | + users and admins alike, so the decision was taken to remove it and implement |
1079 | + a custom communications mechanism between the region controller and cluster |
1080 | + controllers. The new mechanism is bidirectional and allowed the complex |
1081 | + interactions to take place that are required as part of the robustness |
1082 | + improvements. |
1083 | + |
1084 | + Since a constant connection is maintained, as a side effect the web UI now |
1085 | + shows whether each cluster is connected or not. |
1086 | + |
1087 | +**Support for other OSes.** |
1088 | + Non-Ubuntu OSes are fully supported now. This includes: |
1089 | + - Windows |
1090 | + - Centos |
1091 | + - SuSE |
1092 | + |
1093 | +**Custom Images.** |
1094 | + MAAS now supports the deployment of Custom Images. Custom images can be |
1095 | + uploaded via the API. The usage of custom images allows the deployment of |
1096 | + other Ubuntu Flavors, such as Ubuntu Desktop. |
1097 | + |
1098 | +**maas-proxy.** |
1099 | + MAAS now uses maas-proxy as the default proxy solution instead of |
1100 | + squid-deb-proxy. On a fresh install, MAAS will use maas-proxy by default. |
1101 | + On upgrades from previous releases, MAAS will install maas-proxy instead of |
1102 | + squid-deb-proxy. |
1103 | + |
1104 | +Minor notable changes |
1105 | +--------------------- |
1106 | + |
1107 | +**Better handling of networks.** |
1108 | + All networks referred to by cluster interfaces are now automatically |
1109 | + registered on the Network page. Any node network interfaces are |
1110 | + automatically linked to the relevant Network. |
1111 | + |
1112 | +.. Note:: |
1113 | + Commissioning currently requires an IP address to be available for each |
1114 | + network interface on a network that MAAS manages; this allows MAAS to |
1115 | + auto-populate its networks database. In general you should use a |
1116 | + well-sized network (/16 recommended if you will be using containers and |
1117 | + VMs) and dynamic pool. If this feature risks causing IP exhaustion for |
1118 | + your deployment and you do not need the auto-populate functionality, you |
1119 | + can disable it by running the following command on your region controller:: |
1120 | + |
1121 | + sudo maas <profile> maas set-config name=enable_dhcp_discovery_on_unconfigured_interfaces value=False |
1122 | + |
1123 | +**Improved logging.** |
1124 | + A total overhaul of where logging is produced was undertaken, and now |
1125 | + all the main events in MAAS are selectively reported to syslog with the |
1126 | + "maas" prefix from both the region and cluster controllers alike. If MAAS |
1127 | + is installed using the standard Ubuntu packaging, its syslog entries are |
1128 | + redirected to /var/log/maas/maas.log. |
1129 | + |
1130 | + On the clusters, pserv.log is now less chatty and contains only errors. |
1131 | + On the region controller appservers, maas-django.log contains only appserver |
1132 | + errors. |
1133 | + |
1134 | +**Static IP selection.** |
1135 | + The API was extended so that specific IPs can be pre-allocated for network |
1136 | + interfaces on nodes and for user-allocated IPs. |
1137 | + |
1138 | +**Pronounceable random hostnames.** |
1139 | + The old auto-generated 5-letter names were replaced with a pseudo-random |
1140 | + name that is produced from a dictionary giving names of the form |
1141 | + 'adjective-noun'. |
1142 | + |
1143 | + |
1144 | +Known Problems & Workarounds |
1145 | +---------------------------- |
1146 | + |
1147 | +**Upgrade issues** |
1148 | + There may be upgrade issues for users currently on MAAS 1.5 and 1.6; while we |
1149 | + have attempted to reproduce and address all the issues reported, some bugs |
1150 | + remain inconclusive. We recommend a full, tested backup of the MAAS servers |
1151 | + before attempting the upgrade to 1.7. If you do encounter issues, please file |
1152 | + these and flag them to the attention of the MAAS team and we will address them |
1153 | + in point-releases. See bugs `1381058`_, `1382266`_, `1379890`_, `1379532`_, |
1154 | + and `1379144`_. |
1155 | + |
1156 | +.. _1381058: |
1157 | + https://launchpad.net/bugs/1381058 |
1158 | +.. _1382266: |
1159 | + https://launchpad.net/bugs/1382266 |
1160 | +.. _1379890: |
1161 | + https://launchpad.net/bugs/1379890 |
1162 | +.. _1379532: |
1163 | + https://launchpad.net/bugs/1379532 |
1164 | +.. _1379144: |
1165 | + https://launchpad.net/bugs/1379144 |
1166 | + |
1167 | +**Split Region/Cluster set-ups** |
1168 | + If you site your cluster on a separate host to the region, it needs a |
1169 | + security key to be manually installed by running |
1170 | + ``maas-provision install-shared-secret`` on the cluster host. |
1171 | + |
1172 | +**Private boot streams** |
1173 | + If you had private boot image stream information configured in MAAS 1.5 or |
1174 | + 1.6, upgrading to 1.7 will not take that into account and it will need to be |
1175 | + manually entered on the settings page in the MAAS UI (bug `1379890`_) |
1176 | + |
1177 | +.. _1379890: |
1178 | + https://launchpad.net/bugs/1379890 |
1179 | + |
1180 | +**Concurrency issues** |
1181 | + Concurrency issues expose us to races when simultaneous operations are |
1182 | + triggered. This is the source of many hard to reproduce issues which will |
1183 | + require us to change the default database isolation level. We intend to address |
1184 | + this in the first point release of 1.7. |
1185 | + |
1186 | +**Destroying a Juju environment** |
1187 | + When attempting to "juju destroy" an environment, nodes must be in the DEPLOYED |
1188 | + state; otherwise, the destroy will fail. You should wait for all in-progress |
1189 | + actions on the MAAS cluster to conclude before issuing the command. (bug |
1190 | + `1381619`_) |
1191 | + |
1192 | +.. _1381619: |
1193 | + https://launchpad.net/bugs/_1381619 |
1194 | + |
1195 | +**AMT power control** |
1196 | + A few AMT-related issues remain, with workarounds: |
1197 | + |
1198 | + * Commissioning NUC reboots instead of shutting down (bug `1368685`_). There |
1199 | + is `a workaround in the power template`_ |
1200 | + |
1201 | + * MAAS (amttool) cannot control AMT version > 8. See `workaround described in |
1202 | + bug 1331214`_ |
1203 | + |
1204 | + * AMT NUC stuck at boot prompt instead of powering down (no ACPI support in |
1205 | + syslinux poweroff) (bug `1376716`_). See the `ACPI-only workaround`_ |
1206 | + |
1207 | +.. _1368685: |
1208 | + https://bugs.launchpad.net/maas/+bug/1368685 |
1209 | +.. _a workaround in the power template: |
1210 | + https://bugs.launchpad.net/maas/+bug/1368685/comments/8 |
1211 | +.. _workaround described in bug 1331214: |
1212 | + https://bugs.launchpad.net/maas/+bug/1331214/comments/18 |
1213 | +.. _1376716: |
1214 | + https://bugs.launchpad.net/maas/+bug/1376716 |
1215 | +.. _ACPI-only workaround: |
1216 | + https://bugs.launchpad.net/maas/+bug/1376716/comments/12 |
1217 | + |
1218 | + |
1219 | +**Disk wiping** |
1220 | + If you enable disk wiping, juju destroy-environment may fail for you. The |
1221 | + current workaround is to wait and re-issue the command. This will be fixed in |
1222 | + future versions of MAAS & Juju. (bug `1386327`_) |
1223 | + |
1224 | +.. _1386327: |
1225 | + https://bugs.launchpad.net/maas/+bug/1386327 |
1226 | + |
1227 | +**BIND with DNSSEC** |
1228 | + If you are using BIND with a forwarder that uses DNSSEC and have not |
1229 | + configured certificates, you will need to explicitly disable that feature in |
1230 | + your BIND configuration (1384334) |
1231 | + |
1232 | +.. _1384334: |
1233 | + https://bugs.launchpad.net/maas/+bug/1384334 |
1234 | + |
1235 | +**Boot source selections on the API** |
1236 | + Use of API to change image selections can leave DB in a bad state |
1237 | + (bug `1376812`_). It can be fixed by issuing direct database updates. |
1238 | + |
1239 | +.. _1376812: |
1240 | + https://bugs.launchpad.net/maas/+bug/1376812 |
1241 | + |
1242 | +**Disabling DNS** |
1243 | + Disabling DNS may not work (bug `1383768`_) |
1244 | + |
1245 | +.. _1383768: |
1246 | + https://bugs.launchpad.net/maas/+bug/1383768 |
1247 | + |
1248 | +**Stale DNS zone files** |
1249 | + Stale DNS zone files may be left behind if the MAAS domainname is changed |
1250 | + (bug `1383329`_) |
1251 | + |
1252 | +.. _1383329: |
1253 | + https://bugs.launchpad.net/maas/+bug/1383329 |
1254 | + |
1255 | + |
1256 | + |
1257 | +Major bugs fixed in this release |
1258 | +-------------------------------- |
1259 | + |
1260 | +See https://launchpad.net/maas/+milestone/1.7.0 for full details. |
1261 | + |
1262 | +#1081660 If maas-enlist fails to reach a DNS server, the node will be named ";; connection timed out; no servers could be reached" |
1263 | + |
1264 | +#1087183 MaaS cloud-init configuration specifies 'manage_etc_hosts: localhost' |
1265 | + |
1266 | +#1328351 ConstipationError: When the cluster runs the "import boot images" task it blocks other tasks |
1267 | + |
1268 | +#1342117 CLI command to set up node-group-interface fails with /usr/lib/python2.7/dist-packages/maascli/__main__.py: error: u'name' |
1269 | + |
1270 | +#1349254 Duplicate FQDN can be configured on MAAS via CLI or API |
1271 | + |
1272 | +#1352575 BMC password showing in the apache2 logs |
1273 | + |
1274 | +#1355534 UnknownPowerType traceback in appserver log |
1275 | + |
1276 | +#1363850 Auto-enlistment not reporting power parameters |
1277 | + |
1278 | +#1363900 Dev server errors while trying to write to '/var/lib/maas' |
1279 | + |
1280 | +#1363999 Not assigning static IP addresses |
1281 | + |
1282 | +#1364481 http 500 error doesn't contain a stack trace |
1283 | + |
1284 | +#1364993 500 error when trying to acquire a commissioned node (AddrFormatError: failed to detect a valid IP address from None) |
1285 | + |
1286 | +#1365130 django-admin prints spurious messages to stdout, breaking scripts |
1287 | + |
1288 | +#1365850 DHCP scan using cluster interface name as network interface? |
1289 | + |
1290 | +#1366172 NUC does not boot after power off/power on |
1291 | + |
1292 | +#1366212 Large dhcp leases file leads to tftp timeouts |
1293 | + |
1294 | +#1366652 Leaking temporary directories |
1295 | + |
1296 | +#1368269 internal server error when deleting a node |
1297 | + |
1298 | +#1368590 Power actions are not serialized. |
1299 | + |
1300 | +#1370534 Recurrent update of the power state of nodes crashes if the connection to the BMC fails. |
1301 | + |
1302 | +#1370958 excessive pserv logging |
1303 | + |
1304 | +#1372767 Twisted web client does not support IPv6 address |
1305 | + |
1306 | +#1372944 Twisted web client fails looking up IPv6 address hostname |
1307 | + |
1308 | +#1373031 Cannot register cluster |
1309 | + |
1310 | +#1373103 compose_curtin_network_preseed breaks installation of all other operating systems |
1311 | + |
1312 | +#1373368 Conflicting power actions being dropped on the floor can result in leaving a node in an inconsistent state |
1313 | + |
1314 | +#1373699 Cluster Listing Page lacks feedback about the images each cluster has |
1315 | + |
1316 | +#1374102 No retries for AMT power? |
1317 | + |
1318 | +#1375980 Nodes failed to transition out of "New" state on bulk commission |
1319 | + |
1320 | +#1376023 After performing bulk action on maas nodes, Internal Server Error |
1321 | + |
1322 | +#1376888 Nodes can't be deleted if DHCP management is off. |
1323 | + |
1324 | +#1377099 Bulk operation leaves nodes in inconsistent state |
1325 | + |
1326 | +#1379209 When a node has multiple interfaces on a network MAAS manages, MAAS assigns static IP addresses to all of them |
1327 | + |
1328 | +#1379744 Cluster registration is fragile and insecure |
1329 | + |
1330 | +#1380932 MAAS does not cope with changes of the dhcp daemons |
1331 | + |
1332 | +#1381605 Not all the DNS records are being added when deploying multiple nodes |
1333 | + |
1334 | +#1012954 If a power script fails, there is no UI feedback |
1335 | + |
1336 | +#1186196 "Starting a node" has different meanings in the UI and in the API. |
1337 | + |
1338 | +#1237215 maas and curtin do not indicate failure reasonably |
1339 | + |
1340 | +#1273222 MAAS doesn't check return values of power actions |
1341 | + |
1342 | +#1288502 archive and proxy settings not honoured for commissioning |
1343 | + |
1344 | +#1316919 Checks don't exist to confirm a node will actually boot |
1345 | + |
1346 | +#1321885 IPMI detection and automatic setting fail in ubuntu 14.04 maas |
1347 | + |
1348 | +#1325610 node marked "Ready" before poweroff complete |
1349 | + |
1350 | +#1325638 Add hardware enablement for Universal Management Gateway |
1351 | + |
1352 | +#1340188 unallocated node started manually, causes AssertionError for purpose poweroff |
1353 | + |
1354 | +#1341118 No feedback when IPMI credentials fail |
1355 | + |
1356 | +#1341121 No feedback to user when cluster is not running |
1357 | + |
1358 | +#1341581 power state is not represented in api and ui |
1359 | + |
1360 | +#1341800 MAAS doesn't support soft power off through the API |
1361 | + |
1362 | +#1344177 hostnames can't be changed while a node is acquired |
1363 | + |
1364 | +#1347518 Confusing error message when API key is wrong |
1365 | + |
1366 | +#1349496 Unable to request a specific static IP on the API |
1367 | + |
1368 | +#1349736 MAAS logging is too verbose and not very useful |
1369 | + |
1370 | +#1349917 guess_server_address() can return IPAddress or hostname |
1371 | + |
1372 | +#1350103 No support for armhf/keystone architecture |
1373 | + |
1374 | +#1350856 Can't constrain acquisition of nodes by not having a tag |
1375 | + |
1376 | +#1356880 MAAS shouldn't allow changing the hostname of a deployed node |
1377 | + |
1378 | +#1357714 Virsh power driver does not seem to work at all |
1379 | + |
1380 | +#1358859 Commissioning output xml is hard to understand, would be nice to have yaml as an output option. |
1381 | + |
1382 | +#1359169 MAAS should handle invalid consumers gracefully |
1383 | + |
1384 | +#1359822 Gateway is missing in network definition |
1385 | + |
1386 | +#1363913 Impossible to remove last MAC from network in UI |
1387 | + |
1388 | +#1364228 Help text for node hostname is wrong |
1389 | + |
1390 | +#1364591 MAAS Archive Mirror does not respect non-default port |
1391 | + |
1392 | +#1365616 Non-admin access to cluster controller config |
1393 | + |
1394 | +#1365619 DNS should be an optional field in the network definition |
1395 | + |
1396 | +#1365776 commissioning results view for a node also shows installation results |
1397 | + |
1398 | +#1366812 Old boot resources are not being removed on clusters |
1399 | + |
1400 | +#1367455 MAC address for node's IPMI is reversed looked up to yield IP address using case sensitive comparison |
1401 | + |
1402 | +#1373580 [SRU] Glen m700 cartridge list as ARM64/generic after enlist |
1403 | + |
1404 | +#1373723 Releasing a node without power parameters ends up in not being able to release a node |
1405 | + |
1406 | +#1233158 no way to get power parameters in api |
1407 | + |
1408 | +#1319854 `maas login` tells you you're logged in successfully when you're not |
1409 | + |
1410 | +#1368480 Need API to gather image metadata across all of MAAS |
1411 | + |
1412 | +#1281406 Disk/memory space on Node edit page have no units |
1413 | + |
1414 | +#1299231 MAAS DHCP/DNS can't manage more than a /16 network |
1415 | + |
1416 | +#1357381 maas-region-admin createadmin shows error if not params given |
1417 | + |
1418 | +#1376393 powerkvm boot loader installs even when not needed |
1419 | + |
1420 | +#1287224 MAAS random generated hostnames are not pronounceable |
1421 | + |
1422 | +#1348364 non-maas managed subnets cannot query maas DNS |
1423 | + |
1424 | + |
1425 | +>>>>>>> MERGE-SOURCE |
1426 | 1.6.1 |
1427 | ===== |
1428 | |
1429 | |
1430 | === modified file 'src/maasserver/api/nodes.py' |
1431 | === modified file 'src/maasserver/api/tests/test_enlistment.py' |
1432 | === modified file 'src/maasserver/api/tests/test_node.py' |
1433 | --- src/maasserver/api/tests/test_node.py 2014-11-05 16:29:35 +0000 |
1434 | +++ src/maasserver/api/tests/test_node.py 2014-11-06 01:24:10 +0000 |
1435 | @@ -26,7 +26,12 @@ |
1436 | from maasserver.enum import ( |
1437 | IPADDRESS_TYPE, |
1438 | NODE_STATUS, |
1439 | - NODE_STATUS_CHOICES, |
1440 | +<<<<<<< TREE |
1441 | + NODE_STATUS_CHOICES, |
1442 | +======= |
1443 | + NODE_STATUS_CHOICES, |
1444 | + NODE_STATUS_CHOICES_DICT, |
1445 | +>>>>>>> MERGE-SOURCE |
1446 | ) |
1447 | from maasserver.fields import ( |
1448 | MAC, |
1449 | |
1450 | === modified file 'src/maasserver/api/tests/test_nodes.py' |
1451 | === modified file 'src/maasserver/bootresources.py' |
1452 | === modified file 'src/maasserver/forms.py' |
1453 | === modified file 'src/maasserver/forms_settings.py' |
1454 | --- src/maasserver/forms_settings.py 2014-09-25 21:06:39 +0000 |
1455 | +++ src/maasserver/forms_settings.py 2014-11-06 01:24:10 +0000 |
1456 | @@ -266,6 +266,16 @@ |
1457 | "Erase nodes' disks prior to releasing.") |
1458 | } |
1459 | }, |
1460 | + 'enable_dhcp_discovery_on_unconfigured_interfaces': { |
1461 | + 'default': False, |
1462 | + 'form': forms.BooleanField, |
1463 | + 'form_kwargs': { |
1464 | + 'required': False, |
1465 | + 'label': ( |
1466 | + "Perform DHCP discovery on unconfigured network " |
1467 | + "interfaces of commissioning nodes."), |
1468 | + } |
1469 | + }, |
1470 | } |
1471 | |
1472 | |
1473 | |
1474 | === modified file 'src/maasserver/middleware.py' |
1475 | === modified file 'src/maasserver/models/config.py' |
1476 | --- src/maasserver/models/config.py 2014-10-08 20:41:31 +0000 |
1477 | +++ src/maasserver/models/config.py 2014-11-06 01:24:10 +0000 |
1478 | @@ -60,6 +60,7 @@ |
1479 | # Third Party |
1480 | 'enable_third_party_drivers': True, |
1481 | 'enable_disk_erasing_on_release': False, |
1482 | + 'enable_dhcp_discovery_on_unconfigured_interfaces': True, |
1483 | ## /settings |
1484 | } |
1485 | |
1486 | |
1487 | === modified file 'src/maasserver/models/node.py' |
1488 | --- src/maasserver/models/node.py 2014-11-05 23:39:16 +0000 |
1489 | +++ src/maasserver/models/node.py 2014-11-06 01:24:10 +0000 |
1490 | @@ -319,6 +319,146 @@ |
1491 | available_nodes = self.get_nodes(for_user, NODE_PERMISSION.VIEW) |
1492 | return available_nodes.filter(status=NODE_STATUS.READY) |
1493 | |
1494 | +<<<<<<< TREE |
1495 | +======= |
1496 | + def stop_nodes(self, ids, by_user, stop_mode='hard'): |
1497 | + """Request on given user's behalf that the given nodes be shut down. |
1498 | + |
1499 | + Shutdown is only requested for nodes that the user has ownership |
1500 | + privileges for; any other nodes in the request are ignored. |
1501 | + |
1502 | + :param ids: The `system_id` values for nodes to be shut down. |
1503 | + :type ids: Sequence |
1504 | + :param by_user: Requesting user. |
1505 | + :type by_user: User_ |
1506 | + :param stop_mode: Power off mode - usually 'soft' or 'hard'. |
1507 | + :type stop_mode: unicode |
1508 | + :return: Those Nodes for which shutdown was actually requested. |
1509 | + :rtype: list |
1510 | + """ |
1511 | + # Obtain node model objects for each node specified. |
1512 | + nodes = self.get_nodes(by_user, NODE_PERMISSION.EDIT, ids=ids) |
1513 | + |
1514 | + # Helper function to whittle the list of nodes down to those that we |
1515 | + # can actually stop, and keep hold of their power control info. |
1516 | + def gen_power_info(nodes): |
1517 | + for node in nodes: |
1518 | + power_info = node.get_effective_power_info() |
1519 | + if power_info.can_be_stopped: |
1520 | + # Smuggle in a hint about how to power-off the node. |
1521 | + power_info.power_parameters['power_off_mode'] = stop_mode |
1522 | + yield node, power_info |
1523 | + |
1524 | + # Create info that we can pass into the reactor (no model objects). |
1525 | + nodes_stop_info = list( |
1526 | + (node.system_id, node.hostname, node.nodegroup.uuid, power_info) |
1527 | + for node, power_info in gen_power_info(nodes)) |
1528 | + powered_systems = [ |
1529 | + system_id for system_id, _, _, _ in nodes_stop_info] |
1530 | + |
1531 | + # Request that these nodes be powered off and wait for the |
1532 | + # commands to return or fail. |
1533 | + deferreds = power_off_nodes(nodes_stop_info).viewvalues() |
1534 | + wait_for_power_commands(deferreds) |
1535 | + |
1536 | + # Return a list of those nodes that we've sent power commands for. |
1537 | + return list( |
1538 | + node for node in nodes if node.system_id in powered_systems) |
1539 | + |
1540 | + def start_nodes(self, ids, by_user, user_data=None): |
1541 | + """Request on given user's behalf that the given nodes be started up. |
1542 | + |
1543 | + Power-on is only requested for nodes that the user has ownership |
1544 | + privileges for; any other nodes in the request are ignored. |
1545 | + |
1546 | + Nodes are also ignored if they don't have a valid power type |
1547 | + configured. |
1548 | + |
1549 | + :param ids: The `system_id` values for nodes to be started. |
1550 | + :type ids: Sequence |
1551 | + :param by_user: Requesting user. |
1552 | + :type by_user: User_ |
1553 | + :param user_data: Optional blob of user-data to be made available to |
1554 | + the nodes through the metadata service. If not given, any |
1555 | + previous user data is used. |
1556 | + :type user_data: unicode |
1557 | + :return: Those Nodes for which power-on was actually requested. |
1558 | + :rtype: list |
1559 | + |
1560 | + :raises MultipleFailures: When there are failures originating from a |
1561 | + remote process. There could be one or more failures -- it's not |
1562 | + strictly *multiple* -- but they do all originate from comms with |
1563 | + remote processes. |
1564 | + :raises: `StaticIPAddressExhaustion` if there are not enough IP |
1565 | + addresses left in the static range.. |
1566 | + """ |
1567 | + # Avoid circular imports. |
1568 | + from metadataserver.models import NodeUserData |
1569 | + |
1570 | + # Obtain node model objects for each node specified. |
1571 | + nodes = self.get_nodes(by_user, NODE_PERMISSION.EDIT, ids=ids) |
1572 | + |
1573 | + # Record the same user data for all nodes we've been *requested* to |
1574 | + # start, regardless of whether or not we actually can; the user may |
1575 | + # choose to manually start them. |
1576 | + NodeUserData.objects.bulk_set_user_data(nodes, user_data) |
1577 | + |
1578 | + # Claim static IP addresses for all nodes we've been *requested* to |
1579 | + # start, such that they're recorded in the database. This results in a |
1580 | + # mapping of nodegroups to (ips, macs). |
1581 | + static_mappings = defaultdict(dict) |
1582 | + for node in nodes: |
1583 | + if node.status == NODE_STATUS.ALLOCATED: |
1584 | + claims = node.claim_static_ip_addresses() |
1585 | + # If the PXE mac is on a managed interface then we can ask |
1586 | + # the cluster to generate the DHCP host map(s). |
1587 | + if node.is_pxe_mac_on_managed_interface(): |
1588 | + static_mappings[node.nodegroup].update(claims) |
1589 | + node.start_deployment() |
1590 | + |
1591 | + # XXX 2014-06-17 bigjools bug=1330765 |
1592 | + # If the above fails it needs to release the static IPs back to the |
1593 | + # pool. An enclosing transaction or savepoint from the caller may take |
1594 | + # care of this, given that a serious problem above will result in an |
1595 | + # exception. If we're being belt-n-braces though it ought to clear up |
1596 | + # before returning too. As part of the robustness work coming up, it |
1597 | + # also needs to inform the user. |
1598 | + |
1599 | + # Update host maps and wait for them so that we can report failures |
1600 | + # directly to the caller. |
1601 | + update_host_maps_failures = list(update_host_maps(static_mappings)) |
1602 | + if len(update_host_maps_failures) != 0: |
1603 | + raise MultipleFailures(*update_host_maps_failures) |
1604 | + |
1605 | + # Update the DNS zone with the new static IP info as necessary. |
1606 | + from maasserver.dns.config import change_dns_zones |
1607 | + change_dns_zones({node.nodegroup for node in nodes}) |
1608 | + |
1609 | + # Helper function to whittle the list of nodes down to those that we |
1610 | + # can actually start, and keep hold of their power control info. |
1611 | + def gen_power_info(nodes): |
1612 | + for node in nodes: |
1613 | + power_info = node.get_effective_power_info() |
1614 | + if power_info.can_be_started: |
1615 | + yield node, power_info |
1616 | + |
1617 | + # Create info that we can pass into the reactor (no model objects). |
1618 | + nodes_start_info = list( |
1619 | + (node.system_id, node.hostname, node.nodegroup.uuid, power_info) |
1620 | + for node, power_info in gen_power_info(nodes)) |
1621 | + powered_systems = [ |
1622 | + system_id for system_id, _, _, _ in nodes_start_info] |
1623 | + |
1624 | + # Request that these nodes be powered off and wait for the |
1625 | + # commands to return or fail. |
1626 | + deferreds = power_on_nodes(nodes_start_info).viewvalues() |
1627 | + wait_for_power_commands(deferreds) |
1628 | + |
1629 | + # Return a list of those nodes that we've sent power commands for. |
1630 | + return list( |
1631 | + node for node in nodes if node.system_id in powered_systems) |
1632 | + |
1633 | +>>>>>>> MERGE-SOURCE |
1634 | |
1635 | def patch_pgarray_types(): |
1636 | """Monkey-patch incompatibility with recent versions of `djorm_pgarray`. |
1637 | @@ -1227,6 +1367,7 @@ |
1638 | self.distro_series = '' |
1639 | self.license_key = '' |
1640 | self.save() |
1641 | +<<<<<<< TREE |
1642 | |
1643 | # Clear installation results |
1644 | NodeResult.objects.filter( |
1645 | @@ -1239,6 +1380,16 @@ |
1646 | from maasserver.dns.config import change_dns_zones |
1647 | change_dns_zones([self.nodegroup]) |
1648 | |
1649 | +======= |
1650 | + |
1651 | + # Do these after updating the node to avoid creating deadlocks with |
1652 | + # other node editing operations. |
1653 | + deallocated_ips = StaticIPAddress.objects.deallocate_by_node(self) |
1654 | + self.delete_host_maps(deallocated_ips) |
1655 | + from maasserver.dns.config import change_dns_zones |
1656 | + change_dns_zones([self.nodegroup]) |
1657 | + |
1658 | +>>>>>>> MERGE-SOURCE |
1659 | # We explicitly commit here because during bulk node actions we |
1660 | # want to make sure that each successful state transition is |
1661 | # recorded in the DB. |
1662 | @@ -1412,6 +1563,7 @@ |
1663 | return self.pxe_mac |
1664 | |
1665 | return self.macaddress_set.first() |
1666 | +<<<<<<< TREE |
1667 | |
1668 | def is_pxe_mac_on_managed_interface(self): |
1669 | pxe_mac = self.get_pxe_mac() |
1670 | @@ -1541,3 +1693,13 @@ |
1671 | d = power_off_nodes([stop_info]).values().pop() |
1672 | wait_for_power_command(d) |
1673 | return True |
1674 | +======= |
1675 | + |
1676 | + def is_pxe_mac_on_managed_interface(self): |
1677 | + pxe_mac = self.get_pxe_mac() |
1678 | + if pxe_mac is not None: |
1679 | + cluster_interface = pxe_mac.cluster_interface |
1680 | + if cluster_interface is not None: |
1681 | + return cluster_interface.is_managed |
1682 | + return False |
1683 | +>>>>>>> MERGE-SOURCE |
1684 | |
1685 | === modified file 'src/maasserver/models/staticipaddress.py' |
1686 | === modified file 'src/maasserver/models/tests/test_bootsource.py' |
1687 | --- src/maasserver/models/tests/test_bootsource.py 2014-11-03 01:36:42 +0000 |
1688 | +++ src/maasserver/models/tests/test_bootsource.py 2014-11-06 01:24:10 +0000 |
1689 | @@ -15,6 +15,7 @@ |
1690 | __all__ = [] |
1691 | |
1692 | import os |
1693 | +from unittest import skip |
1694 | |
1695 | from django.core.exceptions import ValidationError |
1696 | from maasserver.bootsources import cache_boot_sources |
1697 | @@ -98,6 +99,9 @@ |
1698 | [], |
1699 | boot_source_dict['selections']) |
1700 | |
1701 | + # XXX: GavinPanella 2014-10-28 bug=1376317: This test is fragile, possibly |
1702 | + # due to isolation issues. |
1703 | + @skip("Possible isolation issues") |
1704 | def test_calls_cache_boot_sources_on_create(self): |
1705 | mock_callLater = self.patch(reactor, 'callLater') |
1706 | BootSource.objects.create( |
1707 | |
1708 | === modified file 'src/maasserver/models/tests/test_node.py' |
1709 | --- src/maasserver/models/tests/test_node.py 2014-11-05 15:28:40 +0000 |
1710 | +++ src/maasserver/models/tests/test_node.py 2014-11-06 01:24:10 +0000 |
1711 | @@ -19,6 +19,7 @@ |
1712 | timedelta, |
1713 | ) |
1714 | import random |
1715 | +from unittest import skip |
1716 | |
1717 | import crochet |
1718 | from django.core.exceptions import ValidationError |
1719 | @@ -2083,6 +2084,422 @@ |
1720 | self.assertThat(erase_mock, MockNotCalled()) |
1721 | |
1722 | |
1723 | +<<<<<<< TREE |
1724 | +======= |
1725 | +class NodeManagerTest_StartNodes(MAASServerTestCase): |
1726 | + |
1727 | + def setUp(self): |
1728 | + super(NodeManagerTest_StartNodes, self).setUp() |
1729 | + self.useFixture(RegionEventLoopFixture("rpc")) |
1730 | + self.useFixture(RunningEventLoopFixture()) |
1731 | + self.rpc_fixture = self.useFixture(MockLiveRegionToClusterRPCFixture()) |
1732 | + |
1733 | + def prepare_rpc_to_cluster(self, nodegroup): |
1734 | + protocol = self.rpc_fixture.makeCluster( |
1735 | + nodegroup, cluster_module.CreateHostMaps, cluster_module.PowerOn, |
1736 | + cluster_module.StartMonitors) |
1737 | + protocol.CreateHostMaps.side_effect = always_succeed_with({}) |
1738 | + protocol.StartMonitors.side_effect = always_succeed_with({}) |
1739 | + protocol.PowerOn.side_effect = always_succeed_with({}) |
1740 | + return protocol |
1741 | + |
1742 | + def make_acquired_nodes_with_macs(self, user, nodegroup=None, count=3): |
1743 | + nodes = [] |
1744 | + for _ in xrange(count): |
1745 | + node = factory.make_node_with_mac_attached_to_nodegroupinterface( |
1746 | + nodegroup=nodegroup, status=NODE_STATUS.READY) |
1747 | + self.prepare_rpc_to_cluster(node.nodegroup) |
1748 | + node.acquire(user) |
1749 | + nodes.append(node) |
1750 | + return nodes |
1751 | + |
1752 | + def test__sets_user_data(self): |
1753 | + user = factory.make_User() |
1754 | + nodegroup = factory.make_NodeGroup() |
1755 | + self.prepare_rpc_to_cluster(nodegroup) |
1756 | + nodes = self.make_acquired_nodes_with_macs(user, nodegroup) |
1757 | + user_data = factory.make_bytes() |
1758 | + |
1759 | + with TwistedLoggerFixture() as twisted_log: |
1760 | + Node.objects.start_nodes( |
1761 | + list(node.system_id for node in nodes), |
1762 | + user, user_data=user_data) |
1763 | + |
1764 | + # All three nodes have been given the same user data. |
1765 | + nuds = NodeUserData.objects.filter( |
1766 | + node_id__in=(node.id for node in nodes)) |
1767 | + self.assertEqual({user_data}, {nud.data for nud in nuds}) |
1768 | + # No complaints are made to the Twisted log. |
1769 | + self.assertFalse(twisted_log.containsError(), twisted_log.output) |
1770 | + |
1771 | + def test__resets_user_data(self): |
1772 | + user = factory.make_User() |
1773 | + nodegroup = factory.make_NodeGroup() |
1774 | + self.prepare_rpc_to_cluster(nodegroup) |
1775 | + nodes = self.make_acquired_nodes_with_macs(user, nodegroup) |
1776 | + |
1777 | + with TwistedLoggerFixture() as twisted_log: |
1778 | + Node.objects.start_nodes( |
1779 | + list(node.system_id for node in nodes), |
1780 | + user, user_data=None) |
1781 | + |
1782 | + # All three nodes have been given the same user data. |
1783 | + nuds = NodeUserData.objects.filter( |
1784 | + node_id__in=(node.id for node in nodes)) |
1785 | + self.assertThat(list(nuds), HasLength(0)) |
1786 | + # No complaints are made to the Twisted log. |
1787 | + self.assertFalse(twisted_log.containsError(), twisted_log.output) |
1788 | + |
1789 | + def test__claims_static_ip_addresses(self): |
1790 | + user = factory.make_User() |
1791 | + nodegroup = factory.make_NodeGroup() |
1792 | + self.prepare_rpc_to_cluster(nodegroup) |
1793 | + nodes = self.make_acquired_nodes_with_macs(user, nodegroup) |
1794 | + |
1795 | + claim_static_ip_addresses = self.patch_autospec( |
1796 | + Node, "claim_static_ip_addresses", spec_set=False) |
1797 | + claim_static_ip_addresses.return_value = {} |
1798 | + |
1799 | + with TwistedLoggerFixture() as twisted_log: |
1800 | + Node.objects.start_nodes( |
1801 | + list(node.system_id for node in nodes), user) |
1802 | + |
1803 | + for node in nodes: |
1804 | + self.expectThat(claim_static_ip_addresses, MockAnyCall(node)) |
1805 | + # No complaints are made to the Twisted log. |
1806 | + self.assertFalse(twisted_log.containsError(), twisted_log.output) |
1807 | + |
1808 | + def test__claims_static_ip_addresses_for_allocated_nodes_only(self): |
1809 | + user = factory.make_User() |
1810 | + nodegroup = factory.make_NodeGroup() |
1811 | + self.prepare_rpc_to_cluster(nodegroup) |
1812 | + nodes = self.make_acquired_nodes_with_macs(user, nodegroup, count=2) |
1813 | + |
1814 | + # Change the status of the first node to something other than |
1815 | + # allocated. |
1816 | + broken_node, allocated_node = nodes |
1817 | + broken_node.status = NODE_STATUS.BROKEN |
1818 | + broken_node.save() |
1819 | + |
1820 | + claim_static_ip_addresses = self.patch_autospec( |
1821 | + Node, "claim_static_ip_addresses", spec_set=False) |
1822 | + claim_static_ip_addresses.return_value = {} |
1823 | + |
1824 | + with TwistedLoggerFixture() as twisted_log: |
1825 | + Node.objects.start_nodes( |
1826 | + list(node.system_id for node in nodes), user) |
1827 | + |
1828 | + # Only one call is made to claim_static_ip_addresses(), for the |
1829 | + # still-allocated node. |
1830 | + self.assertThat( |
1831 | + claim_static_ip_addresses, |
1832 | + MockCalledOnceWith(allocated_node)) |
1833 | + # No complaints are made to the Twisted log. |
1834 | + self.assertFalse(twisted_log.containsError(), twisted_log.output) |
1835 | + |
1836 | + def test__updates_host_maps(self): |
1837 | + user = factory.make_User() |
1838 | + nodes = self.make_acquired_nodes_with_macs(user) |
1839 | + |
1840 | + update_host_maps = self.patch(node_module, "update_host_maps") |
1841 | + update_host_maps.return_value = [] # No failures. |
1842 | + |
1843 | + with TwistedLoggerFixture() as twisted_log: |
1844 | + Node.objects.start_nodes( |
1845 | + list(node.system_id for node in nodes), user) |
1846 | + |
1847 | + # Host maps are updated. |
1848 | + self.assertThat( |
1849 | + update_host_maps, MockCalledOnceWith({ |
1850 | + node.nodegroup: { |
1851 | + ip_address.ip: mac.mac_address |
1852 | + for ip_address in mac.ip_addresses.all() |
1853 | + } |
1854 | + for node in nodes |
1855 | + for mac in node.mac_addresses_on_managed_interfaces() |
1856 | + })) |
1857 | + # No complaints are made to the Twisted log. |
1858 | + self.assertFalse(twisted_log.containsError(), twisted_log.output) |
1859 | + |
1860 | + def test__propagates_errors_when_updating_host_maps(self): |
1861 | + user = factory.make_User() |
1862 | + nodes = self.make_acquired_nodes_with_macs(user) |
1863 | + |
1864 | + update_host_maps = self.patch(node_module, "update_host_maps") |
1865 | + update_host_maps.return_value = [ |
1866 | + Failure(AssertionError("That is so not true")), |
1867 | + Failure(ZeroDivisionError("I cannot defy mathematics")), |
1868 | + ] |
1869 | + |
1870 | + with TwistedLoggerFixture() as twisted_log: |
1871 | + error = self.assertRaises( |
1872 | + MultipleFailures, Node.objects.start_nodes, |
1873 | + list(node.system_id for node in nodes), user) |
1874 | + |
1875 | + self.assertSequenceEqual( |
1876 | + update_host_maps.return_value, error.args) |
1877 | + |
1878 | + # No complaints are made to the Twisted log. |
1879 | + self.assertFalse(twisted_log.containsError(), twisted_log.output) |
1880 | + |
1881 | + def test__updates_dns(self): |
1882 | + user = factory.make_User() |
1883 | + nodes = self.make_acquired_nodes_with_macs(user) |
1884 | + |
1885 | + change_dns_zones = self.patch(dns_config, "change_dns_zones") |
1886 | + |
1887 | + with TwistedLoggerFixture() as twisted_log: |
1888 | + Node.objects.start_nodes( |
1889 | + list(node.system_id for node in nodes), user) |
1890 | + |
1891 | + self.assertThat( |
1892 | + change_dns_zones, MockCalledOnceWith( |
1893 | + {node.nodegroup for node in nodes})) |
1894 | + |
1895 | + # No complaints are made to the Twisted log. |
1896 | + self.assertFalse(twisted_log.containsError(), twisted_log.output) |
1897 | + |
1898 | + def test__starts_nodes(self): |
1899 | + user = factory.make_User() |
1900 | + nodes = self.make_acquired_nodes_with_macs(user) |
1901 | + power_infos = list( |
1902 | + node.get_effective_power_info() |
1903 | + for node in nodes) |
1904 | + |
1905 | + power_on_nodes = self.patch(node_module, "power_on_nodes") |
1906 | + power_on_nodes.return_value = {} |
1907 | + |
1908 | + with TwistedLoggerFixture() as twisted_log: |
1909 | + Node.objects.start_nodes( |
1910 | + list(node.system_id for node in nodes), user) |
1911 | + |
1912 | + self.assertThat(power_on_nodes, MockCalledOnceWith(ANY)) |
1913 | + |
1914 | + nodes_start_info_observed = power_on_nodes.call_args[0][0] |
1915 | + nodes_start_info_expected = [ |
1916 | + (node.system_id, node.hostname, node.nodegroup.uuid, power_info) |
1917 | + for node, power_info in izip(nodes, power_infos) |
1918 | + ] |
1919 | + |
1920 | + # If the following fails the diff is big, but it's useful. |
1921 | + self.maxDiff = None |
1922 | + |
1923 | + self.assertItemsEqual( |
1924 | + nodes_start_info_expected, |
1925 | + nodes_start_info_observed) |
1926 | + |
1927 | + # No complaints are made to the Twisted log. |
1928 | + self.assertFalse(twisted_log.containsError(), twisted_log.output) |
1929 | + |
1930 | + def test__raises_failures_for_nodes_that_cannot_be_started(self): |
1931 | + power_on_nodes = self.patch(node_module, "power_on_nodes") |
1932 | + power_on_nodes.return_value = { |
1933 | + factory.make_name("system_id"): defer.fail( |
1934 | + ZeroDivisionError("Defiance is futile")), |
1935 | + factory.make_name("system_id"): defer.succeed({}), |
1936 | + } |
1937 | + |
1938 | + failures = self.assertRaises( |
1939 | + MultipleFailures, Node.objects.start_nodes, [], |
1940 | + factory.make_User()) |
1941 | + [failure] = failures.args |
1942 | + self.assertThat(failure.value, IsInstance(ZeroDivisionError)) |
1943 | + |
1944 | + def test__marks_allocated_node_as_deploying(self): |
1945 | + user = factory.make_User() |
1946 | + [node] = self.make_acquired_nodes_with_macs(user, count=1) |
1947 | + nodes_started = Node.objects.start_nodes([node.system_id], user) |
1948 | + self.assertItemsEqual([node], nodes_started) |
1949 | + self.assertEqual( |
1950 | + NODE_STATUS.DEPLOYING, reload_object(node).status) |
1951 | + |
1952 | + def test__does_not_change_state_of_deployed_node(self): |
1953 | + user = factory.make_User() |
1954 | + node = factory.make_Node( |
1955 | + power_type='ether_wake', status=NODE_STATUS.DEPLOYED, |
1956 | + owner=user) |
1957 | + factory.make_MACAddress(node=node) |
1958 | + power_on_nodes = self.patch(node_module, "power_on_nodes") |
1959 | + power_on_nodes.return_value = { |
1960 | + node.system_id: defer.succeed({}), |
1961 | + } |
1962 | + nodes_started = Node.objects.start_nodes([node.system_id], user) |
1963 | + self.assertItemsEqual([node], nodes_started) |
1964 | + self.assertEqual( |
1965 | + NODE_STATUS.DEPLOYED, reload_object(node).status) |
1966 | + |
1967 | + def test__only_returns_nodes_for_which_power_commands_have_been_sent(self): |
1968 | + user = factory.make_User() |
1969 | + node1, node2 = self.make_acquired_nodes_with_macs(user, count=2) |
1970 | + node1.power_type = 'ether_wake' # Can be started. |
1971 | + node1.save() |
1972 | + node2.power_type = '' # Undefined power type, cannot be started. |
1973 | + node2.save() |
1974 | + nodes_started = Node.objects.start_nodes( |
1975 | + [node1.system_id, node2.system_id], user) |
1976 | + self.assertItemsEqual([node1], nodes_started) |
1977 | + |
1978 | + def test__does_not_try_to_start_nodes_not_allocated_to_user(self): |
1979 | + user1 = factory.make_User() |
1980 | + [node1] = self.make_acquired_nodes_with_macs(user1, count=1) |
1981 | + node1.power_type = 'ether_wake' # can be started. |
1982 | + node1.save() |
1983 | + user2 = factory.make_User() |
1984 | + [node2] = self.make_acquired_nodes_with_macs(user2, count=1) |
1985 | + node2.power_type = 'ether_wake' # can be started. |
1986 | + node2.save() |
1987 | + |
1988 | + self.patch(node_module, 'power_on_nodes') |
1989 | + self.patch(node_module, 'wait_for_power_commands') |
1990 | + nodes_started = Node.objects.start_nodes( |
1991 | + [node1.system_id, node2.system_id], user1) |
1992 | + |
1993 | + # Since no power commands were sent to the node, it isn't |
1994 | + # returned by start_nodes(). |
1995 | + # test__only_returns_nodes_for_which_power_commands_have_been_sent() |
1996 | + # demonstrates this behaviour. |
1997 | + self.assertItemsEqual([node1], nodes_started) |
1998 | + |
1999 | + # XXX: GavinPanella 2014-10-30 bug=1387696: Flaky test, returning wrong IP |
2000 | + # addresses. Appears unrelated to the changes that highlighted this. |
2001 | + @skip("Flaky; returns incorrect IP addresses") |
2002 | + def test__does_not_generate_host_maps_if_not_on_managed_interface(self): |
2003 | + cluster = factory.make_NodeGroup() |
2004 | + managed_interface = factory.make_NodeGroupInterface( |
2005 | + nodegroup=cluster, |
2006 | + management=NODEGROUPINTERFACE_MANAGEMENT.DHCP_AND_DNS) |
2007 | + unmanaged_interface = factory.make_NodeGroupInterface( |
2008 | + nodegroup=cluster, |
2009 | + management=NODEGROUPINTERFACE_MANAGEMENT.DEFAULT) |
2010 | + user = factory.make_User() |
2011 | + [node1, node2] = self.make_acquired_nodes_with_macs( |
2012 | + user, nodegroup=cluster, count=2) |
2013 | + # Give the node a PXE MAC address on the cluster's interface. |
2014 | + node1_mac = node1.get_pxe_mac() |
2015 | + node1_mac.cluster_interface = managed_interface |
2016 | + node1_mac.save() |
2017 | + node2_mac = node1.get_pxe_mac() |
2018 | + node2_mac.cluster_interface = unmanaged_interface |
2019 | + node2_mac.save() |
2020 | + |
2021 | + node1_ip = factory.make_ipv4_address() |
2022 | + claim_static_ip_addresses = self.patch( |
2023 | + node_module.Node, 'claim_static_ip_addresses') |
2024 | + claim_static_ip_addresses.side_effect = [ |
2025 | + [(node1_ip, node1_mac.mac_address)], |
2026 | + [(factory.make_ipv4_address(), node2_mac.mac_address)], |
2027 | + ] |
2028 | + |
2029 | + update_host_maps = self.patch(node_module, "update_host_maps") |
2030 | + Node.objects.start_nodes([node1.system_id, node2.system_id], user) |
2031 | + self.expectThat(update_host_maps, MockCalledOnceWith(ANY)) |
2032 | + |
2033 | + observed_static_mappings = update_host_maps.call_args[0][0] |
2034 | + |
2035 | + [observed_cluster] = observed_static_mappings.keys() |
2036 | + self.expectThat(observed_cluster.uuid, Equals(cluster.uuid)) |
2037 | + |
2038 | + observed_claims = observed_static_mappings.values() |
2039 | + self.expectThat( |
2040 | + observed_claims, |
2041 | + Equals([{node1_ip: node1_mac.mac_address}])) |
2042 | + |
2043 | + |
2044 | +class NodeManagerTest_StopNodes(MAASServerTestCase): |
2045 | + |
2046 | + def make_nodes_with_macs(self, user, nodegroup=None, count=3): |
2047 | + nodes = [] |
2048 | + for _ in xrange(count): |
2049 | + node = factory.make_node_with_mac_attached_to_nodegroupinterface( |
2050 | + nodegroup=nodegroup, status=NODE_STATUS.READY, |
2051 | + power_type='virsh') |
2052 | + node.acquire(user) |
2053 | + nodes.append(node) |
2054 | + return nodes |
2055 | + |
2056 | + def test_stop_nodes_stops_nodes(self): |
2057 | + wait_for_power_commands = self.patch_autospec( |
2058 | + node_module, 'wait_for_power_commands') |
2059 | + power_off_nodes = self.patch_autospec(node_module, "power_off_nodes") |
2060 | + power_off_nodes.side_effect = lambda nodes: { |
2061 | + system_id: Deferred() for system_id, _, _, _ in nodes} |
2062 | + |
2063 | + user = factory.make_User() |
2064 | + nodes = self.make_nodes_with_macs(user) |
2065 | + power_infos = list(node.get_effective_power_info() for node in nodes) |
2066 | + |
2067 | + stop_mode = factory.make_name('stop-mode') |
2068 | + nodes_stopped = Node.objects.stop_nodes( |
2069 | + list(node.system_id for node in nodes), user, stop_mode) |
2070 | + |
2071 | + self.assertItemsEqual(nodes, nodes_stopped) |
2072 | + self.assertThat(power_off_nodes, MockCalledOnceWith(ANY)) |
2073 | + self.assertThat(wait_for_power_commands, MockCalledOnceWith(ANY)) |
2074 | + |
2075 | + nodes_stop_info_observed = power_off_nodes.call_args[0][0] |
2076 | + nodes_stop_info_expected = [ |
2077 | + (node.system_id, node.hostname, node.nodegroup.uuid, power_info) |
2078 | + for node, power_info in izip(nodes, power_infos) |
2079 | + ] |
2080 | + |
2081 | + # The stop mode is added into the power info that's passed. |
2082 | + for _, _, _, power_info in nodes_stop_info_expected: |
2083 | + power_info.power_parameters['power_off_mode'] = stop_mode |
2084 | + |
2085 | + # If the following fails the diff is big, but it's useful. |
2086 | + self.maxDiff = None |
2087 | + |
2088 | + self.assertItemsEqual( |
2089 | + nodes_stop_info_expected, |
2090 | + nodes_stop_info_observed) |
2091 | + |
2092 | + def test_stop_nodes_ignores_uneditable_nodes(self): |
2093 | + owner = factory.make_User() |
2094 | + nodes = self.make_nodes_with_macs(owner) |
2095 | + |
2096 | + user = factory.make_User() |
2097 | + nodes_stopped = Node.objects.stop_nodes( |
2098 | + list(node.system_id for node in nodes), user) |
2099 | + |
2100 | + self.assertItemsEqual([], nodes_stopped) |
2101 | + |
2102 | + def test_stop_nodes_does_not_attempt_power_off_if_no_power_type(self): |
2103 | + # If the node has a power_type set to UNKNOWN_POWER_TYPE, stop_nodes() |
2104 | + # won't attempt to power it off. |
2105 | + user = factory.make_User() |
2106 | + [node] = self.make_nodes_with_macs(user, count=1) |
2107 | + node.power_type = "" |
2108 | + node.save() |
2109 | + |
2110 | + nodes_stopped = Node.objects.stop_nodes([node.system_id], user) |
2111 | + self.assertItemsEqual([], nodes_stopped) |
2112 | + |
2113 | + def test_stop_nodes_does_not_attempt_power_off_if_cannot_be_stopped(self): |
2114 | + # If the node has a power_type that MAAS knows stopping does not work, |
2115 | + # stop_nodes() won't attempt to power it off. |
2116 | + user = factory.make_User() |
2117 | + [node] = self.make_nodes_with_macs(user, count=1) |
2118 | + node.power_type = "ether_wake" |
2119 | + node.save() |
2120 | + |
2121 | + nodes_stopped = Node.objects.stop_nodes([node.system_id], user) |
2122 | + self.assertItemsEqual([], nodes_stopped) |
2123 | + |
2124 | + def test__raises_failures_for_nodes_that_cannot_be_stopped(self): |
2125 | + power_off_nodes = self.patch(node_module, "power_off_nodes") |
2126 | + power_off_nodes.return_value = { |
2127 | + factory.make_name("system_id"): defer.fail( |
2128 | + ZeroDivisionError("Ee by gum lad, that's a rum 'un.")), |
2129 | + factory.make_name("system_id"): defer.succeed({}), |
2130 | + } |
2131 | + |
2132 | + failures = self.assertRaises( |
2133 | + MultipleFailures, Node.objects.stop_nodes, [], factory.make_User()) |
2134 | + [failure] = failures.args |
2135 | + self.assertThat(failure.value, IsInstance(ZeroDivisionError)) |
2136 | + |
2137 | + |
2138 | +>>>>>>> MERGE-SOURCE |
2139 | class TestNodeTransitionMonitors(MAASServerTestCase): |
2140 | |
2141 | def prepare_rpc(self): |
2142 | |
2143 | === modified file 'src/maasserver/node_action.py' |
2144 | === modified file 'src/maasserver/node_status.py' |
2145 | === modified file 'src/maasserver/tests/test_bootresources.py' |
2146 | --- src/maasserver/tests/test_bootresources.py 2014-11-03 01:10:28 +0000 |
2147 | +++ src/maasserver/tests/test_bootresources.py 2014-11-06 01:24:10 +0000 |
2148 | @@ -1063,8 +1063,13 @@ |
2149 | bootresources, 'cache_boot_sources') |
2150 | write_all_keyrings = self.patch( |
2151 | bootresources, 'write_all_keyrings') |
2152 | +<<<<<<< TREE |
2153 | write_all_keyrings.return_value = [sentinel.source] |
2154 | image_descriptions = self.patch( |
2155 | +======= |
2156 | + fake_write_all_keyrings.return_value = [sentinel.source] |
2157 | + fake_image_descriptions = self.patch( |
2158 | +>>>>>>> MERGE-SOURCE |
2159 | bootresources, 'download_all_image_descriptions') |
2160 | descriptions = Mock() |
2161 | descriptions.is_empty.return_value = False |
2162 | @@ -1082,15 +1087,29 @@ |
2163 | self.expectThat( |
2164 | write_all_keyrings, |
2165 | MockCalledOnceWith(ANY, [])) |
2166 | +<<<<<<< TREE |
2167 | self.expectThat( |
2168 | image_descriptions, |
2169 | MockCalledOnceWith([sentinel.source])) |
2170 | self.expectThat( |
2171 | map_products, |
2172 | +======= |
2173 | + self.assertThat( |
2174 | + fake_image_descriptions, |
2175 | + MockCalledOnceWith([sentinel.source])) |
2176 | + self.assertThat( |
2177 | + fake_map_products, |
2178 | +>>>>>>> MERGE-SOURCE |
2179 | MockCalledOnceWith(descriptions)) |
2180 | +<<<<<<< TREE |
2181 | self.expectThat( |
2182 | download_all_boot_resources, |
2183 | MockCalledOnceWith([sentinel.source], sentinel.mapping)) |
2184 | +======= |
2185 | + self.assertThat( |
2186 | + fake_download_all_boot_resources, |
2187 | + MockCalledOnceWith([sentinel.source], sentinel.mapping)) |
2188 | +>>>>>>> MERGE-SOURCE |
2189 | |
2190 | def test__import_resources_has_env_GNUPGHOME_set(self): |
2191 | fake_image_descriptions = self.patch( |
2192 | |
2193 | === modified file 'src/maasserver/tests/test_middleware.py' |
2194 | === modified file 'src/maasserver/tests/test_node_action.py' |
2195 | === modified file 'src/maasserver/views/nodes.py' |
2196 | --- src/maasserver/views/nodes.py 2014-11-05 15:24:51 +0000 |
2197 | +++ src/maasserver/views/nodes.py 2014-11-06 01:24:10 +0000 |
2198 | @@ -612,8 +612,13 @@ |
2199 | context['nodecommissionresults'] = commissioning_results |
2200 | |
2201 | installation_results = NodeResult.objects.filter( |
2202 | +<<<<<<< TREE |
2203 | node=node, result_type=RESULT_TYPE.INSTALLATION) |
2204 | if len(installation_results) > 1: |
2205 | +======= |
2206 | + node=node, result_type=RESULT_TYPE.INSTALLING) |
2207 | + if len(installation_results) > 1: |
2208 | +>>>>>>> MERGE-SOURCE |
2209 | for result in installation_results: |
2210 | result.name = re.sub('[_.]', ' ', result.name) |
2211 | context['nodeinstallresults'] = installation_results |
2212 | |
2213 | === modified file 'src/maasserver/views/tests/test_nodes.py' |
2214 | --- src/maasserver/views/tests/test_nodes.py 2014-11-05 15:24:51 +0000 |
2215 | +++ src/maasserver/views/tests/test_nodes.py 2014-11-06 01:24:10 +0000 |
2216 | @@ -1639,6 +1639,7 @@ |
2217 | else: |
2218 | self.fail("Found more than one link: %s" % links) |
2219 | |
2220 | +<<<<<<< TREE |
2221 | def get_installation_results_link(self, display): |
2222 | """Find the results link in `display`. |
2223 | |
2224 | @@ -1656,6 +1657,25 @@ |
2225 | elif len(links) > 1: |
2226 | return links |
2227 | |
2228 | +======= |
2229 | + def get_installing_results_link(self, display): |
2230 | + """Find the results link in `display`. |
2231 | + |
2232 | + :param display: Results display section for a node, as returned by |
2233 | + `request_results_display`. |
2234 | + :return: `lxml.html.HtmlElement` for the link to the node's |
2235 | + installation results, as found in `display`; or `None` if it was |
2236 | + not present. |
2237 | + """ |
2238 | + links = display.cssselect('a') |
2239 | + if len(links) == 0: |
2240 | + return None |
2241 | + elif len(links) == 1: |
2242 | + return links[0] |
2243 | + elif len(links) > 1: |
2244 | + return links |
2245 | + |
2246 | +>>>>>>> MERGE-SOURCE |
2247 | def test_view_node_links_to_commissioning_results_if_appropriate(self): |
2248 | self.client_log_in(as_admin=True) |
2249 | result = factory.make_NodeResult_for_commissioning() |
2250 | @@ -1735,8 +1755,13 @@ |
2251 | self.logged_in_user = user |
2252 | result = factory.make_NodeResult_for_installation(node=node) |
2253 | section = self.request_results_display( |
2254 | +<<<<<<< TREE |
2255 | result.node, RESULT_TYPE.INSTALLATION) |
2256 | link = self.get_installation_results_link(section) |
2257 | +======= |
2258 | + result.node, RESULT_TYPE.INSTALLING) |
2259 | + link = self.get_installing_results_link(section) |
2260 | +>>>>>>> MERGE-SOURCE |
2261 | self.assertNotIn( |
2262 | normalise_whitespace(link.text_content()), |
2263 | ('', None)) |
2264 | @@ -1778,6 +1803,35 @@ |
2265 | ContainsAll( |
2266 | [normalise_whitespace(link.text_content()) for link in links])) |
2267 | |
2268 | + def test_view_node_shows_single_installing_result(self): |
2269 | + self.client_log_in(as_admin=True) |
2270 | + result = factory.make_NodeResult_for_installing() |
2271 | + section = self.request_results_display( |
2272 | + result.node, RESULT_TYPE.INSTALLING) |
2273 | + link = self.get_installing_results_link(section) |
2274 | + self.assertEqual( |
2275 | + "install log", |
2276 | + normalise_whitespace(link.text_content())) |
2277 | + |
2278 | + def test_view_node_shows_multiple_installing_results(self): |
2279 | + self.client_log_in(as_admin=True) |
2280 | + node = factory.make_Node() |
2281 | + num_results = randint(2, 5) |
2282 | + results_names = [] |
2283 | + for _ in range(num_results): |
2284 | + node_result = factory.make_NodeResult_for_installing(node=node) |
2285 | + results_names.append(node_result.name) |
2286 | + section = self.request_results_display( |
2287 | + node, RESULT_TYPE.INSTALLING) |
2288 | + links = self.get_installing_results_link(section) |
2289 | + expected_results_names = list(reversed(results_names)) |
2290 | + observed_results_names = list( |
2291 | + normalise_whitespace(link.text_content()) |
2292 | + for link in links) |
2293 | + self.assertListEqual( |
2294 | + expected_results_names, |
2295 | + observed_results_names) |
2296 | + |
2297 | |
2298 | class NodeListingSelectionJSControls(SeleniumTestCase): |
2299 | |
2300 | |
2301 | === modified file 'src/metadataserver/api.py' |
2302 | --- src/metadataserver/api.py 2014-11-04 07:49:58 +0000 |
2303 | +++ src/metadataserver/api.py 2014-11-06 01:24:10 +0000 |
2304 | @@ -76,7 +76,7 @@ |
2305 | NodeUserData, |
2306 | ) |
2307 | from metadataserver.models.commissioningscript import ( |
2308 | - BUILTIN_COMMISSIONING_SCRIPTS, |
2309 | + get_builtin_commissioning_scripts, |
2310 | ) |
2311 | from piston.utils import rc |
2312 | from provisioningserver.events import ( |
2313 | @@ -229,8 +229,10 @@ |
2314 | script_result = int(request.POST.get('script_result', 0)) |
2315 | for name, uploaded_file in request.FILES.items(): |
2316 | raw_content = uploaded_file.read() |
2317 | - if name in BUILTIN_COMMISSIONING_SCRIPTS: |
2318 | - postprocess_hook = BUILTIN_COMMISSIONING_SCRIPTS[name]['hook'] |
2319 | + builtin_commissioning_scripts = ( |
2320 | + get_builtin_commissioning_scripts()) |
2321 | + if name in builtin_commissioning_scripts: |
2322 | + postprocess_hook = builtin_commissioning_scripts[name]['hook'] |
2323 | postprocess_hook( |
2324 | node=node, output=raw_content, |
2325 | exit_status=script_result) |
2326 | @@ -280,8 +282,20 @@ |
2327 | |
2328 | if node.status == NODE_STATUS.COMMISSIONING: |
2329 | self._store_commissioning_results(node, request) |
2330 | +<<<<<<< TREE |
2331 | store_node_power_parameters(node, request) |
2332 | node.stop_transition_monitor() |
2333 | +======= |
2334 | + # XXX 2014-10-21 newell, bug=1382075 |
2335 | + # Auto detection for IPMI tries to save power parameters |
2336 | + # for Moonshot. This causes issues if the node's power type |
2337 | + # is already MSCM as it uses SSH instead of IPMI. This fix |
2338 | + # is temporary as power parameters should not be overwritten |
2339 | + # during commissioning because MAAS already has knowledge to |
2340 | + # boot the node. |
2341 | + if node.power_type != "mscm": |
2342 | + store_node_power_parameters(node, request) |
2343 | +>>>>>>> MERGE-SOURCE |
2344 | target_status = self.signaling_statuses.get(status) |
2345 | elif node.status == NODE_STATUS.DEPLOYING: |
2346 | self._store_installation_results(node, request) |
2347 | |
2348 | === modified file 'src/metadataserver/models/commissioningscript.py' |
2349 | --- src/metadataserver/models/commissioningscript.py 2014-10-29 22:37:02 +0000 |
2350 | +++ src/metadataserver/models/commissioningscript.py 2014-11-06 01:24:10 +0000 |
2351 | @@ -14,8 +14,8 @@ |
2352 | |
2353 | __metaclass__ = type |
2354 | __all__ = [ |
2355 | - 'BUILTIN_COMMISSIONING_SCRIPTS', |
2356 | 'CommissioningScript', |
2357 | + 'get_builtin_commissioning_scripts', |
2358 | 'inject_lldp_result', |
2359 | 'inject_lshw_result', |
2360 | 'inject_result', |
2361 | @@ -43,6 +43,7 @@ |
2362 | ) |
2363 | from lxml import etree |
2364 | from maasserver.fields import MAC |
2365 | +from maasserver.models import Config |
2366 | from maasserver.models.tag import Tag |
2367 | from metadataserver import DefaultMeta |
2368 | from metadataserver.enum import RESULT_TYPE |
2369 | @@ -352,6 +353,7 @@ |
2370 | LIST_MODALIASES_OUTPUT_NAME = '00-maas-04-list-modaliases.out' |
2371 | LIST_MODALIASES_SCRIPT = \ |
2372 | 'find /sys -name modalias -print0 | xargs -0 cat | sort -u' |
2373 | +DHCP_UNCONFIGURED_INTERFACES_NAME = '00-maas-05-dhcp-unconfigured-ifaces' |
2374 | |
2375 | |
2376 | def null_hook(node, output, exit_status): |
2377 | @@ -397,7 +399,7 @@ |
2378 | 'content': LIST_MODALIASES_SCRIPT.encode('ascii'), |
2379 | 'hook': null_hook, |
2380 | }, |
2381 | - '00-maas-05-dhcp-unconfigured-ifaces': { |
2382 | + DHCP_UNCONFIGURED_INTERFACES_NAME: { |
2383 | 'content': make_function_call_script(dhcp_explore), |
2384 | 'hook': null_hook, |
2385 | }, |
2386 | @@ -430,6 +432,20 @@ |
2387 | add_names_to_scripts(BUILTIN_COMMISSIONING_SCRIPTS) |
2388 | |
2389 | |
2390 | +def get_builtin_commissioning_scripts(): |
2391 | + """Get the builtin commissioning scripts. |
2392 | + |
2393 | + The builtin scripts exposed may vary based on config settings. |
2394 | + """ |
2395 | + scripts = BUILTIN_COMMISSIONING_SCRIPTS.copy() |
2396 | + |
2397 | + config_key = 'enable_dhcp_discovery_on_unconfigured_interfaces' |
2398 | + if not Config.objects.get_config(config_key): |
2399 | + del scripts[DHCP_UNCONFIGURED_INTERFACES_NAME] |
2400 | + |
2401 | + return scripts |
2402 | + |
2403 | + |
2404 | def add_script_to_archive(tarball, name, content, mtime): |
2405 | """Add a commissioning script to an archive of commissioning scripts.""" |
2406 | assert isinstance(content, bytes), "Script content must be binary." |
2407 | @@ -447,7 +463,7 @@ |
2408 | """Utility for the collection of `CommissioningScript`s.""" |
2409 | |
2410 | def _iter_builtin_scripts(self): |
2411 | - for script in BUILTIN_COMMISSIONING_SCRIPTS.itervalues(): |
2412 | + for script in get_builtin_commissioning_scripts().itervalues(): |
2413 | yield script['name'], script['content'] |
2414 | |
2415 | def _iter_user_scripts(self): |
2416 | @@ -500,8 +516,9 @@ |
2417 | NodeResult.objects.store_data( |
2418 | node, name, script_result=exit_status, |
2419 | result_type=RESULT_TYPE.COMMISSIONING, data=Bin(output)) |
2420 | - if name in BUILTIN_COMMISSIONING_SCRIPTS: |
2421 | - postprocess_hook = BUILTIN_COMMISSIONING_SCRIPTS[name]['hook'] |
2422 | + builtin_commissioning_scripts = get_builtin_commissioning_scripts() |
2423 | + if name in builtin_commissioning_scripts: |
2424 | + postprocess_hook = builtin_commissioning_scripts[name]['hook'] |
2425 | postprocess_hook(node=node, output=output, exit_status=exit_status) |
2426 | |
2427 | |
2428 | |
2429 | === modified file 'src/metadataserver/models/tests/test_noderesults.py' |
2430 | --- src/metadataserver/models/tests/test_noderesults.py 2014-10-29 22:37:02 +0000 |
2431 | +++ src/metadataserver/models/tests/test_noderesults.py 2014-11-06 01:24:10 +0000 |
2432 | @@ -35,6 +35,7 @@ |
2433 | |
2434 | from fixtures import FakeLogger |
2435 | from maasserver.fields import MAC |
2436 | +from maasserver.models import Config |
2437 | from maasserver.models.tag import Tag |
2438 | from maasserver.testing.factory import factory |
2439 | from maasserver.testing.orm import reload_object |
2440 | @@ -55,7 +56,10 @@ |
2441 | ) |
2442 | from metadataserver.models.commissioningscript import ( |
2443 | ARCHIVE_PREFIX, |
2444 | + BUILTIN_COMMISSIONING_SCRIPTS, |
2445 | + DHCP_UNCONFIGURED_INTERFACES_NAME, |
2446 | extract_router_mac_addresses, |
2447 | + get_builtin_commissioning_scripts, |
2448 | inject_lldp_result, |
2449 | inject_lshw_result, |
2450 | inject_result, |
2451 | @@ -676,3 +680,26 @@ |
2452 | logger = self.useFixture(FakeLogger(name='commissioningscript')) |
2453 | update_hardware_details(factory.make_Node(), b"garbage", exit_status=1) |
2454 | self.assertEqual("", logger.output) |
2455 | + |
2456 | + |
2457 | +class TestGetBuiltinCommissioningScripts(MAASServerTestCase): |
2458 | + |
2459 | + def test__includes_all_builtin_commissioning_scripts_by_default(self): |
2460 | + self.assertItemsEqual( |
2461 | + BUILTIN_COMMISSIONING_SCRIPTS, |
2462 | + get_builtin_commissioning_scripts(), |
2463 | + ) |
2464 | + |
2465 | + def test__excludes_dhcp_discovery_when_disabled(self): |
2466 | + Config.objects.set_config( |
2467 | + 'enable_dhcp_discovery_on_unconfigured_interfaces', False) |
2468 | + self.assertNotIn( |
2469 | + DHCP_UNCONFIGURED_INTERFACES_NAME, |
2470 | + get_builtin_commissioning_scripts()) |
2471 | + |
2472 | + def test__includes_dhcp_discovery_when_enabled(self): |
2473 | + Config.objects.set_config( |
2474 | + 'enable_dhcp_discovery_on_unconfigured_interfaces', True) |
2475 | + self.assertIn( |
2476 | + DHCP_UNCONFIGURED_INTERFACES_NAME, |
2477 | + get_builtin_commissioning_scripts()) |
2478 | |
2479 | === modified file 'src/metadataserver/tests/test_api.py' |
2480 | --- src/metadataserver/tests/test_api.py 2014-11-04 07:49:41 +0000 |
2481 | +++ src/metadataserver/tests/test_api.py 2014-11-06 01:24:10 +0000 |
2482 | @@ -812,6 +812,21 @@ |
2483 | node = reload_object(node) |
2484 | self.assertEqual(0, len(node.tags.all())) |
2485 | |
2486 | + def test_signal_current_power_type_mscm_does_not_store_params(self): |
2487 | + node = factory.make_Node( |
2488 | + power_type="mscm", status=NODE_STATUS.COMMISSIONING) |
2489 | + client = make_node_client(node=node) |
2490 | + params = dict( |
2491 | + power_address=factory.make_string(), |
2492 | + power_user=factory.make_string(), |
2493 | + power_pass=factory.make_string()) |
2494 | + response = call_signal( |
2495 | + client, power_type="moonshot", power_parameters=json.dumps(params)) |
2496 | + self.assertEqual(httplib.OK, response.status_code, response.content) |
2497 | + node = reload_object(node) |
2498 | + self.assertEqual("mscm", node.power_type) |
2499 | + self.assertNotEqual(params, node.power_parameters) |
2500 | + |
2501 | def test_signal_refuses_bad_power_type(self): |
2502 | node = factory.make_Node(status=NODE_STATUS.COMMISSIONING) |
2503 | client = make_node_client(node=node) |
2504 | |
2505 | === modified file 'src/provisioningserver/rpc/boot_images.py' |
2506 | --- src/provisioningserver/rpc/boot_images.py 2014-10-30 13:17:51 +0000 |
2507 | +++ src/provisioningserver/rpc/boot_images.py 2014-11-06 01:24:10 +0000 |
2508 | @@ -18,8 +18,12 @@ |
2509 | "is_import_boot_images_running", |
2510 | ] |
2511 | |
2512 | +<<<<<<< TREE |
2513 | import os |
2514 | from urlparse import urlparse |
2515 | +======= |
2516 | +from urlparse import urlparse |
2517 | +>>>>>>> MERGE-SOURCE |
2518 | |
2519 | from provisioningserver import concurrency |
2520 | from provisioningserver.auth import get_maas_user_gpghome |
2521 | @@ -47,6 +51,16 @@ |
2522 | return hosts |
2523 | |
2524 | |
2525 | +def get_hosts_from_sources(sources): |
2526 | + """Return set of hosts that are contained in the given sources.""" |
2527 | + hosts = set() |
2528 | + for source in sources: |
2529 | + url = urlparse(source['url']) |
2530 | + if url.hostname is not None: |
2531 | + hosts.add(url.hostname) |
2532 | + return hosts |
2533 | + |
2534 | + |
2535 | @synchronous |
2536 | def _run_import(sources, http_proxy=None, https_proxy=None): |
2537 | """Run the import. |
2538 | |
2539 | === modified file 'src/provisioningserver/rpc/clusterservice.py' |
2540 | === modified file 'src/provisioningserver/rpc/power.py' |
2541 | === modified file 'src/provisioningserver/rpc/tests/test_boot_images.py' |
2542 | --- src/provisioningserver/rpc/tests/test_boot_images.py 2014-10-30 13:17:51 +0000 |
2543 | +++ src/provisioningserver/rpc/tests/test_boot_images.py 2014-11-06 01:24:10 +0000 |
2544 | @@ -76,6 +76,13 @@ |
2545 | self.assertItemsEqual(hosts, get_hosts_from_sources(sources)) |
2546 | |
2547 | |
2548 | +class TestGetHostsFromSources(PservTestCase): |
2549 | + |
2550 | + def test__returns_set_of_hosts_from_sources(self): |
2551 | + sources, hosts = make_sources() |
2552 | + self.assertItemsEqual(hosts, get_hosts_from_sources(sources)) |
2553 | + |
2554 | + |
2555 | class TestRunImport(PservTestCase): |
2556 | |
2557 | def make_archive_url(self, name=None): |
2558 | |
2559 | === modified file 'src/provisioningserver/rpc/tests/test_clusterservice.py' |
2560 | === modified file 'src/provisioningserver/rpc/tests/test_power.py' |
Approved for a backport by Andres earlier in the bug.