Merge lp:~ltrager/maas/lp1636858_2.0 into lp:~maas-committers/maas/trunk
- lp1636858_2.0
- Merge into trunk
Proposed by
Lee Trager
Status: | Superseded | ||||
---|---|---|---|---|---|
Proposed branch: | lp:~ltrager/maas/lp1636858_2.0 | ||||
Merge into: | lp:~maas-committers/maas/trunk | ||||
Diff against target: |
4239 lines (+3217/-21) (has conflicts) 43 files modified
Makefile (+1/-1) docs/about.rst (+9/-0) docs/changelog.rst (+524/-0) docs/index.rst (+9/-0) src/maasserver/api/account.py (+23/-0) src/maasserver/api/interfaces.py (+4/-0) src/maasserver/api/tests/test_ipaddresses.py (+9/-0) src/maasserver/api/tests/test_machine.py (+37/-0) src/maasserver/api/tests/test_machines.py (+20/-0) src/maasserver/bootresources.py (+37/-0) src/maasserver/dhcp.py (+72/-0) src/maasserver/enum.py (+6/-0) src/maasserver/migrations/builtin/maasserver/0066_allow_squashfs.py (+22/-0) src/maasserver/models/node.py (+105/-17) src/maasserver/models/staticipaddress.py (+94/-0) src/maasserver/models/tests/test_node.py (+177/-0) src/maasserver/models/tests/test_staticipaddress.py (+68/-3) src/maasserver/node_status.py (+16/-0) src/maasserver/preseed_network.py (+20/-0) src/maasserver/rpc/tests/test_regionservice.py (+14/-0) src/maasserver/static/css/maas-styles.css.OTHER (+1/-0) src/maasserver/static/js/angular/controllers/tests/test_node_details_storage.js (+89/-0) src/maasserver/static/partials/node-details.html (+6/-0) src/maasserver/static/partials/node-events.html (+14/-0) src/maasserver/static/partials/nodes-list.html (+173/-0) src/maasserver/static/scss/maas/components/_accordion.scss.OTHER (+112/-0) src/maasserver/templates/maasserver/base.html (+7/-0) src/maasserver/templates/maasserver/index.html (+21/-0) src/maasserver/templates/maasserver/prefs.html (+5/-0) src/maasserver/testing/factory.py (+5/-0) src/maasserver/tests/test_bootresources.py (+108/-0) src/maasserver/tests/test_dhcp.py (+74/-0) src/maasserver/tests/test_preseed.py (+27/-0) src/maasserver/tests/test_preseed_network.py (+9/-0) src/maasserver/tests/test_start_up.py (+93/-0) src/maasserver/triggers/tests/test_websocket_listener.py (+124/-0) src/maasserver/views/tests/test_images.py.OTHER (+991/-0) src/metadataserver/api.py (+7/-0) src/provisioningserver/dns/tests/test_zoneconfig.py (+47/-0) src/provisioningserver/events.py (+11/-0) src/provisioningserver/plugin.py (+12/-0) src/provisioningserver/power/schema.py (+6/-0) src/provisioningserver/tests/test_plugin.py (+8/-0) Text conflict in docs/changelog.rst Text conflict in src/maasserver/api/account.py Text conflict in src/maasserver/api/interfaces.py Text conflict in src/maasserver/api/tests/test_ipaddresses.py Text conflict in src/maasserver/api/tests/test_machine.py Text conflict in src/maasserver/api/tests/test_machines.py Text conflict in src/maasserver/bootresources.py Text conflict in src/maasserver/dhcp.py Text conflict in src/maasserver/enum.py Conflict adding file src/maasserver/migrations/builtin/maasserver/0066_allow_squashfs.py. Moved existing file to src/maasserver/migrations/builtin/maasserver/0066_allow_squashfs.py.moved. Text conflict in src/maasserver/models/node.py Text conflict in src/maasserver/models/staticipaddress.py Text conflict in src/maasserver/models/tests/test_node.py Text conflict in src/maasserver/models/tests/test_staticipaddress.py Text conflict in src/maasserver/preseed_network.py Contents conflict in src/maasserver/static/css/maas-styles.css Text conflict in src/maasserver/static/js/angular/controllers/tests/test_node_details_storage.js Text conflict in src/maasserver/static/partials/node-details.html Text conflict in src/maasserver/static/partials/node-events.html Text conflict in src/maasserver/static/partials/nodes-list.html Conflict adding files to src/maasserver/static/scss/maas. Created directory. Conflict because src/maasserver/static/scss/maas is not versioned, but has versioned children. Versioned directory. Conflict adding files to src/maasserver/static/scss/maas/components. Created directory. Conflict because src/maasserver/static/scss/maas/components is not versioned, but has versioned children. Versioned directory. Contents conflict in src/maasserver/static/scss/maas/components/_accordion.scss Text conflict in src/maasserver/templates/maasserver/base.html Text conflict in src/maasserver/templates/maasserver/index.html Text conflict in src/maasserver/templates/maasserver/prefs.html Text conflict in src/maasserver/testing/factory.py Text conflict in src/maasserver/tests/test_bootresources.py Text conflict in src/maasserver/tests/test_dhcp.py Text conflict in src/maasserver/tests/test_preseed.py Text conflict in src/maasserver/tests/test_preseed_network.py Text conflict in src/maasserver/tests/test_start_up.py Text conflict in src/maasserver/triggers/tests/test_websocket_listener.py Contents conflict in src/maasserver/views/tests/test_images.py Text conflict in src/metadataserver/api.py Text conflict in src/provisioningserver/events.py Text conflict in src/provisioningserver/plugin.py Text conflict in src/provisioningserver/power/schema.py Text conflict in src/provisioningserver/tests/test_plugin.py |
||||
To merge this branch: | bzr merge lp:~ltrager/maas/lp1636858_2.0 | ||||
Related bugs: |
|
Reviewer | Review Type | Date Requested | Status |
---|---|---|---|
MAAS Maintainers | Pending | ||
Review via email: mp+309412@code.launchpad.net |
Commit message
Backport from 2.1.1 - If the power_parameters are the empty string set them to an empty dictionary, don't use json.loads
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 2016-10-17 06:38:56 +0000 |
3 | +++ Makefile 2016-10-26 23:39:26 +0000 |
4 | @@ -588,7 +588,7 @@ |
5 | |
6 | # Old names. |
7 | PACKAGING := $(abspath ../packaging.trunk) |
8 | -PACKAGING_BRANCH := lp:~maas-maintainers/maas/packaging |
9 | +PACKAGING_BRANCH := lp:~maas-maintainers/maas/packaging-2.0 |
10 | |
11 | packaging-tree = $(PACKAGING) |
12 | packaging-branch = $(PACKAGING_BRANCH) |
13 | |
14 | === modified file 'buildout.cfg' |
15 | === modified file 'docs/about.rst' |
16 | --- docs/about.rst 2016-04-11 16:23:26 +0000 |
17 | +++ docs/about.rst 2016-10-26 23:39:26 +0000 |
18 | @@ -1,6 +1,15 @@ |
19 | About this documentation |
20 | ======================== |
21 | |
22 | +This is API and developer documentation only. MAAS 2.0 user documentation is |
23 | +published on `http://maas.io_` and its source is found on `GitHub_`. |
24 | + |
25 | +.. _http://maas.io: |
26 | + http://maas.io/docs/ |
27 | + |
28 | +.. _GitHub: |
29 | + https://github.com/CanonicalLtd/maas-docs |
30 | + |
31 | This is the documentation for Canonical's MAAS software. If you aren't |
32 | sure what that is, you should probably skip everything else and head |
33 | straight to the :ref:`orientation` section where it is explained. |
34 | |
35 | === modified file 'docs/changelog.rst' |
36 | --- docs/changelog.rst 2016-09-27 22:26:08 +0000 |
37 | +++ docs/changelog.rst 2016-10-26 23:39:26 +0000 |
38 | @@ -2,6 +2,7 @@ |
39 | Changelog |
40 | ========= |
41 | |
42 | +<<<<<<< TREE |
43 | 2.1.0 Alpha 4 |
44 | ============= |
45 | |
46 | @@ -399,12 +400,532 @@ |
47 | http://launchpad.net/bugs/1618572 |
48 | |
49 | |
50 | +======= |
51 | +2.0.1 |
52 | +===== |
53 | + |
54 | +Issues fixed in this release |
55 | +---------------------------- |
56 | + |
57 | +LP: #1636251 resolv.conf search path doesn't match the domain for the host. |
58 | + |
59 | +LP: #1615686 Single-IP dynamic ranges resulted in internal errors. |
60 | + |
61 | +2.0.0 |
62 | +===== |
63 | + |
64 | +Important announcements |
65 | +---------------------- |
66 | + |
67 | +**MAAS 2.0 supported on Ubuntu 16.04 LTS (Xenial)** |
68 | + MAAS version 2.0 is supported on Ubuntu 16.04 LTS. MAAS 2.0 and greater |
69 | + will NOT be supported on Ubuntu 14.04 LTS. The latest MAAS 1.9 point release |
70 | + will continue to be supported on Ubuntu 14.04 LTS (Trusty) until it reaches |
71 | + end-of-life. |
72 | + |
73 | + Upgrades are supported for users running Ubuntu 14.04 systems running |
74 | + MAAS 1.9 or earlier. Upon upgrading to Ubuntu 16.04, the MAAS |
75 | + database and configuration will be seamlessly migrated to the supported |
76 | + MAAS version. |
77 | + |
78 | + Please see the “Other Notable Changes” section below for more details |
79 | + regarding the reasons for this change. |
80 | + |
81 | +**Terminology Changes** |
82 | + Cluster controllers have been renamed to rack controllers. |
83 | + |
84 | + Starting from MAAS 2.0, MAAS cluster controllers have been deprecated, |
85 | + along with the legacy ``nodegroups`` API. The new API endpoint is |
86 | + ``rackcontrollers``, which provides feature parity with earlier versions |
87 | + of MAAS. |
88 | + |
89 | + For more information on rack controllers, refer to the `Major new Features` |
90 | + section bellow or refer to :ref:`rack-configuration`. |
91 | + |
92 | +**API 1.0 has been deprecated, introducing API 2.0** |
93 | + Starting from MAAS 2.0, the MAAS REST API version 1.0 has been deprecated. |
94 | + MAAS 2.0 drops support for the legacy 1.0 API, in favor of API version 2.0. |
95 | + With the introduction of the new API version, various endpoints have now |
96 | + been deprecated, and new end-points have been introduced. API users will |
97 | + need to update their client tools to reflect the changes. |
98 | + |
99 | + For more information on API 2.0, refer to :ref:`API documentation <region-controller-api>`. |
100 | + |
101 | +**Static IP ranges have been deprecated** |
102 | + Starting from MAAS 2.0, static IP ranges (previously found on the cluster |
103 | + interface page) have been deprecated. MAAS now assumes total control of a |
104 | + subnet. MAAS will automatically assign IP addresses to deployed machines, |
105 | + as long as those IP addresses are not within a dynamic or a reserved |
106 | + IP range. Users are now only required to specify one or more dynamic ranges |
107 | + per subnet. Dynamic ranges are used for auto-enlistment, commissioning, and |
108 | + any other systems configured for DHCP. IP addresses in-use for purposes such |
109 | + as devices, default gateways, DNS servers, rack and region controllers, and |
110 | + BMCs are automatically avoided when assigning IP addresses. Reserved IP |
111 | + ranges may be added if MAAS should avoid certain ranges of IP addresses in |
112 | + the subnet. |
113 | + |
114 | +**maas-region-controller-min has been renamed to maas-region-api** |
115 | + The ``maas-region-controller-min`` package has been renamed to |
116 | + ``maas-region-api``. This package provides API services for MAAS |
117 | + (``maas-regiond``) and can be used to scale out the API front-end of |
118 | + a MAAS region. |
119 | + |
120 | +**MAAS user creation been moved to 'maas' command** |
121 | + Starting from MAAS 2.0, the ``maas`` command now provides the ability to |
122 | + create admin users. The ``maas-region createadmin`` command has been |
123 | + deprecated. New administrators should now be created with |
124 | + ``maas createadmin``. |
125 | + |
126 | +**maas-provision command has been replaced** |
127 | + The MAAS rack controller command-line interface (``maas-provision``) has |
128 | + been replaced by the ``maas-rack`` command. |
129 | + |
130 | +**maas-region-admin command has been replaced with maas-region** |
131 | + The MAAS region controller command-line interface (``maas-region-admin``) |
132 | + has been replaced by the ``maas-region`` command. Note that this command |
133 | + provides an interface to interact directly with Django, which should only be |
134 | + used for debugging purposes. |
135 | + |
136 | +**Debian Installer is no longer installed or supported** |
137 | + Because support for the Debian Installer (DI) has been dropped (as |
138 | + of MAAS 1.9), MAAS no longer downloads DI-related files from simplestreams. |
139 | + Upon upgrading to MAAS 2.0, DI-related files will be removed from the MAAS |
140 | + region (and all rack controllers). |
141 | + |
142 | + |
143 | +Major new features |
144 | +------------------ |
145 | + |
146 | +**MAAS Rack Controllers and High Availability** |
147 | + Starting from MAAS 2.0, MAAS **cluster controllers** have been renamed to |
148 | + **rack controllers**. |
149 | + |
150 | + * The ``nodegroups`` and ``nodegroups/(group)/interfaces`` API endpoints |
151 | + have been deprecated. In MAAS 2.0, the ``rackcontrollers`` interface |
152 | + partially replaces this functionality. For defining dynamic and reserved |
153 | + ranges, or specifying default gateways, use the ``subnets`` endpoint. For |
154 | + enabling or disabling DHCP, use the ``fabrics/(fabric)/vlans`` endpoint. |
155 | + |
156 | + * The **Clusters** tab is no longer available in the Web UI. |
157 | + Controllers are now found under the **Nodes** tab, where each region |
158 | + and/or rack controller can be found. Other cluster interface properties |
159 | + have been moved to the Subnet and VLAN details pages under the **Networks** |
160 | + tab. |
161 | + |
162 | + * Machines no longer belong to a specific controller. |
163 | + In earlier versions of MAAS, machines would directly assigned to a cluster |
164 | + controller. The cluster controller that the machine belonged to would not |
165 | + only perform DHCP for that machine, but also all the PXE booting and power |
166 | + management. |
167 | + |
168 | + In order to support high availability for rack controllers, (starting from |
169 | + MAAS 2.0) machines no longer belong to a specific rack controller. The best |
170 | + controller to manage a machine is now determined at runtime. |
171 | + |
172 | + * DHCP is now configured per-VLAN. |
173 | + In earlier versions of MAAS, DHCP was directly linked and configured |
174 | + per cluster interface. As of MAAS 2.0, DHCP is now configured and managed |
175 | + per-VLAN, which allows any rack controller to potentially provide DHCP in |
176 | + a high-availability environment. |
177 | + |
178 | + * Rack controllers have been enabled for high availability. |
179 | + Starting from MAAS 2.0, rack controllers in the same VLAN become |
180 | + candidates to manage DHCP, PXE/TFTP, and power control for machines on |
181 | + the VLAN. MAAS now supports the concept of a **primary** and a |
182 | + **secondary** rack controller. If a secondary controller determines that |
183 | + the primary controller is unavailable, it will assume control of those |
184 | + services. |
185 | + |
186 | + * Added ``maas-rack support-dump`` command. |
187 | + For increased support observability, users can now dump the contents of |
188 | + several internal MAAS data structures by executing ``sudo maas-rack |
189 | + support-dump``. This command will dump networking diagnostics, rack |
190 | + configuration, and image information. Information can be restricted to a |
191 | + particular category by using the ``--networking``, ``--config``, or |
192 | + ``--images`` options. |
193 | + |
194 | + * Rack controllers can now be found under the **Nodes** tab in the Web UI. |
195 | + MAAS 2.0 Adds a new **Controllers** section under thee **Nodes** tab. This |
196 | + section will now list all rack and region controllers. Under a rack |
197 | + controller, the user will be able to see service tracking, connected VLANs, |
198 | + rack interfaces and other relevant information. |
199 | + |
200 | +**Region Controller Redundancy (High Availability)** |
201 | + Starting from MAAS 2.0, MAAS provides the ability to scale out (provide |
202 | + redundancy for) the MAAS region controller API, HTTP server, and DNS. This |
203 | + will allow administrators to set up multiple MAAS region controllers |
204 | + (``maas-region-api``) against a common database, providing redundancy of |
205 | + services. With further manual configuration, users will be able to setup |
206 | + the MAAS region controller in high availability mode. |
207 | + |
208 | +**New Networks Web UI** |
209 | + MAAS 2.0 introduces a few new Web UI features that were not available in |
210 | + previous versions of MAAS. |
211 | + |
212 | + * Added fabric and space details pages. |
213 | + |
214 | + * Added the ability to add and remove fabrics, spaces, subnets and VLANs. |
215 | + This can be done using the actions menu on the **Networks** tab. |
216 | + |
217 | + The ability to delete fabrics, spaces, subnets and VLANs is also available |
218 | + from the details page for each respective object. |
219 | + |
220 | +**DNS Management** |
221 | + MAAS 2.0 extends DNS management by adding the following features: |
222 | + |
223 | + * Ability to create multiple DNS domains. |
224 | + * Ability to add multiple records (``CNAME``, ``TXT``, ``MX``, ``SRV``) per |
225 | + domain. (API only) |
226 | + * Ability to select the domain for machines and devices. |
227 | + * Ability to assign (additional) names to IP addresses. (API only) |
228 | + * For deployed machines, ``A`` records continue to be created for |
229 | + the IP of the PXE interface. |
230 | + * Additional PTR records are now created for all non-PXE interfaces in |
231 | + the form: ``<interface>.<machine fully-qualified-domain-name>`` |
232 | + * Reverse DNS is now generated for only the subnet specified, rather |
233 | + than the parent /24 or /16. By default, `RFC2137`_ glue is provided |
234 | + for networks smaller than /24. This can be disabled or changed |
235 | + on a per-subnet basis via the API. |
236 | + |
237 | +.. _RFC2137: |
238 | + https://tools.ietf.org/html/rfc2137 |
239 | + |
240 | +**IP Ranges** |
241 | + Previous versions of MAAS used the concepts of a **dynamic range** and |
242 | + **static range**, which were properties of each cluster interface. This |
243 | + has been redesigned for MAAS 2.0 as follows: |
244 | + |
245 | + * Dynamic ranges have been migrated from earlier MAAS releases as-is. |
246 | + |
247 | + * Because static ranges have been removed from MAAS, each static |
248 | + range has been migrated to one or more reserved ranges, which |
249 | + represent the opposite of the previous static range. (MAAS now |
250 | + assumes it has full control of each managed subnet, and is free |
251 | + to assign IP addresses as it sees fit, unless told otherwise.) |
252 | + |
253 | + For example, if in an earlier MAAS release a cluster interface was |
254 | + configured on 192.168.0.1/24, with a dynamic range of 192.168.0.2 |
255 | + through 192.168.0.99, and a static range of 192.168.0.100 through |
256 | + 192.168.0.199, this will be migrated to:: |
257 | + |
258 | + IP range #1 (dynamic): 192.168.0.2 - 192.168.0.99 |
259 | + IP range #2 (reserved): 192.168.0.200 - 192.168.0.254 |
260 | + |
261 | + Since 192.168.0.100 - 192.168.0.199 (the previous static range) |
262 | + is not accounted for, MAAS assumes it is free to allocate static |
263 | + IP addresses from that range. |
264 | + |
265 | + * Scalability is now possible by means of adding a second dynamic |
266 | + IP range to a VLAN. (To deal with IP address exhaustion, MAAS |
267 | + supports multiple dynamic ranges on one or more subnets within |
268 | + a DHCP-enabled VLAN.) |
269 | + |
270 | + * Reserved ranges can now be allocated to a particular MAAS user. |
271 | + |
272 | + * A comment field has been added, so that users can indicate why |
273 | + a particular range of IP addresses is reserved. |
274 | + |
275 | + * IP ranges can be configured in the Web UI via the Subnet |
276 | + details page, or using the ``subnets`` REST API endpoint. |
277 | + |
278 | +**API 2.0 and MAAS CLI Updates** |
279 | + Version 1.0 of the MAAS REST API has been removed and replaced with the 2.0 |
280 | + version of the REST API. As such, new endpoints and commands have been |
281 | + introduced: |
282 | + |
283 | + * RackControllers - This endpoint/command has the following operations |
284 | + in addition to the base operations provided by nodes: |
285 | + |
286 | + * ``import-boot-images`` - Import the boot images on all rack |
287 | + controllers |
288 | + * ``describe-power-types`` - Query all of the rack controllers for |
289 | + power information |
290 | + |
291 | + * RackController - This endpoint/command has the following operations |
292 | + in addition to the base operations provided by nodes |
293 | + |
294 | + * ``import-boot-images`` - Import boot images on the given rack |
295 | + controller |
296 | + * ``refresh`` - refresh the hardware information for the given rack |
297 | + controller |
298 | + |
299 | + * Machines - This endpoint/command replaces many of the operations |
300 | + previously found in the nodes endpoint/command. The machines |
301 | + endpoint/command has the following operations in addition to the |
302 | + base operations provided by nodes. |
303 | + |
304 | + * ``power-parameters`` - Retrieve power parameters for multiple |
305 | + machines |
306 | + * ``list-allocated`` - Fetch machines that were allocated to the |
307 | + user/oauth token. |
308 | + * ``allocate`` - Allocate an available machine for deployment. |
309 | + * ``accept`` - Accept declared machine into MAAS. |
310 | + * ``accept-all`` - Accept all declared machines into MAAS. |
311 | + * ``create`` - Create a new machine. |
312 | + * ``add-chassis`` - Add special hardware types. |
313 | + * ``release`` - Release multiple machines. |
314 | + |
315 | + * Machine - This endpoint/command replaces many of the operations |
316 | + previously found in the node endpoint/command. The machine |
317 | + endpoint/command has the following operations in addition to the |
318 | + base operations provided by node. |
319 | + |
320 | + * ``power-parameters`` - Obtain power parameters for the given machine. |
321 | + * ``deploy`` - Deploy an operating system to a given machine. |
322 | + * ``abort`` - Abort the machines current operation. |
323 | + * ``get-curtin-config`` - Return the rendered curtin configuration for |
324 | + the machine. |
325 | + * ``power-off`` - Power off the given machine. |
326 | + * ``set-storage-layout`` - Change the storage layout of the given |
327 | + machine. |
328 | + * ``power-on`` - Turn on the given machine. |
329 | + * ``release`` - Release a given machine. |
330 | + * ``clear-default-gateways`` - Clear any set default gateways on the |
331 | + machine. |
332 | + * ``update`` - Change machine configuration. |
333 | + * ``query-power-state`` - Query the power state of a machine. |
334 | + * ``commission`` - Begin commissioning process for a machine |
335 | + |
336 | + Other endpoints/commands have changed: |
337 | + |
338 | + * All list commands/operations have been converted to read |
339 | + * All new and add commands/operations have been converted to create |
340 | + * Nodes - The nodes endpoint/command is now a base endpoint/command |
341 | + for all other node types(devices, machines, and rack-controllers). |
342 | + As such most operations have been moved to the machines |
343 | + endpoint/command.The following operations remain as they can be |
344 | + used on all node types. |
345 | + |
346 | + * ``is-registered`` - Returns whether or not the given MAC address is |
347 | + registered with this MAAS. |
348 | + * ``set-zone`` - Assign multiple nodes to a physical zone at once. |
349 | + * ``read`` - List nodes visible to the user, optionally filtered by |
350 | + criteria. |
351 | + |
352 | + * Node - The node endpoint/command is now a base endpoint/command for |
353 | + all other node types(devices, machines, and rack-controllers). As |
354 | + such most operations have been moved to the machine endpoint/command. |
355 | + The following operations remain as they can be used on all node types. |
356 | + |
357 | + * ``read`` - Read information about a specific node |
358 | + * ``details`` - Obtain various system details. |
359 | + * ``delete`` - Delete a specific node. |
360 | + |
361 | + * With the migration of nodes to machines the following items previously |
362 | + outputted with the list command have been changed or removed from the |
363 | + machines read command: |
364 | + |
365 | + * ``status - Will now show all status types |
366 | + * ``substatus``, ``substatus_action``, ``substatus_message``, |
367 | + ``substatus_name`` - Replaced by ``status``, ``status_action``, |
368 | + ``status_message``, ``status_name``. |
369 | + * ``boot_type - Removed, MAAS 2.0 only supports fastpath. |
370 | + * ``pxe_mac`` - Replaced by ``boot_interface``. |
371 | + * ``hostname`` - Now only displays the hostname (without the domain) of |
372 | + the machine. ``fqdn`` and ``domain`` attributes can now be used instead. |
373 | + |
374 | + * And other endpoints/commands have been deprecated: |
375 | + |
376 | + * NodeGroups - Replacement operations are found in the |
377 | + RackControllers, Machines, and BootResources endpoints/commands. |
378 | + * NodeGroupInterfaces - replacement operations are found in the |
379 | + RackController, IPRanges, Subnets, and VLANS endpoints/commands. |
380 | + |
381 | +**Extended Storage Support** |
382 | + MAAS 2.0 Storage Model has been extended to support: |
383 | + |
384 | + * XFS as a filesystem. |
385 | + * Mount options. |
386 | + * Swap partitions. (MAAS 1.9 only supported the creation of a swap |
387 | + file in the filesystem.) |
388 | + * ``tmps``/``ramfs`` support. |
389 | + |
390 | + All of these options are currently available over the CLI. |
391 | + |
392 | +**DHCP Snippets** |
393 | + MAAS 2.0 introduces the ability to define DHCP snippets. This |
394 | + feature allows administrators to manage DHCP directly from MAAS, removing |
395 | + the need to manually modify template files. The following types of DHCP |
396 | + snippets can be defined: |
397 | + |
398 | + * **Host snippets** - used for configuration for a particular node in MAAS. |
399 | + * **Subnet snippets** - used for configuration for a specific subnet in MAAS. |
400 | + * **Global snippets** - used for configuration that will affect DHCP (isc-dhcp) |
401 | + as a whole. |
402 | + |
403 | + For more information, see :ref:`DHCP Snippets <dhcpsnippets>`. |
404 | + |
405 | +Minor new features |
406 | +------------------ |
407 | + |
408 | +**MAAS proxy is now managed** |
409 | + Starting from MAAS 2.0, MAAS now manages the configuration for |
410 | + ``maas-proxy``. This allows MAAS to lock down the proxy so that it only |
411 | + allows traffic from networks MAAS knows about. For more information, see |
412 | + :ref:`MAAS Proxy <proxy>`. |
413 | + |
414 | +**rsyslog during enlistment and commissioning** |
415 | + MAAS 2.0 now enables ``rsyslog`` for the enlistment and commissioning |
416 | + environment (when using Xenial as the commissioning image). This allows users |
417 | + to see ``cloud-init``'s syslog information in ``/var/log/maas/rsyslog/``. |
418 | + |
419 | +**Ability to change a machine’s domain name from the UI** |
420 | + MAAS 2.0 introduces the ability to change a machine’s DNS domain |
421 | + via the Web UI. It was previously supported on the API only. |
422 | + |
423 | +**Networks listing page** |
424 | + In the **Networks** tab, a new networks overview has been introduced, which |
425 | + provides a high-level view of the MAAS networking mode. The network model |
426 | + can be grouped by either fabrics or spaces. |
427 | + |
428 | +**Service Tracking** |
429 | + MAAS now tracks the status of the services required for its operation, such |
430 | + as ``bind``, ``maas-dhcpd``, ``maas-dhcpd6``, ``tgt``, and ``maas-proxy``. |
431 | + |
432 | + |
433 | +Other notable changes |
434 | +--------------------- |
435 | + |
436 | +**MAAS 2.0 requires Python 3.5** |
437 | + Starting with MAAS 2.0, MAAS has now been ported to Python 3.5 (the default |
438 | + version of Python in Ubuntu 16.04 "Xenial"). |
439 | + |
440 | +**MAAS 2.0 now fully supports native Django 1.8 migration system** |
441 | + MAAS is now based on Django 1.8. Django 1.8 has dropped support for the |
442 | + South migration system in favor of the native Django migration |
443 | + system, which breaks backwards compatibility with previous versions of |
444 | + Django. |
445 | + |
446 | + MAAS continues to support a full upgrade path. MAAS versions 1.5, 1.7, 1.8, |
447 | + and 1.9 have been tested and confirmed to upgrade seamlessly to MAAS 2.0. |
448 | + |
449 | +**Instant DHCP lease notifications** |
450 | + MAAS no longer scans the leases file every 5 minutes. ``isc-dhcp-server`` |
451 | + now directly notifies MAAS if a lease is committed, released, or expires. |
452 | + |
453 | +**Host entries in DHCP** |
454 | + Host entries are now rendered in the DHCP configuration instead |
455 | + of placed in the leases file. This removes any state that previously |
456 | + existed in the DHCP lease database on the cluster controller. |
457 | + |
458 | + Starting with MAAS 2.0, if the dhcpd.leases file is lost (such as during a |
459 | + failure scenario in a high availability environment), MAAS will be able to |
460 | + reconstruct it. |
461 | + |
462 | +**Power control is no longer specific to a rack controller** |
463 | + MAAS selects one of the available rack controllers to power control |
464 | + or query a BMC. The same rack controller that powers the BMC does |
465 | + not need to be the rack controller that the machine PXE boots from. |
466 | + |
467 | + |
468 | +2.0.0 (rc4) |
469 | +=========== |
470 | + |
471 | +Issues fixed in this release |
472 | +---------------------------- |
473 | + |
474 | +LP: #1592666 Mirror URL contains double slash (/) after hostname, impacting proxy cachaility. |
475 | + |
476 | +LP: #1604461 [2.0rc2] Static IP address are allowed to be created in a dynamic range. |
477 | + |
478 | +LP: #1610397 When juju adds containers to MAAS, ensure they inherit the parent machine domain name, if none is passed by Juju. |
479 | + |
480 | +LP: #1611342 UI error while generating a MAAS key (token). |
481 | + |
482 | +LP: #1610414 apiclient.maas_client.MAASClient.post() always sets an op in the query string |
483 | + |
484 | + |
485 | +2.0.0 (rc3) |
486 | +=========== |
487 | + |
488 | +Issues fixed in this release |
489 | +---------------------------- |
490 | + |
491 | +LP: #1557434 For the MAAS CLI, mimic the error behaviour provided by argparse 1.1 on PyPI when insufficient arguments are given. |
492 | + |
493 | +LP: #1594991 MAAS displays every power query on the summarized view of node event log. |
494 | + |
495 | +LP: #1603147 Commissioning dropdown is grey and checkmarks are missing. |
496 | + |
497 | +LP: #1576116 [2.0rc1] MAAS does not respect default subnet's DNS server when choosing default DNS |
498 | + |
499 | +LP: #1600720 [2.0rc1] MAAS doesn't honor DNS settings for a subnet for DHCP |
500 | + |
501 | +LP: #1598028 [2.0] Loading latest machine events can make web browser unresponsive |
502 | + |
503 | +LP: #1604128 [2.0rc2] Unable to add a public SSH Key due to lp1604147 |
504 | + |
505 | +LP: #1604169 [2.0] maas login yields "ImportError: No module named 'maasserver'" |
506 | + |
507 | +LP: #1604962 Fixes to correctly log cloud-init/curtin FAIL events in the node event log. |
508 | + |
509 | +LP: #1604987 Fixes to correctly log "mark_failed" events. |
510 | + |
511 | +LP: #1602721 [2.0rc2] Can't get node-results via cli/api |
512 | + |
513 | +LP: #1604465 [2.0] RackController.get_image_sync_status causes huge load on regiond process |
514 | + |
515 | +LP: #1598149 [2.0rc2] MAAS is not automatically monitoring timeouts for commissioning. |
516 | + |
517 | +LP: #1605252 [2.0] Error messaging about monitor expiry has been dropped |
518 | + |
519 | + |
520 | +2.0.0 (rc2) |
521 | +=========== |
522 | + |
523 | +Issues fixed in this release |
524 | +---------------------------- |
525 | + |
526 | +LP: #1582070 Pick up wrong grub.cfg if another filesystem exists |
527 | + |
528 | +LP: #1599223 [2.0] confusing reverse DNS lookups because MAAS creates multiple PTR records |
529 | + |
530 | +LP: #1600259 [2.0] reverse DNS sometimes assigns FQDN where it should assign IFACE.FQDN |
531 | + |
532 | +LP: #1599997 [2.0rc1] after upgrade from 2.0b3, Error on request (13) subnet.list: 'NoneType' object is not iterable |
533 | + |
534 | +LP: #1598461 [2.0rc1] Image import dates are inconsistent |
535 | + |
536 | +LP: #1598937 [2.0rc1] Following fresh install maas command fails - PermissionError: [Errno 13] Permission denied: '/home/ubuntu/.maascli.db' |
537 | + |
538 | +LP: #1597787 [1.9.3,2.0] cannot create more than 4 partitions when disk is configured with mbr |
539 | + |
540 | +LP: #1600267 [1.9,2.0,UX] Can't add aliases when parent interface is set to 'DCHP' |
541 | + |
542 | +LP: #1600198 [1.9,2.0,UX] Creating a Bcache disk is not prevented when is not created in partition |
543 | + |
544 | + |
545 | +>>>>>>> MERGE-SOURCE |
546 | 2.0.0 (rc1) |
547 | =========== |
548 | |
549 | Issues fixed in this release |
550 | ---------------------------- |
551 | |
552 | +LP: #1576357 Determine a method for how to reconnect a deleted rack controller |
553 | + |
554 | +LP: #1592246 [2.0b7, regression] maas-rack register makes up a new hostname |
555 | + |
556 | +LP: #1595753 [beta8] HMC power driver regression -- Not able to connect via SSH. |
557 | + |
558 | +LP: #1592885 [2.0b7] Date and time format should be consistent accross logs |
559 | + |
560 | +LP: #1597324 [2.0b8] Unable to set default gateway interface |
561 | + |
562 | +LP: #1515188 [1.9] VMware power management fails when VMs are organized in nested subfolders |
563 | + |
564 | +LP: #1596046 [2.0] maas 2.0 pxeboot fails on PowerNV |
565 | + |
566 | +LP: #1600267 [1.9,2.0,UX] Can't add aliases when parent interface is set to 'DCHP' |
567 | + |
568 | +LP: #1598937 [2.0 rc1] Following fresh install maas command fails - PermissionError: [Errno 13] Permission denied: '/home/ubuntu/.maascli.db' |
569 | + |
570 | +2.0.0 (beta8) |
571 | +============= |
572 | + |
573 | +Issues fixed in this release |
574 | +---------------------------- |
575 | + |
576 | LP: #1590081 Allow ed25519 and ecdsa ssh keys |
577 | |
578 | LP: #1462078 [2.0b2, UI] Can't add a device and it does not show why |
579 | @@ -1344,6 +1865,7 @@ |
580 | |
581 | .. _1553617: |
582 | https://launchpad.net/bugs/1553617 |
583 | +<<<<<<< TREE |
584 | |
585 | |
586 | 1.9.1 |
587 | @@ -3978,3 +4500,5 @@ |
588 | #1185897 expose ability to re-commission node in api and cli |
589 | |
590 | #997092 Can't delete allocated node even if owned by self |
591 | +======= |
592 | +>>>>>>> MERGE-SOURCE |
593 | |
594 | === modified file 'docs/index.rst' |
595 | --- docs/index.rst 2016-08-15 09:54:43 +0000 |
596 | +++ docs/index.rst 2016-10-26 23:39:26 +0000 |
597 | @@ -6,6 +6,15 @@ |
598 | |
599 | This is the documentation for the `MAAS project`_. |
600 | |
601 | +This is API and developer documentation only. MAAS 2.0 user documentation is |
602 | +published on `http://maas.io_` and its source is found on `GitHub_`. |
603 | + |
604 | +.. _http://maas.io: |
605 | + http://maas.io/docs/ |
606 | + |
607 | +.. _GitHub: |
608 | + https://github.com/CanonicalLtd/maas-docs |
609 | + |
610 | Metal as a Service -- MAAS -- lets you treat physical servers like |
611 | virtual machines in the cloud. Rather than having to manage each |
612 | server individually, MAAS turns your bare metal into an elastic |
613 | |
614 | === modified file 'src/apiclient/tests/test_maas_client.py' |
615 | === modified file 'src/maas/settings.py' |
616 | === modified file 'src/maasserver/api/account.py' |
617 | --- src/maasserver/api/account.py 2016-10-12 15:26:17 +0000 |
618 | +++ src/maasserver/api/account.py 2016-10-26 23:39:26 +0000 |
619 | @@ -5,12 +5,21 @@ |
620 | |
621 | __all__ = [ |
622 | 'AccountHandler', |
623 | +<<<<<<< TREE |
624 | ] |
625 | |
626 | import http.client |
627 | import json |
628 | |
629 | from django.http import HttpResponse |
630 | +======= |
631 | + ] |
632 | + |
633 | +import http.client |
634 | +import json |
635 | + |
636 | +from django.http import HttpResponse |
637 | +>>>>>>> MERGE-SOURCE |
638 | from maasserver.api.support import ( |
639 | operation, |
640 | OperationsHandler, |
641 | @@ -55,16 +64,30 @@ |
642 | |
643 | """ |
644 | profile = request.user.userprofile |
645 | +<<<<<<< TREE |
646 | consumer_name = get_optional_param(request.data, 'name') |
647 | consumer, token = profile.create_authorisation_token(consumer_name) |
648 | auth_info = { |
649 | +======= |
650 | + consumer, token = profile.create_authorisation_token() |
651 | + auth_info = { |
652 | +>>>>>>> MERGE-SOURCE |
653 | 'token_key': token.key, 'token_secret': token.secret, |
654 | +<<<<<<< TREE |
655 | 'consumer_key': consumer.key, 'name': consumer.name |
656 | } |
657 | return HttpResponse( |
658 | json.dumps(auth_info), |
659 | content_type='application/json; charset=utf-8', |
660 | status=int(http.client.OK)) |
661 | +======= |
662 | + 'consumer_key': consumer.key, |
663 | + } |
664 | + return HttpResponse( |
665 | + json.dumps(auth_info), |
666 | + content_type='application/json; charset=utf-8', |
667 | + status=int(http.client.OK)) |
668 | +>>>>>>> MERGE-SOURCE |
669 | |
670 | @operation(idempotent=False) |
671 | def delete_authorisation_token(self, request): |
672 | |
673 | === modified file 'src/maasserver/api/blockdevices.py' |
674 | === modified file 'src/maasserver/api/boot_resources.py' |
675 | === modified file 'src/maasserver/api/events.py' |
676 | === modified file 'src/maasserver/api/interfaces.py' |
677 | --- src/maasserver/api/interfaces.py 2016-10-20 16:04:24 +0000 |
678 | +++ src/maasserver/api/interfaces.py 2016-10-26 23:39:26 +0000 |
679 | @@ -415,6 +415,7 @@ |
680 | :param parents: Parent interfaces that make this bond. |
681 | |
682 | Fields for VLAN interface: |
683 | +<<<<<<< TREE |
684 | |
685 | :param tags: Tags for the interface. |
686 | :param vlan: Tagged VLAN the interface is connected to. |
687 | @@ -424,6 +425,9 @@ |
688 | |
689 | :param name: Name of the interface. |
690 | :param mac_address: MAC address of the interface. |
691 | +======= |
692 | + |
693 | +>>>>>>> MERGE-SOURCE |
694 | :param tags: Tags for the interface. |
695 | :param vlan: VLAN the interface is connected to. |
696 | :param parent: Parent interface for this bridge interface. |
697 | |
698 | === modified file 'src/maasserver/api/ip_addresses.py' |
699 | === modified file 'src/maasserver/api/machines.py' |
700 | === modified file 'src/maasserver/api/tests/test_api.py' |
701 | === modified file 'src/maasserver/api/tests/test_blockdevice.py' |
702 | === modified file 'src/maasserver/api/tests/test_ipaddresses.py' |
703 | --- src/maasserver/api/tests/test_ipaddresses.py 2016-10-18 16:31:31 +0000 |
704 | +++ src/maasserver/api/tests/test_ipaddresses.py 2016-10-26 23:39:26 +0000 |
705 | @@ -25,6 +25,7 @@ |
706 | ) |
707 | from maasserver.testing.factory import factory |
708 | from maasserver.utils.converters import json_load_bytes |
709 | +<<<<<<< TREE |
710 | from maasserver.utils.orm import ( |
711 | reload_object, |
712 | transactional, |
713 | @@ -375,6 +376,14 @@ |
714 | def setUp(self): |
715 | register_view("maasserver_discovery") |
716 | return super().setUp() |
717 | +======= |
718 | +from maasserver.utils.orm import reload_object |
719 | +from netaddr import IPAddress |
720 | +from testtools.matchers import Equals |
721 | + |
722 | + |
723 | +class TestIPAddressesAPI(APITestCase.ForUser): |
724 | +>>>>>>> MERGE-SOURCE |
725 | |
726 | def post_reservation_request( |
727 | self, subnet=None, ip_address=None, network=None, mac=None, |
728 | |
729 | === modified file 'src/maasserver/api/tests/test_machine.py' |
730 | --- src/maasserver/api/tests/test_machine.py 2016-10-20 19:43:37 +0000 |
731 | +++ src/maasserver/api/tests/test_machine.py 2016-10-26 23:39:26 +0000 |
732 | @@ -1231,6 +1231,43 @@ |
733 | machine = reload_object(machine) |
734 | self.assertEqual(old_zone, machine.zone) |
735 | |
736 | +<<<<<<< TREE |
737 | +======= |
738 | + def test_PUT_sets_disable_ipv4(self): |
739 | + self.become_admin() |
740 | + original_setting = factory.pick_bool() |
741 | + machine = factory.make_Node( |
742 | + owner=self.user, |
743 | + architecture=make_usable_architecture(self), |
744 | + power_type='manual', |
745 | + disable_ipv4=original_setting) |
746 | + new_setting = not original_setting |
747 | + |
748 | + response = self.client.put( |
749 | + self.get_machine_uri(machine), {'disable_ipv4': new_setting}) |
750 | + self.assertEqual(http.client.OK, response.status_code) |
751 | + |
752 | + machine = reload_object(machine) |
753 | + self.assertEqual(new_setting, machine.disable_ipv4) |
754 | + |
755 | + def test_PUT_leaves_disable_ipv4_unchanged_by_default(self): |
756 | + self.become_admin() |
757 | + original_setting = factory.pick_bool() |
758 | + machine = factory.make_Node( |
759 | + owner=self.user, |
760 | + architecture=make_usable_architecture(self), |
761 | + power_type='manual', |
762 | + disable_ipv4=original_setting) |
763 | + self.assertEqual(original_setting, machine.disable_ipv4) |
764 | + |
765 | + response = self.client.put( |
766 | + self.get_machine_uri(machine), {'zone': factory.make_Zone()}) |
767 | + self.assertEqual(http.client.OK, response.status_code) |
768 | + |
769 | + machine = reload_object(machine) |
770 | + self.assertEqual(original_setting, machine.disable_ipv4) |
771 | + |
772 | +>>>>>>> MERGE-SOURCE |
773 | def test_PUT_updates_swap_size(self): |
774 | self.become_admin() |
775 | machine = factory.make_Node( |
776 | |
777 | === modified file 'src/maasserver/api/tests/test_machines.py' |
778 | --- src/maasserver/api/tests/test_machines.py 2016-10-26 21:16:16 +0000 |
779 | +++ src/maasserver/api/tests/test_machines.py 2016-10-26 23:39:26 +0000 |
780 | @@ -204,6 +204,7 @@ |
781 | "Select a valid choice. %s is not one of the " |
782 | "available choices." % power_type, validation_errors[0]) |
783 | |
784 | +<<<<<<< TREE |
785 | def test_POST_new_handles_empty_str_power_parameters(self): |
786 | # Regression test for LP:1636858 |
787 | response = self.client.post( |
788 | @@ -243,6 +244,25 @@ |
789 | power_address, machine.power_parameters['power_address']) |
790 | self.assertEqual(power_id, machine.power_parameters['power_id']) |
791 | |
792 | +======= |
793 | + def test_POST_new_handles_empty_str_power_parameters(self): |
794 | + # Regression test for LP:1636858 |
795 | + response = self.client.post( |
796 | + reverse('machines_handler'), |
797 | + { |
798 | + 'architecture': make_usable_architecture(self), |
799 | + 'mac_addresses': ['aa:bb:cc:dd:ee:ff'], |
800 | + 'power_type': '', |
801 | + 'power_parameters': '', |
802 | + }) |
803 | + self.assertEqual(http.client.OK, response.status_code) |
804 | + system_id = json.loads( |
805 | + response.content.decode(settings.DEFAULT_CHARSET))['system_id'] |
806 | + machine = Machine.objects.get(system_id=system_id) |
807 | + self.assertEquals('', machine.power_type) |
808 | + self.assertItemsEqual({}, machine.power_parameters) |
809 | + |
810 | +>>>>>>> MERGE-SOURCE |
811 | def test_GET_lists_machines(self): |
812 | # The api allows for fetching the list of Machines. |
813 | machine1 = factory.make_Node() |
814 | |
815 | === modified file 'src/maasserver/api/tests/test_node.py' |
816 | === modified file 'src/maasserver/api/utils.py' |
817 | === modified file 'src/maasserver/bootresources.py' |
818 | --- src/maasserver/bootresources.py 2016-10-12 15:26:17 +0000 |
819 | +++ src/maasserver/bootresources.py 2016-10-26 23:39:26 +0000 |
820 | @@ -733,6 +733,7 @@ |
821 | {'sha256': rfile.largefile.sha256}) |
822 | maaslog.debug("Finalizing boot image %s.", ident) |
823 | |
824 | +<<<<<<< TREE |
825 | # Ensure that the size of the largefile starts at zero. |
826 | rfile.largefile.size = 0 |
827 | transactional(rfile.largefile.save)(update_fields=['size']) |
828 | @@ -745,10 +746,21 @@ |
829 | database per chunk. This makes the process be reported correctly. |
830 | """ |
831 | with rfile.largefile.content.open('wb') as stream: |
832 | +======= |
833 | + # Ensure that the size of the largefile starts at zero. |
834 | + rfile.largefile.size = 0 |
835 | + rfile.largefile.save(update_fields=['size']) |
836 | + |
837 | + # Write the contents into the database, while calculating the sha256 |
838 | + # hash for the read data. |
839 | + with rfile.largefile.content.open('wb') as stream: |
840 | + while True: |
841 | +>>>>>>> MERGE-SOURCE |
842 | buf = reader.read(self.read_size) |
843 | stream.seek(0, 2) |
844 | stream.write(buf) |
845 | cksummer.update(buf) |
846 | +<<<<<<< TREE |
847 | buf_len = len(buf) |
848 | rfile.largefile.size += buf_len |
849 | rfile.largefile.save(update_fields=['size']) |
850 | @@ -765,6 +777,13 @@ |
851 | # Don't check the checksum if finalization was cancelled. |
852 | if self._cancel_finalize: |
853 | return |
854 | +======= |
855 | + buf_len = len(buf) |
856 | + rfile.largefile.size += buf_len |
857 | + rfile.largefile.save(update_fields=['size']) |
858 | + if buf_len != self.read_size: |
859 | + break |
860 | +>>>>>>> MERGE-SOURCE |
861 | |
862 | if not cksummer.check(): |
863 | # Calculated sha256 hash from the data does not match, what |
864 | @@ -1091,6 +1110,7 @@ |
865 | def insert_item(self, data, src, target, pedigree, contentsource): |
866 | """Overridable from `BasicMirrorWriter`.""" |
867 | item = sutil.products_exdata(src, pedigree) |
868 | +<<<<<<< TREE |
869 | product_name = pedigree[0] |
870 | version_name = pedigree[1] |
871 | versions = src['products'][product_name]['versions'] |
872 | @@ -1110,6 +1130,23 @@ |
873 | return |
874 | else: |
875 | self.store.insert(item, contentsource) |
876 | +======= |
877 | + product_name = pedigree[0] |
878 | + version_name = pedigree[1] |
879 | + versions = src['products'][product_name]['versions'] |
880 | + items = versions[version_name]['items'] |
881 | + if ( |
882 | + item['ftype'] == BOOT_RESOURCE_FILE_TYPE.ROOT_IMAGE and |
883 | + BOOT_RESOURCE_FILE_TYPE.SQUASHFS_IMAGE in items.keys()): |
884 | + # If both a SquashFS and root-image.gz are available only insert |
885 | + # the SquashFS image. |
886 | + return |
887 | + elif item['ftype'] not in dict(BOOT_RESOURCE_FILE_TYPE_CHOICES).keys(): |
888 | + # Skip filetypes that we don't know about. |
889 | + return |
890 | + else: |
891 | + self.store.insert(item, contentsource) |
892 | +>>>>>>> MERGE-SOURCE |
893 | |
894 | |
895 | def download_boot_resources(path, store, product_mapping, |
896 | |
897 | === modified file 'src/maasserver/dhcp.py' |
898 | --- src/maasserver/dhcp.py 2016-10-12 15:26:17 +0000 |
899 | +++ src/maasserver/dhcp.py 2016-10-26 23:39:26 +0000 |
900 | @@ -372,6 +372,7 @@ |
901 | |
902 | @typed |
903 | def make_subnet_config( |
904 | +<<<<<<< TREE |
905 | rack_controller, subnet, maas_dns_server, |
906 | ntp_servers: Union[list, dict], default_domain, |
907 | failover_peer=None, subnets_dhcp_snippets: list=None): |
908 | @@ -381,7 +382,13 @@ |
909 | include in DHCP responses, or a dict; if the latter, it ought to match |
910 | the output from `get_ntp_server_addresses_for_rack`. |
911 | """ |
912 | +======= |
913 | + rack_controller, subnet, maas_dns_server, ntp_server, default_domain, |
914 | + failover_peer=None, subnets_dhcp_snippets=[]): |
915 | + """Return DHCP subnet configuration dict for a rack interface.""" |
916 | +>>>>>>> MERGE-SOURCE |
917 | ip_network = subnet.get_ipnetwork() |
918 | +<<<<<<< TREE |
919 | if subnet.dns_servers is not None and len(subnet.dns_servers) > 0: |
920 | # Replace MAAS DNS with the servers defined on the subnet. |
921 | dns_servers = [IPAddress(server) for server in subnet.dns_servers] |
922 | @@ -400,6 +407,15 @@ |
923 | else: |
924 | ntp_servers = [ntp_server] |
925 | |
926 | +======= |
927 | + if subnet.dns_servers is not None and len(subnet.dns_servers) > 0: |
928 | + # Replace MAAS DNS with the servers defined on the subnet. |
929 | + dns_servers = ", ".join(subnet.dns_servers) |
930 | + elif maas_dns_server is not None and len(maas_dns_server) > 0: |
931 | + dns_servers = maas_dns_server |
932 | + else: |
933 | + dns_servers = "" |
934 | +>>>>>>> MERGE-SOURCE |
935 | return { |
936 | 'subnet': str(ip_network.network), |
937 | 'subnet_mask': str(ip_network.netmask), |
938 | @@ -486,7 +502,11 @@ |
939 | for subnet in subnets: |
940 | subnet_configs.append( |
941 | make_subnet_config( |
942 | +<<<<<<< TREE |
943 | rack_controller, subnet, maas_dns_server, ntp_servers, |
944 | +======= |
945 | + rack_controller, subnet, maas_dns_server, ntp_server, |
946 | +>>>>>>> MERGE-SOURCE |
947 | domain, peer_name, subnets_dhcp_snippets)) |
948 | |
949 | # Generate the hosts for all subnets. |
950 | @@ -558,6 +578,7 @@ |
951 | for vlan, (subnets_v4, subnets_v6) in vlan_subnets.items(): |
952 | # IPv4 |
953 | if len(subnets_v4) > 0: |
954 | +<<<<<<< TREE |
955 | try: |
956 | config = get_dhcp_configure_for( |
957 | 4, rack_controller, vlan, subnets_v4, ntp_servers, |
958 | @@ -580,8 +601,33 @@ |
959 | }) |
960 | hosts_v4.extend(hosts) |
961 | interfaces_v4.add(interface) |
962 | +======= |
963 | + try: |
964 | + config = get_dhcp_configure_for( |
965 | + 4, rack_controller, vlan, subnets_v4, ntp_server, |
966 | + default_domain, dhcp_snippets) |
967 | + except DHCPConfigurationError as e: |
968 | + # XXX bug #1602412: this silently breaks DHCPv4, but we cannot |
969 | + # allow it to crash here since DHCPv6 might be able to run. |
970 | + # This error may be irrelevant if there is an IPv4 network in |
971 | + # the MAAS model which is not configured on the rack, and the |
972 | + # user only wants to serve DHCPv6. But it is still something |
973 | + # worth noting, so log it and continue. |
974 | + log.err(e) |
975 | + else: |
976 | + failover_peer, subnets, hosts, interface = config |
977 | + if failover_peer is not None: |
978 | + failover_peers_v4.append(failover_peer) |
979 | + shared_networks_v4.append({ |
980 | + "name": "vlan-%d" % vlan.id, |
981 | + "subnets": subnets, |
982 | + }) |
983 | + hosts_v4.extend(hosts) |
984 | + interfaces_v4.add(interface) |
985 | +>>>>>>> MERGE-SOURCE |
986 | # IPv6 |
987 | if len(subnets_v6) > 0: |
988 | +<<<<<<< TREE |
989 | try: |
990 | config = get_dhcp_configure_for( |
991 | 6, rack_controller, vlan, subnets_v6, |
992 | @@ -605,6 +651,32 @@ |
993 | hosts_v6.extend(hosts) |
994 | interfaces_v6.add(interface) |
995 | return DHCPConfigurationForRack( |
996 | +======= |
997 | + try: |
998 | + config = get_dhcp_configure_for( |
999 | + 6, rack_controller, vlan, subnets_v6, |
1000 | + ntp_server, default_domain, dhcp_snippets) |
1001 | + except DHCPConfigurationError as e: |
1002 | + # XXX bug #1602412: this silently breaks DHCPv6, but we cannot |
1003 | + # allow it to crash here since DHCPv4 might be able to run. |
1004 | + # This error may be irrelevant if there is an IPv6 network in |
1005 | + # the MAAS model which is not configured on the rack, and the |
1006 | + # user only wants to serve DHCPv4. But it is still something |
1007 | + # worth noting, so log it and continue. |
1008 | + log.err(e) |
1009 | + else: |
1010 | + failover_peer, subnets, hosts, interface = config |
1011 | + if failover_peer is not None: |
1012 | + failover_peers_v6.append(failover_peer) |
1013 | + shared_networks_v6.append({ |
1014 | + "name": "vlan-%d" % vlan.id, |
1015 | + "subnets": subnets, |
1016 | + }) |
1017 | + hosts_v6.extend(hosts) |
1018 | + interfaces_v6.add(interface) |
1019 | + return ( |
1020 | + get_omapi_key(), |
1021 | +>>>>>>> MERGE-SOURCE |
1022 | failover_peers_v4, shared_networks_v4, hosts_v4, interfaces_v4, |
1023 | failover_peers_v6, shared_networks_v6, hosts_v6, interfaces_v6, |
1024 | get_omapi_key(), global_dhcp_snippets) |
1025 | |
1026 | === modified file 'src/maasserver/dns/tests/test_zonegenerator.py' |
1027 | === modified file 'src/maasserver/dns/zonegenerator.py' |
1028 | === modified file 'src/maasserver/enum.py' |
1029 | --- src/maasserver/enum.py 2016-09-08 17:26:54 +0000 |
1030 | +++ src/maasserver/enum.py 2016-10-26 23:39:26 +0000 |
1031 | @@ -341,9 +341,15 @@ |
1032 | #: Root Image (gets converted to root-image root-tgz, on the rack) |
1033 | ROOT_IMAGE = 'root-image.gz' |
1034 | |
1035 | +<<<<<<< TREE |
1036 | #: Root image in SquashFS form, does not need to be converted |
1037 | SQUASHFS_IMAGE = 'squashfs' |
1038 | |
1039 | +======= |
1040 | + # Root image in SquashFS form, does not need to be converted |
1041 | + SQUASHFS_IMAGE = 'squashfs' |
1042 | + |
1043 | +>>>>>>> MERGE-SOURCE |
1044 | #: Boot Kernel (ISCSI kernel) |
1045 | BOOT_KERNEL = 'boot-kernel' |
1046 | |
1047 | |
1048 | === modified file 'src/maasserver/forms.py' |
1049 | === modified file 'src/maasserver/forms_interface_link.py' |
1050 | === modified file 'src/maasserver/forms_settings.py' |
1051 | === added file 'src/maasserver/migrations/builtin/maasserver/0066_allow_squashfs.py' |
1052 | --- src/maasserver/migrations/builtin/maasserver/0066_allow_squashfs.py 1970-01-01 00:00:00 +0000 |
1053 | +++ src/maasserver/migrations/builtin/maasserver/0066_allow_squashfs.py 2016-10-26 23:39:26 +0000 |
1054 | @@ -0,0 +1,22 @@ |
1055 | +# -*- coding: utf-8 -*- |
1056 | +from __future__ import unicode_literals |
1057 | + |
1058 | +from django.db import ( |
1059 | + migrations, |
1060 | + models, |
1061 | +) |
1062 | + |
1063 | + |
1064 | +class Migration(migrations.Migration): |
1065 | + |
1066 | + dependencies = [ |
1067 | + ('maasserver', '0065_larger_osystem_and_distro_series'), |
1068 | + ] |
1069 | + |
1070 | + operations = [ |
1071 | + migrations.AlterField( |
1072 | + model_name='bootresourcefile', |
1073 | + name='filetype', |
1074 | + field=models.CharField(choices=[('root-tgz', 'Root Image (tar.gz)'), ('root-dd', 'Root Compressed DD (dd -> tar.gz)'), ('root-image.gz', 'Compressed Root Image'), ('squashfs', 'SquashFS Root Image'), ('boot-kernel', 'Linux ISCSI Kernel'), ('boot-initrd', 'Initial ISCSI Ramdisk'), ('boot-dtb', 'ISCSI Device Tree Blob')], default='root-tgz', max_length=20, editable=False), |
1075 | + ), |
1076 | + ] |
1077 | |
1078 | === renamed file 'src/maasserver/migrations/builtin/maasserver/0066_allow_squashfs.py' => 'src/maasserver/migrations/builtin/maasserver/0066_allow_squashfs.py.moved' |
1079 | === modified file 'src/maasserver/models/bootresource.py' |
1080 | === modified file 'src/maasserver/models/node.py' |
1081 | --- src/maasserver/models/node.py 2016-10-26 18:28:12 +0000 |
1082 | +++ src/maasserver/models/node.py 2016-10-26 23:39:26 +0000 |
1083 | @@ -123,8 +123,12 @@ |
1084 | COMMISSIONING_LIKE_STATUSES, |
1085 | get_failed_status, |
1086 | is_failed_status, |
1087 | +<<<<<<< TREE |
1088 | MONITORED_STATUSES, |
1089 | NODE_FAILURE_MONITORED_STATUS_TIMEOUTS, |
1090 | +======= |
1091 | + NODE_FAILURE_MONITORED_STATUS_TIMEOUTS, |
1092 | +>>>>>>> MERGE-SOURCE |
1093 | NODE_TRANSITIONS, |
1094 | ) |
1095 | from maasserver.rpc import ( |
1096 | @@ -220,23 +224,57 @@ |
1097 | |
1098 | # Return type from `get_effective_power_info`. |
1099 | PowerInfo = namedtuple("PowerInfo", ( |
1100 | - "can_be_started", |
1101 | - "can_be_stopped", |
1102 | - "can_be_queried", |
1103 | - "power_type", |
1104 | - "power_parameters", |
1105 | -)) |
1106 | - |
1107 | -DefaultGateways = namedtuple("DefaultGateways", ( |
1108 | - "ipv4", |
1109 | - "ipv6", |
1110 | -)) |
1111 | - |
1112 | -GatewayDefinition = namedtuple("GatewayDefinition", ( |
1113 | - "interface_id", |
1114 | - "subnet_id", |
1115 | - "gateway_ip", |
1116 | -)) |
1117 | +<<<<<<< TREE |
1118 | + "can_be_started", |
1119 | + "can_be_stopped", |
1120 | + "can_be_queried", |
1121 | + "power_type", |
1122 | + "power_parameters", |
1123 | +)) |
1124 | + |
1125 | +DefaultGateways = namedtuple("DefaultGateways", ( |
1126 | + "ipv4", |
1127 | + "ipv6", |
1128 | +)) |
1129 | + |
1130 | +GatewayDefinition = namedtuple("GatewayDefinition", ( |
1131 | + "interface_id", |
1132 | + "subnet_id", |
1133 | + "gateway_ip", |
1134 | +)) |
1135 | +======= |
1136 | + "can_be_started", |
1137 | + "can_be_stopped", |
1138 | + "can_be_queried", |
1139 | + "power_type", |
1140 | + "power_parameters", |
1141 | +)) |
1142 | + |
1143 | +DefaultGateways = namedtuple("DefaultGateways", ( |
1144 | + "ipv4", |
1145 | + "ipv6", |
1146 | +)) |
1147 | + |
1148 | +GatewayDefinition = namedtuple("GatewayDefinition", ( |
1149 | + "interface_id", |
1150 | + "subnet_id", |
1151 | + "gateway_ip", |
1152 | +)) |
1153 | + |
1154 | +# The sequence from which the decimal form of node system IDs should be |
1155 | +# pulled. At the time of writing this should match the definition in migration |
1156 | +# 0021_create_node_system_id_sequence, and vice-versa. |
1157 | +node_system_id = Sequence( |
1158 | + "maasserver_node_system_id_seq", cycle=False, |
1159 | + minvalue=(24 ** 5), maxvalue=((24 ** 6) - 1), |
1160 | + start=15600471, owner="maasserver_node.system_id", |
1161 | +) |
1162 | + |
1163 | + |
1164 | +def generate_node_system_ids(): |
1165 | + """Return an iterable that yields short system IDs.""" |
1166 | + return map(znums.from_int, node_system_id) |
1167 | +>>>>>>> MERGE-SOURCE |
1168 | |
1169 | |
1170 | def generate_node_system_id(): |
1171 | @@ -1127,6 +1165,7 @@ |
1172 | This is the maximum time the commissioning is allowed to take. |
1173 | """ |
1174 | # Return a *very* conservative estimate for now. |
1175 | +<<<<<<< TREE |
1176 | return timedelta( |
1177 | minutes=NODE_FAILURE_MONITORED_STATUS_TIMEOUTS[ |
1178 | NODE_STATUS.COMMISSIONING]).total_seconds() |
1179 | @@ -1140,6 +1179,11 @@ |
1180 | return timedelta( |
1181 | minutes=NODE_FAILURE_MONITORED_STATUS_TIMEOUTS[ |
1182 | NODE_STATUS.ENTERING_RESCUE_MODE]).total_seconds() |
1183 | +======= |
1184 | + return timedelta( |
1185 | + minutes=NODE_FAILURE_MONITORED_STATUS_TIMEOUTS[ |
1186 | + NODE_STATUS.COMMISSIONING]).total_seconds() |
1187 | +>>>>>>> MERGE-SOURCE |
1188 | |
1189 | def get_releasing_time(self): |
1190 | """Return the releasing time of this node (in seconds). |
1191 | @@ -2865,6 +2909,7 @@ |
1192 | if not gateway_ipv6: |
1193 | gateway_ipv6 = self._get_gateway_tuple_by_family( |
1194 | found_gateways, IPADDRESS_FAMILY.IPv6) |
1195 | +<<<<<<< TREE |
1196 | return DefaultGateways._make((gateway_ipv4, gateway_ipv6)) |
1197 | |
1198 | def get_default_dns_servers(self, ipv4=True, ipv6=True): |
1199 | @@ -2907,6 +2952,49 @@ |
1200 | ipv4=(ipv4 and gateways.ipv4 is not None), |
1201 | ipv6=(ipv6 and gateways.ipv6 is not None)) |
1202 | return [maas_dns_server] |
1203 | +======= |
1204 | + return DefaultGateways._make((gateway_ipv4, gateway_ipv6)) |
1205 | + |
1206 | + def get_default_dns_servers(self): |
1207 | + """Return the default DNS servers for this node.""" |
1208 | + # Circular imports. |
1209 | + from maasserver.dns.zonegenerator import get_dns_server_address |
1210 | + |
1211 | + gateways = self.get_default_gateways() |
1212 | + |
1213 | + # Try first to use DNS servers from default gateway subnets. |
1214 | + if gateways.ipv4 is not None: |
1215 | + subnet = Subnet.objects.get(id=gateways.ipv4.subnet_id) |
1216 | + if subnet.dns_servers is not None and len(subnet.dns_servers) > 0: |
1217 | + # An IPv4 subnet is hosting the default gateway and has DNS |
1218 | + # servers defined. IPv4 DNS servers take first-priority. |
1219 | + return subnet.dns_servers |
1220 | + if gateways.ipv6 is not None: |
1221 | + subnet = Subnet.objects.get(id=gateways.ipv6.subnet_id) |
1222 | + if subnet.dns_servers is not None and len(subnet.dns_servers) > 0: |
1223 | + # An IPv6 subnet is hosting the default gateway and has DNS |
1224 | + # servers defined. IPv6 DNS servers take second-priority. |
1225 | + return subnet.dns_servers |
1226 | + |
1227 | + # No default gateway subnet has specific DNS servers defined, so |
1228 | + # use MAAS for the default DNS server. |
1229 | + if gateways.ipv4 is None and gateways.ipv6 is None: |
1230 | + # If there are no default gateways, the default is the MAAS |
1231 | + # region IP address. |
1232 | + maas_dns_server = get_dns_server_address( |
1233 | + rack_controller=self.get_boot_rack_controller()) |
1234 | + else: |
1235 | + # Choose an address consistent with the primary address-family |
1236 | + # in use, as indicated by the presence (or not) of a gateway. |
1237 | + # Note that this path is only taken if the MAAS URL is set to |
1238 | + # a hostname, and the hostname resolves to both an IPv4 and an |
1239 | + # IPv6 address. |
1240 | + maas_dns_server = get_dns_server_address( |
1241 | + rack_controller=self.get_boot_rack_controller(), |
1242 | + ipv4=(gateways.ipv4 is not None), |
1243 | + ipv6=(gateways.ipv6 is not None)) |
1244 | + return [maas_dns_server] |
1245 | +>>>>>>> MERGE-SOURCE |
1246 | |
1247 | def get_boot_purpose(self): |
1248 | """ |
1249 | |
1250 | === modified file 'src/maasserver/models/staticipaddress.py' |
1251 | --- src/maasserver/models/staticipaddress.py 2016-10-17 23:38:43 +0000 |
1252 | +++ src/maasserver/models/staticipaddress.py 2016-10-26 23:39:26 +0000 |
1253 | @@ -298,6 +298,11 @@ |
1254 | # DISTINCT ON returns the first matching row for any given |
1255 | # hostname, using the query's ordering. Here, we're trying to |
1256 | # return the IPs for the oldest Interface address. |
1257 | +<<<<<<< TREE |
1258 | +======= |
1259 | + # |
1260 | + # For nodes that have disable_ipv4 set, leave out any IPv4 address. |
1261 | +>>>>>>> MERGE-SOURCE |
1262 | default_ttl = "%d" % Config.objects.get_config('default_dns_ttl') |
1263 | if raw_ttl: |
1264 | ttl_clause = """node.address_ttl""" |
1265 | @@ -364,7 +369,15 @@ |
1266 | query_parms = [] |
1267 | sql_query += """ |
1268 | staticip.ip IS NOT NULL AND |
1269 | +<<<<<<< TREE |
1270 | host(staticip.ip) != '' |
1271 | +======= |
1272 | + host(staticip.ip) != '' AND |
1273 | + ( |
1274 | + node.disable_ipv4 IS FALSE OR |
1275 | + family(staticip.ip) <> 4 |
1276 | + ) |
1277 | +>>>>>>> MERGE-SOURCE |
1278 | ORDER BY |
1279 | node.hostname, |
1280 | is_boot DESC, |
1281 | @@ -400,6 +413,7 @@ |
1282 | END, |
1283 | interface.id |
1284 | """ |
1285 | +<<<<<<< TREE |
1286 | iface_sql_query = """ |
1287 | SELECT |
1288 | CONCAT(node.hostname, '.', domain.name) AS fqdn, |
1289 | @@ -448,6 +462,58 @@ |
1290 | assigned DESC, /* Return all assigned IPs for a node first. */ |
1291 | interface.id |
1292 | """ |
1293 | +======= |
1294 | + iface_sql_query = """ |
1295 | + SELECT |
1296 | + CONCAT(node.hostname, '.', domain.name) AS fqdn, |
1297 | + node.system_id, |
1298 | + node.node_type, |
1299 | + """ + ttl_clause + """ AS ttl, |
1300 | + staticip.ip, |
1301 | + interface.name |
1302 | + FROM |
1303 | + maasserver_interface AS interface |
1304 | + JOIN maasserver_node AS node ON |
1305 | + node.id = interface.node_id |
1306 | + JOIN maasserver_domain as domain ON |
1307 | + domain.id = node.domain_id |
1308 | + JOIN maasserver_interface_ip_addresses AS link ON |
1309 | + link.interface_id = interface.id |
1310 | + JOIN maasserver_staticipaddress AS staticip ON |
1311 | + staticip.id = link.staticipaddress_id |
1312 | + """ |
1313 | + if isinstance(domain_or_subnet, Domain): |
1314 | + # This logic is similar to the logic in sql_query above. |
1315 | + iface_sql_query += """ |
1316 | + LEFT JOIN maasserver_domain as domain2 ON |
1317 | + /* Pick up another copy of domain looking for instances of |
1318 | + * the name as the top of a domain. |
1319 | + */ |
1320 | + domain2.name = CONCAT( |
1321 | + interface.name, '.', node.hostname, '.', domain.name) |
1322 | + WHERE |
1323 | + (domain2.name IS NOT NULL OR node.domain_id = %s) AND |
1324 | + """ |
1325 | + else: |
1326 | + # For subnets, we need ALL the names, so that we can correctly |
1327 | + # identify which ones should have the FQDN. dns/zonegenerator.py |
1328 | + # optimizes based on this, and only calls once with a subnet, |
1329 | + # expecting to get all the subnets back in one table. |
1330 | + iface_sql_query += """ |
1331 | + WHERE |
1332 | + """ |
1333 | + iface_sql_query += """ |
1334 | + staticip.ip IS NOT NULL AND |
1335 | + host(staticip.ip) != '' AND |
1336 | + ( |
1337 | + node.disable_ipv4 IS FALSE OR |
1338 | + family(staticip.ip) <> 4 |
1339 | + ) |
1340 | + ORDER BY |
1341 | + node.hostname, |
1342 | + interface.id |
1343 | + """ |
1344 | +>>>>>>> MERGE-SOURCE |
1345 | # We get user reserved et al mappings first, so that we can overwrite |
1346 | # TTL as we process the return from the SQL horror above. |
1347 | mapping = self._get_user_reserved_mappings(domain_or_subnet) |
1348 | @@ -456,14 +522,19 @@ |
1349 | iface_is_boot = defaultdict(bool, { |
1350 | hostname: True for hostname in mapping.keys() |
1351 | }) |
1352 | +<<<<<<< TREE |
1353 | assigned_ips = defaultdict(bool) |
1354 | cursor.execute(sql_query, query_parms) |
1355 | +======= |
1356 | + cursor.execute(sql_query, query_parms) |
1357 | +>>>>>>> MERGE-SOURCE |
1358 | # The records from the query provide, for each hostname (after |
1359 | # stripping domain), the boot and non-boot interface ip address in ipv4 |
1360 | # and ipv6. Our task: if there are boot interace IPs, they win. If |
1361 | # there are none, then whatever we got wins. The ORDER BY means that |
1362 | # we will see all of the boot interfaces before we see any non-boot |
1363 | # interface IPs. See Bug#1584850 |
1364 | +<<<<<<< TREE |
1365 | for (fqdn, system_id, node_type, ttl, |
1366 | ip, is_boot) in cursor.fetchall(): |
1367 | mapping[fqdn].node_type = node_type |
1368 | @@ -491,6 +562,29 @@ |
1369 | mapping[name].system_id = system_id |
1370 | mapping[name].ttl = ttl |
1371 | mapping[name].ips.add(ip) |
1372 | +======= |
1373 | + for (fqdn, system_id, node_type, ttl, |
1374 | + ip, is_boot) in cursor.fetchall(): |
1375 | + mapping[fqdn].node_type = node_type |
1376 | + mapping[fqdn].system_id = system_id |
1377 | + mapping[fqdn].ttl = ttl |
1378 | + if is_boot: |
1379 | + iface_is_boot[fqdn] = True |
1380 | + # If we have an IP on the right interface type, save it. |
1381 | + if is_boot == iface_is_boot[fqdn]: |
1382 | + mapping[fqdn].ips.add(ip) |
1383 | + # Next, get all the addresses, on all the interfaces, and add the ones |
1384 | + # that are not already present on the FQDN as $IFACE.$FQDN. |
1385 | + cursor.execute(iface_sql_query, (domain_or_subnet.id,)) |
1386 | + for (fqdn, system_id, node_type, ttl, |
1387 | + ip, iface_name) in cursor.fetchall(): |
1388 | + if ip not in mapping[fqdn].ips: |
1389 | + name = "%s.%s" % (iface_name, fqdn) |
1390 | + mapping[name].node_type = node_type |
1391 | + mapping[name].system_id = system_id |
1392 | + mapping[name].ttl = ttl |
1393 | + mapping[name].ips.add(ip) |
1394 | +>>>>>>> MERGE-SOURCE |
1395 | return mapping |
1396 | |
1397 | def filter_by_ip_family(self, family): |
1398 | |
1399 | === modified file 'src/maasserver/models/subnet.py' |
1400 | === modified file 'src/maasserver/models/tests/test_largefile.py' |
1401 | === modified file 'src/maasserver/models/tests/test_node.py' |
1402 | --- src/maasserver/models/tests/test_node.py 2016-10-26 18:28:12 +0000 |
1403 | +++ src/maasserver/models/tests/test_node.py 2016-10-26 23:39:26 +0000 |
1404 | @@ -364,6 +364,27 @@ |
1405 | racks_and_regions.add(factory.make_Node(node_type=node_type)) |
1406 | self.assertItemsEqual(racks_and_regions, Controller.objects.all()) |
1407 | |
1408 | +<<<<<<< TREE |
1409 | +======= |
1410 | + def test_get_running_controller(self): |
1411 | + rack = factory.make_RackController() |
1412 | + self.useFixture(MAASIDFixture(rack.system_id)) |
1413 | + self.assertEquals(rack, Controller.objects.get_running_controller()) |
1414 | + |
1415 | + def test_get_running_controller_can_ignore_cache(self): |
1416 | + # Store invalid value in cache |
1417 | + self.useFixture(MAASIDFixture(factory.make_string())) |
1418 | + rack = factory.make_RackController() |
1419 | + # Write valid value to disk |
1420 | + maas_id_path = get_path('/var/lib/maas/maas_id') |
1421 | + os.unlink(maas_id_path) |
1422 | + with open(maas_id_path, 'w') as fd: |
1423 | + fd.write(rack.system_id) |
1424 | + self.assertEquals( |
1425 | + rack, Controller.objects.get_running_controller(read_cache=False)) |
1426 | + self.assertEquals(rack.system_id, get_maas_id()) |
1427 | + |
1428 | +>>>>>>> MERGE-SOURCE |
1429 | |
1430 | class TestRackControllerManager(MAASServerTestCase): |
1431 | |
1432 | @@ -4726,6 +4747,7 @@ |
1433 | ), node.get_default_gateways()) |
1434 | |
1435 | |
1436 | +<<<<<<< TREE |
1437 | class TestGetDefaultDNSServers(MAASServerTestCase): |
1438 | """Tests for `Node.get_default_dns_servers`.""" |
1439 | |
1440 | @@ -4901,6 +4923,161 @@ |
1441 | |
1442 | |
1443 | class TestNode_Start(MAASTransactionServerTestCase): |
1444 | +======= |
1445 | +class TestGetDefaultDNSServers(MAASServerTestCase): |
1446 | + """Tests for `Node.get_default_dns_servers`.""" |
1447 | + |
1448 | + def make_Node_with_RackController( |
1449 | + self, ipv4=True, ipv6=True, ipv4_gateway=True, ipv6_gateway=True, |
1450 | + ipv4_subnet_dns=None, ipv6_subnet_dns=None): |
1451 | + ipv4_subnet_dns = [] if ipv4_subnet_dns is None else ipv4_subnet_dns |
1452 | + ipv6_subnet_dns = [] if ipv6_subnet_dns is None else ipv6_subnet_dns |
1453 | + rack_v4 = None |
1454 | + rack_v6 = None |
1455 | + fabric = factory.make_Fabric() |
1456 | + vlan = fabric.get_default_vlan() |
1457 | + if ipv4: |
1458 | + gateway_ip = None if ipv4_gateway else "" |
1459 | + v4_subnet = factory.make_Subnet( |
1460 | + version=4, vlan=vlan, dns_servers=ipv4_subnet_dns, |
1461 | + gateway_ip=gateway_ip) |
1462 | + if ipv6: |
1463 | + gateway_ip = None if ipv6_gateway else "" |
1464 | + v6_subnet = factory.make_Subnet( |
1465 | + version=6, vlan=vlan, dns_servers=ipv6_subnet_dns, |
1466 | + gateway_ip=gateway_ip) |
1467 | + rack = factory.make_RegionRackController() |
1468 | + vlan.primary_rack = rack |
1469 | + vlan.dhcp_on = True |
1470 | + vlan.save() |
1471 | + # In order to determine the correct IP address per-address-family, |
1472 | + # a name lookup is performed on the hostname part of the URL. |
1473 | + # We need to mock that so we can return whatever IP addresses it |
1474 | + # resolves to. |
1475 | + rack.url = "http://region:5240/MAAS/" |
1476 | + if ipv4: |
1477 | + rack_v4 = factory.pick_ip_in_Subnet(v4_subnet) |
1478 | + if ipv6: |
1479 | + rack_v6 = factory.pick_ip_in_Subnet(v6_subnet) |
1480 | + |
1481 | + def get_address(hostname, ip_version=4): |
1482 | + """Mock function to return the IP address of the rack based on the |
1483 | + given address family. |
1484 | + """ |
1485 | + if ip_version == 4: |
1486 | + return {IPAddress(rack_v4)} if rack_v4 else set() |
1487 | + elif ip_version == 6: |
1488 | + return {IPAddress(rack_v6)} if rack_v6 else set() |
1489 | + |
1490 | + resolve_hostname = self.patch( |
1491 | + server_address_module, 'resolve_hostname') |
1492 | + resolve_hostname.side_effect = get_address |
1493 | + rack.interface_set.all().delete() |
1494 | + rackif = factory.make_Interface(vlan=vlan, node=rack) |
1495 | + if ipv4: |
1496 | + rackif.link_subnet(INTERFACE_LINK_TYPE.STATIC, v4_subnet, rack_v4) |
1497 | + if ipv6: |
1498 | + rackif.link_subnet(INTERFACE_LINK_TYPE.STATIC, v6_subnet, rack_v6) |
1499 | + rack.boot_interface = rackif |
1500 | + rack.save() |
1501 | + node = factory.make_Node(status=NODE_STATUS.READY, disable_ipv4=False) |
1502 | + nodeif = factory.make_Interface(vlan=vlan, node=node) |
1503 | + if ipv4: |
1504 | + nodeif.link_subnet(INTERFACE_LINK_TYPE.AUTO, v4_subnet) |
1505 | + if ipv6: |
1506 | + nodeif.link_subnet(INTERFACE_LINK_TYPE.AUTO, v6_subnet) |
1507 | + node.boot_interface = nodeif |
1508 | + node.save() |
1509 | + return rack_v4, rack_v6, node |
1510 | + |
1511 | + def test__uses_rack_ipv4_if_ipv4_only_with_no_gateway(self): |
1512 | + rack_v4, rack_v6, node = self.make_Node_with_RackController( |
1513 | + ipv4=True, ipv4_gateway=False, ipv6=False, ipv6_gateway=False) |
1514 | + self.assertThat(node.get_default_dns_servers(), Equals([rack_v4])) |
1515 | + |
1516 | + def test__uses_rack_ipv4_if_ipv4_only_with_no_gateway_v4_dns(self): |
1517 | + ipv4_subnet_dns = factory.make_ip_address(ipv6=False) |
1518 | + rack_v4, rack_v6, node = self.make_Node_with_RackController( |
1519 | + ipv4=True, ipv4_gateway=False, ipv6=False, ipv6_gateway=False, |
1520 | + ipv4_subnet_dns=[ipv4_subnet_dns]) |
1521 | + self.assertThat( |
1522 | + node.get_default_dns_servers(), Equals([rack_v4])) |
1523 | + |
1524 | + def test__uses_rack_ipv6_if_ipv6_only_with_no_gateway_v6_dns(self): |
1525 | + ipv6_subnet_dns = factory.make_ip_address(ipv6=True) |
1526 | + rack_v4, rack_v6, node = self.make_Node_with_RackController( |
1527 | + ipv4=False, ipv4_gateway=False, ipv6=True, ipv6_gateway=False, |
1528 | + ipv6_subnet_dns=[ipv6_subnet_dns]) |
1529 | + self.assertThat(node.get_default_dns_servers(), Equals([rack_v6])) |
1530 | + |
1531 | + def test__uses_rack_ipv4_if_dual_stack_with_no_gateway(self): |
1532 | + rack_v4, rack_v6, node = self.make_Node_with_RackController( |
1533 | + ipv4=True, ipv4_gateway=False, ipv6=True, ipv6_gateway=False) |
1534 | + self.assertThat(node.get_default_dns_servers(), Equals([rack_v4])) |
1535 | + |
1536 | + def test__uses_rack_ipv4_if_dual_stack_with_ipv4_gateway(self): |
1537 | + rack_v4, rack_v6, node = self.make_Node_with_RackController( |
1538 | + ipv4=True, ipv4_gateway=True, ipv6=True, ipv6_gateway=False) |
1539 | + self.assertThat(node.get_default_dns_servers(), Equals([rack_v4])) |
1540 | + |
1541 | + def test__uses_subnet_ipv4_if_dual_stack_with_ipv4_gateway_with_dns(self): |
1542 | + ipv4_subnet_dns = factory.make_ip_address(ipv6=False) |
1543 | + ipv6_subnet_dns = factory.make_ip_address(ipv6=True) |
1544 | + rack_v4, rack_v6, node = self.make_Node_with_RackController( |
1545 | + ipv4=True, ipv4_gateway=True, ipv6=True, ipv6_gateway=False, |
1546 | + ipv4_subnet_dns=[ipv4_subnet_dns], |
1547 | + ipv6_subnet_dns=[ipv6_subnet_dns]) |
1548 | + self.assertThat( |
1549 | + node.get_default_dns_servers(), Equals([ipv4_subnet_dns])) |
1550 | + |
1551 | + def test__uses_rack_ipv6_if_dual_stack_with_ipv6_gateway(self): |
1552 | + rack_v4, rack_v6, node = self.make_Node_with_RackController( |
1553 | + ipv4=True, ipv4_gateway=False, ipv6=True, ipv6_gateway=True) |
1554 | + self.assertThat(node.get_default_dns_servers(), Equals([rack_v6])) |
1555 | + |
1556 | + def test__uses_subnet_ipv6_if_dual_stack_with_ipv6_gateway(self): |
1557 | + ipv4_subnet_dns = factory.make_ip_address(ipv6=False) |
1558 | + ipv6_subnet_dns = factory.make_ip_address(ipv6=True) |
1559 | + rack_v4, rack_v6, node = self.make_Node_with_RackController( |
1560 | + ipv4=True, ipv4_gateway=False, ipv6=True, ipv6_gateway=True, |
1561 | + ipv4_subnet_dns=[ipv4_subnet_dns], |
1562 | + ipv6_subnet_dns=[ipv6_subnet_dns]) |
1563 | + self.assertThat( |
1564 | + node.get_default_dns_servers(), Equals([ipv6_subnet_dns])) |
1565 | + |
1566 | + def test__uses_rack_ipv4_if_ipv4_with_ipv4_gateway(self): |
1567 | + rack_v4, rack_v6, node = self.make_Node_with_RackController( |
1568 | + ipv4=True, ipv4_gateway=True, ipv6=False, ipv6_gateway=False) |
1569 | + self.assertThat(node.get_default_dns_servers(), Equals([rack_v4])) |
1570 | + |
1571 | + def test__uses_subnet_ipv4_if_ipv4_stack_with_ipv4_gateway_and_dns(self): |
1572 | + ipv4_subnet_dns = factory.make_ip_address(ipv6=False) |
1573 | + ipv6_subnet_dns = factory.make_ip_address(ipv6=True) |
1574 | + rack_v4, rack_v6, node = self.make_Node_with_RackController( |
1575 | + ipv4=True, ipv4_gateway=True, ipv6=False, ipv6_gateway=False, |
1576 | + ipv4_subnet_dns=[ipv4_subnet_dns], |
1577 | + ipv6_subnet_dns=[ipv6_subnet_dns]) |
1578 | + self.assertThat( |
1579 | + node.get_default_dns_servers(), Equals([ipv4_subnet_dns])) |
1580 | + |
1581 | + def test__uses_rack_ipv6_if_ipv6_with_ipv6_gateway(self): |
1582 | + rack_v4, rack_v6, node = self.make_Node_with_RackController( |
1583 | + ipv4=False, ipv4_gateway=False, ipv6=True, ipv6_gateway=True) |
1584 | + self.assertThat(node.get_default_dns_servers(), Equals([rack_v6])) |
1585 | + |
1586 | + def test__uses_subnet_ipv6_if_ipv6_with_ipv6_gateway_and_dns(self): |
1587 | + ipv4_subnet_dns = factory.make_ip_address(ipv6=False) |
1588 | + ipv6_subnet_dns = factory.make_ip_address(ipv6=True) |
1589 | + rack_v4, rack_v6, node = self.make_Node_with_RackController( |
1590 | + ipv4=False, ipv4_gateway=False, ipv6=True, ipv6_gateway=True, |
1591 | + ipv4_subnet_dns=[ipv4_subnet_dns], |
1592 | + ipv6_subnet_dns=[ipv6_subnet_dns]) |
1593 | + self.assertThat( |
1594 | + node.get_default_dns_servers(), Equals([ipv6_subnet_dns])) |
1595 | + |
1596 | + |
1597 | +class TestNode_Start(MAASServerTestCase): |
1598 | +>>>>>>> MERGE-SOURCE |
1599 | """Tests for Node.start().""" |
1600 | |
1601 | def setUp(self): |
1602 | |
1603 | === modified file 'src/maasserver/models/tests/test_staticipaddress.py' |
1604 | --- src/maasserver/models/tests/test_staticipaddress.py 2016-10-18 08:00:37 +0000 |
1605 | +++ src/maasserver/models/tests/test_staticipaddress.py 2016-10-26 23:39:26 +0000 |
1606 | @@ -363,6 +363,7 @@ |
1607 | mapping = StaticIPAddress.objects.get_hostname_ip_mapping(domain) |
1608 | self.assertEqual(expected_mapping, mapping) |
1609 | |
1610 | +<<<<<<< TREE |
1611 | def test_get_hostname_ip_mapping_returns_all_mappings_for_subnet(self): |
1612 | domain = Domain.objects.get_default_domain() |
1613 | expected_mapping = {} |
1614 | @@ -384,14 +385,42 @@ |
1615 | self.assertEqual(expected_mapping, mapping) |
1616 | |
1617 | def test_get_hostname_ip_mapping_returns_fqdn_and_other(self): |
1618 | +======= |
1619 | + def test_get_hostname_ip_mapping_returns_all_mappings_for_subnet(self): |
1620 | + domain = Domain.objects.get_default_domain() |
1621 | + expected_mapping = {} |
1622 | + for _ in range(3): |
1623 | + node = factory.make_Node(interface=True, disable_ipv4=False) |
1624 | + boot_interface = node.get_boot_interface() |
1625 | + subnet = factory.make_Subnet() |
1626 | + staticip = factory.make_StaticIPAddress( |
1627 | + alloc_type=IPADDRESS_TYPE.STICKY, |
1628 | + ip=factory.pick_ip_in_Subnet(subnet), |
1629 | + subnet=subnet, interface=boot_interface) |
1630 | + full_hostname = "%s.%s" % (node.hostname, domain.name) |
1631 | + expected_mapping[full_hostname] = HostnameIPMapping( |
1632 | + node.system_id, 30, {staticip.ip}, node.node_type) |
1633 | + # See also LP#1600259. It doesn't matter what subnet is passed in, you |
1634 | + # get all of them. |
1635 | + mapping = StaticIPAddress.objects.get_hostname_ip_mapping( |
1636 | + Subnet.objects.first()) |
1637 | + self.assertEqual(expected_mapping, mapping) |
1638 | + |
1639 | + def test_get_hostname_ip_mapping_returns_fqdn_and_other(self): |
1640 | +>>>>>>> MERGE-SOURCE |
1641 | hostname = factory.make_name('hostname') |
1642 | domainname = factory.make_name('domain') |
1643 | factory.make_Domain(name=domainname) |
1644 | full_hostname = "%s.%s" % (hostname, domainname) |
1645 | subnet = factory.make_Subnet() |
1646 | node = factory.make_Node_with_Interface_on_Subnet( |
1647 | +<<<<<<< TREE |
1648 | interface=True, hostname=full_hostname, interface_count=3, |
1649 | subnet=subnet) |
1650 | +======= |
1651 | + interface=True, hostname=full_hostname, interface_count=3, |
1652 | + subnet=subnet, disable_ipv4=False) |
1653 | +>>>>>>> MERGE-SOURCE |
1654 | boot_interface = node.get_boot_interface() |
1655 | staticip = factory.make_StaticIPAddress( |
1656 | alloc_type=IPADDRESS_TYPE.STICKY, |
1657 | @@ -516,9 +545,14 @@ |
1658 | alloc_type=IPADDRESS_TYPE.STICKY, |
1659 | subnet=subnet, interface=boot_interface) |
1660 | newer_nic = factory.make_Interface(INTERFACE_TYPE.PHYSICAL, node=node) |
1661 | +<<<<<<< TREE |
1662 | newer_ip = factory.make_StaticIPAddress( |
1663 | alloc_type=IPADDRESS_TYPE.STICKY, subnet=subnet, |
1664 | interface=newer_nic) |
1665 | +======= |
1666 | + newer_ip = factory.make_StaticIPAddress( |
1667 | + alloc_type=IPADDRESS_TYPE.STICKY, interface=newer_nic) |
1668 | +>>>>>>> MERGE-SOURCE |
1669 | mapping = StaticIPAddress.objects.get_hostname_ip_mapping( |
1670 | node.domain) |
1671 | expected_mapping = { |
1672 | @@ -535,11 +569,20 @@ |
1673 | interface=True, hostname=factory.make_name('host')) |
1674 | boot_interface = node.get_boot_interface() |
1675 | staticip = factory.make_StaticIPAddress( |
1676 | +<<<<<<< TREE |
1677 | alloc_type=IPADDRESS_TYPE.STICKY, subnet=subnet, |
1678 | interface=boot_interface) |
1679 | nic = node.get_boot_interface() # equals boot_interface |
1680 | auto_ip = factory.make_StaticIPAddress( |
1681 | alloc_type=IPADDRESS_TYPE.AUTO, subnet=subnet, interface=nic) |
1682 | +======= |
1683 | + alloc_type=IPADDRESS_TYPE.STICKY, |
1684 | + ip=factory.pick_ip_in_Subnet(subnet), |
1685 | + subnet=subnet, interface=boot_interface) |
1686 | + nic = node.get_boot_interface() |
1687 | + auto_ip = factory.make_StaticIPAddress( |
1688 | + alloc_type=IPADDRESS_TYPE.AUTO, interface=nic) |
1689 | +>>>>>>> MERGE-SOURCE |
1690 | mapping = StaticIPAddress.objects.get_hostname_ip_mapping( |
1691 | node.domain) |
1692 | expected_mapping = { |
1693 | @@ -605,15 +648,24 @@ |
1694 | staticip = factory.make_StaticIPAddress( |
1695 | alloc_type=IPADDRESS_TYPE.AUTO, interface=iface, |
1696 | subnet=subnet) |
1697 | - factory.make_StaticIPAddress( |
1698 | + discovered = factory.make_StaticIPAddress( |
1699 | alloc_type=IPADDRESS_TYPE.DISCOVERED, interface=iface, |
1700 | subnet=subnet) |
1701 | mapping = StaticIPAddress.objects.get_hostname_ip_mapping( |
1702 | node.domain) |
1703 | +<<<<<<< TREE |
1704 | expected_mapping = { |
1705 | node.fqdn: HostnameIPMapping( |
1706 | node.system_id, 30, {staticip.ip}, node.node_type)} |
1707 | self.assertEqual(expected_mapping, mapping) |
1708 | +======= |
1709 | + expected_mapping = { |
1710 | + node.fqdn: HostnameIPMapping( |
1711 | + node.system_id, 30, {staticip.ip}, node.node_type), |
1712 | + "%s.%s" % (iface.name, node.fqdn): HostnameIPMapping( |
1713 | + node.system_id, 30, {discovered.ip}, node.node_type)} |
1714 | + self.assertEqual(expected_mapping, mapping) |
1715 | +>>>>>>> MERGE-SOURCE |
1716 | |
1717 | def test_get_hostname_ip_mapping_prefers_bond_with_no_boot_interface(self): |
1718 | subnet = factory.make_Subnet( |
1719 | @@ -831,7 +883,13 @@ |
1720 | mapping = StaticIPAddress.objects.get_hostname_ip_mapping(domain) |
1721 | expected_mapping = { |
1722 | node.fqdn: HostnameIPMapping( |
1723 | +<<<<<<< TREE |
1724 | node.system_id, 30, {sip0.ip}, node.node_type)} |
1725 | +======= |
1726 | + node.system_id, 30, {sip0.ip}, node.node_type), |
1727 | + "%s.%s" % (iface1.name, node.fqdn): HostnameIPMapping( |
1728 | + node.system_id, 30, {sip1.ip}, node.node_type)} |
1729 | +>>>>>>> MERGE-SOURCE |
1730 | self.assertEqual(expected_mapping, mapping) |
1731 | |
1732 | def test_get_hostname_ip_mapping_returns_correct_bond_ip(self): |
1733 | @@ -877,8 +935,15 @@ |
1734 | mapping = StaticIPAddress.objects.get_hostname_ip_mapping(domain) |
1735 | expected_mapping = { |
1736 | node.fqdn: HostnameIPMapping( |
1737 | - node.system_id, 30, {bond_sip.ip}, node.node_type), |
1738 | - } |
1739 | +<<<<<<< TREE |
1740 | + node.system_id, 30, {bond_sip.ip}, node.node_type), |
1741 | + } |
1742 | +======= |
1743 | + node.system_id, 30, {bond_sip.ip}, node.node_type), |
1744 | + "%s.%s" % (iface1.name, node.fqdn): HostnameIPMapping( |
1745 | + node.system_id, 30, {sip1.ip}, node.node_type), |
1746 | + } |
1747 | +>>>>>>> MERGE-SOURCE |
1748 | self.assertEqual(expected_mapping, mapping) |
1749 | |
1750 | |
1751 | |
1752 | === modified file 'src/maasserver/models/tests/test_subnet.py' |
1753 | === modified file 'src/maasserver/node_status.py' |
1754 | --- src/maasserver/node_status.py 2016-10-03 22:13:11 +0000 |
1755 | +++ src/maasserver/node_status.py 2016-10-26 23:39:26 +0000 |
1756 | @@ -218,6 +218,22 @@ |
1757 | NODE_STATUS.EXITING_RESCUE_MODE: 5, |
1758 | } |
1759 | |
1760 | +# State transitions that are monitored for timeouts for when a node |
1761 | +# fails: |
1762 | +# Mapping between in-progress statuses and the corresponding failed |
1763 | +# statuses. |
1764 | +NODE_FAILURE_MONITORED_STATUS_TRANSITIONS = { |
1765 | + NODE_STATUS.COMMISSIONING: NODE_STATUS.FAILED_COMMISSIONING, |
1766 | + NODE_STATUS.DEPLOYING: NODE_STATUS.FAILED_DEPLOYMENT, |
1767 | + NODE_STATUS.RELEASING: NODE_STATUS.FAILED_RELEASING, |
1768 | +} |
1769 | + |
1770 | +NODE_FAILURE_MONITORED_STATUS_TIMEOUTS = { |
1771 | + NODE_STATUS.COMMISSIONING: 20, |
1772 | + NODE_STATUS.DEPLOYING: 40, |
1773 | + NODE_STATUS.RELEASING: 5, |
1774 | +} |
1775 | + |
1776 | # Statuses that correspond to managed steps for which MAAS actively |
1777 | # monitors that the status changes after a fixed period of time. |
1778 | MONITORED_STATUSES = list(NODE_FAILURE_STATUS_TRANSITIONS.keys()) |
1779 | |
1780 | === modified file 'src/maasserver/preseed.py' |
1781 | === modified file 'src/maasserver/preseed_network.py' |
1782 | --- src/maasserver/preseed_network.py 2016-10-24 23:01:00 +0000 |
1783 | +++ src/maasserver/preseed_network.py 2016-10-26 23:39:26 +0000 |
1784 | @@ -6,10 +6,14 @@ |
1785 | __all__ = [ |
1786 | ] |
1787 | |
1788 | +<<<<<<< TREE |
1789 | from collections import defaultdict |
1790 | from operator import attrgetter |
1791 | |
1792 | from maasserver.dns.zonegenerator import get_dns_search_paths |
1793 | +======= |
1794 | +from maasserver.dns.zonegenerator import get_dns_search_paths |
1795 | +>>>>>>> MERGE-SOURCE |
1796 | from maasserver.enum import ( |
1797 | INTERFACE_TYPE, |
1798 | IPADDRESS_FAMILY, |
1799 | @@ -26,7 +30,11 @@ |
1800 | def __init__(self, node): |
1801 | self.node = node |
1802 | self.gateways = node.get_default_gateways() |
1803 | +<<<<<<< TREE |
1804 | self.routes = StaticRoute.objects.all() |
1805 | +======= |
1806 | + self.dns_servers = node.get_default_dns_servers() |
1807 | +>>>>>>> MERGE-SOURCE |
1808 | self.gateway_ipv4_set = False |
1809 | self.gateway_ipv6_set = False |
1810 | # The default value is False: expected keys are 4 and 6. |
1811 | @@ -56,6 +64,7 @@ |
1812 | # Order the network_config where dependencies come first. |
1813 | self._order_config_dependency() |
1814 | |
1815 | +<<<<<<< TREE |
1816 | # If we have no IPv6 addresses present, make sure we claim IPv4, so |
1817 | # that we at least get some address. |
1818 | if not self.addr_family_present[6]: |
1819 | @@ -66,10 +75,21 @@ |
1820 | name |
1821 | for name in sorted(get_dns_search_paths()) |
1822 | if name != self.node.domain.name] |
1823 | +======= |
1824 | + search_list = [self.node.domain.name] + [ |
1825 | + name |
1826 | + for name in sorted(get_dns_search_paths()) |
1827 | + if name != self.node.domain.name] |
1828 | +>>>>>>> MERGE-SOURCE |
1829 | self.network_config.append({ |
1830 | "type": "nameserver", |
1831 | +<<<<<<< TREE |
1832 | "address": default_dns_servers, |
1833 | "search": search_list, |
1834 | +======= |
1835 | + "address": self.dns_servers, |
1836 | + "search": search_list, |
1837 | +>>>>>>> MERGE-SOURCE |
1838 | }) |
1839 | |
1840 | network_config = { |
1841 | |
1842 | === modified file 'src/maasserver/preseed_storage.py' |
1843 | === modified file 'src/maasserver/rpc/nodes.py' |
1844 | === modified file 'src/maasserver/rpc/tests/test_nodes.py' |
1845 | === modified file 'src/maasserver/rpc/tests/test_regionservice.py' |
1846 | --- src/maasserver/rpc/tests/test_regionservice.py 2016-10-12 15:26:17 +0000 |
1847 | +++ src/maasserver/rpc/tests/test_regionservice.py 2016-10-26 23:39:26 +0000 |
1848 | @@ -77,6 +77,7 @@ |
1849 | TwistedLoggerFixture, |
1850 | ) |
1851 | import netaddr |
1852 | +from provisioningserver.path import get_path |
1853 | from provisioningserver.rpc import ( |
1854 | common, |
1855 | exceptions, |
1856 | @@ -87,6 +88,10 @@ |
1857 | from provisioningserver.rpc.testing import call_responder |
1858 | from provisioningserver.rpc.testing.doubles import DummyConnection |
1859 | from provisioningserver.utils import events |
1860 | +from provisioningserver.utils.env import ( |
1861 | + get_maas_id, |
1862 | + set_maas_id, |
1863 | +) |
1864 | from provisioningserver.utils.testing import MAASIDFixture |
1865 | from provisioningserver.utils.twisted import ( |
1866 | callInReactorWithTimeout, |
1867 | @@ -1204,6 +1209,15 @@ |
1868 | if len(exceptions) == 0: |
1869 | return original() |
1870 | else: |
1871 | + # Stick a bad value in maas_id cache to test that maas_id is |
1872 | + # being reread from disk each time. |
1873 | + good_maas_id = get_maas_id() |
1874 | + set_maas_id(factory.make_string()) |
1875 | + # Write the good value to disk |
1876 | + maas_id_path = get_path("/var/lib/maas/maas_id") |
1877 | + os.unlink(maas_id_path) |
1878 | + with open(maas_id_path, "w") as fd: |
1879 | + fd.write(good_maas_id) |
1880 | raise exceptions.pop(0) |
1881 | |
1882 | fake_promote = self.patch(regionservice.RegionAdvertising, "promote") |
1883 | |
1884 | === added file 'src/maasserver/static/css/maas-styles.css.OTHER' |
1885 | --- src/maasserver/static/css/maas-styles.css.OTHER 1970-01-01 00:00:00 +0000 |
1886 | +++ src/maasserver/static/css/maas-styles.css.OTHER 2016-10-26 23:39:26 +0000 |
1887 | @@ -0,0 +1,1 @@ |
1888 | +.fake{display:none}body{font-size:14px}.one-col,.two-col,.three-col,.four-col,.five-col,.six-col,.seven-col,.eight-col,.nine-col,.ten-col,.eleven-col,.twelve-col,.col{-webkit-box-sizing:border-box;-moz-box-sizing:border-box;box-sizing:border-box;clear:none;display:inline-block;float:none;margin-right:2.12766%;margin-bottom:20px;position:relative;width:100%}.twelve-col .one-col,.twelve-col .two-col,.twelve-col .three-col,.twelve-col .four-col,.twelve-col .five-col,.twelve-col .six-col,.twelve-col .seven-col,.twelve-col .eight-col,.twelve-col .nine-col,.twelve-col .ten-col,.twelve-col .eleven-col{width:100%}.last-col,.last{margin-right:0}.clearfix:after,.container:after{clear:both;content:"\0020";display:block;height:0;overflow:hidden;visibility:hidden}.clear{clear:both}.clearfix{display:block}@media only screen and (min-width: 768px){body{font-size:15px}.one-col,.two-col,.three-col,.four-col,.five-col,.six-col,.seven-col,.eight-col,.nine-col,.ten-col,.eleven-col,.twelve-col,.col{float:left}.one-col{width:6.38297%}.two-col{width:14.89361%}.three-col{width:23.40425%}.four-col{width:31.91489%}.five-col{width:40.42553%}.six-col{width:48.93617%}.seven-col{width:57.4468%}.eight-col{width:65.95744%}.nine-col{width:74.46808%}.ten-col{width:82.97872%}.eleven-col{width:91.48936%}.twelve-col{width:100%;margin-right:0}.twelve-col .one-col{width:6.3053%;margin-right:2.21238%}.twelve-col .two-col{width:14.823%;margin-right:2.21238%}.twelve-col .three-col{width:23.3407%;margin-right:2.21238%}.twelve-col .three-col{width:48.8938%;margin-right:2.21238%}.twelve-col .four-col{width:31.8584%;margin-right:2.21238%}.twelve-col .five-col{width:40.3761%;margin-right:2.21238%}.twelve-col .six-col{width:48.8938%;margin-right:2.21238%}.twelve-col .seven-col{width:57.4115%;margin-right:2.21238%}.twelve-col .eight-col{width:65.9292%;margin-right:2.21238%}.twelve-col .nine-col{width:74.4469%;margin-right:2.21238%}.twelve-col .ten-col{width:82.9646%;margin-right:2.21238%}.twelve-col .eleven-col{width:91.4823%;margin-right:2.21238%}.twelve-col .twelve-col{width:100%;margin-right:0}.eleven-col .one-col{width:6.89238%;margin-right:2.41837%}.eleven-col .two-col{width:16.20314%;margin-right:2.41837%}.eleven-col .three-col{width:25.5139%;margin-right:2.41837%}.eleven-col .four-col{width:34.82466%;margin-right:2.41837%}.eleven-col .five-col{width:44.13542%;margin-right:2.41837%}.eleven-col .six-col{width:53.44619%;margin-right:2.41837%}.eleven-col .seven-col{width:62.75695%;margin-right:2.41837%}.eleven-col .eight-col{width:72.06771%;margin-right:2.41837%}.eleven-col .nine-col{width:81.37847%;margin-right:2.41837%}.eleven-col .ten-col{width:90.68923%;margin-right:2.41837%}.eleven-col .eleven-col{width:100%;margin-right:0}.ten-col .one-col{width:7.6%;margin-right:2.66666%}.ten-col .two-col{width:17.86666%;margin-right:2.66666%}.ten-col .three-col{width:28.13333%;margin-right:2.66666%}.ten-col .four-col{width:38.4%;margin-right:2.66666%}.ten-col .five-col{width:48.66666%;margin-right:2.66666%}.ten-col .six-col{width:58.93333%;margin-right:2.66666%}.ten-col .seven-col{width:69.19999%;margin-right:2.66666%}.ten-col .eight-col{width:79.46666%;margin-right:2.66666%}.ten-col .nine-col{width:89.73333%;margin-right:2.66666%}.ten-col .ten-col{width:100%;margin-right:0}.nine-col .one-col{width:8.46953%;margin-right:2.97176%}.nine-col .two-col{width:19.91084%;margin-right:2.97176%}.nine-col .three-col{width:31.35215%;margin-right:2.97176%}.nine-col .four-col{width:42.79346%;margin-right:2.97176%}.nine-col .five-col{width:54.23476%;margin-right:2.97176%}.nine-col .six-col{width:65.67607%;margin-right:2.97176%}.nine-col .seven-col{width:77.11738%;margin-right:2.97176%}.nine-col .eight-col{width:88.55869%;margin-right:2.97176%}.nine-col .nine-col{width:100%;margin-right:0}.eight-col .one-col{width:9.56375%;margin-right:3.3557%}.eight-col .two-col{width:22.48322%;margin-right:3.3557%}.eight-col .three-col{width:35.40268%;margin-right:3.3557%}.eight-col .four-col{width:48.32214%;margin-right:3.3557%}.eight-col .five-col{width:61.24161%;margin-right:3.3557%}.eight-col .six-col{width:74.16107%;margin-right:3.3557%}.eight-col .seven-col{width:87.08053%;margin-right:3.3557%}.eight-col .eight-col{width:100%;margin-right:0}.seven-col .one-col{width:10.98265%;margin-right:3.85356%}.seven-col .two-col{width:25.81888%;margin-right:3.85356%}.seven-col .three-col{width:40.6551%;margin-right:3.85356%}.seven-col .four-col{width:55.49132%;margin-right:3.85356%}.seven-col .five-col{width:70.32755%;margin-right:3.85356%}.seven-col .six-col{width:85.16377%;margin-right:3.85356%}.seven-col .seven-col{width:100%;margin-right:0}.six-col .one-col{width:12.89592%;margin-right:4.52488%}.six-col .two-col{width:30.31674%;margin-right:4.52488%}.six-col .three-col{width:47.73755%;margin-right:4.52488%}.six-col .four-col{width:65.15837%;margin-right:4.52488%}.six-col .five-col{width:82.57918%;margin-right:4.52488%}.six-col .six-col{width:100%;margin-right:0}.five-col .one-col{width:15.61643%;margin-right:5.47945%}.five-col .two-col{width:36.71232%;margin-right:5.47945%}.five-col .three-col{width:57.80821%;margin-right:5.47945%}.five-col .four-col{width:78.9041%;margin-right:5.47945%}.five-col .five-col{width:100%;margin-right:0}.four-col .one-col{width:19.79166%;margin-right:6.94444%}.four-col .two-col{width:46.52777%;margin-right:6.94444%}.four-col .three-col{width:73.26388%;margin-right:6.94444%}.four-col .four-col{width:100%;margin-right:0}.three-col .one-col{width:27.01421%;margin-right:9.47867%}.three-col .two-col{width:63.5071%;margin-right:9.47867%}.three-col .three-col{width:100%;margin-right:0}.two-col .one-col{width:42.53731%;margin-right:14.92537%}.two-col .two-col{width:100%;margin-right:0}.one-col .one-col{width:100%;margin-right:0}.twelve-col .last-col{margin-right:0}.eleven-col .last-col{margin-right:0}.ten-col .last-col{margin-right:0}.nine-col .last-col{margin-right:0}.eight-col .last-col{margin-right:0}.seven-col .last-col{margin-right:0}.six-col .last-col{margin-right:0}.five-col .last-col{margin-right:0}.four-col .last-col{margin-right:0}.three-col .last-col{margin-right:0}.two-col .last-col{margin-right:0}.one-col .last-col{margin-right:0}.row,#context-footer{-webkit-border-radius:0;-moz-border-radius:0;border-radius:0;margin:0;padding:40px 40px 20px}.row:after{content:".";visibility:hidden;display:block;height:0;clear:both}.row-feature{background:none}.container{-webkit-box-sizing:border-box;-moz-box-sizing:border-box;box-sizing:border-box;margin:20px 20px 0;width:100%}.append-one{margin-right:8.51063%}.append-two{margin-right:17.02127%}.append-three{margin-right:25.53191%}.append-four{margin-right:34.04255%}.append-five{margin-right:42.55319%}.append-six{margin-right:51.06383%}.append-seven{margin-right:59.57446%}.append-eight{margin-right:68.0851%}.append-nine{margin-right:76.59574%}.append-ten{margin-right:85.10638%}.append-eleven{margin-right:93.61702%}.prepend-one{margin-left:8.51063%}.prepend-two{margin-left:17.02127%}.prepend-three{margin-left:25.53191%}.prepend-four{margin-left:34.04255%}.prepend-five{margin-left:42.55319%}.prepend-six{margin-left:51.06383%}.prepend-seven{margin-left:59.57446%}.prepend-eight{margin-left:68.0851%}.prepend-nine{margin-left:76.59574%}.prepend-ten{margin-left:85.10638%}.prepend-eleven{margin-left:93.61702%}.push-one{margin-left:57px}.pull-one,.pull-two,.pull-three,.pull-four,.pull-five,.pull-six,.pull-seven,.pull-eight,.pull-nine,.pull-ten,.pull-eleven{float:left;position:relative}.pull-one{margin-left:-6.38297%}.pull-two{margin-left:-17.02127%}.pull-three{margin-left:-25.53191%}.pull-four{margin-left:-34.04255%}.pull-five{margin-left:-34.04255%}.pull-six{margin-left:-51.06383%}.pull-seven{margin-left:-59.57446%}.pull-eight{margin-left:-68.0851%}.pull-nine{margin-left:-76.59574%}.pull-ten{margin-left:-85.10638%}.pull-eleven{margin-left:-93.61702%}.push-1,.push-two,.push-three,.push-four,.push-five,.push-six,.push-seven,.push-eight,.push-nine,.push-ten,.push-eleven{float:left;position:relative}.push-one{margin:0 -8.51063% 0 8.51063%}.push-two{margin:0 -19.14893% 0 19.14893%}.push-three{margin:0 -27.65957% 0 27.65957%}.push-four{margin:0 -36.17021% 0 36.17021%}.push-five{margin:0 -36.17021% 0 36.17021%}.push-six{margin:0 -53.19149% 0 53.19149%}.push-seven{margin:0 -61.70212% 0 61.70212%}.push-eight{margin:0 -70.21276% 0 70.21276%}.push-nine{margin:0 -78.7234% 0 78.7234%}.push-ten{margin:0 -87.23404% 0 87.23404%}.push-eleven{margin:0 -95.74468% 0 95.74468%}}@media only screen and (min-width: 984px){body{font-size:16px}.one-col,.two-col,.three-col,.four-col,.five-col,.six-col,.seven-col,.eight-col,.nine-col,.ten-col,.eleven-col,.twelve-col,.col{float:left}.one-col{width:6.38297%}.two-col{width:14.89361%}.three-col{width:23.40425%}.four-col{width:31.91489%}.five-col{width:40.42553%}.six-col{width:48.93617%}.seven-col{width:57.4468%}.eight-col{width:65.95744%}.nine-col{width:74.46808%}.ten-col{width:82.97872%}.eleven-col{width:91.48936%}.three-col:nth-child(1):nth-last-child(4),.three-col:nth-child(2):nth-last-child(3),.three-col:nth-child(3):nth-last-child(2),.three-col:nth-child(4):nth-last-child(1){width:23.36%}.three-col:nth-of-type(2){margin-right:2.21238%}.twelve-col{width:100%;margin-right:0}.twelve-col .one-col{width:6.3053%;margin-right:2.21238%}.twelve-col .two-col{width:14.823%;margin-right:2.21238%}.twelve-col .three-col{width:23.3407%;margin-right:2.21238%}.twelve-col .three-col:nth-child(1):nth-last-child(4),.twelve-col .three-col:nth-child(2):nth-last-child(3),.twelve-col .three-col:nth-child(3):nth-last-child(2),.twelve-col .three-col:nth-child(4):nth-last-child(1){width:23.3407%}.twelve-col .three-col:nth-of-type(2){margin-right:2.21238%}.twelve-col .four-col{width:31.8584%;margin-right:2.21238%}.twelve-col .five-col{width:40.3761%;margin-right:2.21238%}.twelve-col .six-col{width:48.8938%;margin-right:2.21238%}.twelve-col .seven-col{width:57.4115%;margin-right:2.21238%}.twelve-col .eight-col{width:65.9292%;margin-right:2.21238%}.twelve-col .nine-col{width:74.4469%;margin-right:2.21238%}.twelve-col .ten-col{width:82.9646%;margin-right:2.21238%}.twelve-col .eleven-col{width:91.4823%;margin-right:2.21238%}.twelve-col .twelve-col{width:100%;margin-right:0}.eleven-col .one-col{width:6.89238%;margin-right:2.41837%}.eleven-col .two-col{width:16.20314%;margin-right:2.41837%}.eleven-col .three-col{width:25.5139%;margin-right:2.41837%}.eleven-col .four-col{width:34.82466%;margin-right:2.41837%}.eleven-col .five-col{width:44.13542%;margin-right:2.41837%}.eleven-col .six-col{width:53.44619%;margin-right:2.41837%}.eleven-col .seven-col{width:62.75695%;margin-right:2.41837%}.eleven-col .eight-col{width:72.06771%;margin-right:2.41837%}.eleven-col .nine-col{width:81.37847%;margin-right:2.41837%}.eleven-col .ten-col{width:90.68923%;margin-right:2.41837%}.eleven-col .eleven-col{width:100%;margin-right:0}.ten-col .one-col{width:7.6%;margin-right:2.66666%}.ten-col .two-col{width:17.86666%;margin-right:2.66666%}.ten-col .three-col{width:28.13333%;margin-right:2.66666%}.ten-col .four-col{width:38.4%;margin-right:2.66666%}.ten-col .five-col{width:48.66666%;margin-right:2.66666%}.ten-col .six-col{width:58.93333%;margin-right:2.66666%}.ten-col .seven-col{width:69.19999%;margin-right:2.66666%}.ten-col .eight-col{width:79.46666%;margin-right:2.66666%}.ten-col .nine-col{width:89.73333%;margin-right:2.66666%}.ten-col .ten-col{width:100%;margin-right:0}.nine-col .one-col{width:8.46953%;margin-right:2.97176%}.nine-col .two-col{width:19.91084%;margin-right:2.97176%}.nine-col .three-col{width:31.35215%;margin-right:2.97176%}.nine-col .four-col{width:42.79346%;margin-right:2.97176%}.nine-col .five-col{width:54.23476%;margin-right:2.97176%}.nine-col .six-col{width:65.67607%;margin-right:2.97176%}.nine-col .seven-col{width:77.11738%;margin-right:2.97176%}.nine-col .eight-col{width:88.55869%;margin-right:2.97176%}.nine-col .nine-col{width:100%;margin-right:0}.eight-col .one-col{width:9.56375%;margin-right:3.3557%}.eight-col .two-col{width:22.48322%;margin-right:3.3557%}.eight-col .three-col{width:35.40268%;margin-right:3.3557%}.eight-col .four-col{width:48.32214%;margin-right:3.3557%}.eight-col .five-col{width:61.24161%;margin-right:3.3557%}.eight-col .six-col{width:74.16107%;margin-right:3.3557%}.eight-col .seven-col{width:87.08053%;margin-right:3.3557%}.eight-col .eight-col{width:100%;margin-right:0}.seven-col .one-col{width:10.98265%;margin-right:3.85356%}.seven-col .two-col{width:25.81888%;margin-right:3.85356%}.seven-col .three-col{width:40.6551%;margin-right:3.85356%}.seven-col .four-col{width:55.49132%;margin-right:3.85356%}.seven-col .five-col{width:70.32755%;margin-right:3.85356%}.seven-col .six-col{width:85.16377%;margin-right:3.85356%}.seven-col .seven-col{width:100%;margin-right:0}.six-col .one-col{width:12.89592%;margin-right:4.52488%}.six-col .two-col{width:30.31674%;margin-right:4.52488%}.six-col .three-col{width:47.73755%;margin-right:4.52488%}.six-col .four-col{width:65.15837%;margin-right:4.52488%}.six-col .five-col{width:82.57918%;margin-right:4.52488%}.six-col .six-col{width:100%;margin-right:0}.five-col .one-col{width:15.61643%;margin-right:5.47945%}.five-col .two-col{width:36.71232%;margin-right:5.47945%}.five-col .three-col{width:57.80821%;margin-right:5.47945%}.five-col .four-col{width:78.9041%;margin-right:5.47945%}.five-col .five-col{width:100%;margin-right:0}.four-col .one-col{width:19.79166%;margin-right:6.94444%}.four-col .two-col{width:46.52777%;margin-right:6.94444%}.four-col .three-col{width:73.26388%;margin-right:6.94444%}.four-col .four-col{width:100%;margin-right:0}.three-col .one-col{width:27.01421%;margin-right:9.47867%}.three-col .two-col{width:63.5071%;margin-right:9.47867%}.three-col .three-col{width:100%;margin-right:0}.two-col .one-col{width:42.53731%;margin-right:14.92537%}.two-col .two-col{width:100%;margin-right:0}.one-col .one-col{width:100%;margin-right:0}.twelve-col .last-col{margin-right:0}.eleven-col .last-col{margin-right:0}.ten-col .last-col{margin-right:0}.nine-col .last-col{margin-right:0}.eight-col .last-col{margin-right:0}.seven-col .last-col{margin-right:0}.six-col .last-col{margin-right:0}.five-col .last-col{margin-right:0}.four-col .last-col{margin-right:0}.three-col .last-col{margin-right:0}.two-col .last-col{margin-right:0}.one-col .last-col{margin-right:0}.row,#context-footer{-webkit-border-radius:0;-moz-border-radius:0;border-radius:0;margin:0;padding:40px 40px 20px}.row:after{content:".";visibility:hidden;display:block;height:0;clear:both}.row-feature{background:none}.container{-webkit-box-sizing:border-box;-moz-box-sizing:border-box;box-sizing:border-box;margin:20px 20px 0;width:100%}.append-one{margin-right:8.51063%}.append-two{margin-right:17.02127%}.append-three{margin-right:25.53191%}.append-four{margin-right:34.04255%}.append-five{margin-right:42.55319%}.append-six{margin-right:51.06383%}.append-seven{margin-right:59.57446%}.append-eight{margin-right:68.0851%}.append-nine{margin-right:76.59574%}.append-ten{margin-right:85.10638%}.append-eleven{margin-right:93.61702%}.prepend-one{margin-left:8.51063%}.prepend-two{margin-left:17.02127%}.prepend-three{margin-left:25.53191%}.prepend-four{margin-left:34.04255%}.prepend-five{margin-left:42.55319%}.prepend-six{margin-left:51.06383%}.prepend-seven{margin-left:59.57446%}.prepend-eight{margin-left:68.0851%}.prepend-nine{margin-left:76.59574%}.prepend-ten{margin-left:85.10638%}.prepend-eleven{margin-left:93.61702%}.push-one{margin-left:57px}.pull-one,.pull-two,.pull-three,.pull-four,.pull-five,.pull-six,.pull-seven,.pull-eight,.pull-nine,.pull-ten,.pull-eleven{float:left;position:relative}.pull-one{margin-left:-6.38297%}.pull-two{margin-left:-17.02127%}.pull-three{margin-left:-25.53191%}.pull-four{margin-left:-34.04255%}.pull-five{margin-left:-34.04255%}.pull-six{margin-left:-51.06383%}.pull-seven{margin-left:-59.57446%}.pull-eight{margin-left:-68.0851%}.pull-nine{margin-left:-76.59574%}.pull-ten{margin-left:-85.10638%}.pull-eleven{margin-left:-93.61702%}.push-1,.push-two,.push-three,.push-four,.push-five,.push-six,.push-seven,.push-eight,.push-nine,.push-ten,.push-eleven{float:left;position:relative}.push-one{margin:0 -8.51063% 0 8.51063%}.push-two{margin:0 -19.14893% 0 19.14893%}.push-three{margin:0 -27.65957% 0 27.65957%}.push-four{margin:0 -36.17021% 0 36.17021%}.push-five{margin:0 -36.17021% 0 36.17021%}.push-six{margin:0 -53.19149% 0 53.19149%}.push-seven{margin:0 -61.70212% 0 61.70212%}.push-eight{margin:0 -70.21276% 0 70.21276%}.push-nine{margin:0 -78.7234% 0 78.7234%}.push-ten{margin:0 -87.23404% 0 87.23404%}.push-eleven{margin:0 -95.74468% 0 95.74468%}}html,body,div,span,applet,object,iframe,h1,h2,h3,h4,h5,h6,p,blockquote,pre,a,acronym,address,big,cite,code,del,dfn,em,img,ins,kbd,q,s,samp,small,strike,strong,sub,sup,tt,var,b,u,i,center,dl,ol,ul,li,form,label,legend,table,caption,tbody,tfoot,thead,tr,th,td,article,aside,canvas,details,embed,figure,figcaption,footer,header,menu,nav,output,ruby,section,summary,time,mark,audio,video{border:0;margin:0;padding:0;vertical-align:baseline}article,aside,details,figcaption,figure,footer,header,nav,section{display:block}audio,canvas,video{display:inline-block;*display:inline;*zoom:1}audio:not([controls]){display:none}[hidden]{display:none}@font-face{font-family:'Ubuntu';font-style:normal;font-weight:300;src:url("../fonts/ubuntu-l-webfont.eot");src:url("../fonts/ubuntu-l-webfont.eot?#iefix") format("embedded-opentype"),url("../fonts/ubuntu-l-webfont.woff") format("woff"),url("../fonts/ubuntu-l-webfont.ttf") format("truetype"),url("../fonts/ubuntu-l-webfont.svg#ubuntulight") format("svg")}@font-face{font-family:'Ubuntu';font-style:normal;font-weight:400;src:local("Ubuntu");src:url("../fonts/ubuntu-r-webfont.eot");src:url("../fonts/ubuntu-r-webfont.eot?#iefix") format("embedded-opentype"),url("../fonts/ubuntu-r-webfont.woff") format("woff"),url("../fonts/ubuntu-r-webfont.ttf") format("truetype"),url("../fonts/ubuntu-r-webfont.svg#ubunturegular") format("svg")}@font-face{font-family:'Ubuntu';font-style:normal;font-weight:500;src:url("../fonts/ubuntu-m-webfont.eot");src:local("Ubuntu Medium"),local("Ubuntu-Medium"),url("../fonts/ubuntu-m-webfont.eot?#iefix") format("embedded-opentype"),url("../fonts/ubuntu-m-webfont.woff") format("woff"),url("../fonts/ubuntu-m-webfont.ttf") format("truetype"),url("../fonts/ubuntu-m-webfont.svg#ubuntumedium") format("svg")}@font-face{font-family:'Ubuntu';font-style:normal;font-weight:700;src:url("../fonts/ubuntu-b-webfont.eot");src:local("Ubuntu Bold"),local("Ubuntu-Bold"),url("../fonts/ubuntu-b-webfont.eot?#iefix") format("embedded-opentype"),url("../fonts/ubuntu-b-webfont.woff") format("woff"),url("../fonts/ubuntu-b-webfont.ttf") format("truetype"),url("../fonts/ubuntu-b-webfont.svg#ubuntubold") format("svg")}@font-face{font-family:'Ubuntu';font-style:italic;font-weight:300;src:url("../fonts/ubuntu-li-webfont.eot");src:local("Ubuntu Light Italic"),local("Ubuntu-LightItalic"),url("../fonts/ubuntu-li-webfont.eot?#iefix") format("embedded-opentype"),url("../fonts/ubuntu-li-webfont.woff") format("woff"),url("../fonts/ubuntu-li-webfont.ttf") format("truetype"),url("../fonts/ubuntu-li-webfont.svg#ubuntulight_italic") format("svg")}@font-face{font-family:'Ubuntu';font-style:italic;font-weight:400;src:local("Ubuntu Italic"),local("Ubuntu-Italic"),url("https://themes.googleusercontent.com/static/fonts/ubuntu/v5/GZMdC02DTXXx8AdUvU2etw.woff") format("woff")}@font-face{font-family:'Ubuntu';font-style:italic;font-weight:500;src:url("../fonts/ubuntu-mi-webfont.eot");src:local("Ubuntu Medium Italic"),local("Ubuntu-MediumItalic"),url("../fonts/ubuntu-mi-webfont.eot?#iefix") format("embedded-opentype"),url("../fonts/ubuntu-mi-webfont.woff") format("woff"),url("../fonts/ubuntu-mi-webfont.ttf") format("truetype"),url("../fonts/ubuntu-mi-webfont.svg#ubuntumedium_italic") format("svg")}@font-face{font-family:'Ubuntu';font-style:italic;font-weight:700;src:local("Ubuntu Bold Italic"),local("Ubuntu-BoldItalic"),url("https://themes.googleusercontent.com/static/fonts/ubuntu/v5/pqisLQoeO9YTDCNnlQ9bfz8E0i7KZn-EPnyo3HZu7kw.woff") format("woff")}html{font-size:100%}body{color:#333;font-family:Ubuntu, Arial, "libra sans", sans-serif;font-weight:300}blockquote,q{quotes:none}blockquote{margin:28px 20px}blockquote:before,blockquote:after,q:before,q:after{content:"";content:none}legend{border:0;*margin-left:-7px}figure{margin:0}abbr,acronym{cursor:help}a:focus{outline:thin dotted}a:hover,a:active{outline:0}a:link,a:visited{color:#dd4814;text-decoration:none}a:hover,a:active,a:focus{text-decoration:underline}a.link-arrow:after{content:"\0000a0›"}nav ul li h2 a:after{content:"\0000a0›"}nav ul li a:after,.carousel ul li a:after,ul li p a:after{content:""}ol,ul{margin-left:20px;margin-bottom:20px}ol ol,ul ul,ol ul,ul ol{margin-bottom:0}nav ul,nav ol{list-style:none;list-style-image:none}svg:not(:root){overflow:hidden}img{border:0;height:auto;max-width:100%}img.left{margin-right:20px}img.right{margin-left:20px}.middle img{vertical-align:middle;margin-top:4em}h1,h2,h3,h4,h5,h6{font-weight:300;line-height:1.3}h1{font-size:1.625em;margin-bottom:.5em}h2{font-size:1.438em;margin-bottom:.5em}h3{font-size:1.219em;margin-bottom:.522em}h4{font-size:1.25em;font-weight:400;margin-bottom:.615em}h5{font-size:1em;font-weight:700;margin-bottom:1em}h6{font-size:.723em;font-weight:400;margin-bottom:1em;letter-spacing:.1em;text-transform:uppercase}p,li{font-size:1em;line-height:1.5;margin:0;margin-bottom:.75em;padding:0}h2 span,h1 span{display:block}p+h2,ul+h2,ol+h2,pre+h2{margin-top:0.5625em}header nav a:link{font-weight:normal}p+h3,ul+h3,ol+h3,pre+h3{margin-top:0.78261em}p+h4,ul+h4,ol+h4,pre+h4{margin-top:1.39286em}ol+h2,p+h2,pre+h2,ul+h2{margin-top:.563em}ol+h3,p+h3,pre+h3,ul+h3{margin-top:.783em}ol+h4,p+h4,pre+h4,ul+h4{margin-top:1.219em}li{margin-bottom:.4em}li:last-of-type{margin-bottom:0}ins{background:#fffbeb;text-decoration:none}small,.smaller{font-size:13px}sub,sup{font-size:75%;line-height:0;position:relative;vertical-align:baseline}sup{vertical-align:text-top}sub{vertical-align:text-bottom}dfn{font-style:italic}mark{background:#ff0;color:#000}code,pre{font-family:"Ubuntu Mono", "Consolas", "Monaco", "Lucida Console", "Courier New", Courier, monospace}pre{-webkit-border-radius:4px;-moz-border-radius:4px;border-radius:4px;background:#fdf6f2;padding:.6em 1em;white-space:pre-wrap;word-wrap:break-word}blockquote{margin:0}blockquote>p{font-size:0.92857em;font-weight:100;margin:0 0 .4em 0}blockquote small{font-size:.813em;line-height:1.4}button,input,select,textarea{font-family:Ubuntu,Arial,"libra sans",sans-serif;margin:0;vertical-align:baseline;*vertical-align:middle}select{font-size:1em;font-weight:300}button,input{line-height:normal}button,input[type="button"],input[type="reset"],input[type="submit"]{cursor:pointer;-webkit-appearance:button;*overflow:visible}input[type="checkbox"],input[type="radio"]{box-sizing:border-box;padding:0}input[type="search"]{-webkit-border-radius:2px;-moz-border-radius:2px;border-radius:2px;-moz-box-sizing:content-box;-webkit-appearance:none;-webkit-box-sizing:content-box;box-sizing:content-box;font-family:Ubuntu,Arial,"libra sans",sans-serif;font-weight:300;outline:none;padding:0.6956522em 0.869565em}input[type="search"]::-webkit-search-decoration{-webkit-appearance:none}button::-moz-focus-inner,input::-moz-focus-inner{border:0;padding:0}textarea{overflow:auto;vertical-align:top}form fieldset{-webkit-border-radius:4px;-moz-border-radius:4px;border-radius:4px;background-repeat:no-repeat;background-color:#EFEEEC;background-position:-15px -15px;border:0;margin-bottom:8px;padding:15px 20px}form fieldset h3{border-bottom:1px dotted #dfdcd9;margin-bottom:9px;padding-bottom:10px}form fieldset li:first-child{margin-top:0}form input[type="text"],form input[type="email"],form input[type="tel"],form textarea{-webkit-appearance:none;-webkit-border-radius:2px;-moz-border-radius:2px;border-radius:2px;background:#fff;border:1px solid #D2D2D2;display:block;font-family:Ubuntu,Arial,"libra sans",sans-serif;font-size:1em;font-weight:300;padding:0.6956522em 0.869565em}form input:focus,form textarea:focus{border:1px solid #dd4814}form textarea[readonly='readonly']{color:#999}form input[type="checkbox"],form input[type="radio"]{margin:0;width:auto}form input[type="checkbox"]+label,form input[type="radio"]+label{display:inline;margin-left:5px;vertical-align:middle;width:auto}form input[type="submit"]{font-size:1.14286em;-webkit-border-radius:4px;-moz-border-radius:4px;border-radius:4px;background-color:#dd4814;background-image:-moz-linear-gradient(#f26120, #dd4814);background-image:-webkit-gradient(linear, 0% 0%, 0% 100%, from(#f26120), to(#dd4814));background-image:-webkit-linear-gradient(#f26120, #dd4814);background-image:-o-linear-gradient(#f26120, #dd4814);-moz-box-shadow:none;-webkit-box-shadow:none;box-shadow:none;border:0;color:#fff;display:block;padding:10px 14px;text-shadow:none;width:auto;margin-bottom:0}form input[type="submit"]:hover{background:#dd4814}form label{cursor:pointer;display:block;margin-bottom:4px}form label span{color:#df382c}form ul{margin-left:0}form li{list-style:none outside none;margin-top:14px}form button[type="submit"]{border:0;display:inline-block;font-family:Ubuntu, Arial, "libra sans", sans-serif;text-decoration:none;font-weight:300}form input[type="reset"]{display:none}table{border-collapse:collapse;border-spacing:0;overflow-x:scroll;margin-bottom:20px;margin:0 0 2.85714em 0;width:100%}table th,table td{padding:15px 10px;background:#f0edea;border:1px dotted #888}table td{text-align:center;vertical-align:middle}table thead th{border-collapse:separate;border-spacing:0 10px;background:#fee3d2;color:#333333;font-weight:normal}table tbody th{text-align:left;font-weight:normal;font-weight:300}table th[scope="col"]{text-align:center}table thead th:first-of-type{text-align:left}@media only screen and (max-width: 768px){table{display:block}}@media only screen and (min-width: 984px){form fieldset{padding:15px 20px}img{max-width:none}}.audience-consumer{color:#333}.audience-consumer .row-box,.audience-consumer .main-content{color:#333}.audience-consumer .inner-wrapper{background:#fff}.audience-consumer .quote-right-top{padding:60px 60px 0 40px;background:url("/sites/ubuntu/latest/u/img/patterns/quote-orange-br-287x287.png") no-repeat;height:287px;position:absolute;right:-40px;text-align:left;top:-90px;width:31.91489%}.audience-consumer .quote-right-top p{font-size:1.14286em;margin:0.769em;padding-bottom:0;color:#fff}.audience-consumer .quote-right-top p cite{font-size:0.85714em;color:#fff;padding:0}.audience-consumer .quote-right-top p a,.audience-consumer .quote-right p a{color:#fff}.audience-consumer .quote-right{font-size:1.28571em;color:#fff;padding:50px 100px 0 50px;text-indent:-6px;background:url("/sites/ubuntu/latest/u/img/patterns/quote-orange-bl-287x287.png") no-repeat;min-height:287px;position:absolute;right:-20px;text-align:left;top:-90px;width:21.2006% em}.audience-consumer .quote-right cite{font-style:normal;margin-left:6px}.audience-consumer .quote-right-alt{background:url(/sites/ubuntu/latest/u/img/patterns/quote-white-br-360x360.png) 0 -100px no-repeat;color:#dd4814;padding:50px 50px 0 50px}.audience-consumer .quote-right-right{background:url("/sites/ubuntu/latest/u/img/patterns/quote-orange-br-287x287.png") no-repeat}.audience-enterprise h1{margin:0 0 18px 0}.audience-enterprise td{background:#fff}.audience-enterprise th,.audience-enterprise td{padding:6px 10px;background:#fff}.audience-enterprise th[scope="col"]{background:#E2D4DC;color:#772953}.audience-enterprise tbody th[rowspan]{background:#F7F2F6}.audience-enterprise tfoot th[rowspan]{background:#dfdcd9}.audience-enterprise tfoot td,.audience-enterprise tfoot th{font-weight:normal;background:#dfdcd9}.audience-enterprise .inner-wrapper{background:#2c001e;color:#fff}.audience-enterprise .row-box{background:#fff;color:#333}.row-enterprise{background:#772953;color:#fff;-webkit-border-radius:0;-moz-border-radius:0;border-radius:0}.row-enterprise .box,.row-enterprise div{background:#772953;color:#fff}.row-enterprise a{color:#fff}.enterprise-dot-pattern{background:url("/sites/ubuntu/latest/u/img/patterns/enterprise-dot-pattern.png")}.developer-dot-pattern{background:url("/sites/ubuntu/latest/u/img/patterns/developer-dot-pattern.png")}.wrapper,header.banner .nav-primary,nav div.footer-a div,.inline-lists ul,.legal{-webkit-box-sizing:border-box;-moz-box-sizing:border-box;box-sizing:border-box;width:auto}.inner-wrapper{-webkit-box-sizing:border-box;-moz-box-sizing:border-box;box-sizing:border-box;background:#fff;clear:both;display:block;float:left;width:100%;margin:0;padding-bottom:20px;position:relative;z-index:1}@media only screen and (min-width: 768px){.med-six-col .three-col{width:48%}.med-six-col .three-col:nth-of-type(2n){margin-right:0}}@media only screen and (min-width: 769px){.inner-wrapper{border-radius:4px;padding-bottom:20px}}@media only screen and (min-width: 984px){.wrapper{-webkit-box-sizing:border-box;-moz-box-sizing:border-box;box-sizing:border-box;background:#fff;margin:0 auto;position:relative;text-align:left;width:984px}.inner-wrapper{-moz-box-shadow:0 0 3px #c9c9c9;-webkit-box-shadow:0 0 3px #c9c9c9;box-shadow:0 0 3px #c9c9c9;margin:10px 0 30px}.three-col,.med-six-col .three-col{width:23.30%}.three-col.last-col:nth-of-type(2n){margin-right:0}.med-six-col .three-col:nth-of-type(2n){margin-right:20px}.med-six-col .three-col.last-col{margin-right:0}}.left{float:left}.right{float:right}.caps{text-transform:uppercase}img{border:0 none;height:auto;max-width:100%}img.left{margin-right:0}img.touch-border{margin-bottom:-3px}.accessibility-aid,.off-left{position:absolute;left:-999em}a.external{-moz-background-size:0.7em 0.7em;-webkit-background-size:0.7em 0.7em;-o-background-size:0.7em 0.7em;background-size:0.7em 0.7em;padding-right:.9em;background-image:url("//assets.ubuntu.com/sites/ubuntu/latest/u/img/external-link-orange.svg");background-position:right 1px;background-repeat:no-repeat}.opera-mini a.external,.no-svg a.external{background-image:url("//assets.ubuntu.com/sites/ubuntu/latest/u/img/external-link-orange.png")}.text-center,.align-center{text-align:center}.no-margin{margin:0}.no-margin-bottom{margin-bottom:0}.no-padding-bottom{padding-bottom:0}.pull-left-20{margin-left:-20px}.pull-right-20{margin-right:-20px}.pull-left-40{margin-left:-40px}.pull-right-40{margin-right:-41px}.no-border{border:0}.link-top{font-size:1em;clear:both;margin-bottom:40px;margin-top:-40px}.link-top a{background:#fff;margin-right:10px;margin-top:-17px;padding:5px;float:right}.pull-bottom-right{position:absolute;right:0;bottom:0;left:auto}.box .pull-bottom-right{-webkit-border-radius:0 0 4px 0;-moz-border-radius:0 0 4px 0;border-radius:0 0 4px 0}.pull-bottom-left{margin-left:-20px;margin-bottom:-21px}.pull-top-right{margin-left:-20px;margin-top:-21px}div.box-image-centered span img.priority-0,div.row-image-centered span img.priority-0,div.row.row-image-centered span img.priority-0,img.priority-0{position:absolute;left:-999em}.priority-0,.not-for-small{position:absolute;left:-999em}.video-container{position:relative;padding-bottom:56.25%;padding-top:30px;height:0;overflow:hidden}.video-container iframe{position:absolute;top:0;left:0;width:100%;height:100%}.video-container+h3,.video-container+.video-title{margin-top:20px}@media only screen and (max-width: 768px){.pull-right-40{margin-right:-30px}.pull-bottom-right,.pull-bottom-left{position:static}img.pull-bottom-left{margin-bottom:0;margin-left:0}}@media only screen and (min-width: 768px){div.box-image-centered span img.priority-0,div.row-image-centered span img.priority-0,div.row.row-image-centered span img.priority-0,img.priority-0{position:relative;left:auto}.priority-0,.not-for-small{position:relative;left:auto}.for-mobile,.for-small{position:absolute;left:-999em}.pull-right{float:right;margin-right:-30px}img.pull-left{margin-left:-30px}img.touch-border{float:left;margin-bottom:-30px}}@media only screen and (min-width: 769px){img.left{margin-right:20px}}@media only screen and (min-width: 984px){img.touch-border{float:left;margin-bottom:-40px}img.pull-left{margin-left:-40px}.pull-right{float:right;margin-right:-40px}.for-tablet,.for-medium{display:none}.no-border{border:0}}.caps-centered,.muted-heading{font-size:.875em;margin-bottom:20px;text-align:center;text-transform:uppercase}p.intro{font-size:1.14286em;line-height:1.4}.row div p:last-child,.row div ul:last-child{margin-bottom:0}.four-col p:last-child{margin-bottom:0}.note{color:#888;font-size:.813em}@media only screen and (min-width: 768px){p.intro{font-size:1.13333em}}@media only screen and (min-width: 984px){h1{font-size:2.8125em}h2{font-size:2em;margin-bottom:.375em}h3{font-size:1.438em;margin-bottom:.522em}h4{font-size:1em;margin-bottom:.75em}h5{font-size:1em}p,li,code,pre{font-size:16px;line-height:1.5;margin-bottom:.75em}p.intro{font-size:1.25em}}header.banner{border-top:0;min-width:100%;width:auto;background:#dd4814;display:block;position:relative;z-index:2}header.banner .nav-primary{border:0;margin:0 auto;overflow:hidden}header.banner .nav-primary ul{border-right:1px solid #ed6637;float:left;margin:0;position:relative}header.banner .nav-primary ul li{border-left:1px solid #c64012;float:left;list-style-image:none;margin:0;text-indent:0;vertical-align:bottom}header.banner .nav-primary ul li:last-child{border-right:1px solid #c64012}header.banner .nav-primary ul li a:link,header.banner .nav-primary ul li a:visited{font-size:14px;border-left:1px solid #ec5b29;color:#fff;display:block;margin-bottom:0;padding:14px 14px 13px;position:relative;text-align:center;text-decoration:none;-webkit-font-smoothing:subpixel-antialiased;-moz-font-smoothing:subpixel-antialiased;-o-font-smoothing:subpixel-antialiased;font-smoothing:subpixel-antialiased}header.banner .nav-primary ul a.active{background:#B83A10;border-left:1px solid #ec5b29}header.banner .nav-primary ul li a:hover{background:#e1662f;border-top:0;-moz-box-shadow:inset 0 2px 2px -2px #777;-webkit-box-shadow:inset 0 2px 2px -2px #777;box-shadow:inset 0 2px 2px -2px #777}#main-navigation-link{display:none}header.banner .nav-toggle{position:absolute;right:0;display:block;width:48px;height:48px;text-indent:-99999px;background-image:url(//assets.ubuntu.com/sites/ubuntu/latest/u/img/icons/navigation-menu-plain.svg);-moz-background-size:25px auto;-webkit-background-size:25px auto;-o-background-size:25px auto;background-size:25px auto;background-repeat:no-repeat;background-position:center center;cursor:pointer}header.banner .no-script{display:none}.opera-mini header.banner .nav-toggle,.no-svg header.banner .nav-toggle{background-image:url(//assets.ubuntu.com/sites/ubuntu/latest/u/img/icons/navigation-menu-plain.png)}header.banner nav ul{background-color:#f0f0f0;display:none;float:left}header.banner .nav-primary.active{-moz-box-shadow:0 1px 2px 1px rgba(120,120,120,0.2);-webkit-box-shadow:0 1px 2px 1px rgba(120,120,120,0.2);box-shadow:0 1px 2px 1px rgba(120,120,120,0.2);padding:0;border-bottom:1px solid #d4d7d4}header nav ul.active{display:block}header.banner .nav-primary ul li,header.banner .nav-primary ul li a:link,header.banner .nav-primary ul li a:visited,header.banner .nav-primary ul li a:active{display:block;padding:0;margin:0;border:none}header.banner .nav-primary ul li a:hover{-moz-box-shadow:none;-webkit-box-shadow:none;box-shadow:none;background-color:#d0d0d0}header.banner .nav-primary ul li a.active{background-color:#ddd}header.banner .nav-primary ul li{border-bottom:1px solid #F2F2F4;font-size:16px}header.banner .nav-primary ul li:last-child{border:0}header.banner nav.nav-primary ul li a:link,header.banner .nav-primary ul li a:visited,header.banner .nav-primary ul li a:hover,header.banner .nav-primary ul li a:active{padding:14px 14px 13px;text-align:left}header.banner nav.nav-primary ul.active li ul{display:none}#menu.active:after{background-image:url(//assets.ubuntu.com/sites/ubuntu/latest/u/img/patterns/nav-arrow.svg);background-repeat:no-repeat;background-position:50% 26px;content:"";display:block;height:23px;margin-left:0;padding-bottom:17px;position:relative;top:-3px;width:48px;z-index:999}html.no-svg #menu.active:after,.opera-mini #menu.active:after{background-image:url(//assets.ubuntu.com/sites/ubuntu/latest/u/img/patterns/nav-arrow.png)}.nav-secondary{border-bottom:1px solid #dfdcd9;margin-bottom:0}.nav-secondary ul{float:left;margin-bottom:10px;margin-left:2px}.nav-secondary ul li{float:left;margin-top:16px;font-size:14px;margin-right:15px}.nav-secondary ul li a:link,.nav-secondary ul li a:visited{color:#333;font-size:14px;float:left}.nav-secondary ul li a:hover,.nav-secondary ul li a:active{color:#dd4814;text-decoration:none}.nav-secondary ul li,.nav-secondary ul li.active a:link,.nav-secondary ul li.active a:visited{color:#dd4814;text-decoration:none}.nav-secondary ul.breadcrumb{margin-left:20px}.nav-secondary ul.breadcrumb li,.nav-secondary ul.breadcrumb li a:link,.nav-secondary ul.breadcrumb li a:visited{color:#888;margin-right:8px}.nav-secondary ul.breadcrumb li.active a:link,.nav-secondary ul.breadcrumb li.active a:visited{color:#dd4814}header.banner h2{font-size:1.78571em;display:block;left:4px;margin-bottom:0;position:relative;text-transform:lowercase;top:14px}header.banner h2 a:link,header.banner h2 a:visited,header.banner a{color:#fff;float:left;text-decoration:none}header.banner .logo{border-left:0;float:left;height:48px;overflow:hidden}header.banner .logo-ubuntu{background:url("//assets.ubuntu.com/sites/ubuntu/latest/u/img/ubuntu-logo.png") no-repeat scroll 0 10px transparent;font-size:18px;margin-bottom:0;position:relative;text-transform:lowercase;float:left;margin:0;display:inline-block;height:32px;min-width:128px;margin-right:-20px;margin-left:10px;padding:7px 14px 9px 0}header.banner .logo-ubuntu img{margin-right:8px;position:absolute;left:-999em}header.banner .logo-ubuntu span{float:left;font-size:23px;font-weight:300;padding-left:122px;padding-right:20px;position:relative;top:5px}header.banner .nav-primary.nav-left .logo-ubuntu{float:right}header.banner .nav-primary.nav-right .logo-ubuntu{background-image:url("//assets.ubuntu.com/sites/ubuntu/latest/u/img/logos/logo-ubuntu-white.svg");background-size:107px 25px;float:left}html.no-svg header.banner .nav-primary.nav-right .logo-ubuntu,.opera-mini header.banner .nav-primary.nav-right .logo-ubuntu{background-image:url("//assets.ubuntu.com/sites/ubuntu/latest/u/img/logos/logo-ubuntu-white.png")}@media only screen and (max-width: 295px){header.banner .nav-primary.nav-right .logo-ubuntu,header.banner .logo-ubuntu{-moz-background-size:20px 20px;-webkit-background-size:20px 20px;-o-background-size:20px 20px;background-size:20px 20px;background:url("//assets.ubuntu.com/sites/ubuntu/latest/u/img/logos/logo-ubuntu_cof-white_orange-hex.svg") 0 50% no-repeat;min-width:0;width:38px}header.banner .logo-ubuntu span{padding-left:38px}}html.no-svg header.banner .logo-ubuntu,.opera-mini header.banner .logo-ubuntu{background-image:url("//assets.ubuntu.com/sites/ubuntu/latest/u/img/logos/logo-ubuntu_cof-white_orange-hex.png")}@media only screen and (max-width: 768px){header.banner .nav-primary{-moz-box-shadow:0 1px 2px 1px rgba(0,0,0,0.2);-webkit-box-shadow:0 1px 2px 1px rgba(0,0,0,0.2);box-shadow:0 1px 2px 1px rgba(0,0,0,0.2)}header.banner .nav-primary.active{-moz-box-shadow:none;-webkit-box-shadow:none;box-shadow:none;padding:0}header nav ul.active{float:left}header nav ul.active li:last-child a:link,header nav ul.active li:last-child a:visited{border-bottom:0}header.banner .nav-primary ul{position:relative;width:100%}header.banner .nav-primary ul li.active a:link,header.banner .nav-primary ul li.active a:visited{color:#333;font-weight:700}header.banner .nav-primary ul li,header.banner .nav-primary ul li a:link,header.banner .nav-primary ul li a:visited,header.banner .nav-primary ul li a:active{-webkit-box-sizing:border-box;-moz-box-sizing:border-box;box-sizing:border-box;background:transparent;border:none;display:block;font-weight:300;margin:0;padding:0;width:100%}header.banner .nav-primary ul li a:link,header.banner .nav-primary ul li a:visited,header.banner .nav-primary ul li a:hover,header.banner .nav-primary ul li a:active{background-color:#f0f0f0;border-bottom:1px solid #d4d7d4;color:#333333;font-size:1em}header.banner .nav-primary ul li:nth-last-child(-n+2) a:link,header.banner .nav-primary ul li:nth-last-child(-n+2) a:visited{border:0}header.banner .nav-primary ul li a:hover{-moz-box-shadow:none;-webkit-box-shadow:none;box-shadow:none;background:#f8f8f8}header.banner .nav-primary ul li a.active{background-color:#ddd}header.banner nav.nav-primary ul li a:link,header.banner .nav-primary ul li a:visited,header.banner .nav-primary ul li a:hover,header.banner .nav-primary ul li a:active{padding:8px 10px;text-align:left}header.banner .nav-primary ul li{-webkit-box-sizing:border-box;-moz-box-sizing:border-box;box-sizing:border-box;background:transparent;border-bottom:0;border-right:1px solid #d4d7d4;float:left;width:50%}.nav-secondary{background:#fff}.nav-secondary ul.second-level-nav{border-top:1px solid #d4d7d4;display:none;margin-bottom:0;margin-left:0;padding-bottom:10px;padding-top:10px;width:100%}.nav-secondary ul.second-level-nav li{-webkit-box-sizing:border-box;-moz-box-sizing:border-box;box-sizing:border-box;width:50%;margin:0;float:left}.nav-secondary ul.second-level-nav li a,.nav-secondary ul.second-level-nav li a:link,.nav-secondary ul.second-level-nav li a:visited{-moz-box-sizing:border-box;-webkit-box-sizing:border-box;box-sizing:border-box;display:block;height:100%;padding:10px 10px 10px 20px;width:100%}.nav-secondary ul.second-level-nav li.active a,.nav-secondary ul.second-level-nav li.active a:link,.nav-secondary ul.second-level-nav li.active a:visited{color:#333;font-weight:700}.nav-secondary ul.third-level-nav{display:none;margin-bottom:0;width:100%;padding-bottom:20px}.nav-secondary ul.third-level-nav li{-webkit-box-sizing:border-box;-moz-box-sizing:border-box;box-sizing:border-box;width:50%;margin:0;float:left;padding-left:30px}.nav-secondary ul.third-level-nav li a,.nav-secondary ul.third-level-nav li a:link,.nav-secondary ul.third-level-nav li a:visited{-webkit-box-sizing:border-box;-moz-box-sizing:border-box;box-sizing:border-box;padding:10px 10px 10px 0;display:block;width:100%;height:100%}.nav-secondary ul.third-level-nav li.active a,.nav-secondary ul.third-level-nav li.active a:link,.nav-secondary ul.third-level-nav li.active a:visited{color:#333;font-weight:700}.nav-secondary ul.third-level-nav li.single-link{width:100%}.nav-secondary ul.third-level-nav li:only-child{width:100%}.nav-secondary ul.breadcrumb{-webkit-box-sizing:border-box;-moz-box-sizing:border-box;box-sizing:border-box;width:100%;margin-left:0;margin-bottom:0}.nav-secondary ul.breadcrumb li:first-of-type{border-bottom:1px solid #d4d7d4;margin-bottom:-1px}.nav-secondary ul.breadcrumb li{-webkit-box-sizing:border-box;-moz-box-sizing:border-box;box-sizing:border-box;color:#fff;width:100%;display:block;height:40px;margin:0}.nav-secondary ul.breadcrumb li a,.nav-secondary ul.breadcrumb li a:link,.nav-secondary ul.breadcrumb li a:visited{-webkit-box-sizing:border-box;-moz-box-sizing:border-box;box-sizing:border-box;font-size:16px;width:100%;color:#333333;display:block;margin-right:0;text-decoration:none;padding:8px 10px 0 10px}.nav-secondary ul.breadcrumb li.active{margin-top:12px}.nav-secondary ul.breadcrumb li.active a,.nav-secondary ul.breadcrumb li.active a:link,.nav-secondary ul.breadcrumb li.active a:visited{color:#333;font-weight:700}.nav-secondary ul.breadcrumb li:nth-of-type(2n){margin-top:12px}.nav-secondary ul.breadcrumb li .after{background-image:url(//assets.ubuntu.com/sites/ubuntu/latest/u/img/patterns/nav-down-arrow.svg);background-position:center center;background-repeat:no-repeat;background-size:18px;float:right;height:18px;margin-right:-5px;margin-top:-6px;padding:10px;position:relative;right:0;top:0;width:18px}.nav-secondary ul.breadcrumb li+li{display:none}.nav-secondary ul.breadcrumb li+li a:link,.nav-secondary ul.breadcrumb li+li a:active,.nav-secondary ul.breadcrumb li+li a:visited{padding-left:20px}.nav-secondary ul.breadcrumb li+li a.after{background-image:none}.nav-secondary.open ul.breadcrumb li a:after,.nav-secondary.open ul.breadcrumb li a:link:after,.nav-secondary.open ul.breadcrumb li a:visited:after{background-image:url(//assets.ubuntu.com/sites/ubuntu/latest/u/img/patterns/nav-up-arrow.svg)}.nav-secondary.open ul.breadcrumb li+li a.after{background-image:none}.nav-secondary.open ul.breadcrumb li .after{background-image:url(//assets.ubuntu.com/sites/ubuntu/latest/u/img/patterns/nav-up-arrow.svg);margin-top:-7px}.nav-secondary.open ul.second-level-nav,.nav-secondary.open ul.third-level-nav,.nav-secondary.open ul.breadcrumb li+li{display:block}.no-js .nav-secondary ul.second-level-nav{display:block}.no-js #main-navigation-link{position:absolute;right:10px;top:12px;width:20px;height:28px;z-index:999;text-indent:-999em;display:block}.no-js #main-navigation-link a{background-image:url("//assets.ubuntu.com/sites/ubuntu/latest/u/img/icons/navigation-menu-plain.svg");background-position:center center;background-repeat:no-repeat;background-size:25px auto;display:block;width:28px;height:28px;position:absolute}html.no-svg .nav-secondary ul.breadcrumb li .after,.opera-mini .nav-secondary ul.breadcrumb li .after{background-image:url(//assets.ubuntu.com/sites/ubuntu/latest/u/img/patterns/nav-down-arrow.png)}html.no-svg .nav-secondary ul.breadcrumb.open ul.breadcrumb li a:after,html.no-svg .nav-secondary ul.breadcrumb.open ul.breadcrumb li a:link:after,html.no-svg .nav-secondary ul.breadcrumb.open ul.breadcrumb li a:visited:after,.opera-mini .nav-secondary ul.breadcrumb.open ul.breadcrumb li a:after,.opera-mini .nav-secondary ul.breadcrumb.open ul.breadcrumb li a:link:after,.opera-mini .nav-secondary ul.breadcrumb.open ul.breadcrumb li a:visited:after{background-image:url(//assets.ubuntu.com/sites/ubuntu/latest/u/img/patterns/nav-up-arrow.png)}html.no-svg .nav-secondary ul.breadcrumb.open ul.breadcrumb li .after,.opera-mini .nav-secondary ul.breadcrumb.open ul.breadcrumb li .after{background-image:url(//assets.ubuntu.com/sites/ubuntu/latest/u/img/patterns/nav-up-arrow.png)}html.no-svg header.banner .nav-primary #google-appliance-search-form button[type="submit"],.opera-mini header.banner .nav-primary #google-appliance-search-form button[type="submit"]{background-image:url("//assets.ubuntu.com/sites/ubuntu/latest/u/img/search-black.png")}html.no-svg .nav-secondary ul.breadcrumb li .after,.opera-mini .nav-secondary ul.breadcrumb li .after{background-image:url(//assets.ubuntu.com/sites/ubuntu/latest/u/img/patterns/nav-down-arrow.png)}html.no-svg .nav-secondary ul.breadcrumb.open ul.breadcrumb li a:after,html.no-svg .nav-secondary ul.breadcrumb.open ul.breadcrumb li a:link:after,html.no-svg .nav-secondary ul.breadcrumb.open ul.breadcrumb li a:visited:after,.opera-mini .nav-secondary ul.breadcrumb.open ul.breadcrumb li a:after,.opera-mini .nav-secondary ul.breadcrumb.open ul.breadcrumb li a:link:after,.opera-mini .nav-secondary ul.breadcrumb.open ul.breadcrumb li a:visited:after{background-image:url(//assets.ubuntu.com/sites/ubuntu/latest/u/img/patterns/nav-up-arrow.png)}html.no-svg .nav-secondary ul.breadcrumb.open ul.breadcrumb li .after,.opera-mini .nav-secondary ul.breadcrumb.open ul.breadcrumb li .after{background-image:url(//assets.ubuntu.com/sites/ubuntu/latest/u/img/patterns/nav-up-arrow.png)}html.no-svg header.banner .nav-primary #google-appliance-search-form button[type="submit"],.opera-mini header.banner .nav-primary #google-appliance-search-form button[type="submit"]{background-image:url("//assets.ubuntu.com/sites/ubuntu/latest/u/img/search-black.png")}header.banner .nav-toggle{background-image:url("//assets.ubuntu.com/sites/ubuntu/latest/u/img/icons/navigation-menu-plain.svg");background-position:center center;background-repeat:no-repeat;background-size:25px auto;cursor:pointer;display:block;height:48px;position:absolute;right:0;text-indent:-99999px;width:48px}html.no-svg header.banner .nav-toggle,.opera-mini header.banner .nav-toggle{background-image:url("//assets.ubuntu.com/sites/ubuntu/latest/u/img/icons/navigation-menu-plain.png")}}@media only screen and (min-width: 768px){header.banner .nav-primary ul li{border-bottom:0}}@media only screen and (min-width: 769px){header.banner{-moz-box-shadow:0 2px 2px -2px #777777 inset, 2px 1px #FFFFFF;-webkit-box-shadow:0 2px 2px -2px #777777 inset, 2px 1px #FFFFFF;box-shadow:0 2px 2px -2px #777777 inset, 2px 1px #FFFFFF}header.banner nav.nav-primary{-moz-box-shadow:none;-webkit-box-shadow:none;box-shadow:none;border-bottom:0}header.banner .nav-toggle{display:none}header.banner nav ul{background-color:transparent;display:block}header.banner .nav-primary ul li{border-left:1px solid #C64012}header.banner .nav-primary ul li a:active,header.banner .nav-primary ul li a:hover,header.banner .nav-primary ul li a:visited,header.banner nav.nav-primary ul li a:link{border-left:1px solid #EC5B29}header.banner .nav-primary ul li:last-child{border-right:1px solid #C64012;border-left:1px solid #C64012}header.banner .nav-primary ul li a.active{background-color:#B83A10}header.banner .nav-primary ul li a:hover{background-color:#E1662F}.nav-secondary ul:last-child li:last-child{padding-bottom:10px}.nav-secondary ul.breadcrumb li,.nav-secondary ul.second-level-nav li,.nav-secondary ul.third-level-nav li{margin-right:15px}.nav-secondary ul.breadcrumb{float:left}.nav-secondary ul.breadcrumb li{margin-bottom:10px}.nav-secondary ul{float:none;margin-bottom:0}.nav-secondary ul li{margin-bottom:5px}}@media only screen and (min-width: 984px){header.banner{margin-bottom:20px}header.banner nav.nav-primary ul{display:block}header.banner .nav-primary,#nav-global .nav-global-wrapper{width:984px}header.banner .nav-primary.nav-right .logo-ubuntu{margin-left:0}}header.banner .nav-primary ul{position:static}header.banner .nav-primary li ul{-moz-box-shadow:0 2px 2px -1px #777;-webkit-box-shadow:0 2px 2px -1px #777;box-shadow:0 2px 2px -1px #777;-webkit-border-radius:10px;-moz-border-radius:10px;border-radius:10px;background:#f7f7f7;border:1px solid #d5d5d5;display:none;float:none;margin:0;padding:5px 0;position:absolute;top:51px;width:200px}header.banner .nav-primary li:hover ul:after{background:url("//assets.ubuntu.com/sites/ubuntu/latest/u/img/patterns/arrow-up-smaller.png") no-repeat;content:'';display:block;height:8px;left:20px;position:relative;top:-13px;width:200px;z-index:999}.no-generatedcontent header.banner .nav-primary li ul{-webkit-border-radius:0 0 10px 10px;-moz-border-radius:0 0 10px 10px;border-radius:0 0 10px 10px;top:48px}header.banner .nav-primary li ul .arrow-up{display:none}header.banner .nav-primary li ul li{border:0;float:none}header.banner .nav-primary li ul li a:link,header.banner .nav-primary li ul li a:visited{border:0;color:#333333;padding:0 0 11px 14px;text-align:left;width:170px}header.banner .nav-primary li ul li a:hover{background:none repeat scroll 0 0 transparent;-moz-box-shadow:none;-webkit-box-shadow:none;box-shadow:none;color:#DD4814}header.banner .nav-primary li ul li.first a:link,header.banner .nav-primary li ul li.first a:visited,header.banner .nav-primary li ul li:first-of-type a:link{padding:10px 14px}header.banner .nav-primary li ul li.active a:link,header.banner .nav-primary li ul li.active a:visited{background:none repeat scroll 0 0 transparent !important}header.banner .nav-primary li ul .promo{border-top:1px solid #D5D5D5;float:left;margin-top:5px;padding:15px 0 0}header.banner .nav-primary li ul .promo a:link,header.banner .nav-primary li ul .promo a:visited{background:none repeat scroll 0 0 transparent;border-left:0 none;color:#333333;height:auto;padding:0;text-align:left}header.banner .nav-primary li ul .promo p{margin:0 10px}header.banner .nav-primary li ul .promo a:hover{box-shadow:none;color:#DD4814}header.banner .nav-primary li ul .promo img{margin-top:14px;margin-bottom:-6px;-webkit-border-radius:0 0 10px 10px;-moz-border-radius:0 0 10px 10px;border-radius:0 0 10px 10px;position:relative;top:1px}header.banner .nav-primary li ul .promo .category{color:#888;font-size:11px;margin:0 10px;text-transform:uppercase}header.banner .nav-primary li:hover ul{display:block}html.lt-ie8 header.banner .nav-primary li:hover ul{display:none}.header-search,#box-search{padding:7px 0 7px 14px;overflow:hidden}.header-search input[type="search"],.header-search input[type="text"],#box-search input[type="search"],#box-search input[type="text"]{-webkit-appearance:none;-moz-box-shadow:inset 0 1px 4px rgba(0,0,0,0.2);-webkit-box-shadow:inset 0 1px 4px rgba(0,0,0,0.2);box-shadow:inset 0 1px 4px rgba(0,0,0,0.2);-webkit-box-sizing:border-box;-moz-box-sizing:border-box;box-sizing:border-box;-webkit-border-radius:4px;-moz-border-radius:4px;border-radius:4px;-webkit-transition:all 0.5s ease-out;-moz-transition:all 0.5s ease-out;-ms-transition:all 0.5s ease-out;-o-transition:all 0.5s ease-out;transition:all 0.5s ease-out;background-color:#be3d00;border:none;color:#fff;display:block;float:left;font-size:16px;height:2.1em;margin-bottom:0;padding:0.5em 2.5em 0.5em 0.5em;width:100%}.header-search ::-webkit-input-placeholder,#box-search ::-webkit-input-placeholder{color:white;opacity:0.4}.header-search ::-webkit-input-placeholder,#box-search ::-webkit-input-placeholder{color:white;opacity:0.4}.header-search ::-moz-placeholder,#box-search ::-moz-placeholder{color:white;opacity:0.4}.header-search :-ms-input-placeholder,#box-search :-ms-input-placeholder{color:white;opacity:0.4}.header-search input:-moz-placeholder,#box-search input:-moz-placeholder{color:white;opacity:0.4}.header-search ::placeholder,#box-search ::placeholder{color:white;opacity:0.4}.header-search input[type="search"]:focus,#box-search input[type="search"]:focus{background-color:#a63603}.header-search button[type=submit],#box-search button[type=submit]{padding:3px 2px;line-height:0;float:left;margin-left:-40px;display:block;background:none;overflow:visible}.header-search button[type=submit]:hover,#box-search button[type=submit]:hover{background:none}.header-search button[type=submit] img,#box-search button[type=submit] img{height:28px;width:28px}header.banner .search-toggle{-moz-background-size:20px 20px;-webkit-background-size:20px 20px;-o-background-size:20px 20px;background-size:20px 20px;background-image:url("//assets.ubuntu.com/sites/ubuntu/latest/u/img/search_icon_white_64.png");background-image:url("//assets.ubuntu.com/sites/ubuntu/latest/u/img/search.svg");background-position:center center;background-repeat:no-repeat;display:block;height:48px;outline:none;overflow:hidden;position:absolute;right:58px;text-indent:-999em;top:0;width:24px}.search-toggle:link,.search-toggle:active{outline:none}#box-search,.header-search{background:#f0f0f0;border:0;display:none;float:left;margin-bottom:0;position:relative;margin:0 0 -1px 0;padding:0;width:100%;z-index:3}#box-search.active,.header-search.active,.header-search.open{display:block}#box-search div,.header-search div{-moz-box-shadow:inset 0 -4px 4px -4px rgba(0,0,0,0.3),inset 0 5px 5px -5px rgba(0,0,0,0.3);-webkit-box-shadow:inset 0 -4px 4px -4px rgba(0,0,0,0.3),inset 0 5px 5px -5px rgba(0,0,0,0.3);box-shadow:inset 0 -4px 4px -4px rgba(0,0,0,0.3),inset 0 5px 5px -5px rgba(0,0,0,0.3);background:#f0f0f0;margin:10px;position:relative;z-index:1}#box-search form input[type="search"],.header-search form input[type="search"]{font-size:1.14286em;-webkit-border-radius:4px 4px 4px 4px;-moz-border-radius:4px 4px 4px 4px;border-radius:4px 4px 4px 4px;-moz-box-shadow:0 2px 2px rgba(0,0,0,0.3) inset,0 -1px 3px rgba(0,0,0,0.2) inset,0 2px 0 rgba(255,255,255,0.4);-webkit-box-shadow:0 2px 2px rgba(0,0,0,0.3) inset,0 -1px 3px rgba(0,0,0,0.2) inset,0 2px 0 rgba(255,255,255,0.4);box-shadow:0 2px 2px rgba(0,0,0,0.3) inset,0 -1px 3px rgba(0,0,0,0.2) inset,0 2px 0 rgba(255,255,255,0.4);-webkit-box-sizing:border-box;-moz-box-sizing:border-box;box-sizing:border-box;background:#fff;border:0;color:#333;font-size:16px;height:auto;margin:0;float:left;padding:9px 10px;width:100%}.yes-js .header-inner #box-search,.yes-js .header-inner .header-search{display:none}.yes-js .header-inner #box-search form,.yes-js .header-inner .header-search form{-webkit-box-sizing:border-box;-moz-box-sizing:border-box;box-sizing:border-box;margin-left:0;margin-right:0;overflow:hidden;padding:10px;top:0;z-index:999;position:relative;width:100%}@media only screen and (max-width: 768px){header.banner .search-toggle{right:48px}html.no-svg .search-toggle,.opera-mini .search-toggle{background-image:url("//assets.ubuntu.com/sites/ubuntu/latest/u/img/search-white.png")}}@media only screen and (min-width: 768px){header.banner .search-toggle{display:none}}@media only screen and (min-width: 960px){#box-search,.header-search{background:none;overflow:hidden;padding:7px 0 7px 14px;border-right:0 none;float:right;margin-bottom:0;padding-bottom:5px;padding-right:0;padding-top:7px;max-width:220px}#box-search form input[type="text"],#box-search form input[type="search"],.header-search form input[type="text"],.header-search form input[type="search"]{-moz-box-shadow:0 2px 4px rgba(0,0,0,0.4) inset;-webkit-box-shadow:0 2px 4px rgba(0,0,0,0.4) inset;box-shadow:0 2px 4px rgba(0,0,0,0.4) inset;-webkit-box-sizing:content-box;-moz-box-sizing:content-box;box-sizing:content-box;background:url("//assets.ubuntu.com/sites/ubuntu/latest/u/img/icons/icon-search.png") no-repeat scroll 5px center,none repeat scroll 0 0 #BE3D00;border:6px solid #DE6532;border-width:0 0 1px;color:#fff;font-size:0.813em;height:24px;margin-bottom:0;padding:4px 4px 4px 30px;transition:all 0.5s ease 0s;width:86px}}@media only screen and (max-width: 960px){header.banner nav.nav-primary .header-search{padding:0;position:relative;top:0;width:100%}header.banner nav.nav-primary .header-search input[type="search"]{border-radius:0;background:#f7f7f7;color:#333}header.banner nav.nav-primary .header-search button[type="submit"]{width:32px;height:38px;background:url("//assets.ubuntu.com/sites/ubuntu/latest/u/img/search-black.svg") no-repeat scroll center center transparent;background-size:28px 28px}header.banner nav.nav-primary .header-search button[type="submit"] img{max-width:none;display:none}header.banner nav.nav-primary .header-search.open{display:block}header.banner .search-toggle{background-image:url("//assets.ubuntu.com/sites/ubuntu/latest/u/img/search-white.svg");background-position:center center;background-repeat:no-repeat;background-size:25px auto;cursor:pointer;right:0;display:block;height:48px;position:absolute;text-indent:-99999px;width:48px}html.no-svg header.banner .search-toggle,.opera-mini header.banner .search-toggle{background-image:url("//assets.ubuntu.com/sites/ubuntu/latest/u/img/img/search-white.png")}.opera-mini x:-o-prefocus,.opera-mini header.banner .search-toggle{-o-background-size:25px auto;background-size:25px auto}}@media only screen and (min-width: 984px){#box-search,.header-search{display:block;margin-right:0}#box-search form input[type="text"]:focus,.header-search form input[type="text"]:focus{width:160px}}@media only screen and (max-width: 768px){header.banner .search-toggle{right:48px}}body.ubuntu-search .nav-secondary,body.search-results .nav-secondary,body.search-no-results .nav-secondary{display:none}body.ubuntu-search section>h1,body.ubuntu-search section article h1,body.search-results section>h1,body.search-results section article h1,body.search-no-results section>h1,body.search-no-results section article h1{padding-bottom:10px;font-size:1.438em;margin-bottom:0}body.ubuntu-search section>h1,body.search-results section>h1,body.search-no-results section>h1{border-bottom:1px dotted #dfdcd9}body.ubuntu-search .main-search,body.search-results .main-search,body.search-no-results .main-search{padding:20px 0;margin:0 0 20px 0;background-color:transparent}body.ubuntu-search .main-search input[type="search"],body.search-results .main-search input[type="search"],body.search-no-results .main-search input[type="search"]{float:left;width:100%;font-size:2em;border:1px solid #999;-moz-box-sizing:border-box;box-sizing:border-box;padding:0.2em 65px 0.2em 0.2em}body.ubuntu-search .main-search button[type=submit],body.search-results .main-search button[type=submit],body.search-no-results .main-search button[type=submit]{padding:4px;line-height:0;float:left;margin-left:-53px;display:block;background:none;overflow:visible;width:auto;margin-top:-4px}body.ubuntu-search .main-search button[type=submit]:hover,body.search-results .main-search button[type=submit]:hover,body.search-no-results .main-search button[type=submit]:hover{background:none}body.ubuntu-search .main-search button[type=submit] img,body.search-results .main-search button[type=submit] img,body.search-no-results .main-search button[type=submit] img{height:45px;width:45px}body.ubuntu-search .search-result h1 .title-main,body.search-results .search-result h1 .title-main,body.search-no-results .search-result h1 .title-main{margin-right:20px}body.ubuntu-search .search-result h1 .result-url,body.search-results .search-result h1 .result-url,body.search-no-results .search-result h1 .result-url{color:#999;overflow:hidden;text-overflow:ellipsis;display:block;vertical-align:bottom;padding-bottom:2px}body.ubuntu-search .search-result h1 .result-url a,body.search-results .search-result h1 .result-url a,body.search-no-results .search-result h1 .result-url a{color:#999}body.ubuntu-search .search-result p,body.search-results .search-result p,body.search-no-results .search-result p{margin-bottom:0}body.ubuntu-search .num-results,body.search-results .num-results,body.search-no-results .num-results{display:inline-block;margin-left:20px}body.ubuntu-search .bottom-results-total,body.search-results .bottom-results-total,body.search-no-results .bottom-results-total{text-align:center;width:100%;overflow:visible;padding-top:20px;margin:0}body.ubuntu-search .bottom-nav,body.search-results .bottom-nav,body.search-no-results .bottom-nav{overflow:hidden;margin-top:-26px}body.ubuntu-search .bottom-nav ul,body.search-results .bottom-nav ul,body.search-no-results .bottom-nav ul{margin-bottom:0;margin-left:0;padding:0;overflow:hidden}body.ubuntu-search .bottom-nav li,body.search-results .bottom-nav li,body.search-no-results .bottom-nav li{float:left;margin-left:15px}body.ubuntu-search .bottom-nav li:first-child,body.search-results .bottom-nav li:first-child,body.search-no-results .bottom-nav li:first-child{margin-left:0}body.ubuntu-search .nav-back,body.search-results .nav-back,body.search-no-results .nav-back{float:left}body.ubuntu-search .nav-back li:before,body.search-results .nav-back li:before,body.search-no-results .nav-back li:before{content:"\2039";color:#dd4814;margin-right:5px}body.ubuntu-search .nav-back li.item-extreme:before,body.search-results .nav-back li.item-extreme:before,body.search-no-results .nav-back li.item-extreme:before{content:"\2039\2039"}body.ubuntu-search .nav-forward,body.search-results .nav-forward,body.search-no-results .nav-forward{float:right}body.ubuntu-search .nav-forward li:after,body.search-results .nav-forward li:after,body.search-no-results .nav-forward li:after{content:"\203A";color:#dd4814;margin-left:5px}body.ubuntu-search .nav-forward li.item-extreme:after,body.search-results .nav-forward li.item-extreme:after,body.search-no-results .nav-forward li.item-extreme:after{content:"\203A\203A"}body.ubuntu-search .error-notification,body.search-results .error-notification,body.search-no-results .error-notification{background-color:#fdffdc;color:#333;padding:20px;-moz-box-sizing:border-box;box-sizing:border-box;width:100%;margin-top:20px;display:block}body.ubuntu-search .result-line,body.search-results .result-line,body.search-no-results .result-line{color:#ada69e}body.ubuntu-search .results-top,body.search-results .results-top,body.search-no-results .results-top{border-bottom:1px dotted #dfdcd9;padding-bottom:0.5em}body.ubuntu-search .search-container,body.search-results .search-container,body.search-no-results .search-container{padding-bottom:0}@media only screen and (min-width: 768px){.ubuntu-search .main-search button[type=submit]{margin-left:-60px;margin-top:0}}body footer.global #nav-global li:first-of-type a{margin-left:0}footer.global{-webkit-box-sizing:border-box;-moz-box-sizing:border-box;box-sizing:border-box;-moz-box-shadow:inset 0 2px 2px -1px #d3d3d3;-webkit-box-shadow:inset 0 2px 2px -1px #d3d3d3;box-shadow:inset 0 2px 2px -1px #d3d3d3;background:none;border-top:0;clear:both;display:block;padding:30px 10px 20px;position:relative;width:100%}footer.global .legal{margin:0 auto;width:100%}footer.global .legal{background-image:none;position:relative;clear:both;min-height:40px}footer.global .legal p,footer.global .legal ul{padding-left:0}footer.global h2{font-size:0.75em;line-height:1.4;margin-bottom:0;padding-bottom:0.5em}footer.global h2,footer.global h2 a:link,footer.global h2 a:visited{color:#333;font-weight:normal}footer.global nav ul li h2 a:after{content:""}footer.global ul{margin:0}footer.global nav ul li.two-col{display:inline-block;min-height:10em;vertical-align:top}footer.global nav ul li li{font-size:0.85714em;font-size:0.75em;margin-bottom:0}footer.global ul li li a:link,footer.global ul li li a:visited{color:#333;margin-bottom:0}footer.global ul li li a:hover,footer.global ul li li a:active,footer.global h2 a:hover,footer.global h2 a:active{color:#dd4814}footer.global .inline li{display:inline}footer.global p,footer.global ul.inline li a{color:#333;font-size:12px;margin-bottom:0}footer.global ul.inline li a:hover{color:#dd4814}footer.global ul.inline li:after{color:#888;content:"\00b7";vertical-align:middle;margin:0 5px}footer.global ul.inline li:last-child{width:120px}footer.global ul.inline li:last-child:after{content:""}footer.global .inline li{float:none;margin-bottom:0}footer.global .top-link{-moz-box-shadow:0 -4px 4px -4px rgba(0,0,0,0.3) inset;-webkit-box-shadow:0 -4px 4px -4px rgba(0,0,0,0.3) inset;box-shadow:0 -4px 4px -4px rgba(0,0,0,0.3) inset;background:none repeat scroll 0 0 transparent;border:0 none;float:left;font-size:0.75em;letter-spacing:0.05em;margin:0 0 0 -10px;padding-right:20px;text-transform:uppercase;width:100%}footer.global .top-link a{-webkit-box-sizing:border-box;-moz-box-sizing:border-box;box-sizing:border-box;background-image:url("//assets.ubuntu.com/sites/ubuntu/latest/u/img/pictograms/picto-pack/picto-upload-warmgrey.svg");background-position:10px center;background-repeat:no-repeat;background-size:14px 14px;border-bottom:0 none;color:#888888;display:block;float:none;font-weight:400;padding:12px 0 12px 28px}html.no-svg footer.global .top-link a,.opera-mini footer.global .top-link a{background-image:url("//assets.ubuntu.com/sites/ubuntu/latest/u/img/pictograms/picto-pack/picto-upload-warmgrey.png")}@media only screen and (max-width: 768px){footer.no-global .legal{-webkit-box-sizing:content-box;-moz-box-sizing:content-box;box-sizing:content-box;box-shadow:0 2px 2px -1px #D3D3D3 inset;padding-top:10px;margin-left:-10px;padding-left:10px;padding-right:10px}#livechat-eye-catcher{display:block}}@media only screen and (min-width: 768px){footer.global .inline li{display:inline;float:left}}@media only screen and (min-width: 769px){footer.global .top-link{display:none}footer.global .footer-b h2 a i{font-style:normal;display:inline}}@media only screen and (min-width: 984px){footer.global .legal{width:984px}footer.global{padding:30px 0 20px}footer.global .legal{background:url("//assets.ubuntu.com/sites/ubuntu/latest/u/img/logos/logo-ubuntu-grey.png") 100% 0 no-repeat}footer.global .footer-a{display:block}}#context-footer{-webkit-box-sizing:border-box;-moz-box-sizing:border-box;box-sizing:border-box;font-size:1em;border-bottom:0;clear:both;padding-bottom:1px;padding-top:0;position:relative;margin-bottom:0;margin-left:0;margin-right:0;width:100%}#context-footer hr{-moz-box-shadow:inset 0 2px 2px -2px #333;-webkit-box-shadow:inset 0 2px 2px -2px #333;box-shadow:inset 0 2px 2px -2px #333;background:#dd4814;height:14px;margin:0 0 10px;border:0;clear:both}#context-footer div.twelve-col{display:table;float:none;margin-bottom:7px}#context-footer div div{display:block;padding-left:0;margin-bottom:20px}#context-footer div div div{display:block;padding-left:0;margin-bottom:0}#context-footer div div.feature-one{padding-left:0}#context-footer div div.feature-four{margin-bottom:0;margin-right:0}#context-footer>div{padding-left:10px;padding-right:10px}#context-footer ul{margin-bottom:5px}#context-footer li.active{display:none}#context-footer h3{font-size:1.14286em;font-weight:normal}#context-footer .list a:after,#context-footer a.link-arrow:after,#context-footer nav ul li h2 a:after{content:' \203A'}@media only screen and (min-width: 768px){#context-footer{margin-bottom:12px;padding-left:30px;padding-right:30px}#context-footer div+div{width:31%}#context-footer div div.feature-four{padding-bottom:20px}#context-footer hr{margin:0 -30px 40px}#context-footer>div{padding-left:0;padding-right:0}}@media only screen and (min-width: 984px){#context-footer{padding:0 40px 10px}#context-footer div div{display:table-cell;float:none;padding-left:20px;margin-bottom:0}#context-footer hr{margin:0 -40px 40px}}a.link-cta-ubuntu,a.link-cta-canonical,a.link-cta-inverted,button.cta-ubuntu,button.cta-canonical,form button[type="submit"],form input[type="submit"]{-webkit-box-sizing:border-box;-moz-box-sizing:border-box;box-sizing:border-box;font-size:1.14286em;-webkit-border-radius:3px;-moz-border-radius:3px;border-radius:3px;background:#dd4814;color:#fff;text-decoration:none;display:inline-block;margin:0;font-family:Ubuntu, Arial, 'libra sans', sans-serif;font-weight:300;-webkit-font-smoothing:subpixel-antialiased;-moz-font-smoothing:subpixel-antialiased;-o-font-smoothing:subpixel-antialiased;font-smoothing:subpixel-antialiased;padding:8px 14px;width:100%;text-align:center}a.cta-large,button.cta-large{font-size:1.28571em;padding:10px 20px}a.link-cta-canonical,button.cta-canonical,form button.cta-canonical[type="submit"],form input.cta-canonical[type="submit"]{background:#772953;color:#fff}a.link-cta-inverted,button.cta-inverted{background:#fff;color:#333}.row-enterprise a.link-cta-canonical,.row-enterprise button.link-cta-canonical{background:#fff;color:#772953}a.link-cta-ubuntu:hover,a.link-cta-ubuntu:hover,button.cta-ubuntu:hover,form button[type="submit"]:hover,form input[type="submit"]:hover{background:#c03f11;text-decoration:none}a.link-cta-canonical:hover,button.cta-canonical:hover{background:#5f2143;text-decoration:none}a.link-cta-inverted:hover,.row-enterprise a.link-cta-canonical:hover,button.cta-inverted:hover,.row-enterprise button.cta-canonical:hover{background:#fff;text-decoration:underline}a.cta-deactivated,a.cta-deactivated:hover,button.cta-deactivated,button.cta-deactivated:hover{background:#efefef;color:#fff;cursor:not-allowed}@media only screen and (min-width: 768px){a.link-cta-ubuntu,a.link-cta-canonical,a.link-cta-inverted,button.cta-ubuntu,button.cta-canonical,form button[type="submit"],form input[type="submit"]{width:auto}}@media only screen and (min-width: 984px){a.link-cta-ubuntu,a.link-cta-canonical,a.link-cta-inverted,button.cta-ubuntu,button.cta-canonical,form button[type="submit"],form input[type="submit"]{width:auto}}form input,form select,form textarea{-webkit-box-sizing:border-box;-moz-box-sizing:border-box;box-sizing:border-box;width:100%}form .fieldset-submit ul{margin-bottom:0}form fieldset .mktError,form fieldset .errMsg,form fieldset .reqMark{color:#df382c}form fieldset .mktFormMsg{clear:both;display:block}.row{-webkit-box-sizing:border-box;-moz-box-sizing:border-box;box-sizing:border-box;border-bottom:1px dotted #888;clear:both;padding:20px 10px 0;position:relative}.row br{display:none}.row.no-padding-bottom{padding-bottom:0 !important}.row-grey{background:#f7f7f7}.no-border{border:0}#main-content .row-hero{margin-top:20px;padding-top:0}.row-background{color:#fff;background:url("//assets.ubuntu.com/sites/ubuntu/latest/u/img/backgrounds/image-background-wallpaper.jpg") no-repeat scroll 50% 50% #4b1827}.row-background a.alternate{color:#fff;text-decoration:underline}.row-background a.alternate:hover{color:rgba(255,255,255,0.6)}@media only screen and (min-width: 768px){.row-background{background-position:center 50%;background-size:100% auto}}.strip{width:100%;display:block}.strip-dark{background-color:#2c001e;background-image:url("//assets.ubuntu.com/sites/ubuntu/latest/u/img/backgrounds/background-grid.png");background-repeat:repeat;color:#fff}.strip-dark .list-ubuntu li{border:0}.strip-dark .resource{color:#333;-moz-box-shadow:none;-webkit-box-shadow:none;box-shadow:none}.strip-dark .resource:before{border-right-color:#2c001e}#main-content .strip-dark .resource:before{border-bottom-width:29px;right:-2px;top:-1px}#main-content .strip-dark .resource:hover:before{border-bottom-width:34px}#main-content .strip-dark .resource h2{padding-right:20px}.row-aux{background-color:rgba(255,255,255,0.6);text-align:center}.row-aux h2,.row-aux p{text-align:left}.row-aux a p{color:#333;margin-bottom:30px}.row-step h2{position:relative;top:5px}.row-step .step{position:relative;top:-5px;height:32px;width:32px;border-radius:50%;border:3px solid #dd4814;color:#dd4814;line-height:32px;text-align:center;background-color:#fff;font-size:23px;font-weight:400}@media only screen and (min-width: 768px){.row{padding:30px}#main-content .row-hero{margin-top:40px}}@media only screen and (min-width: 769px){.row-step .step{height:42px;width:42px;line-height:42px}.row br{display:block}}@media only screen and (min-width: 984px){.row br{display:block}.row{padding:60px 40px 40px}.no-border{border:0}}.box,.box-grey{-webkit-border-radius:4px;-moz-border-radius:4px;border-radius:4px;padding:1.333em 20px}.box{background:#fff;border:1px solid #dfdcd9}.box-grey{background:#f7f7f7;color:#333}.box-orange{background:#dd4814;color:#fff}.box-highlight{-moz-box-shadow:0 2px 2px 0 #c2c2c2;-webkit-box-shadow:0 2px 2px 0 #c2c2c2;box-shadow:0 2px 2px 0 #c2c2c2;border:1px solid #f7f7f7}.box-textured{-moz-box-shadow:0 2px 2px 0 #c2c2c2;-webkit-box-shadow:0 2px 2px 0 #c2c2c2;box-shadow:0 2px 2px 0 #c2c2c2;background:url("//assets.ubuntu.com/sites/ubuntu/latest/u/img/patterns/grey-textured-background.jpg");border:0}.box-padded{-webkit-border-radius:4px;-moz-border-radius:4px;border-radius:4px;background:#efefef;border:0;margin-bottom:20px;padding:6px 5px}.box-padded h3{font-size:1.39286em;margin-left:5px;margin-top:5px}.box-padded li h3{font-size:1.39286em;margin:0}.box-padded div{-webkit-border-radius:4px;-moz-border-radius:4px;border-radius:4px;background:#fff;overflow:hidden;padding:8px 8px 2px}.box-padded-feature{-webkit-border-radius:4px;-moz-border-radius:4px;border-radius:4px;background:url("//assets.ubuntu.com/sites/ubuntu/latest/u/img/patterns/soft-centre-bkg.gif") repeat scroll 0 0 #a09f9f;border:0;margin-bottom:20px;padding:11px 5px 6px}.box-padded-feature h3{color:#fff;margin-left:5px;font-size:1.39286em}.box-padded-feature h4{font-size:1.14286em;font-weight:normal}.box-padded-feature>div{-webkit-border-radius:4px;-moz-border-radius:4px;border-radius:4px;background:#fff;overflow:hidden;padding:20px 8px}.box-padded-feature div div{margin-bottom:0}.box-padded-feature .inline-icons{display:table;width:100%;margin:0;text-align:center}.box-padded-feature .inline-icons li{display:table-cell;text-align:left;float:none}.box-padded-feature .one-col{width:48px;float:left}.resource{cursor:pointer;padding-bottom:40px;position:relative;-moz-transition:background .2s ease-out;-webkit-transition:background .2s ease-out;transition:background .2s ease-out}.resource h2{padding-right:20px}.resource.five-col h2 a:link,.resource.five-col h2 a:visited,.resource.four-col h2 a:link,.resource.four-col h2 a:visited{font-size:inherit !important}.resource.four-col h2 a:link,.resource.four-col h2 a:visited{font-size:1.125em}.resource.twelve-col h2 a:link,.resource.twelve-col h2 a:visited{font-size:1.40625em}.resource:hover{background-color:#fafafa}.resource:after{-moz-box-shadow:0 -1px 2px 0 #ddd;-webkit-box-shadow:0 -1px 2px 0 #ddd;box-shadow:0 -1px 2px 0 #ddd;content:'';height:1px;position:absolute;right:-6px;top:14px;-ms-transform:rotate(45deg);-webkit-transform:rotate(45deg);transform:rotate(45deg);-moz-transition:all .2s ease-out;-webkit-transition:all .2s ease-out;transition:all .2s ease-out;width:41px;z-index:2}.resource:hover:after{right:-9px;top:18px;width:48px}.resource:before{content:'';position:absolute;-moz-transition:border-width .2s ease-out;-webkit-transition:border-width .2s ease-out;transition:border-width .2s ease-out;top:-2px;right:-3px;width:0;height:0;border-bottom:30px solid #fdfdfd;border-right:30px solid #fff;-webkit-box-shadow:-2px 2px 2px rgba(176,176,176,0.4);-moz-box-shadow:-2px 2px 2px rgba(176,176,176,0.4);box-shadow:-2px 2px 2px rgba(176,176,176,0.4);z-index:2;-webkit-border-radius:0 0 0 0;-moz-border-radius:0 0 0 0;border-radius:0 0 0 0}.resource:hover:before{border-bottom-width:35px;border-right-width:35px}.resource:last-of-type{margin-bottom:30px}.resource .content-cat{background:url("//assets.ubuntu.com/sites/ubuntu/latest/u/img/icons/icon-resource-hub-icon-document.png") left center no-repeat;color:#aea79f;font-size:14px;letter-spacing:1px;margin:0;padding-left:20px;padding:0;position:absolute;text-transform:uppercase}.resource .content-cat-webinar{background:url("//assets.ubuntu.com/sites/ubuntu/latest/u/img/icons/icon-resource-hub-webinar.png") left center no-repeat}.resource.box-image-centered div+span img{margin-top:40px}html.yui3-js-enabled .resource:hover a{text-decoration:underline}.row-grey .resource:before{border-right-color:#f7f7f7}@media only screen and (max-width: 768px){.box-padded-feature .inline-icons li{float:left;display:block}.box-padded-feature .one-col{width:48px;float:left}}.arrow-up,.arrow-down,.arrow-right,.arrow-left{height:11px;position:absolute;width:18px}.arrow-up{background:url("//assets.ubuntu.com/sites/ubuntu/latest/u/img/patterns/arrow-up.png") 0 0 no-repeat;left:20px;top:-11px}.arrow-down{background:url("//assets.ubuntu.com/sites/ubuntu/latest/u/img/patterns/arrow-down.png") 0 0 no-repeat;bottom:-11px;right:20px}.arrow-right{background:url("//assets.ubuntu.com/sites/ubuntu/latest/u/img/patterns/arrow-right.png") 0 0 no-repeat;height:18px;right:-11px;top:20px;width:11px}.arrow-left{background:url("//assets.ubuntu.com/sites/ubuntu/latest/u/img/patterns/arrow-left.png") 0 0 no-repeat;bottom:20px;height:18px;left:-11px;width:11px}div>.arrow-left{left:-10px}@media only screen and (min-width: 769px){html.yui3-js-enabled .arrow{visibility:visible}}.list,.list-ubuntu,.list-canonical{list-style:none;margin-left:0}.list li,.list-ubuntu li,.list-canonical li{border-bottom:1px dotted #888;margin-bottom:0;padding:10px 0}.list li:last-of-type,.list li.last-item,.list-ubuntu li:last-of-type,.list-ubuntu li.last-item,.list-canonical li:last-of-type,.list-canonical li.last-item{border:0;padding-bottom:0}.list article{border-bottom:1px dotted #888;margin-bottom:0;padding:10px 0}.list-spaced article,.list-spaced li{padding:30px 0}nav .list a{display:block}.list-ubuntu li,.list-canonical li{background-repeat:no-repeat;background-position:0 1em;padding-left:25px}.list-ubuntu li{background-image:url("//assets.ubuntu.com/sites/ubuntu/latest/u/img/patterns/tick-orange.svg")}.list-canonical li{background-image:url("//assets.ubuntu.com/sites/ubuntu/latest/u/img/patterns/tick-midaubergine.svg")}.list-warm li{background-image:url("//assets.ubuntu.com/sites/ubuntu/latest/u/img/patterns/tick-warmgrey.svg")}.list-dark li{background-image:url("//assets.ubuntu.com/sites/ubuntu/latest/u/img/patterns/tick-darkaubergine.svg")}.vertical-divider .list-canonical li,.vertical-divider .list-ubuntu li{padding-left:25px}html.no-svg .list-ubuntu li,.opera-mini .list-ubuntu li{background-image:url("//assets.ubuntu.com/sites/ubuntu/latest/u/img/patterns/tick-orange.png")}html.no-svg .list-canonical li,.opera-mini .list-canonical li{background-image:url("//assets.ubuntu.com/sites/ubuntu/latest/u/img/patterns/tick-midaubergine.png")}html.no-svg .list-warm li,.opera-mini .list-warm li{background-image:url("//assets.ubuntu.com/sites/ubuntu/latest/u/img/patterns/tick-warmgrey.png")}html.no-svg .list-dark li,.opera-mini .list-dark li{background-image:url("//assets.ubuntu.com/sites/ubuntu/latest/u/img/patterns/tick-darkaubergine.png")}.no-bullets{list-style:none;margin-left:0}.row .combined-list ul,.row .combined-list div{margin-bottom:0}.row .combined-list li.last-item{border-bottom:1px dotted #888;padding-bottom:10px}.row .combined-list div.last-col,.row .combined-list ul.last-col{margin-bottom:20px}.row .combined-list div.last-col li.last-item,.row .combined-list ul.last-col li.last-item{border-bottom:0;padding-bottom:0}.inline{margin-left:0}.inline li{display:inline;list-style:none;margin-left:0;float:left}@media only screen and (min-width: 768px){.row .combined-list ul,.row .combined-list div{margin-bottom:20px}.row .combined-list li.last-item{border-bottom:0;padding-bottom:0}}ul.inline-logos{float:left;margin-left:0;padding:0;text-align:center;width:100%}ul.inline-logos li{clear:none;display:inline-block;float:none;margin:10px 20px;padding:0}ul.inline-logos li.clear-row{clear:left}ul.inline-logos li.last-item{border:0}ul.inline-logos img{-webkit-transition:all 0.5s ease-out;-moz-transition:all 0.5s ease-out;-ms-transition:all 0.5s ease-out;-o-transition:all 0.5s ease-out;transition:all 0.5s ease-out;vertical-align:middle;max-width:115px;max-height:32px}.inline-icons{margin:0 0 20px}.inline-icons li{margin-right:20px;margin-bottom:20px;text-align:left;display:inline-block}.inline-icons li.last-item{margin-right:0}.inline-icons.no-margin-bottom li{margin-bottom:0}.inline-icons img{vertical-align:middle;max-width:115px;max-height:32px}@media only screen and (max-width: 768px){ul.inline-logos img{max-width:172px;max-height:48px}}@media only screen and (min-width: 769px){ul.inline-logos li{clear:none;display:inline-block;height:auto;margin:20px 0;line-height:60px;padding:0 40px}ul.inline-logos li img{float:none;vertical-align:middle;max-width:200px;max-height:45px}}@media only screen and (min-width: 984px){.inline-icons{text-align:left;margin-bottom:20px}}blockquote.pull-quote{text-indent:0}blockquote.pull-quote p{color:#333;padding-left:10px;padding-right:10px;font-size:1.77379em;text-indent:-.4em;margin-left:.4em;line-height:1.3}blockquote.pull-quote p span{font-weight:bold;color:#dd4814;line-height:0;position:relative;left:-5px}blockquote.pull-quote p span+span{left:5px}blockquote.pull-quote p cite{margin:10px 0 0;font-weight:300;display:block;font-size:.75em;text-indent:0}blockquote.pull-quote.js{padding-left:60px;display:table-cell}blockquote.quote-canonical,blockquote.quote-canonical-white{font-size:1.14286em;background:url("//assets.ubuntu.com/sites/ubuntu/latest/u/img/patterns/quote-white-360x360.png") no-repeat 20px -130px;color:#772953;float:right;font-size:1em;height:215px;margin-top:0;padding:20px 60px 0;position:relative;width:236px}blockquote.quote-canonical-white{background:url("//assets.ubuntu.com/sites/ubuntu/latest/u/img/patterns/quote-aubergine-345x345.png") no-repeat 0 0;color:#fff;padding:80px 60px 0;height:265px}blockquote.quote p:first-child{font-size:1.28571em;line-height:1.3;text-indent:-7px}blockquote.quote-right-bottom{background-image:url("//assets.ubuntu.com/sites/ubuntu/latest/u/img/pictograms/picto-pack/picto-quote-orange.svg");background-repeat:no-repeat;background-size:287px 286px;color:#fff;height:167px;padding:60px 40px;position:static;right:-40px;top:-90px;width:207px}blockquote.quote-right-bottom p{color:#fff}blockquote.quote-grey{font-size:2.57143em;background:url("//assets.ubuntu.com/sites/ubuntu/latest/u/img/patterns/quote-grey-br-211x211.png") no-repeat scroll 0 0 transparent;color:#fff;height:152px;line-height:40px;margin-left:20px;padding:60px 0 0;text-align:center;width:211px}blockquote.quote-bottom-left{background:url("//assets.ubuntu.com/sites/ubuntu/latest/u/img/patterns/quote-orange-bl-287x287.png") no-repeat;color:#fff;height:167px;padding:55px 40px 70px 45px;width:225px}html.no-svg blockquote.quote-right-bottom,.opera-mini blockquote.quote-right-bottom{background-image:url("//assets.ubuntu.com/sites/ubuntu/latest/u/img/pictograms/picto-pack/picto-quote-orange.png")}.row-quote{-webkit-border-radius:0;-moz-border-radius:0;border-radius:0}.row-quote blockquote{-webkit-border-radius:4px;-moz-border-radius:4px;border-radius:4px;margin:0;padding:0}.row-quote blockquote p{margin-bottom:.75em;line-height:1.3;color:#333;padding-left:10px;padding-right:10px;text-indent:0}.row-quote blockquote span{font-weight:bold;color:#dd4814;line-height:0;position:relative;left:-5px}.row-quote blockquote span+span{left:5px}.row-quote blockquote cite{color:#333;font-style:normal;margin-bottom:0;font-size:.75em;text-indent:-14px;text-indent:0}.row-quote .quote-twitter{background:#fcece7 url("//assets.ubuntu.com/sites/ubuntu/latest/u/img/pictograms/pictogram-twitter-115x139.png") 20px bottom no-repeat;padding:20px 20px 20px 23.40425%}.row-quote .quote-twitter-small{background:#fcece7 url("//assets.ubuntu.com/sites/ubuntu/latest/u/img/pictograms/pictogram-twitter-54x63.png") 99% bottom no-repeat;padding:20px 20px 20px 80px}.row-quote .quote-twitter-small p{margin:0;padding:0}blockquote.quote-canonical,blockquote.quote-canonical-white{background:none;color:#333;width:auto;height:auto;padding:0 30px;margin-top:20px}@media only screen and (min-width: 768px){.row-quote blockquote{text-indent:-7px}.pull-quote{text-indent:-.4em}.row-quote blockquote p{font-size:1.77357em}blockquote.pull-quote p,.row-quote blockquote p{padding-left:0;padding-right:0;text-indent:-.7em}blockquote.pull-quote p span,.row-quote blockquote p span{font-size:1.391304348em}blockquote.pull-quote p cite,.row-quote blockquote p cite{margin-left:0;text-indent:0}blockquote.pull-quote p span,.row-quote blockquote p span{top:5px}}@media only screen and (min-width: 769px){.row-quote blockquote p{font-size:1.77357em;text-indent:-.4em}}@media only screen and (min-width: 984px){.row-quote blockquote{padding:0 80px 20px;text-indent:-10px}blockquote.pull-quote p span,.row-quote blockquote p span{top:10px}}html.js .tabbed-content .accordion-button{-webkit-box-sizing:border-box;-moz-box-sizing:border-box;box-sizing:border-box;height:auto;padding-bottom:.6em;padding-right:20px}html.yui3-js-enabled .tabbed-menu{display:none;padding-bottom:20px;padding-top:20px}html.yui3-js-enabled .arrow{display:none;position:absolute;visibility:hidden}html.yui3-js-enabled .tabbed-content{-webkit-border-radius:4px;-moz-border-radius:4px;border-radius:4px;padding:8px 8px 0;background:#f7f7f7;margin-bottom:8px}html.yui3-js-enabled .tabbed-content.hide{display:block;opacity:1 !important}html.yui3-js-enabled .tabbed-content .title{display:none}html.yui3-js-enabled .tabbed-content div{display:none}html.yui3-js-enabled .tabbed-content .accordion-button{-webkit-box-sizing:border-box;-moz-box-sizing:border-box;box-sizing:border-box;background:url("//assets.ubuntu.com/sites/ubuntu/latest/u/img/icons/icon-arrow-down.svg") no-repeat scroll right 3px #f7f7f7;color:#333;display:block;font-size:16px;padding-bottom:.6em;padding-right:20px;width:100%}html.yui3-js-enabled .tabbed-content.open .accordion-button{background-image:url("//assets.ubuntu.com/sites/ubuntu/latest/u/img/icons/icon-arrow-up.svg");margin-bottom:10px}html.yui3-js-enabled .tabbed-content.open div{display:block}html.yui3-js-enabled html.yui3-js-enabled.opera-mini .tabbed-content .accordion-button,html.yui3-js-enabled html.yui3-js-enabled.no-svg .tabbed-content .accordion-button{background-image:url("//assets.ubuntu.com/sites/ubuntu/latest/u/img/icons/icon-arrow-right.png")}html.yui3-js-enabled html.yui3-js-enabled.opera-mini .tabbed-content.open .accordion-button,html.yui3-js-enabled html.yui3-js-enabled.no-svg .tabbed-content.open .accordion-button{background-image:url("//assets.ubuntu.com/sites/ubuntu/latest/u/img/icons/icon-arrow-up.png")}html.yui3-js-enabled html.yui3-js-enabled.opera-mini.tabbed-content .accordion-button{background-image:none;margin-bottom:10px}html.yui3-js-enabled html.yui3-js-enabled.opera-mini.tabbed-content div{display:block}@media only screen and (min-width: 768px){html.yui3-js-enabled .tabbed-menu{display:block}html.yui3-js-enabled .tabbed-content{margin-bottom:20px;padding:40px}html.yui3-js-enabled .tabbed-content.hide{display:none;opacity:0 !important}html.yui3-js-enabled .tabbed-content .title{display:block}html.yui3-js-enabled .tabbed-content div{display:block}html.yui3-js-enabled .tabbed-content .vertical-divider div{display:table-cell}html.yui3-js-enabled .tabbed-content .accordion-button{display:none}}html.yui3-js-enabled .accordion-button.active{background-color:transparent}@media only screen and (min-width: 768px){.tabbed-menu{-moz-box-shadow:0 -1px 10px #cfcfcf inset;-webkit-box-shadow:0 -1px 10px #cfcfcf inset;box-shadow:0 -1px 10px #cfcfcf inset;-webkit-box-sizing:border-box;-moz-box-sizing:border-box;box-sizing:border-box;-webkit-border-radius:4px 4px 0 0;-moz-border-radius:4px 4px 0 0;border-radius:4px 4px 0 0;background:none repeat scroll 0 0 #f7f7f7;padding-bottom:20px;padding-top:20px;position:relative}.tabbed-menu ul{-webkit-box-sizing:border-box;-moz-box-sizing:border-box;box-sizing:border-box;display:table;margin-bottom:0;padding:0;position:relative;table-layout:fixed;width:100%}.tabbed-menu li{text-align:center;display:table-cell}.tabbed-menu a{color:#666;display:block;outline:none}.tabbed-menu a .active{color:#772953;text-decoration:none}.tabbed-menu a:hover{text-decoration:none}.tabbed-menu .arrow{bottom:0;position:absolute}.tabbed-content{*zoom:1;padding:20px 40px 0}.tabbed-content:before,.tabbed-content:after{content:"";display:table}.tabbed-content:after{clear:both}.tabbed-content .row{padding-left:0;padding-right:0}.tabbed-content .main-content{padding-bottom:40px}html.yui3-js-enabled .tabbed-content.hide{display:none;opacity:0}.tabbed-content-bg{-webkit-box-sizing:border-box;-moz-box-sizing:border-box;box-sizing:border-box;background:#fff;margin-left:20px;margin-right:20px}.tabbed-content-bg .row-box{padding-left:0;padding-right:0}html.yui3-js-enabled .arrow{visibility:visible}}.row.vertical-divider{padding-bottom:40px}.vertical-divider div,.vertical-divider li{border-right:0;display:block;padding-left:0;padding-right:0}.vertical-divider-full{padding-bottom:0}.vertical-divider-full>div{padding-bottom:40px}.row.vertical-divider-full{padding-bottom:0}@media only screen and (max-width: 767px){.vertical-divider>div,.vertical-divider>li{border-bottom:1px dotted #888;padding-bottom:20px}.vertical-divider div:last-of-type,.vertical-divider li:last-of-type,.inline-icons li:last-of-type{border-bottom:0;padding-bottom:5px}.row.vertical-divider{padding-bottom:0}.equal-height div,.equal-height li{height:auto !important}}@media only screen and (min-width: 984px){.row.vertical-divider{padding-bottom:60px}.vertical-divider>div,.vertical-divider>li{border-right:1px dotted #888;display:table-cell;float:none;margin-right:0;padding-left:20px;padding-right:20px;vertical-align:top}.vertical-divider>div:last-child,.vertical-divider>li:last-child,.vertical-divider>div.last-col,.vertical-divider>li.last-col,.vertical-divider>div:last-of-type,.vertical-divider>li:last-of-type{border-right:0;padding-right:0}.vertical-divider>div:first-child,.vertical-divider>li:first-child,.vertical-divider>div.first-col,.vertical-divider>li.first-col,.vertical-divider>div:first-of-type,.vertical-divider>li:first-of-type{padding-left:0}}.slider{-webkit-border-radius:4px;-moz-border-radius:4px;border-radius:4px;background:#f7f7f7;padding-top:40px}.slider .slide-window{overflow:hidden;position:relative;height:450px;-moz-transition:left 1s;-webkit-transition:left 1s;-o-transition:left 1s;transition:left 1s}.slider .slide-container{position:absolute;width:2800 px;-moz-transition:left 1s;-webkit-transition:left 1s;-o-transition:left 1s;transition:left 1s;left:0}.slider .slider-dots ul{position:absolute;top:550px;left:220px;z-index:5}.slider .slider-dots li{background-position:0 -8px;background:url("//assets.ubuntu.com/sites/ubuntu/latest/u/img/patterns/sprite-pager.png") no-repeat;float:left;height:7px;list-style-type:none;margin-right:.75em;text-indent:-9999em;width:7px}.slider .slider-dots li.active{background-position:0 0}.slider .slider-dots a{display:block;outline:0}.slider .slide{float:right;width:700px}.slider .slide h3{margin-top:65px;display:inline-block}.slider .slide p{width:350px}.slider .arrow-prev,.slider .arrow-next{font-size:5em;margin-top:150px;display:block;color:#888;outline:0}.slider .arrow-prev:hover,.slider .arrow-next:hover{text-decoration:none;color:#333}.slider .arrow-prev:active,.slider .arrow-next:active{padding-top:1px;text-decoration:none}.slider .arrow-prev:focus,.slider .arrow-next:focus{text-decoration:none}.yui3-tooltip-hidden{display:none}.yui3-tooltip-content{-moz-box-shadow:0 2px 8px rgba(0,0,0,0.2);-webkit-box-shadow:0 2px 8px rgba(0,0,0,0.2);box-shadow:0 2px 8px rgba(0,0,0,0.2);background:url("//assets.ubuntu.com/sites/ubuntu/latest/u/img/patterns/grey-textured-background.jpg") repeat scroll 0 0 transparent;-moz-border-radius:4px;-webkit-border-radius:4px;border-radius:4px;border:1px solid #e3e3e3;color:#333;margin-top:-30px;max-width:520px;position:relative}.yui3-tooltip .yui3-widget-bd{padding:20px;width:320px}.yui3-tooltip .yui3-widget-bd *{max-width:100%}.yui3-tooltip .yui3-widget-bd h5{margin-bottom:10px;font-size:22px;font-weight:300}.yui3-tooltip .yui3-widget-bd img{float:left;margin-right:10px}.yui3-tooltip .yui3-widget-bd q{border-bottom:1px dotted #888;border-top:1px dotted #888;display:block;font-size:16px;font-style:italic;margin-bottom:0;margin-top:20px;padding:10px 0}.yui3-tooltip .yui3-widget-bd p:last-child{margin-bottom:0}.yui3-tooltip .yui3-widget-ft,.yui3-tooltip .yui3-widget-ft div{position:absolute;width:0;height:0;border-style:solid;line-height:0;font-size:0}.yui3-tooltip .yui3-tooltip-align-bottom .yui3-widget-ft,.yui3-tooltip .yui3-tooltip-align-bottom .yui3-widget-ft div{top:-10px;left:50%;margin:0 0 0 -10px;border-width:0 10px 10px;border-color:#efefef transparent}.yui3-tooltip .yui3-tooltip-align-bottom .yui3-widget-ft div{top:0;border-color:#efefef transparent}.tooltip-label{-moz-box-shadow:3px 3px 6px rgba(0,0,0,0.3);-webkit-box-shadow:3px 3px 6px rgba(0,0,0,0.3);box-shadow:3px 3px 6px rgba(0,0,0,0.3);-webkit-border-radius:4px;-moz-border-radius:4px;border-radius:4px;background:#fff;border:1px solid #dfdcd9;display:none;font-size:13px;line-height:1;margin:0;padding:6px 5px;position:absolute;top:-20px;white-space:nowrap;z-index:1000}body,a:link,a:visited{-webkit-font-smoothing:antialiased}code,pre,p{line-height:1.5}body{font-size:16px}@media only screen and (min-width: 768px){code,pre,p{line-height:1.6}body{font-size:16px}}@media (-webkit-min-device-pixel-ratio: 2), (min-resolution: 192dpi){body{font-size:18px}}.not-for-medium{display:none}@media only screen and (min-width: 985px){.not-for-medium{display:block}}header.banner{background:#000;-moz-box-shadow:inset 0 2px 2px -2px #000;-webkit-box-shadow:inset 0 2px 2px -2px #000;box-shadow:inset 0 2px 2px -2px #000;margin-bottom:0}header.banner .nav-primary{-moz-box-shadow:none;-webkit-box-shadow:none;box-shadow:none;*zoom:1}header.banner .nav-primary:before,header.banner .nav-primary:after{content:"";display:table}header.banner .nav-primary:after{clear:both}header.banner nav.nav-primary{border-bottom:1px solid #262626;overflow:visible}header.banner nav.nav-primary .user-nav{float:right;margin-right:20px}header.banner nav.nav-primary .user-dropdown:hover ul:after{display:none}header.banner nav.nav-primary .user-dropdown .menu-link img{margin-right:10px}header.banner nav.nav-primary .user-dropdown .menu-link img.hover{display:none}header.banner nav.nav-primary .user-dropdown .menu-link img.normal{display:inline-block}header.banner nav.nav-primary .user-dropdown .open .menu-link img.hover,header.banner nav.nav-primary .user-dropdown .menu-link:hover img.hover{display:inline-block}header.banner nav.nav-primary .user-dropdown .open .menu-link img.normal,header.banner nav.nav-primary .user-dropdown .menu-link:hover img.normal{display:none}header.banner nav.nav-primary .user-dropdown ul{-webkit-border-radius:0 0 4px 4px;-moz-border-radius:0 0 4px 4px;border-radius:0 0 4px 4px;margin-top:-2px;background-color:#fff;border-width:0}header.banner nav.nav-primary .user-dropdown ul a:hover{background-color:transparent}header.banner nav.nav-primary #user-dropdown .dropdown ul{width:auto}header.banner .nav-primary.nav-right .logo-ubuntu{-moz-background-size:73px 30px;-webkit-background-size:73px 30px;-o-background-size:73px 30px;background-size:73px 30px;background-image:url(../img/logos/logo.svg);background-position:20px;background-repeat:no-repeat;min-width:120px;padding-top:6px;margin-left:0}body.no-svg header.banner .nav-primary.nav-right .logo-ubuntu{background-image:url(../img/logos/logo.png)}input[type=text]::-ms-reveal,input[type=text]::-ms-clear{display:none;width:0;height:0}input[type="search"]::-webkit-search-decoration,input[type="search"]::-webkit-search-cancel-button,input[type="search"]::-webkit-search-results-button,input[type="search"]::-webkit-search-results-decoration{display:none}.contextual-bar{overflow:hidden;background-color:#fff;border-bottom:1px solid #d4d4d4}form.search-form{overflow:hidden;float:right;width:100%;position:relative}form.search-form input{-webkit-border-radius:0;-moz-border-radius:0;border-radius:0;-webkit-box-sizing:border-box;-moz-box-sizing:border-box;box-sizing:border-box;border:0;border-left:0;margin:0;width:100%;height:50px;float:left;font-size:1em;padding-top:0;padding-bottom:0;padding-right:30px;background-color:transparent;-webkit-appearance:none}form.search-form input:focus{border-color:#dd4814}form.search-form button[type=submit],form.search-form button[type=submit]:hover{position:absolute;top:10px;right:10px;display:block;height:30px;width:30px;padding:0;line-height:0;-webkit-appearance:none;background:transparent}form.search-form button img{height:16px}.contextual-nav{border:0;display:block;margin:0;padding-left:10px;background-color:transparent;overflow:hidden;float:left}.contextual-nav li,.contextual-nav li:last-child{font-size:0.875em;float:left;list-style-type:none;margin:0;margin-left:5px}.contextual-nav li a:link,.contextual-nav li a:visited,.contextual-nav .contextual-nav__label{display:block;color:#333;font-weight:300;text-align:center;padding:16px 10px 10px 10px;border-bottom:3px solid transparent}.contextual-nav .contextual-nav__label{color:#cdcdcd}.contextual-nav li a:hover{border-bottom-color:#dd4814;text-decoration:none;color:#dd4814}.contextual-nav li a.active{border-bottom:3px solid #dd4814}.opera-mini header.banner .logo-ubuntu,.no-svg header.banner .logo-ubuntu{background-image:url(../img/logos/logo.png)}@media only screen and (min-width: 769px){header.banner .nav-primary ul li,header.banner .nav-primary ul li:last-child{border-bottom:0;width:auto}header.banner nav.nav-primary li a:link,header.banner nav.nav-primary li a:visited{border-left:1px solid #262626;font-weight:400}header.banner nav.nav-primary ul li a.active{padding-bottom:10px;background-color:#0e0c0b;border-bottom:3px solid #dd4814;border-left:1px solid #262626}header.banner nav.nav-primary ul li{border-left:1px solid #262626}header.banner nav.nav-primary ul li a:hover{background-color:#dd4814}header.banner nav.nav-primary ul{background-color:transparent;border-right:1px solid #262626;display:block}header.banner nav.nav-primary ul li:last-child{border-left:1px solid #262626;border-right:0}header.banner .nav-primary ul li a:active,header.banner .nav-primary ul li a:hover,header.banner .nav-primary ul li a:visited,header.banner nav.nav-primary ul li a:link{border-left:0}header.banner .nav-primary ul li a.external:hover{background-image:url("../img/icons/external-link-grey.png")}form.search-form{width:325px}form.search-form input{border-left:1px solid #d4d4d4;margin:0 20px;width:250px;font-size:0.875em}}@media only screen and (min-width: 1030px){header.banner{height:48px;overflow:hidden}header.banner .nav-primary{width:100%}}body{background-repeat:repeat}.row{border:0;background-color:rgba(255,255,255,0.6)}@media only screen and (min-width: 769px){.append-one{margin-right:10.6%}.row{padding:50px 40px 30px}}.inner-wrapper{*zoom:1;-webkit-border-radius:0;-moz-border-radius:0;border-radius:0;-moz-box-shadow:none;-webkit-box-shadow:none;box-shadow:none;background-color:transparent;background-image:none;margin:0 auto;padding-bottom:0;float:none}.inner-wrapper:before,.inner-wrapper:after{content:"";display:table}.inner-wrapper:after{clear:both}.wrapper{position:static;background:transparent;width:100%;overflow:hidden}img.touch-border{margin-bottom:-50px}.inner-wrapper,footer.global .legal{max-width:1030px;width:auto;padding-left:0;padding-right:0}.touch-top{margin-top:-50px}@media only screen and (max-width: 1030px){.inner-wrapper,footer.global .legal{padding-left:8px;padding-right:8px;width:auto}}.footer-cta{background:#fff;padding-bottom:20px}.footer-wrapper.strip-light{background-color:#fff}.solutions-cta{height:60px;background-color:#dd4814;line-height:60px;text-align:center}.solutions-cta a{color:#fff;font-size:1.25em}footer.global{background-color:white;box-shadow:none;padding-top:0}footer.global .row{padding:10px 0 0}footer.global .two-col{width:46%;float:left;display:inline-block;min-height:200px}footer.global h2{padding-bottom:0;color:#888;font-size:16px}footer.global nav .canonlist ul li,footer.global nav .additional-info ul li{min-height:0;width:48%;float:left}footer.global ul.bullets li:after{line-height:1;color:#888;content:"•";vertical-align:middle;margin:0 5px}footer.global ul.inline li:last-child{width:auto}footer.global a.external{background-image:url("../img/icons/external-link-dark.png")}footer.global .top-link{margin-bottom:10px}footer.global a{color:#333}footer.global a:hover{color:#dd4814}footer.global .legal{background-image:none}footer.global .legal.has-cookie{padding-bottom:70px}footer.global .inner-wrapper{overflow:visible}footer.global a.link-cta-positive,footer.global a.link-cta-negative{width:auto;margin-top:10px;padding-left:20px;padding-right:20px;color:#fff;font-size:14px}footer.global .section__title{background:none;cursor:default}.legal-inner{clear:both;overflow:hidden;float:left;width:100%;padding:20px 10px 0;margin:-3px -10px 0}.social,.social--right{margin-left:0}.social .social__item,.social--right .social__item{display:inline;float:left;padding-right:1em}.social .social__google,.social .social__facebook,.social .social__twitter,.social--right .social__google,.social--right .social__facebook,.social--right .social__twitter{background-image:url("../img/icons/icon-social.png");display:block;width:45px;height:44px}.social .social__google.social__twitter:hover,.social .social__facebook.social__twitter:hover,.social .social__twitter.social__twitter:hover,.social--right .social__google.social__twitter:hover,.social--right .social__facebook.social__twitter:hover,.social--right .social__twitter.social__twitter:hover{background-position:0 -45px}.social .social__google.social__facebook,.social .social__facebook.social__facebook,.social .social__twitter.social__facebook,.social--right .social__google.social__facebook,.social--right .social__facebook.social__facebook,.social--right .social__twitter.social__facebook{background-position:90px 0}.social .social__google.social__facebook:hover,.social .social__facebook.social__facebook:hover,.social .social__twitter.social__facebook:hover,.social--right .social__google.social__facebook:hover,.social--right .social__facebook.social__facebook:hover,.social--right .social__twitter.social__facebook:hover{background-position:90px -45px}.social .social__google.social__google,.social .social__facebook.social__google,.social .social__twitter.social__google,.social--right .social__google.social__google,.social--right .social__facebook.social__google,.social--right .social__twitter.social__google{background-position:135px 0}.social .social__google.social__google:hover,.social .social__facebook.social__google:hover,.social .social__twitter.social__google:hover,.social--right .social__google.social__google:hover,.social--right .social__facebook.social__google:hover,.social--right .social__twitter.social__google:hover{background-position:135px -45px}@media only screen and (min-width: 768px){.social--right{float:right}}#additional-info{border-bottom:0}#additional-info h2:before{background-image:url("../img/icons/external-link-grey.svg"),none;background-repeat:no-repeat;background-size:14px 14px;content:"";display:inline-block;height:15px;margin-right:3px;position:relative;top:3px;width:15px}#additional-info div li{border-left:1px solid #d4d7d4;box-sizing:border-box;display:block;float:left;margin:0;padding:0;width:50%}#additional-info div li a{border-bottom:1px solid #d4d7d4;box-sizing:border-box;color:#333333;display:block;float:left;margin:0;overflow:hidden;padding:8px 10px;text-align:left;white-space:normal;width:100%}#additional-info .section__title{border-bottom:1px solid #d4d7d4;background-position:100% .1em}html.opera-mini footer #nav-global h2:before,html.opera-mini footer #additional-info h2:before,html.no-svg footer #nav-global h2:before,html.no-svg footer #additional-info h2:before{background-image:url("../img/icons/external-link-grey.png")}@media only screen and (min-width: 769px){.footer-wrapper.strip-light{white-space:nowrap}footer.global{padding-top:40px;padding-bottom:40px}footer.global .two-col{width:14.89361%;display:inline-block;min-height:0}footer.global .section{min-height:160px;margin-right:40px;padding-bottom:0;border-right:1px dotted #aaa;border-bottom:0}footer.global .section:last-child{margin-right:0;border-right:0}footer.global li{display:inline;float:left}footer.global ul.no-bullets li{border-right:1px dotted #aaa;padding-right:15px;padding-left:15px}footer.global ul.no-bullets li a{font-size:16px}footer.global ul.no-bullets li a:hover{color:#dd4814}footer.global ul.no-bullets li:last-child{border-right:none}footer.global ul.no-bullets li:first-child{padding-left:0px}#additional-info .section__title{border-bottom:0}#additional-info div li,#additional-info div a:link{width:100%;border:0}}.actions .actions__social-item--twitter,.actions .actions__social-item--google-plus{text-indent:-99999px;background-image:url("../img/icons/icon-social.svg");background-repeat:no-repeat;height:44px;width:44px;overflow:hidden;display:block}.actions .actions__social-item--twitter{background-position:0 0}.actions .actions__social-item--twitter:hover{background-position:0 -45px}.actions .actions__social-item--google-plus{background-position:-45px 0}.actions .actions__social-item--google-plus:hover{background-position:-45px -45px}.anchor{display:inline-block;margin-left:3px;opacity:.0;position:relative;top:1px;width:1em;height:1em;background:url("../img/icons/anchor_16.svg") 0 80% no-repeat;background-size:16px;-moz-transition:opacity .1s;-webkit-transition:opacity .1s;transition:opacity .1s}h1:hover .anchor,h2:hover .anchor,h3:hover .anchor,h4:hover .anchor,dt:hover .anchor,li:hover .anchor{opacity:1}.box{-webkit-border-radius:0;-moz-border-radius:0;border-radius:0;-moz-box-shadow:none;-webkit-box-shadow:none;box-shadow:none;display:block;margin-bottom:0;border-top:0;border-left:0;border-right:0;padding-left:0;padding-right:0}.box-dim{background-color:#fafafa}@media only screen and (min-width: 768px){.box{-webkit-border-radius:4px;-moz-border-radius:4px;border-radius:4px;-moz-box-shadow:0px 1px 1px 0px rgba(0,0,0,0.15);-webkit-box-shadow:0px 1px 1px 0px rgba(0,0,0,0.15);box-shadow:0px 1px 1px 0px rgba(0,0,0,0.15);display:inline-block;margin-bottom:20px;padding-left:20px;padding-right:20px;border:0}}a.indent{-moz-box-shadow:inset 0 1px 2px 0 #333;-webkit-box-shadow:inset 0 1px 2px 0 #333;box-shadow:inset 0 1px 2px 0 #333;background:rgba(0,0,0,0.1);padding:10px 30px;text-weight:normal}a.indent:hover{background:rgba(0,0,0,0.2)}a.link-cta-positive,a.link-cta-negative{-webkit-border-radius:3px;-moz-border-radius:3px;border-radius:3px;-webkit-box-sizing:border-box;-moz-box-sizing:border-box;box-sizing:border-box;background-color:#dd4814;color:#fff;display:inline-block;font-size:1.14286em;font-weight:300;text-decoration:none;margin:0;padding:8px 14px;text-align:center;-moz-transition:background .2s;-webkit-transition:background .2s;transition:background .2s;width:100%}a.link-cta-positive:hover,a.link-cta-negative:hover{background-color:#ae3910}a.link-cta-positive .external,a.link-cta-negative .external{padding-right:1em;background-image:url("../img/icons/external-link-white.svg");background-repeat:no-repeat;background-size:11px;background-position:right top}@media only screen and (min-width: 769px){a.link-cta-positive .external,a.link-cta-negative .external{padding-right:.7em}}a.link-cta-negative{background-color:#b2b2b2}a.link-cta-negative:hover{background-color:#888}.charms__list{list-style:none;margin-bottom:1em;margin-left:0;border-bottom:1px dotted #d4d4d4}.charms__list .charms__list--config{display:none}.charms__list .charms__list--toggle{display:block}.charms__list .charms__list--toggle.is-open+.charms__list--config{display:block}.charms__list .charms__list--config-name{border-top:0}.charms__list--item{font-size:1em;border-top:1px dotted #d4d4d4;padding:10px 0 0 10px;margin-bottom:10px}.charms__list--item:last-of-type{margin-bottom:10px}.charms__list--icon{margin-right:.4em;width:25px;height:25px}.charms__list--toggle{background:url("../img/shared/icon-arrow-down.svg") no-repeat center center;width:16px;height:100%;float:right;text-indent:-99999px;margin-right:20px}.charms__list--toggle.is-open{background-image:url("../img/shared/icon-arrow-up.svg");background-size:14px}.charms__list--config{padding-left:35px;padding-bottom:20px}.charms__list--config-name{font-size:1em;margin-top:15px;padding-top:15px;font-weight:400;border-top:1px dotted #d4d4d4}.charms__list--config-name:first-of-type{border-top:0}.charms__list--config-type{font-weight:400}.charms__list--config-description,.charms__list--config-setting{margin-left:30px;margin-top:8px}.charms__list--config-setting{font-size:0.875em;color:#888;font-family:"Ubuntu Mono","Consolas","Monaco","Lucida Console","Courier New",Courier,monospace}body.no-svg .charms__list .charms__list--toggle{background-image:url("../img/shared/icon-arrow-down.png")}body.no-svg .charms__list .charms__list--toggle.is-open{background-image:url("../img/shared/icon-arrow-up.png")}pre{background:transparent;border:1px solid #888;margin:0 0 1.5em 0}pre:not(:first-child){margin-top:1.5em}code.language-bash .comment{color:#888}.cookie-policy{-moz-box-shadow:0 -1px 2px rgba(0,0,0,0.2);-webkit-box-shadow:0 -1px 2px rgba(0,0,0,0.2);box-shadow:0 -1px 2px rgba(0,0,0,0.2);background-color:#fae4dc;bottom:0;position:fixed;width:100%;z-index:100}.cookie-policy p{-webkit-box-sizing:border-box;-moz-box-sizing:border-box;box-sizing:border-box;font-size:13px;margin-bottom:0;margin-left:0;padding:8px 0;width:100%}.cookie-policy .link-cta{background-image:url(../img/icons/close-orange.svg);background-repeat:no-repeat;color:#fff;float:right;font-size:1em;height:15px;margin:12px 0;margin-top:12px;padding:0;text-decoration:none;text-indent:-9999px;width:16px}html.no-svg .cookie-policy .link-cta,html.opera-mini .cookie-policy .link-cta{background-image:url(../img/icons/close-orange.png)}html.opera-mini .cookie-policy{position:relative;top:0}.deploy-command{margin-bottom:10px;position:relative}.deploy-command .deploy-command__field{-webkit-box-sizing:border-box;-moz-box-sizing:border-box;box-sizing:border-box;-webkit-border-radius:4px 4px 4px 4px;-moz-border-radius:4px 4px 4px 4px;border-radius:4px 4px 4px 4px;-moz-box-shadow:inset 0 1px 2px 0 rgba(0,0,0,0.12);-webkit-box-shadow:inset 0 1px 2px 0 rgba(0,0,0,0.12);box-shadow:inset 0 1px 2px 0 rgba(0,0,0,0.12);background-image:url("../img/icons/code-snippet_16.svg");background-repeat:no-repeat;background-position:5px center;background-color:#fff;background-size:1.142857143em;width:100%;height:37px;border:1px solid #c1c1c1;padding:.6em;color:#888;padding-left:2em;font-size:0.875em;white-space:nowrap;overflow:hidden;cursor:text}.deploy-command .command2clipboard__clip{cursor:pointer;-webkit-border-radius:0 4px 4px 0;-moz-border-radius:0 4px 4px 0;border-radius:0 4px 4px 0;line-height:1;position:absolute;right:1px;top:1px;background-color:#fff;padding:9px 8px 7px;border-left:1px solid #b2b2b2;display:none}.deploy-command .command2clipboard__clip.zeroclipboard-is-hover{background-color:#eee}@media only screen and (min-width: 1030px){.deploy-command .command2clipboard__clip{display:inline-block}}.dropdown-menu{position:relative;display:block}.dropdown-menu.open .menu-link{background-color:#000}.dropdown-menu.open .dropdown{display:block}.dropdown-menu .menu-link .border-box{display:block;color:#f2f2f4}.dropdown-menu .dropdown{-webkit-border-radius:0 0 4px 4px;-moz-border-radius:0 0 4px 4px;border-radius:0 0 4px 4px;display:none;position:absolute;z-index:1000;top:0;left:0;right:0;width:auto;background-color:#fff;box-shadow:0 1px 5px rgba(0,0,0,0.2)}.dropdown-menu .dropdown.right{left:auto;right:0;text-align:right}.dropdown-menu .dropdown.narrow{min-width:140px;width:auto}.dropdown-menu .dropdown a,.dropdown-menu .dropdown p,.dropdown-menu .dropdown li{color:#333}.dropdown-menu .dropdown p{padding:11px 20px}.dropdown-menu .dropdown header,.dropdown-menu .dropdown footer{background-color:#fff}.dropdown-menu .dropdown header{padding:11px 20px;color:#f2f2f4;font-size:16px;font-weight:300}.dropdown-menu .dropdown footer{padding:20px}.dropdown-menu .dropdown ul{width:auto;left:0;right:0}.dropdown-menu .dropdown ul li a{width:auto;display:block;padding:15px 20px}.files .files__list{list-style:none;margin-bottom:1em;border-left:1px solid #cbcbcb;margin-left:1em}@media only screen and (min-width: 768px){.files .files__list{margin-left:0}}.files .files__list li{position:relative}.files .files__list li a:link,.files .files__list li a:visited{color:#333;text-decoration:none}.files .files__list li:before{content:'';width:12px;height:1px;background:#d4d4d4;display:inline-block;position:relative;top:-4px;margin-right:5px}.files .files__list li:last-child:after{content:'';width:4px;height:1em;position:absolute;display:block;left:-2px;top:.85em;background:#fff}.files .files__list ul:last-child>li a:after{content:'';width:4px;height:3em;position:absolute;display:block;left:-1.8em;top:-1.45em;background:white;cursor:default}.files .files__list .files__list{margin-left:1.3em}.files .files__list .files__list--item,.files .files__list .files__list--item-folder{font-size:0.875em;margin-bottom:0.75em}.files .files__list .files__list--item-folder{background-position:center right;background-size:12px;cursor:pointer}.files .files__list .files__list--item-folder:after{font-size:14px;display:block;content:"-";position:absolute;left:-7px;top:4px;padding:0 4px;line-height:0.9em;background:#fff;border:1px solid #888}.files .files__list .files__list--item-folder.is-closed+ul{display:none}.files .files__list .files__list--item-folder.is-closed:after{content:"+";padding:0 2px}.files .files__actions--launchpad{background:url("../img/icons/icon-launchpad.svg") no-repeat;padding-left:1.4em}#main-content .row-hero{padding-top:20px;margin-top:0}#main-content .row-hero .intro{font-size:16px}@media only screen and (min-width: 769px){#main-content .row-hero{padding-top:60px}#main-content .row-hero .intro{font-size:1.4375em;margin-bottom:40px}}.how-to div div img{float:left;margin:0 20px 20px 0}header.banner a.external,header.banner a.external:hover{background-image:url("../img/icons/external-link-grey.png")}a.external,a.external:hover,header.banner nav.nav-primary ul li a.external:link,header.banner nav.nav-primary ul li a.external:visited,header.banner nav.nav-primary ul li a.external:hover{background-repeat:no-repeat}a.external,a.external:hover header.banner nav.nav-primary ul li a.external:link,header.banner nav.nav-primary ul li a.external:visited,header.banner nav.nav-primary ul li a.external:hover{background-position:right 14px top 14px;padding-right:35px;background-size:auto}@media only screen and (max-width: 769px){header.banner nav.nav-primary ul li a.external:link,header.banner nav.nav-primary ul li a.external:visited,header.banner nav.nav-primary ul li a.external:hover{background:none}header.banner nav.nav-primary ul li a.external:after{display:inline-block;width:11px;height:11px;margin-left:0.25em;background-image:url("../img/icons/external-link-dark.png");vertical-align:text-top}}.list__icons{margin-left:0;margin-bottom:5px}.list__icons li{list-style:none;float:left;padding:8px 8px 0 0;margin-bottom:0}.list__icons li img{width:24px;height:24px;vertical-align:top}.list__tick{list-style-image:url("../img/icons/tick.png")}.list__middot{margin-left:0;list-style:none}.list__middot li{display:inline}.list__middot li:after{content:"•";color:#888;margin:0 5px 0 8px;vertical-align:middle}.list__middot li.files__actions--last:after{content:""}.combined-list .list li{border-bottom:1px dotted #888;padding:10px 0}@media only screen and (max-width: 767px){.combined-list .last-col .list li:last-of-type{border-bottom:0;padding-bottom:0}}@media only screen and (min-width: 768px){.combined-list .list li:last-of-type{border-bottom:0;padding-bottom:0}}.events-list li{position:relative;padding-bottom:20px}.events-list dd{margin-left:0;background-position:0 center;background-repeat:no-repeat;background-size:20px 20px;padding:6px 20px 6px 24px}.events-list .event-map{display:none}.events-list .event-date{background-image:url("../img/icons/calendar.svg")}.events-list .location{background-image:url("../img/icons/location.svg")}@media only screen and (min-width: 769px){.events-list .event-details-wrapper{padding-left:120px}.events-list .event-map{-webkit-border-radius:10px;-moz-border-radius:10px;border-radius:10px;position:absolute;left:0;top:0;height:100px;width:100px;float:left;margin-right:10px;margin-top:5px;overflow:hidden;display:block}}body.no-svg .events-list .event-date{background-image:url("../img/icons/calendar.png")}body.no-svg .events-list .location{background-image:url("../img/icons/location.png")}.maintainers .maintainer__email{display:block}.ratings ul{margin-left:2px;margin-bottom:0}.ratings ul li{margin-bottom:0}.ratings ul li img{vertical-align:text-top}.ratings ul li:first-of-type{margin-left:0}.revisions__list{list-style:none;margin-left:0}.revisions__list .revisions__list-item{margin-bottom:1em}.revisions__list .revisions__list_meta{color:#888;margin-bottom:.2em}.revisions__list .revisions__list_meta_date{float:right}.section{-webkit-box-sizing:border-box;-moz-box-sizing:border-box;box-sizing:border-box;overflow:hidden;padding-bottom:20px;padding-top:20px}.section .section__title{background:url(../img/shared/icon-arrow-up.svg) no-repeat center right;cursor:pointer;margin-bottom:1em}.section.is-closed{height:60px}.section.is-closed .section__title{background-image:url(../img/shared/icon-arrow-down.svg)}.no-svg .row.section .section__title{background:url(../img/shared/icon-arrow-up.png)}.no-svg .row.section.is-closed .section__title{background:url(../img/shared/icon-arrow-down.png)}footer .section{margin-bottom:1em;padding-bottom:1em}footer .section.is-closed{height:auto;padding-bottom:0}footer .section.is-closed ul{display:none}@media only screen and (min-width: 769px){.row .section .section__title{background-image:none;cursor:auto}.row .section.is-closed{height:auto}}.list--concealed .list-item{display:none}.list--concealed .list-item:first-of-type{display:list-item}.list--concealed.list--visible-6 .list-item:nth-child(-n+6){display:list-item}.list--concealed.list--visible-4 .list-item:nth-child(-n+4){display:list-item}.list--concealed.list--visible-2 .list-item:nth-child(-n+2){display:list-item}.list--concealed a.btn__see--less{display:none}.list--concealed a.btn__see--more{display:inline}.list--revealed .list__controls,.list--concealed .list__controls{display:block;margin-top:2em}.list--revealed .list--item{display:list-item}.list--revealed a.btn__see--less{display:inline}.list--revealed a.btn__see--more{display:none}a.btn__see--more,a.btn__see--less{-moz-transition:background .2s;-webkit-transition:background .2s;transition:background .2s;-webkit-border-radius:3px;-moz-border-radius:3px;border-radius:3px;color:#333;border-radius:2px;border:1px solid #b2b2b2;background:#fff;padding:0.384615385em 1.153846154em}a.btn__see--more:hover,a.btn__see--less:hover{background:#eee;text-decoration:none}.strip-dark,.strip-light{clear:both}.strip-dark{background-color:#2c001e;background-image:none;background-repeat:repeat;color:#fff}.strip-dark.solid{background-image:none;background-color:#2c001e}.strip-dark ul,.strip-dark ol{margin:0;padding:0}.strip-dark .icon,.strip-dark ol span{-moz-background-size:40px 40px;-webkit-background-size:40px 40px;-o-background-size:40px 40px;background-size:40px 40px;background-image:url(../img/icons/list-icon-background.png);background-repeat:no-repeat;display:block;margin:0 20px 20px 0;padding:24px;float:left;width:16px;height:16px;padding:12px}.strip-light .icon,.strip-dark .icon{position:absolute}.strip-light{background-color:rgba(255,255,255,0.6)}.strip-dark ol,.strip-dark ul{padding:20px 0}.strip-light .icon{display:block;background-image:url(../img/icons/list-icon-background.png);padding:24px;margin:0 auto 48px}.strip-dark .connected-list li,.strip-light .connected-list li{margin-bottom:10px;min-height:52px}.strip-dark .connected-list li p,.strip-light .connected-list li p,.strip-dark .connected-list li h3,.strip-light .connected-list li h3{padding-left:50px}.strip-dark ol.connected-list li p .strip-dark ol.connected-list li h3{padding-left:50px}.strip-dark ol.connected-list li span{float:left;font-size:22px;font-weight:normal;height:26px;margin-left:0;margin-right:20px;padding-top:2px;position:absolute;text-align:center;width:16px}.strip-white{background:#fff}.strip-trans{background:transparent}.strip-green{background-image:linear-gradient(to right, #6fad23 0%, #7cc227 100%);overflow:hidden}.strip-green,.strip-green a{color:#fff}.strip-blue{background-image:linear-gradient(to right, #1076a2 0%, #359fcd 100%);overflow:hidden}.strip-blue,.strip-blue a{color:#fff}.tag-list{list-style:none;margin:0}.tag-list--item{display:inline-block;text-transform:lowercase}.tag-list--item a:link,.tag-list--item a:visited{color:#333}.tag-list--item a:after{content:','}.tag-list--item:last-child a:after{content:''}#twitter-feed,#blog-feed{margin:35px 0}#twitter-feed>ul,#blog-feed>ul{list-style:none;margin-left:0}#twitter-feed>ul li,#blog-feed>ul li{position:relative;margin-bottom:30px}#twitter-feed .user,#blog-feed .user{font-size:0.875em;margin-bottom:0.5em}#twitter-feed .user img,#blog-feed .user img{-webkit-border-radius:4px;-moz-border-radius:4px;border-radius:4px;width:24px;height:24px;vertical-align:middle;margin-right:5px}#twitter-feed .user a:link,#twitter-feed .user a:visited,#twitter-feed .user a:hover,#blog-feed .user a:link,#blog-feed .user a:visited,#blog-feed .user a:hover{text-decoration:none}#twitter-feed .user span[data-scribe="element:name"],#blog-feed .user span[data-scribe="element:name"]{color:#333}#twitter-feed .tweet,#blog-feed .tweet{padding-left:33px;margin-bottom:.3em}#twitter-feed .timePosted,#blog-feed .timePosted{padding-left:33px;font-size:0.875em}#twitter-feed .interact,#blog-feed .interact{padding-left:33px}#twitter-feed .interact a:link,#twitter-feed .interact a:visited,#blog-feed .interact a:link,#blog-feed .interact a:visited{margin-right:20px}@media only screen and (min-width: 769px){#twitter-feed .timePosted{position:absolute;top:0;right:0;padding-left:0}}.spaced-segment{margin-bottom:50px}.spaced-segment h3{margin-bottom:1.3em}.strip-dark,.strip-light{clear:both}.strip-dark{background-color:#2c001e;background-image:none;background-repeat:repeat;color:#fff}.strip-dark.solid{background-image:none;background-color:#2c001e}.strip-dark ul,.strip-dark ol{margin:0;padding:0}.strip-dark .icon,.strip-dark ol span{-moz-background-size:40px 40px;-webkit-background-size:40px 40px;-o-background-size:40px 40px;background-size:40px 40px;background-image:url(../img/icons/list-icon-background.png);background-repeat:no-repeat;display:block;margin:0 20px 20px 0;padding:24px;float:left;width:16px;height:16px;padding:12px}.strip-light .icon,.strip-dark .icon{position:absolute}.strip-light{background-color:rgba(255,255,255,0.6)}.strip-dark ol,.strip-dark ul{padding:20px 0}.strip-light .icon{display:block;background-image:url(../img/icons/list-icon-background.png);padding:24px;margin:0 auto 48px}.strip-dark .connected-list li,.strip-light .connected-list li{margin-bottom:10px;min-height:52px}.strip-dark .connected-list li p,.strip-light .connected-list li p,.strip-dark .connected-list li h3,.strip-light .connected-list li h3{padding-left:50px}.strip-dark ol.connected-list li p .strip-dark ol.connected-list li h3{padding-left:50px}.strip-dark ol.connected-list li span{float:left;font-size:22px;font-weight:normal;height:26px;margin-left:0;margin-right:20px;padding-top:2px;position:absolute;text-align:center;width:16px}.strip-white{background:#fff}.strip-trans{background:transparent}.strip-green{background-image:linear-gradient(to right, #6fad23 0%, #7cc227 100%);overflow:hidden}.strip-green,.strip-green a{color:#fff}.strip-blue{background-image:linear-gradient(to right, #1076a2 0%, #359fcd 100%);overflow:hidden}.strip-blue,.strip-blue a{color:#fff}@media only screen and (min-width: 769px){.tip,.command2clipboard__clip{position:relative;display:inline-block}.tip .tip-content,.command2clipboard__clip .tip-content{position:absolute;z-index:98;left:-1000px;right:-1000px;top:-30px;font-weight:300;margin:auto;display:block;text-align:center;white-space:nowrap}.tip:hover .tip-content:after,.command2clipboard__clip.zeroclipboard-is-hover .tip-content:after{display:table;z-index:98;margin:auto;color:#fff;border-radius:3px;background:#000;box-shadow:none;font-size:12px;content:attr(data-tooltip);padding:4px 6px;white-space:nowrap;text-align:center}.tip:hover .tip-content:before,.command2clipboard__clip.zeroclipboard-is-hover .tip-content:before{position:absolute;top:100%;left:50%;margin-left:-5px;content:'';border:solid transparent;border-width:5px;border-top-color:#000}}.fake{display:block}*{margin:0}html{height:100%}body{height:100%;font-size:1.0em;font-family:'Ubuntu', Arial, 'libra sans', sans-serif;font-weight:300}hr{border:none;background:#B2B2B2;width:100%;height:1px;display:block;width:100%;float:left;margin-bottom:20px}ul,ol{margin-left:0}a:link,a:visited{color:#333;text-decoration:none;border-bottom:1px solid #CDCDCD}a:link:hover,a:visited:hover{color:#dd4814;text-decoration:none}a:active,a:focus{outline:none}::selection{color:#FFF;background:#dd4814}::-moz-selection{color:#FFF;background:#dd4814}@font-face{font-family:"Ubuntu";font-style:normal;font-weight:300;src:url("../fonts/ubuntu-l-webfont.eot?") format("eot"),url("../fonts/ubuntu-l-webfont.woff") format("woff"),url("../fonts/ubuntu-l-webfont.ttf") format("truetype"),url("../fonts/ubuntu-l-webfont.svg#Ubuntu") format("svg")}@font-face{font-family:"Ubuntu";font-style:italic;font-weight:300;src:url("../fonts/ubuntu-li-webfont.eot?") format("eot"),url("../fonts/ubuntu-li-webfont.woff2") format("woff2"),url("../fonts/ubuntu-li-webfont.woff") format("woff"),url("../fonts/ubuntu-li-webfont.ttf") format("truetype"),url("../fonts/ubuntu-li-webfont.svg#Ubuntu") format("svg")}@font-face{font-family:"Ubuntu";font-style:normal;font-weight:400;src:url("../fonts/ubuntu-r-webfont.eot?") format("eot"),url("../fonts/ubuntu-r-webfont.woff") format("woff"),url("../fonts/ubuntu-r-webfont.ttf") format("truetype"),url("../fonts/ubuntu-r-webfont.svg#Ubuntu") format("svg")}@font-face{font-family:"Ubuntu";font-style:normal;font-weight:500;src:url("../fonts/ubuntu-m-webfont.eot?") format("eot"),url("../fonts/ubuntu-m-webfont.woff") format("woff"),url("../fonts/ubuntu-m-webfont.ttf") format("truetype"),url("../fonts/ubuntu-m-webfont.svg#Ubuntu") format("svg")}@font-face{font-family:"Ubuntu";font-style:italic;font-weight:500;src:url("../fonts/ubuntu-mi-webfont.eot?") format("eot"),url("../fonts/ubuntu-mi-webfont.woff2") format("woff2"),url("../fonts/ubuntu-mi-webfont.woff") format("woff"),url("../fonts/ubuntu-mi-webfont.ttf") format("truetype"),url("../fonts/ubuntu-mi-webfont.svg#Ubuntu") format("svg")}@font-face{font-family:"Ubuntu";font-style:normal;font-weight:700;src:url("../fonts/ubuntu-b-webfont.eot?") format("eot"),url("../fonts/ubuntu-b-webfont.woff2") format("woff2"),url("../fonts/ubuntu-b-webfont.woff") format("woff"),url("../fonts/ubuntu-b-webfont.ttf") format("truetype"),url("../fonts/ubuntu-b-webfont.svg#Ubuntu") format("svg")}@font-face{font-family:'Ubuntu';font-style:italic;font-weight:400;src:url("https://themes.googleusercontent.com/static/fonts/ubuntu/v5/GZMdC02DTXXx8AdUvU2etw.woff") format("woff")}@font-face{font-family:'Ubuntu';font-style:italic;font-weight:700;src:url("https://themes.googleusercontent.com/static/fonts/ubuntu/v5/pqisLQoeO9YTDCNnlQ9bfz8E0i7KZn-EPnyo3HZu7kw.woff") format("woff")}.accordion{-webkit-box-sizing:border-box;-moz-box-sizing:border-box;box-sizing:border-box;-webkit-border-radius:2px;-moz-border-radius:2px;border-radius:2px;list-style:none;background:#FFF;box-shadow:0 1px 1px rgba(0,0,0,0.1);margin-bottom:40px}.disabled .accordion{opacity:.5;pointer-events:none}.accordion .accordion__title{border-bottom:1px dotted #B2B2B2;padding:13px 20px 12px;margin:0;font-size:1.3em}.accordion .accordion__tab{border-bottom:1px dotted #B2B2B2}.accordion .accordion__tab:last-of-type{border:none}.accordion .accordion__tab .accordion__tab-title{padding:12px 20px;margin:0;color:#888;cursor:pointer;background:transparent url("../img/icons/accordion-open.svg") top 20px right 20px no-repeat}.accordion .accordion__tab .accordion__tab-title.active{background-image:url("../img/icons/accordion-close.svg")}.accordion .accordion__tab .accordion__tab-title.active+.accordion__tab-content{max-height:400px;transition:max-height .5s ease-in;overflow-y:auto}.accordion .accordion__tab .accordion__tab-content{max-height:0;transition:max-height .5s ease-out;overflow:hidden}.accordion .accordion__tab .accordion__tab-content .accordion__tab-list{list-style-type:none;padding:0 20px 14px;margin:0}.accordion .accordion__tab .accordion__tab-content .accordion__tab-list .accordion__tab-item{margin-bottom:0.15em}.accordion .accordion__tab .accordion__tab-content .accordion__tab-list .accordion__tab-item .accordion__tab-link{-webkit-box-sizing:border-box;-moz-box-sizing:border-box;box-sizing:border-box;color:#333;width:100%;display:inline-block;padding-right:20px;border-bottom:0}.accordion .accordion__tab .accordion__tab-content .accordion__tab-list .accordion__tab-item .accordion__tab-link:hover{color:#dd4814;text-decoration:none}.disabled .accordion .accordion__tab .accordion__tab-content .accordion__tab-list .accordion__tab-item .accordion__tab-link{color:#333}.accordion .accordion__tab .accordion__tab-content .accordion__tab-list .accordion__tab-item.active{font-weight:400}.accordion .accordion__tab .accordion__tab-content .accordion__tab-list .accordion__tab-item.active .accordion__tab-link{background:transparent url("../img/icons/cross.svg") top 7px right 0px no-repeat}.accordion .accordion__tab .accordion__tab-content .accordion__tab-list .accordion__tab-item.active:hover{color:#dd4814}.accordion .accordion__tab .accordion__tab-content .accordion__tab-list .accordion__tab-item.active:hover .accordion__tab-link{color:#dd4814;background-image:url("../img/icons/cross-orange.svg")}.cta-group .cta-group__link{-webkit-box-sizing:border-box;-moz-box-sizing:border-box;box-sizing:border-box;-webkit-border-radius:3px 0px 0px 3px;-moz-border-radius:3px 0px 0px 3px;border-radius:3px 0px 0px 3px;display:inline-block;padding:10px 14px;text-align:center;color:#fff;background-color:#dd4814}.cta-group .cta-group__link:hover{cursor:pointer;text-decoration:none;background-color:#c03f11}.cta-group.secondary .cta-group__link{color:#dd4814;border:1px solid #b2b2b2;background-color:#FFF;line-height:1}.cta-group.secondary .cta-group__link:hover{cursor:pointer;background-color:#F2F2F2}a.link-cta-ubuntu,button.cta-ubuntu,input[type='submit'],form button[type='submit'],form input[type='submit']{color:#fff;font-size:1em;border:none;max-height:37px;border-bottom:0}a.link-cta-ubuntu:hover,button.cta-ubuntu:hover,input[type='submit']:hover,form button[type='submit']:hover,form input[type='submit']:hover{color:#fff}a.link-cta-ubuntu[disabled],a.link-cta-ubuntu.disabled,button.cta-ubuntu[disabled],button.cta-ubuntu.disabled,input[type='submit'][disabled],input[type='submit'].disabled,form button[type='submit'][disabled],form button[type='submit'].disabled,form input[type='submit'][disabled],form input[type='submit'].disabled{cursor:default;opacity:.5}a.link-cta-ubuntu.clear,button.cta-ubuntu.clear,input[type='submit'].clear,form button[type='submit'].clear,form input[type='submit'].clear{background:none;color:#333}a.link-cta-ubuntu.secondary,button.cta-ubuntu.secondary,input[type='submit'].secondary,form button[type='submit'].secondary,form input[type='submit'].secondary{color:#dd4814;border:1px solid #b2b2b2;background:#FFF}a.link-cta-ubuntu.secondary.external,button.cta-ubuntu.secondary.external,input[type='submit'].secondary.external,form button[type='submit'].secondary.external,form input[type='submit'].secondary.external{background-image:url("../img/external-link-black.svg");background-size:16px 16px;background-repeat:no-repeat;background-position:top 8px right 8px}a.link-cta-ubuntu.secondary:hover,button.cta-ubuntu.secondary:hover,input[type='submit'].secondary:hover,form button[type='submit'].secondary:hover,form input[type='submit'].secondary:hover{background-color:#F2F2F2;cursor:pointer}a.link-cta-ubuntu.secondary[disabled],a.link-cta-ubuntu.secondary.disabled,button.cta-ubuntu.secondary[disabled],button.cta-ubuntu.secondary.disabled,input[type='submit'].secondary[disabled],input[type='submit'].secondary.disabled,form button[type='submit'].secondary[disabled],form button[type='submit'].secondary.disabled,form input[type='submit'].secondary[disabled],form input[type='submit'].secondary.disabled{cursor:default;color:#f5ae95;border:1px solid #ddd;background:#FFF;opacity:1}a.link-cta-ubuntu.secondary[disabled]:hover,a.link-cta-ubuntu.secondary.disabled:hover,button.cta-ubuntu.secondary[disabled]:hover,button.cta-ubuntu.secondary.disabled:hover,input[type='submit'].secondary[disabled]:hover,input[type='submit'].secondary.disabled:hover,form button[type='submit'].secondary[disabled]:hover,form button[type='submit'].secondary.disabled:hover,form input[type='submit'].secondary[disabled]:hover,form input[type='submit'].secondary.disabled:hover{background:#FFF}a.link-cta-ubuntu.text-button,button.cta-ubuntu.text-button,input[type='submit'].text-button,form button[type='submit'].text-button,form input[type='submit'].text-button{background-color:transparent;color:#333}a.link-cta-ubuntu.text-button:hover,button.cta-ubuntu.text-button:hover,input[type='submit'].text-button:hover,form button[type='submit'].text-button:hover,form input[type='submit'].text-button:hover{text-decoration:underline}a.link-cta-ubuntu.full,button.cta-ubuntu.full,input[type='submit'].full,form button[type='submit'].full,form input[type='submit'].full{display:block;width:100%}@media screen and (max-width: 768px){a.link-cta-ubuntu,button.cta-ubuntu,input[type='submit'],form button[type='submit'],form input[type='submit']{margin-bottom:20px}}a.link-cta-ubuntu{line-height:20px}.cta-group{float:left;width:auto;clear:both;position:relative;overflow:hidden;min-width:150px}.cta-group .cta-group__link{-webkit-border-radius:3px;-moz-border-radius:3px;border-radius:3px;max-height:36px;padding-right:49px;width:100%;line-height:1.2;position:relative;margin:0;text-align:left}.cta-group .cta-group__link:after{-webkit-border-radius:0px 3px 3px 0px;-moz-border-radius:0px 3px 3px 0px;border-radius:0px 3px 3px 0px;content:'';display:block;height:36px;width:34px;background:red;position:absolute;top:0;right:0;background-image:url("../img/chevron-white.svg");background-color:#dd4814;background-repeat:no-repeat;background-position:center}.cta-group .cta-group__link:hover{background-color:#c03f11}.cta-group .cta-group__link:hover:after{background-color:#c03f11}.cta-group .cta-group__dropdown{-webkit-box-sizing:border-box;-moz-box-sizing:border-box;box-sizing:border-box;-webkit-border-radius:3px;-moz-border-radius:3px;border-radius:3px;right:0;list-style:none;background:#fff;box-shadow:0 1px 1px rgba(0,0,0,0.1);z-index:20;max-height:1000px;transition:max-height 0.3s ease-in;overflow:hidden;position:relative;clear:both}.cta-group .cta-group__dropdown.ng-hide{display:block !important;max-height:0;overflow:hidden;transition:max-height 0.3s ease-out}.cta-group .cta-group__dropdown .cta-group__item{-webkit-box-sizing:border-box;-moz-box-sizing:border-box;box-sizing:border-box;float:left;clear:both;padding:5px 10px;margin:0}.cta-group .cta-group__dropdown .cta-group__item a{color:#333;cursor:pointer;width:100%;float:left;margin:0}.cta-group .cta-group__dropdown .cta-group__item a:hover{color:#dd4814;text-decoration:none}.cta-group.secondary .cta-group__link{float:left;max-height:36px;width:100%}.cta-group.secondary .cta-group__link:after{-webkit-box-sizing:border-box;-moz-box-sizing:border-box;box-sizing:border-box;background-image:url("../img/icons/accordion-open.svg");background-repeat:no-repeat;background-color:#fff;border:1px solid #b2b2b2;border-left:none;top:-1px;right:-1px}.cta-group.secondary .cta-group__link:hover{background-color:#F2F2F2}.cta-group.secondary .cta-group__link:hover:after{background-color:#F2F2F2}.flash-messages{margin:0px auto;padding:0;max-width:1440px}@media screen and (max-width: 1030px){.flash-messages{margin:0px 10px 20px}}.flash-messages .flash-messages__item{-webkit-box-sizing:border-box;-moz-box-sizing:border-box;box-sizing:border-box;-webkit-border-radius:2px;-moz-border-radius:2px;border-radius:2px;list-style:none;padding:15px 20px 15px 45px;margin:0;font-weight:400;font-size:0.875em;background:#FFF;background-position:top 50% left 15px;background-repeat:no-repeat;margin:0 0 20px;box-shadow:0 1px 1px rgba(0,0,0,0.1)}.flash-messages .flash-messages__item.info{background-image:url("../img/icons/info.png");background-image:url("../img/icons/info.svg"),none}.flash-messages .flash-messages__item.success{background-image:url("../img/icons/success.png");background-image:url("../img/icons/success.svg"),none}.flash-messages .flash-messages__item.warning{background-image:url("../img/icons/warning.png");background-image:url("../img/icons/warning.svg"),none}.flash-messages .flash-messages__item.error{background-image:url("../img/icons/error.png");background-image:url("../img/icons/error.svg"),none}input[type='text'],input[type='number'],input[type='search'],input[type='password'],input[type='email'],input[type='url'],textarea,select,tags-input .tags .input,.accounts .api li input[type='text']{-webkit-border-radius:2px;-moz-border-radius:2px;border-radius:2px;-webkit-box-sizing:border-box;-moz-box-sizing:border-box;box-sizing:border-box;-webkit-appearance:textfield;border-radius:2px;border:1px solid #d2d2d2;box-shadow:inset 0 1px 2px rgba(0,0,0,0.12);font-size:16px;margin:0;outline:none;padding:.6956522em .869565em;vertical-align:baseline;font-weight:300}input[type='text']:hover,input[type='number']:hover,input[type='search']:hover,input[type='password']:hover,input[type='email']:hover,input[type='url']:hover,textarea:hover,select:hover,tags-input .tags .input:hover,.accounts .api li input[type='text']:hover{border-color:#B2B2B2;outline:none}input[type='text']:active,input[type='number']:active,input[type='search']:active,input[type='password']:active,input[type='email']:active,input[type='url']:active,textarea:active,select:active,tags-input .tags .input:active,.accounts .api li input[type='text']:active{border-color:#888;outline:none}input[type='text']:focus,input[type='number']:focus,input[type='search']:focus,input[type='password']:focus,input[type='email']:focus,input[type='url']:focus,textarea:focus,select:focus,tags-input .tags .input:focus,.accounts .api li input[type='text']:focus{border-color:#888;outline:none}input.ng-dirty.invalid[type='text'],input.ng-dirty.invalid[type='number'],input.ng-dirty.invalid[type='search'],input.ng-dirty.invalid[type='password'],input.ng-dirty.invalid[type='email'],input.ng-dirty.invalid[type='url'],textarea.ng-dirty.invalid,select.ng-dirty.invalid,tags-input .tags .ng-dirty.invalid.input,.accounts .api li input.ng-dirty.invalid[type='text'],input.ng-dirty.ng-invalid[type='text'],input.ng-dirty.ng-invalid[type='number'],input.ng-dirty.ng-invalid[type='search'],input.ng-dirty.ng-invalid[type='password'],input.ng-dirty.ng-invalid[type='email'],input.ng-dirty.ng-invalid[type='url'],textarea.ng-dirty.ng-invalid,select.ng-dirty.ng-invalid,tags-input .tags .ng-dirty.ng-invalid.input,.accounts .api li input.ng-dirty.ng-invalid[type='text']{border-color:#d90000 !important}input[type='text'] .disabled,input[type='number'] .disabled,input[type='search'] .disabled,input[type='password'] .disabled,input[type='email'] .disabled,input[type='url'] .disabled,textarea .disabled,select .disabled,tags-input .tags .input .disabled,.accounts .api li input[type='text'] .disabled,input[disabled="disabled"][type='text'],input[disabled="disabled"][type='number'],input[disabled="disabled"][type='search'],input[disabled="disabled"][type='password'],input[disabled="disabled"][type='email'],input[disabled="disabled"][type='url'],textarea[disabled="disabled"],select[disabled="disabled"],tags-input .tags [disabled="disabled"].input,.accounts .api li input[disabled="disabled"][type='text']{-webkit-text-fill-color:#333;color:#333;border-color:#e3e3e3;background-color:transparent;cursor:not-allowed}label{position:relative}.disabled label{cursor:default}form li.help-msg{margin-bottom:1em}form li.help-msg .help{color:#888;font-size:0.875em}form label span{color:#888}form fieldset{background:none;margin-left:0;padding:0}input[type='text'],input[type='number'],input[type='search'],input[type='password'],input[type='email'],input[type='url']{padding:7px 10px !important;box-shadow:none;width:100%;border:1px solid #d2d2d2}input[type='text']::-webkit-input-placeholder,input[type='number']::-webkit-input-placeholder,input[type='search']::-webkit-input-placeholder,input[type='password']::-webkit-input-placeholder,input[type='email']::-webkit-input-placeholder,input[type='url']::-webkit-input-placeholder{color:#888}input[type='text']:-moz-placeholder,input[type='number']:-moz-placeholder,input[type='search']:-moz-placeholder,input[type='password']:-moz-placeholder,input[type='email']:-moz-placeholder,input[type='url']:-moz-placeholder{color:#888}input[type='text']::-moz-placeholder,input[type='number']::-moz-placeholder,input[type='search']::-moz-placeholder,input[type='password']::-moz-placeholder,input[type='email']::-moz-placeholder,input[type='url']::-moz-placeholder{color:#888}input[type='text']:-ms-input-placeholder,input[type='number']:-ms-input-placeholder,input[type='search']:-ms-input-placeholder,input[type='password']:-ms-input-placeholder,input[type='email']:-ms-input-placeholder,input[type='url']:-ms-input-placeholder{color:#888}input[type='number']{padding-right:15px}input[type='search']{-webkit-appearance:textfield}input[type='search']::-webkit-search-decoration,input[type='search']::-webkit-search-cancel-button{-webkit-appearance:none}input[type='radio'],input[type='image']{display:inline-block;margin-right:10px}textarea{overflow:auto;height:auto;min-height:175px;padding:7px 10px;max-height:none;vertical-align:top;resize:both;width:100%;box-shadow:none}select{display:block;clear:both;cursor:pointer;margin:0;background-image:url("../img/icons/accordion-open.svg");background-repeat:no-repeat;background-position:top 14px right 10px;background-color:#fff;padding:7px 30px 7px 10px !important;box-shadow:none;width:100%;-moz-appearance:none;text-indent:.01px;text-overflow:''}select[multiple],select[size]{height:auto;background-image:none;padding-top:10px}select:-moz-focusring{color:transparent;text-shadow:0 0 0 #000}select[disabled]{color:#888;background-image:none}select::-ms-expand{display:none}.checkbox:not(:checked),.checkbox:checked{position:absolute;left:-9999px}.checkbox+label{position:relative;padding-left:25px;cursor:pointer;max-width:100%;display:inline;vertical-align:middle;width:auto}.checkbox+label:before{content:'';position:absolute;left:0;top:3px;width:11px;height:11px;border:1px solid #cdcdcd;background:#fff;border-radius:2px}.checkbox:checked+label:before{background-color:#dd4814;border-color:#dd4814}.checkbox:checked+label:after{content:'✔';position:absolute;top:3px;left:2px;font-size:10px;color:#fff;transition:all .2s}.checkbox[disabled]{cursor:not-allowed}.checkbox[disabled]+label{cursor:not-allowed}.checkbox[disabled]+label:before{opacity:.5}.radio:not(:checked),.radio:checked{position:absolute;left:-9999px}.radio+label{position:relative;padding-left:25px;cursor:pointer}.radio+label:before{content:'';position:absolute;left:0;top:1px;width:13px;height:13px;border:1px solid #cdcdcd;background:#fff;border-radius:50%}.radio:checked+label:after{content:'';position:absolute;left:3px;top:4px;width:9px;height:9px;background:#dd4814;border-radius:50%}.field-error,.errors{color:#DF382C}.field-error li,.errors li{margin:7px 0}.field-error .errorlist,.errors .errorlist{margin:0}.field-error .errorlist li,.errors .errorlist li{margin:0 0 14px 0}.inline{display:inline-block;width:100%;font-size:0;margin-bottom:10px}.inline.error{background-color:#fdf5f5;box-shadow:0px 0px 0px 5px #fdf5f5}.inline.error .ng-invalid{border-color:#D2D2D2}.inline:last-of-type{margin-bottom:0}.inline label{display:inline-block;line-height:37px;color:#888;font-size:16px;margin:0}.inline input[type='submit'],.inline input[type='text'],.inline input[type='number'],.inline input[type='search'],.inline input[type='password'],.inline input[type='email'],.inline input[type='checkbox'],.inline select{display:inline-block;clear:none;margin:0;float:none;font-size:16px}.inline input[type='submit']:invalid,.inline input[type='text']:invalid,.inline input[type='number']:invalid,.inline input[type='search']:invalid,.inline input[type='password']:invalid,.inline input[type='email']:invalid,.inline input[type='checkbox']:invalid,.inline select:invalid{-moz-box-shadow:none}.inline input[type='submit']:-moz-submit-invalid,.inline input[type='text']:-moz-submit-invalid,.inline input[type='number']:-moz-submit-invalid,.inline input[type='search']:-moz-submit-invalid,.inline input[type='password']:-moz-submit-invalid,.inline input[type='email']:-moz-submit-invalid,.inline input[type='checkbox']:-moz-submit-invalid,.inline select:-moz-submit-invalid{box-shadow:none}.inline input[type='submit']:-moz-ui-invalid,.inline input[type='text']:-moz-ui-invalid,.inline input[type='number']:-moz-ui-invalid,.inline input[type='search']:-moz-ui-invalid,.inline input[type='password']:-moz-ui-invalid,.inline input[type='email']:-moz-ui-invalid,.inline input[type='checkbox']:-moz-ui-invalid,.inline select:-moz-ui-invalid{box-shadow:none}.inline div{float:none;margin:0}.inline input.cta-ubuntu,.inline a.link-cta-ubuntu,.inline button.cta-ubuntu{font-size:16px}.inline .icon{position:absolute;top:11px;right:10px;cursor:pointer}.inline .error-message{font-size:12px;color:#e85232;margin-top:10px;margin-bottom:10px;font-weight:normal}.form-inline{clear:both}.form-inline label,.form-inline button,.form-inline input[type='submit'],.form-inline input[type='text'],.form-inline input[type='number'],.form-inline input[type='search'],.form-inline input[type='password'],.form-inline input[type='email'],.form-inline input[type='checkbox'],.form-inline select{display:inline-block;width:auto;vertical-align:middle;margin-bottom:0}.form-inline input,.form-inline input[type='submit'] input[type='text'],.form-inline input[type='number'],.form-inline input[type='search'],.form-inline input[type='password'],.form-inline input[type='email'],.form-inline input[type='checkbox'],.form-inline select{margin-left:20px}.form-inline fieldset{width:auto;display:inline-block;margin:0 40px 0 0}.controls{position:absolute;top:0;right:20px}.controls a,.controls button{margin-left:20px}.form .form__siblings{float:left;width:100%}.form .form__siblings:hover .form__group--subtle input,.form .form__siblings:hover .form__group--subtle select,.form .form__siblings:hover .form__group--subtle textarea{border-color:#B2B2B2;background-color:#fff;outline:none}.form .form__siblings.form__siblings--active .form__group--subtle input,.form .form__siblings.form__siblings--active .form__group--subtle select,.form .form__siblings.form__siblings--active .form__group--subtle textarea{border-color:#B2B2B2;background-color:#fff;outline:none}.form .form__group{margin-bottom:10px}.form .form__group .form__group-errors{margin-top:5px}.form .form__group.form__group--inline{width:100%;float:left}.form .form__group.form__group--inline [class*='-col']{margin-bottom:0}.form .form__group.form__group--inline label{display:inline-block;float:none;font-size:16px;margin-top:0;margin-bottom:0;line-height:36px;vertical-align:top}.form .form__group.form__group--inline input,.form .form__group.form__group--inline select,.form .form__group.form__group--inline textarea,.form .form__group.form__group--inline .form__group-input{display:inline-block;clear:none;margin:0;float:none;font-size:16px}.form .form__group.form__group--subtle label{color:#888}.form .form__group.form__group--subtle input,.form .form__group.form__group--subtle select,.form .form__group.form__group--subtle textarea{border-color:#e3e3e3;background-color:transparent}.form .form__group.form__group--subtle input:hover,.form .form__group.form__group--subtle select:hover,.form .form__group.form__group--subtle textarea:hover{border-color:#B2B2B2;background-color:#fff;outline:none}.form .form__group.form__group--subtle input:active,.form .form__group.form__group--subtle input:focus,.form .form__group.form__group--subtle select:active,.form .form__group.form__group--subtle select:focus,.form .form__group.form__group--subtle textarea:active,.form .form__group.form__group--subtle textarea:focus{border-color:#888;outline:none;background-color:#fff}.form.form--inline .form__group{display:inline-block;margin-bottom:0;vertical-align:middle}.form.form--inline .form__group label{display:inline-block;max-width:100%;margin-bottom:0px}.form.form--inline .form__group input,.form.form--inline .form__group select,.form.form--inline .form__group textarea,.form.form--inline .form__group .form__group-input{display:inline-block;width:auto;vertical-align:middle;margin:0}.form .form__help-text{font-size:0.875pxem;color:#888}.onoffswitch{position:relative;display:inline-block;vertical-align:middle;width:38px;-webkit-user-select:none;-moz-user-select:none;-ms-user-select:none}.onoffswitch-checkbox{display:none}.onoffswitch-label{display:block;overflow:hidden;cursor:pointer;border-radius:2px}.onoffswitch-inner{display:block;width:200%;margin-left:-100%;transition:margin 0.3s ease-in 0s}.onoffswitch-inner:before,.onoffswitch-inner:after{display:block;float:left;width:50%;height:18px;padding:0;line-height:18px;font-size:14px;color:white;font-family:Trebuchet, Arial, sans-serif;font-weight:bold;box-sizing:border-box}.onoffswitch-inner:before{content:"";padding-left:10px;background-color:#3FB24F;color:#FFFFFF}.onoffswitch-inner:after{content:"";padding-right:10px;background-color:#EEEEEE;color:#999999;text-align:right}.onoffswitch-switch{display:block;width:19px;margin:0px;background:#FFFFFF;position:absolute;top:0;bottom:0;right:16px;border:1px solid #D2D2D2;border-radius:2px;transition:all 0.3s ease-in 0s}.onoffswitch-checkbox:checked+.onoffswitch-label .onoffswitch-inner{margin-left:0}.onoffswitch-checkbox:checked+.onoffswitch-label .onoffswitch-switch{right:0px}.icon.icon__loading{background:url("../img/in_progress.png") no-repeat;-webkit-animation:spin 1s infinite linear;-moz-animation:spin 1s infinite linear;animation:spin 1s infinite linear}a.icon{cursor:pointer}.icon-controls .icon{display:none;float:right;text-align:left}table tr:hover .icon-controls .icon{display:block}.listing-filter .listing-filter__label{height:53px;line-height:53px}.listing-filter .listing-filter__select{min-width:150px}dl dt{clear:left;color:#888}dl dd{color:#333;margin-left:0}dl dt,dl dd{display:inline-block;float:left;line-height:37px;margin-bottom:10px !important;word-wrap:break-word}.list__tree{list-style:none;border-left:1px solid #d4d4d4;position:relative}.list__tree.list__tree--sub-level{margin-top:10px;margin-left:20px;clear:both}.list__tree.list__tree--sub-level .list__item .list__item-feedback{left:180px}.list__tree .list__item{list-style:none}.list__tree .list__item:before{content:'';width:12px;height:1px;background:#d4d4d4;display:inline-block;position:relative;top:-4px;margin-right:5px}.list__tree .list__item:last-child::after{content:'';width:4px;height:1em;position:absolute;display:block;left:-2px;bottom:-6px;background:#f8f8f8}.list__tree .list__item .list__item-feedback{position:relative;left:200px;margin-top:-24px}tags-input{outline:none}tags-input .host:focus{outline:none}tags-input .tags:focus,tags-input .tags.focused{outline:none}tags-input .tags .tag-list{margin:4px 0 0;padding:0;list-style-type:none;width:100%;float:left}tags-input .tags .tag-item{display:inline-block;float:left;font-family:Ubuntu,Arial,"libra sans",sans-serif;font-size:1em;font-weight:300;height:30px;line-height:30px;cursor:default;color:#000;padding-right:15px;position:relative;margin:0 11px 0 0}tags-input .tags .tag-item .remove-button{display:inline-block;width:12px;height:12px;text-indent:-999em;background:url("../img/icons/cross.svg") no-repeat;background-size:12px 12px;position:absolute;right:0;top:9px;cursor:pointer}tags-input .tags .tag-item .remove-button:hover{text-decoration:none}tags-input .tags .input{padding:7px 10px;width:100% !important;float:left;position:relative !important;left:0}tags-input .tags .input::-ms-clear{display:none}tags-input .autocomplete{float:left;width:100%}tags-input .autocomplete .suggestion-list{background:#FFF;padding:10px 8px;border:1px solid #D2D2D2;border-top:0;border-radius:0 0 2px 2px}tags-input .autocomplete .suggestion-list li:hover{background:#EEE;cursor:pointer}tags-input[disabled] .host:focus{outline:none}tags-input[disabled] .tags{cursor:default}tags-input[disabled] .tags .tag-item .remove-button{cursor:default}tags-input[disabled] .tags .input{cursor:default}.tag-link{margin-right:10px}.tag-link:last-of-type:after{content:''}.table__data tags-input .tags{margin-top:-10px}.table__data tags-input .tags input{margin-left:0;width:50% !important;float:left}.pagination{margin:10px 0;text-align:center}.pagination .inactive{color:#AEA79F}.pagination a,.pagination span{margin:0 5px}.search{position:relative;padding-bottom:20px}.search input[type='search']{-webkit-appearance:textfield}.search .search__input{-webkit-box-sizing:border-box;-moz-box-sizing:border-box;box-sizing:border-box;-webkit-border-radius:4px;-moz-border-radius:4px;border-radius:4px;list-style:none;background:#FFF;box-shadow:0 1px 1px rgba(0,0,0,0.1);width:100%;border:none;padding:10px !important;font-size:1em;max-height:none}.search .search__input::-webkit-input-placeholder{color:#888 !important}.search .search__input:-moz-placeholder{color:#888 !important}.search .search__input::-moz-placeholder{color:#888 !important}.search .search__input:-ms-input-placeholder{color:#888 !important}.search .search__input[disabled="disabled"]{background-color:#fff;opacity:.5;pointer-events:none}.search .search__input[disabled="disabled"]+.search__submit.close{pointer-events:none;opacity:.5}.search .search__submit{position:absolute;top:10px;right:15px;background-color:transparent;background-image:url("../img/search-icon.svg");background-repeat:no-repeat;text-indent:-999em;display:block;width:21px;height:20px;overflow:hidden;outline:none;padding:0;border:none}.search .search__submit:hover{background-color:transparent;background-image:url("../img/search-icon.svg")}.search .search__submit.close{background-image:url("../img/icons/cross.svg");background-size:21px;margin-top:2px}.search .search__submit.close:hover{background-image:url("../img/icons/cross.svg")}.u-margin{margin:20px}.u-margin--none{margin:none !important}.u-margin--tiny{margin:5px !important}.u-margin--small{margin:10px !important}.u-margin--large{margin:40px !important}.u-margin--huge{margin:80px !important}.u-margin--top{margin-top:20px !important}.u-margin--top-none{margin-top:none !important}.u-margin--top-tiny{margin-top:5px !important}.u-margin--top-small{margin-top:10px !important}.u-margin--top-large{margin-top:40px !important}.u-margin--top-huge{margin-top:80px !important}.u-margin--right{margin-right:20px !important}.u-margin--right-none{margin-right:none !important}.u-margin--right-tiny{margin-right:5px !important}.u-margin--right-small{margin-right:10px !important}.u-margin--right-large{margin-right:40px !important}.u-margin--right-huge{margin-right:80px !important}.u-margin--bottom{margin-bottom:20px !important}.u-margin--bottom-none{margin-bottom:none !important}.u-margin--bottom-tiny{margin-bottom:5px !important}.u-margin--bottom-small{margin-bottom:10px !important}.u-margin--bottom-large{margin-bottom:40px !important}.u-margin--bottom-huge{margin-bottom:80px !important}.u-margin--left{margin-left:20px !important}.u-margin--left-none{margin-left:none !important}.u-margin--left-tiny{margin-left:5px !important}.u-margin--left-small{margin-left:10px !important}.u-margin--left-large{margin-left:40px !important}.u-margin--left-huge{margin-left:80px !important}.u-padding{padding:20px}.u-padding--none{padding:none !important}.u-padding--tiny{padding:5px !important}.u-padding--small{padding:10px !important}.u-padding--large{padding:40px !important}.u-padding--huge{padding:80px !important}.u-padding--top{padding-top:20px !important}.u-padding--top-none{padding-top:none !important}.u-padding--top-tiny{padding-top:5px !important}.u-padding--top-small{padding-top:10px !important}.u-padding--top-large{padding-top:40px !important}.u-padding--top-huge{padding-top:80px !important}.u-padding--right{padding-right:20px !important}.u-padding--right-none{padding-right:none !important}.u-padding--right-tiny{padding-right:5px !important}.u-padding--right-small{padding-right:10px !important}.u-padding--right-large{padding-right:40px !important}.u-padding--right-huge{padding-right:80px !important}.u-padding--bottom{padding-bottom:20px !important}.u-padding--bottom-none{padding-bottom:none !important}.u-padding--bottom-tiny{padding-bottom:5px !important}.u-padding--bottom-small{padding-bottom:10px !important}.u-padding--bottom-large{padding-bottom:40px !important}.u-padding--bottom-huge{padding-bottom:80px !important}.u-padding--left{padding-left:20px !important}.u-padding--left-none{padding-left:none !important}.u-padding--left-tiny{padding-left:5px !important}.u-padding--left-small{padding-left:10px !important}.u-padding--left-large{padding-left:40px !important}.u-padding--left-huge{padding-left:80px !important}.spinner-col{width:10px}.spinner{float:left;margin:0 auto;text-indent:-9999em}.spinner.spin{background:url("../img/in_progress.png") no-repeat;background-size:16px 16px;width:16px;height:16px;-webkit-animation:spin 1s infinite linear;-moz-animation:spin 1s infinite linear;animation:spin 1s infinite linear;padding:0}@-webkit-keyframes spin{0%{-webkit-transform:rotate(0deg);transform:rotate(0deg)}100%{-webkit-transform:rotate(360deg);transform:rotate(360deg)}}@keyframes spin{0%{-webkit-transform:rotate(0deg);transform:rotate(0deg)}100%{-webkit-transform:rotate(360deg);transform:rotate(360deg)}}table,.table{border-color:#d2d2d2;border-spacing:0;overflow-x:scroll;margin-bottom:20px;margin:0 0 2.5em;width:100%;text-align:left;border-collapse:separate}table tr,.table table tr,table .table tr,.table .table__row{width:100%;border-color:#b2b2b2;border-bottom-style:dotted;border-bottom-width:1px}table th,.table table th,table .table th,table td,.table table td,table .table td,.table .table__header,.table .table__data{font-size:1em;padding:10px;box-sizing:border-box;min-height:21px;background:none;border:0;text-align:left;border-collapse:separate;vertical-align:top;backface-visibility:hidden;position:relative}table thead tr,.table table thead tr,table thead .table tr,.table .table__head .table__row{color:#888;border-bottom:1px solid}table thead tr:hover,.table .table__head .table__row:hover{background-color:transparent}table thead th,.table table thead th,table thead .table th,.table .table__head .table__header{font-size:0.8125em;background:none;color:#888;font-size:13px}table thead th input[type="radio"]+label,.table .table__head .table__header input[type="radio"]+label,table thead th input[type="checkbox"]+label,.table .table__head .table__header input[type="checkbox"]+label{margin:0;top:-3px}table thead th a:link,table thead th a:visited,.table .table__head .table__header .table__header-link{color:#888}table thead th a:hover:link,table thead th a:hover:visited,.table .table__head .table__header .table__header-link:hover{color:#333;text-decoration:none;border-bottom:1px solid #333}table thead th a.active:link,table thead th a.active:visited,.table .table__head .table__header .active.table__header-link{color:#333;text-decoration:none}table thead th a.sort:link,table thead th a.sort:visited,.table .table__head .table__header .sort.table__header-link{border-bottom:1px solid #333}table thead .divide,.table .table__head .table__header-divide,.table .table__head .divide{width:1px;display:inline-block;background:#888;height:10px;margin:0 5px}table .numerical,.table .numerical{text-align:right}table input,.table input,table select,.table select{margin:0 0 0 -14px}table input[type="radio"]+label,.table input[type="radio"]+label,table input[type="checkbox"]+label,.table input[type="checkbox"]+label{margin:0;top:-1px}table th{color:#888;border-bottom:1px solid}table td{border-color:#b2b2b2;border-bottom-style:dotted;border-bottom-width:1px}table td a:link,table td a:visited{color:#333;border-bottom:1px solid #d2d2d2}table td a:link:hover,table td a:visited:hover{text-decoration:none;color:#dd4814}.table{display:table}.table .table__row{float:left;display:table-row}.table .table__row:hover{background-color:#fff}.table .table__row:hover .table__input{background-color:#fff;border-color:#d2d2d2;background-position:right 10px top 16px}.table .table__row:hover .table__input[disabled]{border-color:transparent}.table .table__row:hover .table__controls{z-index:1;opacity:1}.table .table__row:hover .table__controls--secondary{z-index:1;opacity:1}.table .table__row.active{background-color:#fff}.table .table__row.active .table__input{background-color:#fff;border-color:#d2d2d2;background-position:right 10px top 16px}.table .table__row.active .table__controls{z-index:1;opacity:1}.table .table__row.active .table__controls--secondary{z-index:1;opacity:1}.table .table__row.active .table__dropdown .table__row{display:none}.table .table__row.active .table__dropdown .table__row.active{display:block}.table .table__header,.table .table__data{display:table-cell;float:left}.table .table__data a:link,.table .table__data a:visited{color:#333;border-bottom:1px solid #d2d2d2}.table .table__data a:link:hover,.table .table__data a:visited:hover{text-decoration:none;color:#dd4814}.table .table__head{display:table-head;width:100%;box-sizing:border-box}.table .table__body{display:table-row-group}.table .table__footer{display:table-footer-group}.table .table__label{clear:both;display:block;margin-top:11px;color:#bcbcbc}.table .table__label a{color:#bcbcbc}.table .table__label a:hover{color:#dd4814}.table .table__label.active a{color:#dd4814}.table .table__controls{width:100%;text-align:right;opacity:0;z-index:-1000}.table .table__controls--secondary{opacity:0;z-index:-1000;width:auto;text-align:left}.table .table__controls a,.table .table__controls a:link,.table .table__controls a:visited{color:#333;border-bottom:1px solid #d2d2d2}.table .table__controls a:hover,.table .table__controls a:link:hover,.table .table__controls a:visited:hover{text-decoration:none;color:#dd4814}.table .table__input{display:inline-block;margin:-7px 0 -7px -14px;background-color:transparent;border-color:transparent;background-position:-9999px -9999px}.table .table__input[disabled]{background-color:transparent;border-color:transparent;pointer-events:none;background-position:-9999px -9999px;color:#333}.table .table__dropdown{width:100%}.table .table__dropdown .table__row{border-bottom:0;display:none;position:relative}.table .table__dropdown .table__row:before{display:block;margin:0 auto;width:calc(100% - 20px);border-top:1px dotted #d2d2d2;position:absolute;height:1px;content:'';top:0;left:10px}.table .table__dropdown .table__row.table__dropdown-row--head{border-bottom:0}.table .table__dropdown .table__row.table__dropdown-row--head .table__header{color:#bcbcbc;font-size:13px}.table .table__dropdown .table__row.no-border:before{display:none}.table .table__dropdown .table__row.border:before{display:block;margin:0 auto;width:calc(100% - 20px);border-top:1px dotted #d2d2d2;position:absolute;height:1px;content:'';top:0;left:10px}.table .table__dropdown .table__row.active .table__input{background-color:#fff;border-color:#d2d2d2;background-position:right 10px top 16px;pointer-events:all}.table .table__dropdown .table__row.active .table__input[disabled]{border-color:transparent;cursor:pointer}.table .table__dropdown--info .table__row{border-bottom:0}.table .table__dropdown--info .table__data{color:#bcbcbc}.form .form__group input,.form .form__group select{margin:0}.table--error{border-color:#d83832}.table--error .table__header,.table--error .table__data,.table--error th,.table--error td{border-color:#d83832;background-color:#f9dedd}.table--warning{border-color:#eca918}.table--warning .table__header,.table--warning .table__data,.table--warning th,.table--warning td{border-color:#eca918;background-color:#fcefd4}.table--success{border-color:#38b44a}.table--success .table__header,.table--success .table__data,.table--success th,.table--success td{border-color:#38b44a;background-color:#caeecf}.table--information{border-color:#2ab7ec}.table--information .table__header,.table--information .table__data,.table--information th,.table--information td{border-color:#2ab7ec;background-color:#e5f6fd}.table-col--1{width:1%}.table-col--2{width:2%}.table-col--3{width:3%}.table-col--4{width:4%}.table-col--5{width:5%}.table-col--6{width:6%}.table-col--7{width:7%}.table-col--8{width:8%}.table-col--9{width:9%}.table-col--10{width:10%}.table-col--11{width:11%}.table-col--12{width:12%}.table-col--13{width:13%}.table-col--14{width:14%}.table-col--15{width:15%}.table-col--16{width:16%}.table-col--17{width:17%}.table-col--18{width:18%}.table-col--19{width:19%}.table-col--20{width:20%}.table-col--21{width:21%}.table-col--22{width:22%}.table-col--23{width:23%}.table-col--24{width:24%}.table-col--25{width:25%}.table-col--26{width:26%}.table-col--27{width:27%}.table-col--28{width:28%}.table-col--29{width:29%}.table-col--30{width:30%}.table-col--31{width:31%}.table-col--32{width:32%}.table-col--33{width:33%}.table-col--34{width:34%}.table-col--35{width:35%}.table-col--36{width:36%}.table-col--37{width:37%}.table-col--38{width:38%}.table-col--39{width:39%}.table-col--40{width:40%}.table-col--41{width:41%}.table-col--42{width:42%}.table-col--43{width:43%}.table-col--44{width:44%}.table-col--45{width:45%}.table-col--46{width:46%}.table-col--47{width:47%}.table-col--48{width:48%}.table-col--49{width:49%}.table-col--50{width:50%}.table-col--51{width:51%}.table-col--52{width:52%}.table-col--53{width:53%}.table-col--54{width:54%}.table-col--55{width:55%}.table-col--56{width:56%}.table-col--57{width:57%}.table-col--58{width:58%}.table-col--59{width:59%}.table-col--60{width:60%}.table-col--61{width:61%}.table-col--62{width:62%}.table-col--63{width:63%}.table-col--64{width:64%}.table-col--65{width:65%}.table-col--66{width:66%}.table-col--67{width:67%}.table-col--68{width:68%}.table-col--69{width:69%}.table-col--70{width:70%}.table-col--71{width:71%}.table-col--72{width:72%}.table-col--73{width:73%}.table-col--74{width:74%}.table-col--75{width:75%}.table-col--76{width:76%}.table-col--77{width:77%}.table-col--78{width:78%}.table-col--79{width:79%}.table-col--80{width:80%}.table-col--81{width:81%}.table-col--82{width:82%}.table-col--83{width:83%}.table-col--84{width:84%}.table-col--85{width:85%}.table-col--86{width:86%}.table-col--87{width:87%}.table-col--88{width:88%}.table-col--89{width:89%}.table-col--90{width:90%}.table-col--91{width:91%}.table-col--92{width:92%}.table-col--93{width:93%}.table-col--94{width:94%}.table-col--95{width:95%}.table-col--96{width:96%}.table-col--97{width:97%}.table-col--98{width:98%}.table-col--99{width:99%}.table-col--100{width:100%}h1 span,h2 span,h3 span,h4 span,h5 span{color:#888;font-size:75%;padding-left:20px}h1{font-size:2em}h2{font-size:1.5em}h3{font-size:1.25em}h4{font-size:1em;font-weight:300}h5{font-size:0.875em}pre{border:0;background-color:#FFF;border-radius:2px}pre code{counter-reset:line-numbering}pre code .line{float:left}pre code .line::before{content:counter(line-numbering);counter-increment:line-numbering;padding-right:1em;width:1.5em;text-align:right;opacity:0.5;pointer-events:none;user-select:none}.yui3-node-add-widget{width:65.9292%;margin-right:2.21238%}@media screen and (max-width: 768px){.yui3-node-add-widget{width:100%;margin:0}}.yui3-node-add-widget .buttons{margin-top:30px}.yui3-node-add-widget .add-link img.icon{margin-right:6px}.yui3-overlay{-webkit-border-radius:0 0 6px 6px;-moz-border-radius:0 0 6px 6px;border-radius:0 0 6px 6px;background-color:#fff;-webkit-box-shadow:0 0 10px 0 rgba(0,0,0,0.5);box-shadow:0 0 10px 0 rgba(0,0,0,0.5)}.yui3-overlay ul{padding:5px 0}.yui3-overlay li{float:none}.yui3-overlay li:last-child a{border-bottom:none}.yui3-overlay a{display:block;padding:6px 20px;color:#dd4814 !important;border-bottom:1px solid #e5e2e0}.yui3-overlay a:focus,.yui3-overlay a:hover{background-color:#f2f2f2}.yui3-overlay-hidden{display:none}.yui3-widget-mask{background-color:#000;opacity:0.3}.yui3-panel{-webkit-border-radius:0 0 6px 6px;-moz-border-radius:0 0 6px 6px;border-radius:0 0 6px 6px;background-color:#FFF;padding:50px 80px 50px 80px;-webkit-box-shadow:0 0 15px 0 #000;box-shadow:0 0 15px 0 #000}.yui3-panel .yui3-button{float:right}.yui3-panel .yui3-button.link-button{float:left;padding-left:0;padding-right:0;color:#dd4814;border:none;background:none;-webkit-box-shadow:none;box-shadow:none;font-size:13px}.yui3-widget-hd{margin-bottom:30px;font-size:24px}.yui3-widget-ft{margin-top:50px}.yui3-widget-button-wrapper{width:100%}.color--success{color:#38B44A}.color--error{color:#D83832}.icon{margin-left:5px;width:16px;height:16px;display:inline-block;text-indent:-999em;background-repeat:no-repeat;background-size:16px 16px;vertical-align:middle;margin-top:-3px;position:relative;text-align:left;border-bottom:0 !important;padding:0}.icon:hover{border-bottom:0}.icon.info{background-image:url("../img/icons/info.png");background-image:url("../img/icons/info.svg"),none}.icon.help{background-image:url("../img/icons/help.png");background-image:url("../img/icons/help.svg"),none}.icon.edit{background-image:url("../img/icons/edit.png");background-image:url("../img/icons/edit.svg"),none}.icon.delete{background-image:url("../img/icons/delete.png");background-image:url("../img/icons/delete.svg"),none}.icon.remove{background-image:url("../img/icons/filter-remove.svg"),none}.icon.warning{background-image:url("../img/icons/warning.png");background-image:url("../img/icons/warning.svg"),none}.icon.debug{background-image:url("../img/icons/debug.png");background-image:url("../img/icons/debug.svg"),none}.icon.success,.icon.tick{background-image:url("../img/icons/success.png");background-image:url("../img/icons/success.svg"),none}.icon.simple-tick{background-image:url("../img/icons/green-tick.svg")}.icon.error{background-image:url("../img/icons/error.png");background-image:url("../img/icons/error.svg"),none}.icon.partition{background-image:url("../img/icons/partition.svg")}.icon.add{background-image:url("../img/icons/add.svg")}.icon.tags{background-image:url("../img/icons/tags.svg")}.icon.mount{background-image:url("../img/icons/mount.svg")}.icon.unmount{background-image:url("../img/icons/unmount.svg")}.icon.notification-error{background-image:url("../img/icons/notification-error.png");background-size:12px 11px;background-position:top 1px center}.icon.open{background-image:url("../img/icons/accordion-open.svg")}.icon.close{background-image:url("../img/icons/accordion-close.svg")}.clear{clear:both}.hidden{display:none}.align-right{text-align:right}.align-center{text-align:center}.align-left{text-align:left}.right{float:right !important}.left{float:left !important}.border{border-top:1px dotted #B2B2B2}.border.bottom{border-bottom:1px dotted #B2B2B2}.border.solid{border-style:solid}.vertical-align{position:relative;top:50%;-webkit-transform:translateY(-50%);-ms-transform:translateY(-50%);transform:translateY(-50%)}.margin-top{margin-top:20px}.margin-top--five{margin-top:5px}.margin-top--ten{margin-top:10px}.margin-right{margin-right:20px !important}.margin-right--ten{margin-right:10px !important}.margin-bottom{margin-bottom:20px}.margin-bottom--ten{margin-bottom:10px !important}.margin-left{margin-left:20px !important}.margin-left--ten{margin-left:10px !important}.margin-left--thirty{margin-left:30px !important}.padding{padding:20px}.padding--ten{padding:10px}.padding-top{padding-top:20px}.padding-top--ten{padding-top:10px !important}.padding-right{padding-right:20px}.padding-right--ten{padding-right:10px}.padding-bottom{padding-bottom:20px !important}.padding-bottom--ten{padding-bottom:10px !important}.padding-left{padding-left:20px !important}.padding-left--ten{padding-left:10px}.padding-left--30,.padding-left--thirty{padding-left:30px !important}.padding-left--35{padding-left:35px !important}.padding-left--45{padding-left:45px !important}.padding-left--50{padding-left:50px !important}.border-top{border-top:1px dotted #888}.add-machine__list .add-machine__details,.border-bottom{border-bottom:1px dotted #888}.border--light{border-color:#d2d2d2}.no-margin{margin:0 !important}.no-margin-top{margin-top:0 !important}.no-padding{padding:0}.no-padding-top{padding-top:0 !important}.no-padding-left{padding-left:0}.no-padding-bottom{padding-bottom:0 !important}.no-margin-bottom{margin-bottom:0}.no-border--top{border-top:0 !important}.width--auto{width:auto}.width--half{width:50%}.width--full{width:100%}.box-sizing{box-sizing:border-box}.u-display--inline{display:inline}.u-display--inline-block{display:inline-block}.tooltip{position:relative}.tooltip::before{position:absolute;left:50%;-webkit-transform:translateX(-50%) translateY(-14px);-moz-transform:translateX(-50%) translateY(-14px);transform:translateX(-50%) translateY(-14px);content:'';width:0;height:0;border-left:5px solid transparent;border-right:5px solid transparent;z-index:-1;visibility:hidden;transition:opacity 0.2s ease-out;border-top:5px solid #333;opacity:0;bottom:100%;margin-bottom:-11px}.tooltip::after{content:attr(data-tooltip);font-size:13px;font-weight:400;line-height:16px;position:absolute;z-index:-1;visibility:hidden;left:50%;bottom:100%;-webkit-transform:translateX(-50%) translateY(-8px);-moz-transform:translateX(-50%) translateY(-8px);transform:translateX(-50%) translateY(-8px);background:#333;color:#FFF;padding:10px;height:auto;text-indent:0;opacity:0;transition:opacity 0.2s ease-out;border-radius:5px;box-shadow:0px 1px 3px 0 rgba(51,51,51,0.2);white-space:pre;box-sizing:border-box}.tooltip:hover::after,.tooltip:hover::before{opacity:1 !important;z-index:1000;visibility:visible}.tooltip.tooltip--right::before{border-top:5px solid transparent;border-right:5px solid #333;border-bottom:5px solid transparent;left:100%;bottom:inherit;top:50%;-webkit-transform:translateX(4px) translateY(-50%);-moz-transform:translateX(4px) translateY(-50%);transform:translateX(4px) translateY(-50%)}.tooltip.tooltip--right::after{left:100%;bottom:inherit;top:50%;margin-bottom:-14px;-webkit-transform:translateX(14px) translateY(-50%);-moz-transform:translateX(14px) translateY(-50%);transform:translateX(14px) translateY(-50%)}.footer-wrapper{border-top:1px dotted #b2b2b2}.footer-wrapper footer.global{background-color:transparent;padding-top:0px;padding-right:20px;padding-left:20px;background-image:url("../img/logos/logo-ubuntu-orange.png");background-image:url("../img/logos/logo-ubuntu-orange.svg"),none;background-size:107px 25px;background-position:top 20px right 20px;background-repeat:no-repeat;max-width:1480px}.footer-wrapper footer.global p{font-size:0.875em}.footer-wrapper footer.global a{margin:0 5px}.footer-wrapper footer.global .version{font-weight:500;margin-right:5px}.footer-wrapper footer.global .copy{margin-top:10px}.footer-wrapper footer.global .legal{max-width:1440px}.wrapper{min-height:100%;height:auto !important;height:100%;margin:0 auto -112px;position:relative;background:rgba(255,255,255,0.6);padding-top:172px}.wrapper:after{content:'';position:absolute;display:block;top:0;right:0;bottom:0;left:0;background:url("../img/backgrounds/image-background-paper.png");height:100%;width:100%;z-index:-1}.inner-wrapper{max-width:1480px;padding:0px 20px;margin:0 auto;position:relative}.push,.footer-wrapper{height:112px}.row{background-color:transparent;border-bottom:1px dotted #CCC}.row:last-child{border-bottom:none}@media only screen and (min-width: 768px){.equal-height{display:flex;flex-wrap:wrap;flex-direction:row;width:100%}.equal-height>.equal-height__item{box-sizing:border-box;display:flex;flex:auto;flex-direction:column}}header.banner{overflow:visible;z-index:20;position:fixed;top:0}header.banner .logo{padding-left:15px}header.banner .logo a{border-bottom:0}header.banner nav.nav-primary{border-bottom:none}header.banner .nav-primary.nav-right .logo-ubuntu{-moz-background-size:100px 30px;-webkit-background-size:100px 30px;-o-background-size:100px 30px;background-size:100px 30px;background-position:5px 9px;background-image:url("../img/logos/logo.png");background-image:url("../img/logos/logo.svg"),none}header.banner .nav-primary li:hover ul:after{display:none}@media screen and (max-width: 768px){header.banner .nav-primary ul{border-right:none}}header.banner #right-nav{float:right;margin-right:20px}@media screen and (max-width: 768px){header.banner #right-nav{margin-right:0}}header.banner #user-link{position:relative}@media screen and (max-width: 768px){header.banner #user-link{border-top:1px solid #d4d7d4;width:100%}}header.banner #user-link>a{padding-bottom:12px}@media screen and (max-width: 768px){header.banner #user-link>a{display:none}}header.banner #user-link .normal,header.banner #user-link .hover{margin-right:7px}@media screen and (max-width: 768px){header.banner #user-link .normal,header.banner #user-link .hover{display:none}}header.banner #user-link .hover{display:none}header.banner #user-link .nav{-webkit-border-radius:0px 0px 4px 4px;-moz-border-radius:0px 0px 4px 4px;border-radius:0px 0px 4px 4px;background-color:#FFF;border:none;display:none;position:absolute;right:0;top:48px;box-shadow:0px 2px 4px rgba(0,0,0,0.15)}@media screen and (min-width: 769px){header.banner #user-link .nav a.active{background:none;border:none}}header.banner #user-link .nav a:hover{background-color:transparent}@media screen and (max-width: 768px){header.banner #user-link .nav a:hover{background-color:#F8F8F8;color:#333}}@media screen and (max-width: 768px){header.banner #user-link .nav{background:none;position:relative;top:inherit;width:100%;box-shadow:none;float:left;padding:0}header.banner #user-link .nav li{border-bottom:1px solid #d4d7d4;width:100%;float:left}header.banner #user-link .nav li a{padding:10px 14px;width:100%}}@media screen and (max-width: 768px){header.banner #user-link:hover>a{background-color:transparent}}header.banner #user-link:hover .normal{display:none}header.banner #user-link:hover .hover{display:inline-block}@media screen and (max-width: 768px){header.banner #user-link:hover .hover{display:none}}header.banner #user-link:hover .nav{display:block}header.banner .nav-toggle{background-image:url("../img/icons/navigation-menu-plain.png");background-image:url("../img/icons/navigation-menu-plain.svg"),none;top:0}@media screen and (max-width: 768px){header.banner .nav-toggles .open{display:block}header.banner .nav-toggles .close{display:none}header.banner #canonlist:target ul{display:block}header.banner #canonlist:target+.nav-toggles .open{display:none}header.banner #canonlist:target+.nav-toggles .close{display:block}}.page-header{-webkit-box-sizing:border-box;-moz-box-sizing:border-box;box-sizing:border-box;background:#FFF;box-shadow:0 1px 1px rgba(0,0,0,0.1);width:100%;float:left;position:fixed;z-index:10;top:48px;min-height:104px}.page-header .page-header__nav{position:absolute;top:10px;z-index:1000}.page-header .page-header__nav a{font-weight:300}.page-header .page-header__title{font-size:2em;width:auto;padding:31px 0;margin:0;float:left;font-size:32px}.page-header .page-header__title [contenteditable="true"]{padding:8px 10px;width:auto;box-sizing:border-box;border:1px solid transparent;margin:-10px 0 -10px -10px;border-radius:2px;color:#333;cursor:default;font-size:32px;display:inline-block}.page-header .page-header__title [contenteditable="true"].editmode,.page-header .page-header__title [contenteditable="true"].editable:hover{border:1px solid #D2D2D2;cursor:text}.page-header .page-header__title [contenteditable="true"].editmode:hover,.page-header .page-header__title [contenteditable="true"]:active,.page-header .page-header__title [contenteditable="true"]:focus{outline:none;background-color:#FFF;border:1px solid #B2B2B2}.page-header .page-header__title [contenteditable="true"].invalid,.page-header .page-header__title [contenteditable="true"].invalid:hover,.page-header .page-header__title [contenteditable="true"].invalid:active,.page-header .page-header__title [contenteditable="true"].invalid:focus{border-color:#d90000}.page-header .page-header__title [contenteditable="true"] br{display:none}.page-header .page-header__title .page-header__title-dot{display:inline-block;width:auto;padding:0}.page-header .page-header__title .page-header__title-domain{display:inline-block;width:auto;max-height:59px;line-height:25px;min-height:59px;background-position:top 27px right 10px;margin:-9px 0;font-size:32px}.page-header .page-header__title .icon{vertical-align:3px;margin-right:10px}.page-header .page-header__title .page-header__title--identicator{font-size:0.6em;width:auto;display:inline-block;position:relative;top:0px;padding-left:20px;margin-left:10px}.page-header .page-header__title .page-header__title--identicator a{color:#888;border-bottom:0}.page-header .page-header__title .page-header__title--identicator a:hover{text-decoration:none;border-bottom:3px solid #888}.page-header .page-header__title .page-header__title--identicator a:focus,.page-header .page-header__title .page-header__title--identicator a:active{text-decoration:none}.page-header .page-header__title .page-header__title--identicator a.active{color:#333;border-bottom:3px solid #dd4814}.page-header .page-header__title .page-header__title--identicator a.active:hover{text-decoration:none;cursor:default}.page-header .page-header__title .page-header__title--identicator .divide{width:1px;display:inline-block;background:#D2D2D2;height:11px;padding:0;margin:0 5px}.page-header .page-header__title .page-header__title--identicator .page-header__title-loadmore{font-size:14px;margin-left:10px}.page-header .page-header__title .page-header__title--identicator .page-header__title-loadmore:hover{border:0;text-decoration:underline}.page-header .page-header__title .link-cta-ubuntu,.page-header .page-header__title .alt{font-size:16px;margin-left:20px;position:relative;vertical-align:middle;margin-top:-5px}.page-header .page-header__actions{float:right;padding:34px 0;margin-bottom:0}.page-header .page-header__actions .page-header__cta{float:right;position:relative;height:auto;max-height:36px}.page-header .page-header__actions .page-header__cta .cta-group{float:right}.page-header .page-header__actions .page-header__cta .page-header__cta-feedback{display:inline-block;position:relative;line-height:36px;text-align:right;color:#dd4814;margin-right:20px;cursor:pointer}.page-header .page-header__actions .page-header__cta .page-header__cta-feedback:hover{text-decoration:underline}.page-header .page-header__dropdown{float:left;width:100%;max-height:1000px;transition:max-height 0.3s ease-in;overflow:hidden;border-color:#888 !important}.page-header .page-header__dropdown.ng-hide{display:block !important;max-height:0;overflow:hidden;transition:max-height 0.3s ease-out;border-top:none}.page-header .page-header__dropdown .page-header__feedback{border-top:1px dotted #888;display:block;float:left;width:100%;padding:20px 0}.page-header .page-header__dropdown .page-header__feedback .page-header__feedback-message{-webkit-box-sizing:border-box;-moz-box-sizing:border-box;box-sizing:border-box;margin:0;background-position:top 3px left 0px;background-repeat:no-repeat;padding:6px 0 5px 25px;width:auto;display:inline-block;position:relative}.page-header .page-header__dropdown .page-header__feedback .page-header__feedback-message.info,.page-header .page-header__dropdown .page-header__feedback .page-header__feedback-message.error{background-image:url("../img/icons/error.png");background-image:url("../img/icons/error.svg"),none;background-position:0px 9px}.page-header .page-header__dropdown .page-header__feedback .page-header__feedback-message.info.progress,.page-header .page-header__dropdown .page-header__feedback .page-header__feedback-message.error.progress{width:100%}.page-header .page-header__dropdown .page-header__feedback .page-header__feedback-message.warning{background-image:url("../img/icons/warning.png");background-image:url("../img/icons/warning.svg"),none;background-position:0px 9px}.page-header .page-header__dropdown .page-header__feedback .page-header__feedback-message.progress{padding-left:0}.page-header .page-header__dropdown .page-header__feedback .page-header__feedback-message.progress .loader{position:relative;top:1px}.title .title__indicator .title__link{color:#888;font-size:20px}.title .title__indicator .title__link:hover{color:#333;text-decoration:none;border-bottom:1px solid #333}.title .title__indicator .title__link:focus,.title .title__indicator .title__link:active{text-decoration:none}.title .title__indicator .title__link.active{color:#333;border-bottom:1px #dd4814 solid}.title .title__indicator .divide{width:1px;display:inline-block;background:#D2D2D2;height:11px;padding:0;margin:0 5px}.accounts .logout .divide{padding:0 20px 0 30px;display:inline-block}.accounts .api li{position:relative}.accounts .api li input[type='text']{line-height:30px;padding-right:30px;width:100%}.accounts .api li input[type='text']::-webkit-input-placeholder{color:#333}.accounts .api li input[type='text']:-moz-placeholder{color:#333}.accounts .api li input[type='text']::-moz-placeholder{color:#333}.accounts .api li input[type='text']:-ms-input-placeholder{color:#333}.accounts .api li .delete-link{position:absolute;top:7px;right:7px}form.page-title-form{margin-bottom:30px}form.page-title-form input{-webkit-border-radius:5px;-moz-border-radius:5px;border-radius:5px;border:1px solid transparent;background-color:transparent;font-size:36px;line-height:26px;color:#333;margin:6px 10px;padding:4px;height:auto;box-shadow:none}form.page-title-form input:hover{outline:none;background:#FFF;border-color:#D2D2D2;box-shadow:inset 0 1px 1px rgba(0,0,0,0.1)}form.page-title-form input:focus{border:1px solid #dd4814;background-color:#fff;outline:none}.small-icon{width:12px}.images-info{text-align:center;padding:10px}.images-warning{-webkit-box-sizing:border-box;-moz-box-sizing:border-box;box-sizing:border-box;-webkit-border-radius:2px;-moz-border-radius:2px;border-radius:2px;list-style:none;padding:15px 20px 15px 45px;margin:0;font-weight:400;font-size:0.875em;background:#FFF;background-position:top 50% left 15px;background-repeat:no-repeat;margin:0 0 10px;box-shadow:0 1px 1px rgba(0,0,0,0.1);border:1px solid #EEE;background-image:url("../img/icons/warning.png");background-image:url("../img/icons/warning.svg"),none}#loader{width:10px;margin:16px auto 0 auto}#importing{-webkit-box-sizing:border-box;-moz-box-sizing:border-box;box-sizing:border-box;-webkit-border-radius:2px;-moz-border-radius:2px;border-radius:2px;list-style:none;padding:15px 20px 15px 45px;margin:0;font-weight:400;font-size:0.875em;background:#FFF;background-position:top 50% left 15px;background-repeat:no-repeat;margin:0 0 10px;box-shadow:0 1px 1px rgba(0,0,0,0.1);border:1px solid #EEE;position:relative}#importing .spinner{position:absolute;left:15px}.importing-dot{opacity:0;-webkit-animation:dot 1.3s infinite;animation:dot 1.3s infinite}.selector{-webkit-box-sizing:border-box;-moz-box-sizing:border-box;box-sizing:border-box;-webkit-border-radius:2px;-moz-border-radius:2px;border-radius:2px;padding:20px;background:#FFF;margin:0 0 20px;box-shadow:0 1px 1px rgba(0,0,0,0.1);border:1px solid #EEE;width:100%;float:left}.selector h2{font-size:1em;font-weight:300}.selector h2 img{-webkit-box-sizing:border-box;-moz-box-sizing:border-box;box-sizing:border-box;display:inline-block;background:url("../img/icons/help.svg") no-repeat;width:16px;height:16px;padding-left:16px;margin-left:5px}.selector .selector-available,.selector .selector-chosen{width:46%;float:left;margin:0;text-align:left}@media screen and (max-width: 768px){.selector .selector-available,.selector .selector-chosen{width:100%}}.selector .selector-available h2,.selector .selector-chosen h2{background:none;border:none}.selector .selector-available select,.selector .selector-chosen select{margin-bottom:10px}.selector ul.selector-chooser{width:8%;float:left;margin:20% 0 0}@media screen and (max-width: 768px){.selector ul.selector-chooser{width:100%;margin:0 0 10px;text-align:center}}.selector ul.selector-chooser li{width:100%;text-align:center}@media screen and (max-width: 768px){.selector ul.selector-chooser li{width:auto;display:inline-block;height:16px;width:16px;margin:0 20px}}.selector ul.selector-chooser li a{display:block;text-indent:999em;width:16px;height:16px;overflow:hidden;margin:0 auto}.selector ul.selector-chooser li a.selector-add{background-image:url("../img/icons/chevron_right.svg")}@media screen and (max-width: 768px){.selector ul.selector-chooser li a.selector-add{background-image:url("../img/icons/chevron_down.svg")}}.selector ul.selector-chooser li a.selector-remove{background-image:url("../img/icons/chevron_left.svg")}@media screen and (max-width: 768px){.selector ul.selector-chooser li a.selector-remove{background-image:url("../img/icons/chevron_up.svg")}}.selector .selector-filter img{display:none}.selector .selector-filter input{background-image:url("../img/search-orange.png");background-repeat:no-repeat;background-position:top 7px right 8px}.selector select#id_mac_addresses_to.filtered{height:269px !important}.nodes{position:relative}.nodes .search{position:absolute;right:0}.nodes .search input[type='submit']{position:absolute;top:8px;right:12px;background-color:transparent;background-image:url("../img/search-icon.svg");background-repeat:no-repeat;text-indent:-999em;display:block;width:21px;height:20px;overflow:hidden;outline:none;padding:0}.nodes .search input[type='submit']:hover{color:transparent;background-color:transparent;background-image:url("../img/search-icon.svg");background-repeat:no-repeat}@media screen and (max-width: 768px){.nodes .search{position:relative}}@media screen and (max-width: 768px){.nodes .actions select{width:100%;margin-bottom:20px}.nodes .actions input{position:absolute;right:0;top:0}}.powerstates{width:14px;height:15px;display:inline-block}.powerstates.power-on{background:transparent url("../img/icons/power-on.svg") left top no-repeat}.powerstates.power-off{background:transparent url("../img/icons/power-off.svg") left top no-repeat}.powerstates.power-unknown{background:none}.powerstates.power-error{background:transparent url("../img/icons/power-error.svg") left top no-repeat}.powerstates.power-check-ok{-webkit-border-radius:50%;-moz-border-radius:50%;border-radius:50%;width:10px;height:10px;color:#33CC00}.powerstates.power-check-error{-webkit-border-radius:50%;-moz-border-radius:50%;border-radius:50%;width:10px;height:10px;color:#FF0000}.node-actions .link-cta-ubuntu,.node-actions .cta-ubuntu{margin-bottom:10px;float:left;font-size:16px}.buttons{margin-top:30px}#network-interfaces li{list-style-type:none}#content-discovery-data{padding-top:20px;margin-top:20px;border-top:1px dotted #B2B2B2}#content-discovery-data .slider{height:0;overflow:hidden}.slider{padding-top:0 !important}.slider .content{-webkit-border-radius:0 0 4px 4px;-moz-border-radius:0 0 4px 4px;border-radius:0 0 4px 4px;box-shadow:0 1px 1px rgba(0,0,0,0.1);background:#FFF;border:1px solid #EEE;border-top:none;padding:20px}.slider .content pre{margin:0}.slider .tabs{-webkit-box-sizing:border-box;-moz-box-sizing:border-box;box-sizing:border-box;-webkit-border-radius:4px 4px 0 0;-moz-border-radius:4px 4px 0 0;border-radius:4px 4px 0 0;padding:8px 20px;margin:0;font-weight:400;font-size:0.875em;background:#FFF;background-position:top 50% left 15px;background-repeat:no-repeat;border:1px solid #EEE}.add-machine__list{padding:13px 0 20px;border-top:1px dotted #888;margin-bottom:0}.add-machine__list .add-machine__details{background:transparent url("../img/icons/accordion-open.svg") top 12px right 10px no-repeat}.add-machine__list .add-machine__details>div{margin-bottom:0}.add-machine__list .add-machine__details .add-machine__details-form{display:none}.add-machine__list .add-machine__details.active{background-image:url("../img/icons/accordion-close.svg")}.add-machine__list .add-machine__details.active .add-machine__details-form{display:block}.power-status{display:inline-block;font-size:100%;padding-left:0}.power-status--power{display:inline-block;margin-left:20px;position:relative;font-size:100%}.power-status--power.checking{color:#2AB7EC;background:url("../img/status_in_progress.svg");padding-left:20px}.power-status--power.on{padding-left:20px;color:#38B44A;background:transparent url("../img/icons/power-on.svg") left top 4px no-repeat}.power-status--power.off{padding-left:20px;color:#D2D2D2;background:transparent url("../img/icons/power-off.svg") left top 4px no-repeat}.power-status--power.error{padding-left:20px;color:#DB3832;background:transparent url("../img/icons/power-error.svg") left top 4px no-repeat}.power-status--power .power-check{font-size:0.75em;color:#D2D2D2;display:inline-block;padding:0}.power-status--power .power-check .power-check__link{color:#888;text-decoration:none;margin-left:5px}.power-status--power .power-check .power-check__link:hover{border-bottom:0 !important;text-decoration:underline !important}.loading,.loader{background:url("../img/in_progress.png") no-repeat;background-size:16px 16px;width:16px;height:16px;-webkit-animation:spin 1s infinite linear;-moz-animation:spin 1s infinite linear;animation:spin 1s infinite linear;padding:0;display:inline-block}.details__used{color:#BCBCBC}.details .details__label{clear:both;display:block;margin-top:11px;color:#BCBCBC}.details .details__label a{color:#BCBCBC}.details .details__label a:hover{color:#dd4814}.details .details__label.active a{color:#dd4814}.details .details__controls{width:100%;text-align:right;opacity:0;z-index:-1000}.details .details__controls--secondary{opacity:0;z-index:-1000;width:auto;text-align:left}.details .table-row .details__input{display:inline-block;margin:-7px 0 -8px -14px;background-color:transparent;border-color:transparent;background-position:-9999px -9999px}.details .table-row .details__text{line-height:37px}.details .table-row:hover .details__input{background-color:#FFF;border-color:#D2D2D2;background-position:right 10px top 16px}.details .table-row:hover .details__controls{z-index:1;opacity:1}.details .table-row:hover .details__controls--secondary{z-index:1;opacity:1}.details .table-row.active:hover .details__input{background-color:transparent;border-color:transparent;pointer-events:none;background-position:-9999px -9999px}.details .table-row.active:hover .details__controls{opacity:0;z-index:-1000;pointer-events:none}.details .table-row.active:hover .details__controls--secondary{z-index:-1000;opacity:0;pointer-events:none}.details .table-row label{font-size:13px;color:#BCBCBC}.details .table-row input,.details .table-row select{margin:0 0 0 -14px}.details .table-row input[type="radio"]{margin-left:0}.details .details__dropdown .details__row{border-bottom:0;position:relative}.details .details__dropdown .details__row:before{display:block;margin:0 auto;width:calc(100% - 20px);border-top:1px dotted #B2B2B2;position:relative;height:1px;content:''}.details .details__dropdown .details__row.details__row--head{border-bottom:0}.details .details__dropdown .details__row.details__row--head .table-cell{color:#BCBCBC;font-size:13px}.details .details__dropdown .details__row.no-border{border:0}.details .details__dropdown .details__row.no-border:before{border:0}.details .details__dropdown--info .table-row{border-bottom:0}.details .details__dropdown--info .table-cell{color:#BCBCBC} |
1889 | |
1890 | === modified file 'src/maasserver/static/js/angular/controllers/node_details_networking.js' |
1891 | === modified file 'src/maasserver/static/js/angular/controllers/node_details_storage.js' |
1892 | === modified file 'src/maasserver/static/js/angular/controllers/node_events.js' |
1893 | === modified file 'src/maasserver/static/js/angular/controllers/tests/test_node_details_networking.js' |
1894 | === modified file 'src/maasserver/static/js/angular/controllers/tests/test_node_details_storage.js' |
1895 | --- src/maasserver/static/js/angular/controllers/tests/test_node_details_storage.js 2016-10-12 16:34:23 +0000 |
1896 | +++ src/maasserver/static/js/angular/controllers/tests/test_node_details_storage.js 2016-10-26 23:39:26 +0000 |
1897 | @@ -2810,6 +2810,7 @@ |
1898 | }); |
1899 | }); |
1900 | |
1901 | +<<<<<<< TREE |
1902 | describe("getCannotCreateBcacheMsg", function() { |
1903 | |
1904 | it("returns msg if no cachesets", |
1905 | @@ -3075,6 +3076,94 @@ |
1906 | |
1907 | }); |
1908 | |
1909 | +======= |
1910 | + describe("getCannotCreateBcacheMsg", function() { |
1911 | + |
1912 | + it("returns msg if no cachesets", |
1913 | + function() { |
1914 | + var controller = makeController(); |
1915 | + $scope.available = [ |
1916 | + { |
1917 | + fstype: null, |
1918 | + $selected: true, |
1919 | + has_partitions: false |
1920 | + } |
1921 | + ]; |
1922 | + $scope.cachesets = []; |
1923 | + expect($scope.getCannotCreateBcacheMsg()).toBe( |
1924 | + "Create at least one cache set to create bcache"); |
1925 | + }); |
1926 | + |
1927 | + it("returns msg if two selected", function() { |
1928 | + var controller = makeController(); |
1929 | + $scope.cachesets = [{}]; |
1930 | + $scope.available = [ { $selected: true }, { $selected: true }]; |
1931 | + expect($scope.getCannotCreateBcacheMsg()).toBe( |
1932 | + "Select only one available device to create bcache"); |
1933 | + }); |
1934 | + |
1935 | + it("returns msg if selected has fstype", function() { |
1936 | + var controller = makeController(); |
1937 | + $scope.available = [ |
1938 | + { |
1939 | + fstype: "ext4", |
1940 | + $selected: true, |
1941 | + has_partitions: false |
1942 | + } |
1943 | + ]; |
1944 | + $scope.cachesets = [{}]; |
1945 | + |
1946 | + expect($scope.getCannotCreateBcacheMsg()).toBe( |
1947 | + "Device is formatted; unformat the device to create bcache"); |
1948 | + }); |
1949 | + |
1950 | + it("returns msg if selected is volume group", function() { |
1951 | + var controller = makeController(); |
1952 | + $scope.available = [ |
1953 | + { |
1954 | + type: "lvm-vg", |
1955 | + fstype: null, |
1956 | + $selected: true, |
1957 | + has_partitions: false |
1958 | + } |
1959 | + ]; |
1960 | + $scope.cachesets = [{}]; |
1961 | + expect($scope.getCannotCreateBcacheMsg()).toBe( |
1962 | + "Cannot use a logical volume as a backing device for bcache."); |
1963 | + }); |
1964 | + |
1965 | + it("returns msg if selected has partitions", function() { |
1966 | + var controller = makeController(); |
1967 | + $scope.available = [ |
1968 | + { |
1969 | + fstype: null, |
1970 | + $selected: true, |
1971 | + has_partitions: true |
1972 | + } |
1973 | + ]; |
1974 | + $scope.cachesets = [{}]; |
1975 | + expect($scope.getCannotCreateBcacheMsg()).toBe( |
1976 | + "Device has already been partitioned; create a " + |
1977 | + "new partition to use as the bcache backing " + |
1978 | + "device"); |
1979 | + }); |
1980 | + |
1981 | + it("returns null if selected is valid", |
1982 | + function() { |
1983 | + var controller = makeController(); |
1984 | + $scope.available = [ |
1985 | + { |
1986 | + fstype: null, |
1987 | + $selected: true, |
1988 | + has_partitions: false |
1989 | + } |
1990 | + ]; |
1991 | + $scope.cachesets = [{}]; |
1992 | + expect($scope.getCannotCreateBcacheMsg()).toBeNull(); |
1993 | + }); |
1994 | + }); |
1995 | + |
1996 | +>>>>>>> MERGE-SOURCE |
1997 | describe("canCreateBcache", function() { |
1998 | |
1999 | it("returns false when isAvailableDisabled is true", function() { |
2000 | |
2001 | === modified file 'src/maasserver/static/js/angular/controllers/tests/test_node_events.js' |
2002 | === modified file 'src/maasserver/static/js/angular/directives/controller_image_status.js' |
2003 | === modified file 'src/maasserver/static/partials/node-details.html' |
2004 | --- src/maasserver/static/partials/node-details.html 2016-10-26 13:53:50 +0000 |
2005 | +++ src/maasserver/static/partials/node-details.html 2016-10-26 23:39:26 +0000 |
2006 | @@ -1941,9 +1941,15 @@ |
2007 | data-ng-disabled="!canCreateCacheSet()" |
2008 | data-ng-hide="isAllStorageDisabled() || !isSuperUser()" |
2009 | data-ng-click="createCacheSet()">Create cache Set</a> |
2010 | +<<<<<<< TREE |
2011 | <a class="button--secondary button--inline tooltip" |
2012 | aria-label="{$ getCannotCreateBcacheMsg() $}" |
2013 | data-ng-class="{ tooltip: !canCreateBcache() }" |
2014 | +======= |
2015 | + <a class="link-cta-ubuntu secondary margin-right" |
2016 | + data-tooltip="{$ getCannotCreateBcacheMsg() $}" |
2017 | + data-ng-class="{ tooltip: !canCreateBcache() }" |
2018 | +>>>>>>> MERGE-SOURCE |
2019 | data-ng-disabled="!canCreateBcache()" |
2020 | data-ng-hide="isAllStorageDisabled() || !isSuperUser()" |
2021 | data-ng-click="createBcache()">Create bcache</a> |
2022 | |
2023 | === modified file 'src/maasserver/static/partials/node-events.html' |
2024 | --- src/maasserver/static/partials/node-events.html 2016-08-25 20:45:36 +0000 |
2025 | +++ src/maasserver/static/partials/node-events.html 2016-10-26 23:39:26 +0000 |
2026 | @@ -6,6 +6,7 @@ |
2027 | </header> |
2028 | </div> |
2029 | <div class="ng-hide" data-ng-show="loaded"> |
2030 | +<<<<<<< TREE |
2031 | <header class="page-header u-margin--bottom"> |
2032 | <div class="wrapper--inner"> |
2033 | <h1 class="page-header__title">{$ node.fqdn $}</h1> |
2034 | @@ -15,6 +16,19 @@ |
2035 | </p> |
2036 | <div class="page-header__controls u-float--right"> |
2037 | <a class="button--base button--inline" href="#/node/{$ node.system_id $}">‹ Back to machine details</a> |
2038 | +======= |
2039 | + <header class="page-header margin-bottom"> |
2040 | + <div class="inner-wrapper"> |
2041 | + <h1 class="page-header__title eight-col"> |
2042 | + {$ node.fqdn $} |
2043 | + <span class="page-header__title--identicator"> |
2044 | + {$ events.length $} machine events in the past {$ days $} day(s) |
2045 | + <a href="" class="page-header__title-loadmore" data-ng-click="loadMore()">load 1 more day</a> |
2046 | + </span> |
2047 | + </h1> |
2048 | + <div class="page-header__actions four-col last-col"> |
2049 | + <a class="right link-cta-ubuntu text-button" href="#/node/{$ node.system_id $}">‹ Back to machine details</a> |
2050 | +>>>>>>> MERGE-SOURCE |
2051 | </div> |
2052 | </div> |
2053 | </header> |
2054 | |
2055 | === modified file 'src/maasserver/static/partials/nodes-list.html' |
2056 | --- src/maasserver/static/partials/nodes-list.html 2016-10-26 13:53:50 +0000 |
2057 | +++ src/maasserver/static/partials/nodes-list.html 2016-10-26 23:39:26 +0000 |
2058 | @@ -1,3 +1,4 @@ |
2059 | +<<<<<<< TREE |
2060 | <header class="page-header"> |
2061 | <div class="wrapper--inner"> |
2062 | <!-- XXX ricgard 2016-06-16 - Need to add e2e test. --> |
2063 | @@ -237,6 +238,178 @@ |
2064 | </div> |
2065 | <div class="form__group"> |
2066 | <label for="zone" class="form__group-label two-col">Zone</label> |
2067 | +======= |
2068 | +<header class="page-header margin-bottom" data-maas-sticky-header> |
2069 | + <div class="inner-wrapper"> |
2070 | + <h1 class="page-header__title eight-col">{$ $parent.site $} MAAS |
2071 | + <span class="page-header__title--identicator" id="bulk-actions"> |
2072 | + <!-- XXX blake_r 2015-02-19 - Need to add e2e test. --> |
2073 | + <a href="" |
2074 | + class="tooltip" |
2075 | + data-tooltip="A deployable node managed by MAAS." |
2076 | + data-ng-class="{ active: currentpage === 'nodes' }" |
2077 | + data-ng-click="toggleTab('nodes')">{$ nodes.length $} <ng-pluralize count="nodes.length" when="{'one': 'Machine', 'other': 'Machines'}"></ng-pluralize></a> |
2078 | + <span class="divide"></span> |
2079 | + <a href="" |
2080 | + class="tooltip" |
2081 | + data-tooltip="A node known to MAAS, but is not deployable." |
2082 | + data-ng-class="{ active: currentpage === 'devices' }" |
2083 | + data-ng-click="toggleTab('devices')">{$ devices.length $} <ng-pluralize count="devices.length" when="{'one': 'Device', 'other': 'Devices'}"></ng-pluralize> |
2084 | + </a> |
2085 | + <span data-ng-show="isSuperUser()" class="divide"></span> |
2086 | + <a href="" data-ng-show="isSuperUser()" |
2087 | + class="tooltip" |
2088 | + data-tooltip="A node that provides MAAS services." |
2089 | + data-ng-class="{ active: currentpage === 'controllers' }" |
2090 | + data-ng-click="toggleTab('controllers')">{$ controllers.length $} <ng-pluralize count="controllers.length" when="{'one': 'Controller', 'other': 'Controllers'}"></ng-pluralize> |
2091 | + </a> |
2092 | + <span class="power-status--power" data-ng-show="loading"> |
2093 | + <span class="loader"></span> |
2094 | + Loading... |
2095 | + </span> |
2096 | + </span> |
2097 | + </h1> |
2098 | + <!-- XXX blake_r 2015-02-19 - Need to add e2e test. --> |
2099 | + <div data-ng-show="currentpage === 'nodes'"> |
2100 | + <div class="page-header__actions"> |
2101 | + <div class="page-header__cta" data-ng-hide="tabs.nodes.selectedItems.length"> |
2102 | + <div class="right" data-maas-cta="addHardwareOptions" |
2103 | + data-ng-model="addHardwareOption" |
2104 | + data-ng-change="addHardwareOptionChanged()" data-default-title="Add hardware"> |
2105 | + </div> |
2106 | + </div> |
2107 | + <div class="page-header__cta ng-hide" data-ng-show="tabs.nodes.selectedItems.length"> |
2108 | + <span class="page-header__cta-feedback" data-ng-click="showSelected('nodes')"> |
2109 | + {$ tabs.nodes.selectedItems.length $} Selected |
2110 | + </span> |
2111 | + <div data-maas-cta="tabs.nodes.takeActionOptions" |
2112 | + data-ng-model="tabs.nodes.actionOption" |
2113 | + data-ng-change="actionOptionSelected('nodes')"> |
2114 | + </div> |
2115 | + </div> |
2116 | + </div> |
2117 | + <!-- XXX blake_r 2015-02-19 - Need to add e2e test. --> |
2118 | + <div class="page-header__dropdown ng-hide" data-ng-show="tabs.nodes.actionOption"> |
2119 | + <!-- XXX blake_r 2015-02-19 - Need to add e2e test. --> |
2120 | + <div class="page-header__feedback ng-hide" data-ng-hide="isActionError('nodes') || hasActionsInProgress('nodes')"> |
2121 | + <form class="form form--inline"> |
2122 | + <div class="nine-col no-margin-bottom ng-hide" data-ng-show="tabs.nodes.actionOption.name === 'commission'"> |
2123 | + <div class="form__group u-margin--right u-margin--top-tiny"> |
2124 | + <input class="checkbox margin-right" id="enableSSH" type="checkbox" |
2125 | + data-ng-model="tabs.nodes.commissionOptions.enableSSH"> |
2126 | + <label class="checkbox-label" for="enableSSH">Allow SSH access and prevent machine from powering off</label> |
2127 | + </div> |
2128 | + <div class="form__group u-margin--right u-margin--top-tiny"> |
2129 | + <input class="checkbox margin-right" id="skipNetworking" type="checkbox" |
2130 | + data-ng-model="tabs.nodes.commissionOptions.skipNetworking"> |
2131 | + <label class="checkbox-label" for="skipNetworking">Retain network configuration</label> |
2132 | + </div> |
2133 | + <div class="form__group u-margin--top-tiny"> |
2134 | + <input class="checkbox" id="skipStorage" type="checkbox" |
2135 | + data-ng-model="tabs.nodes.commissionOptions.skipStorage"> |
2136 | + <label class="checkbox-label" for="skipStorage">Retain storage configuration</label> |
2137 | + </div> |
2138 | + </div> |
2139 | + <span class="form__group ng-hide" data-ng-show="tabs.nodes.actionOption.name === 'deploy'"> |
2140 | + <label for="image" class="u-margin--right">Choose your image</label> |
2141 | + <span data-maas-os-select="osinfo" data-ng-model="tabs.nodes.osSelection"></span> |
2142 | + </span> |
2143 | + <!-- XXX rbanffy 2015-03-23 - Need to add e2e test. --> |
2144 | + <span class="form__group ng-hide" data-ng-show="tabs.nodes.actionOption.name === 'set-zone'"> |
2145 | + <label for="zone" class="u-margin--right">Select Zone</label> |
2146 | + <select name="zone" id="zone" placeholder="Choose a zone" |
2147 | + data-ng-model="tabs.nodes.zoneSelection" |
2148 | + data-ng-options="zone as zone.name for zone in zones"> |
2149 | + <option value="" disabled="disabled">Choose a zone</option> |
2150 | + </select> |
2151 | + </span> |
2152 | + <div class="right"> |
2153 | + <a href="" class="link-cta-ubuntu text-button" data-ng-click="actionCancel('nodes')">Cancel</a> |
2154 | + <button class="cta-ubuntu" data-ng-click="actionGo('nodes')" data-ng-hide="hasActionsFailed('nodes')">Go</button> |
2155 | + <button class="cta-ubuntu" data-ng-click="actionGo('nodes')" data-ng-show="hasActionsFailed('nodes')">Retry</button> |
2156 | + </div> |
2157 | + </form> |
2158 | + </div> |
2159 | + |
2160 | + <!-- XXX blake_r 2015-02-19 - Need to add e2e test. --> |
2161 | + <div class="page-header__feedback ng-hide" data-ng-show="isActionError('nodes')"> |
2162 | + <!-- XXX blake_r 2015-02-19 - Need to add e2e test. --> |
2163 | + <p class="page-header__feedback-message info" data-ng-hide="isDeployError('nodes') || isSSHKeyError('nodes')"> |
2164 | + {$ tabs.nodes.actionErrorCount $} |
2165 | + <span data-ng-pluralize count="tabs.nodes.selectedItems.length" when="{'one': 'node', 'other': 'nodes'}"></span> |
2166 | + cannot be {$ tabs.nodes.actionOption.sentence $}. To proceed, update your selection. |
2167 | + </p> |
2168 | + <!-- XXX blake_r 2015-02-19 - Need to add e2e test. --> |
2169 | + <p class="page-header__feedback-message info ng-hide" data-ng-show="isDeployError('nodes')"> |
2170 | + {$ tabs.nodes.selectedItems.length $} |
2171 | + <span data-ng-pluralize count="tabs.nodes.selectedItems.length" when="{'one': 'node', 'other': 'nodes'}"></span> |
2172 | + cannot be {$ tabs.nodes.actionOption.sentence $}, because the required boot images have not been imported. To import boot images, visit the <a href="images/">images page</a>. |
2173 | + </p> |
2174 | + <p class="page-header__feedback-message info ng-hide" data-ng-show="isSSHKeyError('nodes')"> |
2175 | + {$ tabs.nodes.selectedItems.length $} |
2176 | + <span data-ng-pluralize count="tabs.nodes.selectedItems.length" when="{'one': 'node', 'other': 'nodes'}"></span> |
2177 | + cannot be {$ tabs.nodes.actionOption.sentence $}, because an SSH key has not been added to your account. To add an SSH key, visit <a href="account/prefs/">your account page</a>. |
2178 | + </p> |
2179 | + </div> |
2180 | + |
2181 | + <!-- XXX blake_r 2015-05-07 - Need to add e2e test. --> |
2182 | + <div class="page-header__feedback ng-hide" data-ng-show="hasActionsInProgress('nodes') || hasActionsFailed('nodes')"> |
2183 | + <p class="page-header__feedback-message progress" data-ng-show="hasActionsInProgress('nodes')"> |
2184 | + <span class="loader"></span> |
2185 | + {$ tabs.nodes.actionProgress.completed $} of {$ tabs.nodes.actionProgress.total $} |
2186 | + nodes have been {$ tabs.nodes.actionOption.sentence $}. |
2187 | + </p> |
2188 | + <p class="page-header__feedback-message error" |
2189 | + data-ng-repeat="(error, nodes) in tabs.nodes.actionProgress.errors"> |
2190 | + The {$ tabs.nodes.actionOption.title.toLowerCase() $} action for {$ nodes.length $} |
2191 | + <span data-ng-pluralize count="nodes.length" when="{'one': 'node', 'other': 'nodes'}"></span> |
2192 | + failed with error: {$ error $} |
2193 | + </p> |
2194 | + </div> |
2195 | + </div> |
2196 | + <div class="page-header__dropdown border ng-hide" data-ng-show="addHardwareScope.viewable" data-ng-controller="AddHardwareController"> |
2197 | + <h3 class="u-padding--top" data-ng-if="showMachine()">Add machine</h3> |
2198 | + <h3 class="u-padding--top" data-ng-if="showChassis()">Add chassis</h3> |
2199 | + <div class="page-header__feedback" data-ng-hide="architectures.length"> |
2200 | + <!-- XXX blake_r 2015-02-24 - Need to add e2e test. --> |
2201 | + <p class="page-header__feedback-message info"> |
2202 | + Cannot add {$ mode $} until boot images have been imported. To fix, visit the <a href="images">images page</a>. |
2203 | + </p> |
2204 | + </div> |
2205 | + <form class="twelve-col ng-hide" data-ng-show="showMachine()"> |
2206 | + <fieldset class="six-col no-padding no-margin-bottom"> |
2207 | + <div class="inline"> |
2208 | + <label for="machine-name" class="two-col">Machine name</label> |
2209 | + <input type="text" id="machine-name" class="three-col" placeholder="Choose a machine name (optional)" |
2210 | + data-ng-model="machine.name"> |
2211 | + </div> |
2212 | + <div class="inline"> |
2213 | + <label for="domain" class="two-col">Domain</label> |
2214 | + <select name="domain" id="domain" class="three-col" |
2215 | + data-ng-model="machine.domain" |
2216 | + data-ng-options="domain as domain.name for domain in domains"> |
2217 | + <option value="" disabled>Choose a domain</option> |
2218 | + </select> |
2219 | + </div> |
2220 | + <div class="inline"> |
2221 | + <label for="architecture" class="two-col">Architecture</label> |
2222 | + <select name="architecture" id="architecture" class="three-col" |
2223 | + data-ng-model="machine.architecture" |
2224 | + data-ng-options="arch for arch in architectures"> |
2225 | + <option value="" disabled>Choose an architecture</option> |
2226 | + </select> |
2227 | + </div> |
2228 | + <div class="inline"> |
2229 | + <label for="min_hwe_kernel" class="two-col">Minimum Kernel</label> |
2230 | + <select name="min_hwe_kernel" id="min_hwe_kernel" class="three-col" placeholder="No minimum kernel" |
2231 | + data-ng-model="machine.min_hwe_kernel" |
2232 | + data-ng-options="hwe_kernel[0] as hwe_kernel[1] for hwe_kernel in hwe_kernels"> |
2233 | + <option value="">No minimum kernel</option> |
2234 | + </select> |
2235 | + </div> |
2236 | + <div class="inline"> |
2237 | + <label for="zone" class="two-col">Zone</label> |
2238 | +>>>>>>> MERGE-SOURCE |
2239 | <select name="zone" id="zone" class="three-col" placeholder="Choose a zone" |
2240 | data-ng-model="machine.zone" |
2241 | data-ng-options="zone as zone.name for zone in zones"> |
2242 | |
2243 | === added directory 'src/maasserver/static/scss/maas' |
2244 | === added directory 'src/maasserver/static/scss/maas/components' |
2245 | === added file 'src/maasserver/static/scss/maas/components/_accordion.scss.OTHER' |
2246 | --- src/maasserver/static/scss/maas/components/_accordion.scss.OTHER 1970-01-01 00:00:00 +0000 |
2247 | +++ src/maasserver/static/scss/maas/components/_accordion.scss.OTHER 2016-10-26 23:39:26 +0000 |
2248 | @@ -0,0 +1,112 @@ |
2249 | +@charset "UTF-8"; |
2250 | + |
2251 | +//// |
2252 | +/// MAAS accordion styles |
2253 | +/// |
2254 | +/// @project MAAS |
2255 | +/// @author Web Team at Canonical Ltd |
2256 | +/// @copyright 2015 Canonical Ltd |
2257 | +/// |
2258 | +//// |
2259 | + |
2260 | +.accordion { |
2261 | + @include box-sizing(); |
2262 | + @include rounded-corners(2px); |
2263 | + list-style: none; |
2264 | + background:#FFF; |
2265 | + box-shadow: 0 1px 1px rgba(0, 0, 0, .1); |
2266 | + margin-bottom: 40px; |
2267 | + |
2268 | + .disabled & { |
2269 | + opacity: .5; |
2270 | + pointer-events: none; |
2271 | + } |
2272 | + |
2273 | + // accordion main sidebar title |
2274 | + .accordion__title { |
2275 | + border-bottom: 1px dotted #B2B2B2; |
2276 | + padding: 13px 20px 12px; |
2277 | + margin: 0; |
2278 | + font-size: 1.3em; |
2279 | + } |
2280 | + |
2281 | + // accordion data block, contains all filter links and controls. |
2282 | + .accordion__tab { |
2283 | + border-bottom: 1px dotted #B2B2B2; |
2284 | + |
2285 | + &:last-of-type { |
2286 | + border: none; |
2287 | + } |
2288 | + |
2289 | + // Block level title |
2290 | + .accordion__tab-title { |
2291 | + padding: 12px 20px; |
2292 | + margin: 0; |
2293 | + color: #888; |
2294 | + cursor: pointer; |
2295 | + background: transparent url('../img/icons/accordion-open.svg') top 20px right 20px no-repeat; |
2296 | + |
2297 | + &.active { |
2298 | + background-image: url('../img/icons/accordion-close.svg'); |
2299 | + |
2300 | + + .accordion__tab-content { |
2301 | + max-height: 400px; |
2302 | + transition: max-height .5s ease-in; |
2303 | + overflow-y: auto; |
2304 | + } |
2305 | + } |
2306 | + } |
2307 | + |
2308 | + // Filter list |
2309 | + .accordion__tab-content { |
2310 | + max-height: 0; |
2311 | + transition: max-height .5s ease-out; |
2312 | + overflow: hidden; |
2313 | + |
2314 | + .accordion__tab-list { |
2315 | + list-style-type: none; |
2316 | + padding: 0 20px 14px; |
2317 | + margin: 0; |
2318 | + |
2319 | + .accordion__tab-item { |
2320 | + margin-bottom: 0.15em; |
2321 | + |
2322 | + .accordion__tab-link { |
2323 | + @include box-sizing(); |
2324 | + color: #333; |
2325 | + width: 100%; |
2326 | + display: inline-block; |
2327 | + padding-right: 20px; |
2328 | + border-bottom: 0; |
2329 | + |
2330 | + &:hover { |
2331 | + color: $ubuntu-orange; |
2332 | + text-decoration: none; |
2333 | + } |
2334 | + |
2335 | + .disabled & { |
2336 | + color: #333; |
2337 | + } |
2338 | + } |
2339 | + |
2340 | + &.active { |
2341 | + font-weight: 400; |
2342 | + |
2343 | + .accordion__tab-link { |
2344 | + background: transparent url('../img/icons/cross.svg') top 7px right 0px no-repeat; |
2345 | + } |
2346 | + |
2347 | + &:hover { |
2348 | + color: $ubuntu-orange; |
2349 | + |
2350 | + .accordion__tab-link { |
2351 | + color: $ubuntu-orange; |
2352 | + background-image: url('../img/icons/cross-orange.svg'); |
2353 | + } |
2354 | + } |
2355 | + } |
2356 | + } |
2357 | + } |
2358 | + } |
2359 | + } |
2360 | +} |
2361 | |
2362 | === modified file 'src/maasserver/templates/maasserver/base.html' |
2363 | --- src/maasserver/templates/maasserver/base.html 2016-10-05 15:01:48 +0000 |
2364 | +++ src/maasserver/templates/maasserver/base.html 2016-10-26 23:39:26 +0000 |
2365 | @@ -196,6 +196,7 @@ |
2366 | <div class="twelve-col"> |
2367 | <div class="eight-col"> |
2368 | {% block footer-copyright %} |
2369 | +<<<<<<< TREE |
2370 | <p> |
2371 | <small> |
2372 | <strong class="u-margin--right">MAAS Version {{version}}</strong> <a href="http://maas.ubuntu.com/{{doc_version}}/changelog.html#id1">View release notes</a> | <a href="https://maas.ubuntu.com/{{doc_version}}/">View documentation</a> |
2373 | @@ -206,6 +207,12 @@ |
2374 | © 2016 Canonical Ltd. Ubuntu and Canonical are registered trademarks of Canonical Ltd. |
2375 | </small> |
2376 | </p> |
2377 | +======= |
2378 | + <p class="twelve-col"> |
2379 | + <span class="version">MAAS Version {{version}}</span> <a href="http://maas.ubuntu.com/{{doc_version}}/changelog.html#id1">View release notes</a>|<a href="http://maas.io/docs">View documentation</a> |
2380 | + </p> |
2381 | + <p class="twelve-col copy">© 2016 Canonical Ltd. Ubuntu and Canonical are registered trademarks of Canonical Ltd.</p> |
2382 | +>>>>>>> MERGE-SOURCE |
2383 | {% endblock %} |
2384 | </div> |
2385 | <div class="four-col last-col"> |
2386 | |
2387 | === modified file 'src/maasserver/templates/maasserver/index.html' |
2388 | --- src/maasserver/templates/maasserver/index.html 2016-10-05 15:01:48 +0000 |
2389 | +++ src/maasserver/templates/maasserver/index.html 2016-10-26 23:39:26 +0000 |
2390 | @@ -146,6 +146,7 @@ |
2391 | <li{% if message.tags %} class="flash-messages__item flash-messages__item--{{ message.tags }}" {% endif %}>{{ message }}</li> |
2392 | {% endfor %} |
2393 | {% endif %} |
2394 | +<<<<<<< TREE |
2395 | </ul> |
2396 | </div> |
2397 | {% endif %} |
2398 | @@ -231,5 +232,25 @@ |
2399 | </div> |
2400 | </div> |
2401 | </footer> |
2402 | +======= |
2403 | + <div id="content" data-ng-view> |
2404 | + </div> |
2405 | + </div> |
2406 | + <div class="push"></div> |
2407 | + </main> |
2408 | + </div> |
2409 | + <div class="footer-wrapper" data-maas-error-toggle> |
2410 | + <footer class="global inner-wrapper clearfix"> |
2411 | + <div class="legal clearfix"> |
2412 | + <div class="legal-inner"> |
2413 | + <p class="twelve-col"> |
2414 | + <span class="version">MAAS Version {{version}}</span> <a href="http://maas.ubuntu.com/{{doc_version}}/changelog.html#id1">View release notes</a>|<a href="http://maas.io/docs">View documentation</a> |
2415 | + </p> |
2416 | + <p class="twelve-col copy">© 2016 Canonical Ltd. Ubuntu and Canonical are registered trademarks of Canonical Ltd.</p> |
2417 | + </div> |
2418 | + </div> |
2419 | + </footer> |
2420 | + </div> |
2421 | +>>>>>>> MERGE-SOURCE |
2422 | </body> |
2423 | </html> |
2424 | |
2425 | === modified file 'src/maasserver/templates/maasserver/prefs.html' |
2426 | --- src/maasserver/templates/maasserver/prefs.html 2016-10-13 13:13:12 +0000 |
2427 | +++ src/maasserver/templates/maasserver/prefs.html 2016-10-26 23:39:26 +0000 |
2428 | @@ -29,8 +29,13 @@ |
2429 | <ul class="no-bullets"> |
2430 | {% for token in user.userprofile.get_authorisation_tokens %} |
2431 | <li class="bundle"> |
2432 | +<<<<<<< TREE |
2433 | <a href="#" class="delete-link icon right"> |
2434 | <img title="Delete MAAS key" class="left" src="{{ STATIC_URL }}assets/images/icons/delete.png" /> |
2435 | +======= |
2436 | + <a href="#" class="u-margin--top-tiny delete-link icon right"> |
2437 | + <img title="Delete MAAS key" class="left" src="{{ STATIC_URL }}img/delete.png" /> |
2438 | +>>>>>>> MERGE-SOURCE |
2439 | </a> |
2440 | <input type="text" value="{{ token.consumer.key }}:{{ token.key }}:{{ token.secret }}" id="{{ token.key }}" class="disabled" /> |
2441 | </li> |
2442 | |
2443 | === modified file 'src/maasserver/testing/factory.py' |
2444 | --- src/maasserver/testing/factory.py 2016-10-24 19:12:33 +0000 |
2445 | +++ src/maasserver/testing/factory.py 2016-10-26 23:39:26 +0000 |
2446 | @@ -441,9 +441,14 @@ |
2447 | ip_address = existing_static_ips[0] |
2448 | bmc_ip_address = self.pick_ip_in_Subnet(ip_address.subnet) |
2449 | node.power_parameters = { |
2450 | +<<<<<<< TREE |
2451 | "power_address": "qemu+ssh://user@%s/system" % ( |
2452 | factory.ip_to_url_format(bmc_ip_address)), |
2453 | "power_id": factory.make_name("power_id"), |
2454 | +======= |
2455 | + "power_address": "qemu+ssh://user@%s/system" % bmc_ip_address, |
2456 | + "power_id": factory.make_name("power_id"), |
2457 | +>>>>>>> MERGE-SOURCE |
2458 | } |
2459 | node.save() |
2460 | |
2461 | |
2462 | === modified file 'src/maasserver/tests/test_bootresources.py' |
2463 | --- src/maasserver/tests/test_bootresources.py 2016-10-12 15:26:17 +0000 |
2464 | +++ src/maasserver/tests/test_bootresources.py 2016-10-26 23:39:26 +0000 |
2465 | @@ -932,6 +932,7 @@ |
2466 | with rfile.largefile.content.open('rb') as stream: |
2467 | written_data = stream.read() |
2468 | self.assertEqual(content, written_data) |
2469 | +<<<<<<< TREE |
2470 | rfile.largefile = reload_object(rfile.largefile) |
2471 | self.assertEqual(rfile.largefile.size, len(written_data)) |
2472 | self.assertEqual(rfile.largefile.size, rfile.largefile.total_size) |
2473 | @@ -948,6 +949,11 @@ |
2474 | self.assertEqual(b'', written_data) |
2475 | rfile.largefile = reload_object(rfile.largefile) |
2476 | self.assertEqual(rfile.largefile.size, 0) |
2477 | +======= |
2478 | + rfile.largefile = reload_object(rfile.largefile) |
2479 | + self.assertEqual(rfile.largefile.size, len(written_data)) |
2480 | + self.assertEqual(rfile.largefile.size, rfile.largefile.total_size) |
2481 | +>>>>>>> MERGE-SOURCE |
2482 | |
2483 | @skip( |
2484 | "XXX blake_r: Skipped because it causes the test that runs after this " |
2485 | @@ -2098,6 +2104,7 @@ |
2486 | response = {"images": [make_rpc_boot_image()]} |
2487 | cluster_rpc.ListBootImages.return_value = succeed(response) |
2488 | self.assertTrue(service.are_boot_images_available_in_any_rack()) |
2489 | +<<<<<<< TREE |
2490 | |
2491 | |
2492 | class TestBootResourceRepoWriter(MAASServerTestCase): |
2493 | @@ -2335,3 +2342,104 @@ |
2494 | mock_insert = self.patch(boot_resource_repo_writer.store, 'insert') |
2495 | boot_resource_repo_writer.insert_item(data, src, None, pedigree, None) |
2496 | self.assertThat(mock_insert, MockNotCalled()) |
2497 | +======= |
2498 | + |
2499 | + |
2500 | +class TestBootResourceRepoWriter(MAASServerTestCase): |
2501 | + """Tests for `BootResourceRepoWriter`.""" |
2502 | + |
2503 | + def create_simplestream(self, ftypes): |
2504 | + version = '16.04' |
2505 | + arch = 'amd64' |
2506 | + subarch = 'hwe-x' |
2507 | + product = "com.ubuntu.maas.daily:v2:boot:%s:%s:%s" % ( |
2508 | + version, arch, subarch) |
2509 | + version = datetime.now().date().strftime('%Y%m%d.0') |
2510 | + versions = { |
2511 | + version: { |
2512 | + 'items': { |
2513 | + ftype: { |
2514 | + 'sha256': factory.make_name('sha256'), |
2515 | + 'path': factory.make_name('path'), |
2516 | + 'ftype': ftype, |
2517 | + 'size': random.randint(0, 2**64), |
2518 | + } for ftype in ftypes |
2519 | + } |
2520 | + } |
2521 | + } |
2522 | + products = { |
2523 | + product: { |
2524 | + 'krel': 'xenial', |
2525 | + 'subarch': subarch, |
2526 | + 'label': 'daily', |
2527 | + 'os': 'ubuntu', |
2528 | + 'arch': arch, |
2529 | + 'subarches': 'generic,%s' % subarch, |
2530 | + 'kflavor': 'generic', |
2531 | + 'version': version, |
2532 | + 'versions': versions, |
2533 | + } |
2534 | + } |
2535 | + src = { |
2536 | + 'datatype': 'image-downloads', |
2537 | + 'format': 'products:1.0', |
2538 | + 'updated': format_datetime(datetime.now()), |
2539 | + 'products': products, |
2540 | + 'content_id': 'com.ubuntu.maas:daily:v2:download' |
2541 | + } |
2542 | + return src, product, version |
2543 | + |
2544 | + def test_insert_prefers_squashfs_over_root_image(self): |
2545 | + boot_resource_repo_writer = BootResourceRepoWriter( |
2546 | + BootResourceStore(), None) |
2547 | + src, product, version = self.create_simplestream([ |
2548 | + BOOT_RESOURCE_FILE_TYPE.ROOT_IMAGE, |
2549 | + BOOT_RESOURCE_FILE_TYPE.SQUASHFS_IMAGE, |
2550 | + ]) |
2551 | + data = src['products'][product]['versions'][version]['items'][ |
2552 | + BOOT_RESOURCE_FILE_TYPE.ROOT_IMAGE] |
2553 | + pedigree = (product, version, BOOT_RESOURCE_FILE_TYPE.ROOT_IMAGE) |
2554 | + mock_insert = self.patch(boot_resource_repo_writer.store, 'insert') |
2555 | + boot_resource_repo_writer.insert_item(data, src, None, pedigree, None) |
2556 | + self.assertThat(mock_insert, MockNotCalled()) |
2557 | + |
2558 | + def test_insert_allows_squashfs(self): |
2559 | + boot_resource_repo_writer = BootResourceRepoWriter( |
2560 | + BootResourceStore(), None) |
2561 | + src, product, version = self.create_simplestream([ |
2562 | + BOOT_RESOURCE_FILE_TYPE.SQUASHFS_IMAGE, |
2563 | + ]) |
2564 | + data = src['products'][product]['versions'][version]['items'][ |
2565 | + BOOT_RESOURCE_FILE_TYPE.SQUASHFS_IMAGE] |
2566 | + pedigree = (product, version, BOOT_RESOURCE_FILE_TYPE.SQUASHFS_IMAGE) |
2567 | + mock_insert = self.patch(boot_resource_repo_writer.store, 'insert') |
2568 | + boot_resource_repo_writer.insert_item(data, src, None, pedigree, None) |
2569 | + self.assertThat(mock_insert, MockCalledOnce()) |
2570 | + |
2571 | + def test_insert_allows_root_image(self): |
2572 | + boot_resource_repo_writer = BootResourceRepoWriter( |
2573 | + BootResourceStore(), None) |
2574 | + src, product, version = self.create_simplestream([ |
2575 | + BOOT_RESOURCE_FILE_TYPE.ROOT_IMAGE, |
2576 | + ]) |
2577 | + data = src['products'][product]['versions'][version]['items'][ |
2578 | + BOOT_RESOURCE_FILE_TYPE.ROOT_IMAGE] |
2579 | + pedigree = (product, version, BOOT_RESOURCE_FILE_TYPE.ROOT_IMAGE) |
2580 | + mock_insert = self.patch(boot_resource_repo_writer.store, 'insert') |
2581 | + boot_resource_repo_writer.insert_item(data, src, None, pedigree, None) |
2582 | + self.assertThat(mock_insert, MockCalledOnce()) |
2583 | + |
2584 | + def test_insert_ignores_unknown_ftypes(self): |
2585 | + boot_resource_repo_writer = BootResourceRepoWriter( |
2586 | + BootResourceStore(), None) |
2587 | + unknown_ftype = factory.make_name('ftype') |
2588 | + src, product, version = self.create_simplestream([ |
2589 | + unknown_ftype, |
2590 | + ]) |
2591 | + data = src['products'][product]['versions'][version]['items'][ |
2592 | + unknown_ftype] |
2593 | + pedigree = (product, version, unknown_ftype) |
2594 | + mock_insert = self.patch(boot_resource_repo_writer.store, 'insert') |
2595 | + boot_resource_repo_writer.insert_item(data, src, None, pedigree, None) |
2596 | + self.assertThat(mock_insert, MockNotCalled()) |
2597 | +>>>>>>> MERGE-SOURCE |
2598 | |
2599 | === modified file 'src/maasserver/tests/test_config_forms.py' |
2600 | === modified file 'src/maasserver/tests/test_dhcp.py' |
2601 | --- src/maasserver/tests/test_dhcp.py 2016-09-30 18:18:11 +0000 |
2602 | +++ src/maasserver/tests/test_dhcp.py 2016-10-26 23:39:26 +0000 |
2603 | @@ -736,6 +736,7 @@ |
2604 | 'dhcp_snippets', |
2605 | ])) |
2606 | |
2607 | +<<<<<<< TREE |
2608 | def test__sets_ipv4_dns_from_arguments(self): |
2609 | rack_controller = factory.make_RackController(interface=False) |
2610 | vlan = factory.make_VLAN() |
2611 | @@ -837,6 +838,79 @@ |
2612 | self.assertThat( |
2613 | config['dns_servers'], |
2614 | Equals([IPAddress(addr) for addr in subnet_dns_servers])) |
2615 | +======= |
2616 | + def test__sets_ipv4_dns_from_arguments(self): |
2617 | + rack_controller = factory.make_RackController(interface=False) |
2618 | + vlan = factory.make_VLAN() |
2619 | + subnet = factory.make_Subnet(vlan=vlan, dns_servers=[], version=4) |
2620 | + factory.make_Interface( |
2621 | + INTERFACE_TYPE.PHYSICAL, vlan=vlan, node=rack_controller) |
2622 | + maas_dns = factory.make_ipv4_address() |
2623 | + ntp = factory.make_name('ntp') |
2624 | + default_domain = Domain.objects.get_default_domain() |
2625 | + config = dhcp.make_subnet_config( |
2626 | + rack_controller, subnet, maas_dns, ntp, default_domain) |
2627 | + self.expectThat(config['dns_servers'], Equals(maas_dns)) |
2628 | + |
2629 | + def test__sets_ipv6_dns_from_arguments(self): |
2630 | + rack_controller = factory.make_RackController(interface=False) |
2631 | + vlan = factory.make_VLAN() |
2632 | + subnet = factory.make_Subnet(vlan=vlan, dns_servers=[], version=6) |
2633 | + factory.make_Interface( |
2634 | + INTERFACE_TYPE.PHYSICAL, vlan=vlan, node=rack_controller) |
2635 | + maas_dns = factory.make_ipv6_address() |
2636 | + ntp = factory.make_name('ntp') |
2637 | + default_domain = Domain.objects.get_default_domain() |
2638 | + config = dhcp.make_subnet_config( |
2639 | + rack_controller, subnet, maas_dns, ntp, default_domain) |
2640 | + self.expectThat(config['dns_servers'], Equals(maas_dns)) |
2641 | + |
2642 | + def test__sets_ntp_from_arguments(self): |
2643 | + rack_controller = factory.make_RackController(interface=False) |
2644 | + vlan = factory.make_VLAN() |
2645 | + subnet = factory.make_Subnet(vlan=vlan, dns_servers=[]) |
2646 | + factory.make_Interface( |
2647 | + INTERFACE_TYPE.PHYSICAL, vlan=vlan, node=rack_controller) |
2648 | + ntp = factory.make_name('ntp') |
2649 | + default_domain = Domain.objects.get_default_domain() |
2650 | + config = dhcp.make_subnet_config( |
2651 | + rack_controller, subnet, "", ntp, default_domain) |
2652 | + self.expectThat(config['ntp_server'], Equals(ntp)) |
2653 | +>>>>>>> MERGE-SOURCE |
2654 | + |
2655 | + def test__overrides_ipv4_dns_from_subnet(self): |
2656 | + rack_controller = factory.make_RackController(interface=False) |
2657 | + vlan = factory.make_VLAN() |
2658 | + subnet = factory.make_Subnet(vlan=vlan, version=4) |
2659 | + maas_dns = factory.make_ipv4_address() |
2660 | + subnet_dns_servers = ["8.8.8.8", "8.8.4.4"] |
2661 | + subnet.dns_servers = subnet_dns_servers |
2662 | + subnet.save() |
2663 | + factory.make_Interface( |
2664 | + INTERFACE_TYPE.PHYSICAL, vlan=vlan, node=rack_controller) |
2665 | + ntp = factory.make_name('ntp') |
2666 | + default_domain = Domain.objects.get_default_domain() |
2667 | + config = dhcp.make_subnet_config( |
2668 | + rack_controller, subnet, maas_dns, ntp, default_domain) |
2669 | + self.expectThat( |
2670 | + config['dns_servers'], Equals(", ".join(subnet_dns_servers))) |
2671 | + |
2672 | + def test__overrides_ipv6_dns_from_subnet(self): |
2673 | + rack_controller = factory.make_RackController(interface=False) |
2674 | + vlan = factory.make_VLAN() |
2675 | + subnet = factory.make_Subnet(vlan=vlan, version=6) |
2676 | + maas_dns = factory.make_ipv6_address() |
2677 | + subnet_dns_servers = ["2001:db8::1", "2001:db8::2"] |
2678 | + subnet.dns_servers = subnet_dns_servers |
2679 | + subnet.save() |
2680 | + factory.make_Interface( |
2681 | + INTERFACE_TYPE.PHYSICAL, vlan=vlan, node=rack_controller) |
2682 | + ntp = factory.make_name('ntp') |
2683 | + default_domain = Domain.objects.get_default_domain() |
2684 | + config = dhcp.make_subnet_config( |
2685 | + rack_controller, subnet, maas_dns, ntp, default_domain) |
2686 | + self.expectThat( |
2687 | + config['dns_servers'], Equals(", ".join(subnet_dns_servers))) |
2688 | |
2689 | def test__sets_domain_name_from_passed_domain(self): |
2690 | rack_controller = factory.make_RackController(interface=False) |
2691 | |
2692 | === modified file 'src/maasserver/tests/test_forms_interface_link.py' |
2693 | === modified file 'src/maasserver/tests/test_preseed.py' |
2694 | --- src/maasserver/tests/test_preseed.py 2016-10-13 16:32:38 +0000 |
2695 | +++ src/maasserver/tests/test_preseed.py 2016-10-26 23:39:26 +0000 |
2696 | @@ -457,6 +457,33 @@ |
2697 | 'server_url', 'syslog_host_port'], |
2698 | context) |
2699 | |
2700 | +<<<<<<< TREE |
2701 | +======= |
2702 | + def test_get_preseed_context_archive_refs(self): |
2703 | + # urlparse lowercases the hostnames. That should not have any |
2704 | + # impact but for testing, create lower-case hostnames. |
2705 | + main_archive = factory.make_url(netloc="main-archive.example.com") |
2706 | + ports_archive = factory.make_url(netloc="ports-archive.example.com") |
2707 | + Config.objects.set_config('main_archive', main_archive) |
2708 | + Config.objects.set_config('ports_archive', ports_archive) |
2709 | + context = get_preseed_context() |
2710 | + parsed_main_archive = urlparse(main_archive) |
2711 | + parsed_ports_archive = urlparse(ports_archive) |
2712 | + self.assertEqual( |
2713 | + ( |
2714 | + parsed_main_archive.hostname, |
2715 | + parsed_main_archive.path.lstrip('/'), |
2716 | + parsed_ports_archive.hostname, |
2717 | + parsed_ports_archive.path.lstrip('/'), |
2718 | + ), |
2719 | + ( |
2720 | + context['main_archive_hostname'], |
2721 | + context['main_archive_directory'], |
2722 | + context['ports_archive_hostname'], |
2723 | + context['ports_archive_directory'], |
2724 | + )) |
2725 | + |
2726 | +>>>>>>> MERGE-SOURCE |
2727 | |
2728 | class TestNodePreseedContext( |
2729 | PreseedRPCMixin, BootImageHelperMixin, MAASTransactionServerTestCase): |
2730 | |
2731 | === modified file 'src/maasserver/tests/test_preseed_network.py' |
2732 | --- src/maasserver/tests/test_preseed_network.py 2016-10-24 23:23:10 +0000 |
2733 | +++ src/maasserver/tests/test_preseed_network.py 2016-10-26 23:39:26 +0000 |
2734 | @@ -243,12 +243,21 @@ |
2735 | |
2736 | def collectDNSConfig(self, node, ipv4=True, ipv6=True): |
2737 | config = "- type: nameserver\n address: %s\n search:\n" % ( |
2738 | +<<<<<<< TREE |
2739 | repr(node.get_default_dns_servers(ipv4=ipv4, ipv6=ipv6))) |
2740 | domain_name = node.domain.name |
2741 | dns_searches = [domain_name] + [ |
2742 | name |
2743 | for name in sorted(get_dns_search_paths()) |
2744 | if name != domain_name] |
2745 | +======= |
2746 | + repr(node.get_default_dns_servers())) |
2747 | + domain_name = node.domain.name |
2748 | + dns_searches = [domain_name] + [ |
2749 | + name |
2750 | + for name in sorted(get_dns_search_paths()) |
2751 | + if name != domain_name] |
2752 | +>>>>>>> MERGE-SOURCE |
2753 | for dns_name in dns_searches: |
2754 | config += " - %s\n" % dns_name |
2755 | return config |
2756 | |
2757 | === modified file 'src/maasserver/tests/test_preseed_storage.py' |
2758 | === modified file 'src/maasserver/tests/test_start_up.py' |
2759 | --- src/maasserver/tests/test_start_up.py 2016-06-29 10:00:16 +0000 |
2760 | +++ src/maasserver/tests/test_start_up.py 2016-10-26 23:39:26 +0000 |
2761 | @@ -145,6 +145,7 @@ |
2762 | self.assertThat(RegionController.objects.all(), HasLength(1)) |
2763 | |
2764 | def test__creates_maas_id_file(self): |
2765 | +<<<<<<< TREE |
2766 | start_up.is_master_process.return_value = False |
2767 | self.assertThat(get_maas_id(), Is(None)) |
2768 | with post_commit_hooks: |
2769 | @@ -173,3 +174,95 @@ |
2770 | Traceback (most recent call last):... |
2771 | Failure: maastesting.factory.TestException#...: boom |
2772 | """)) |
2773 | +======= |
2774 | + self.patch(start_up, "is_master_process").return_value = True |
2775 | + mock_set_maas_id = self.patch_autospec(start_up, "set_maas_id") |
2776 | + self.patch(start_up.RegionController, 'refresh') |
2777 | + with post_commit_hooks: |
2778 | + start_up.inner_start_up() |
2779 | + self.assertThat(mock_set_maas_id, MockCalledOnce()) |
2780 | + |
2781 | + def test__doesnt_create_maas_id_file_if_not_master(self): |
2782 | + self.patch(start_up, "is_master_process").return_value = False |
2783 | + mock_set_maas_id = self.patch_autospec(start_up, "set_maas_id") |
2784 | + with post_commit_hooks: |
2785 | + start_up.inner_start_up() |
2786 | + self.assertThat(mock_set_maas_id, MockNotCalled()) |
2787 | + |
2788 | + |
2789 | +class TestCreateRegionObj(MAASServerTestCase): |
2790 | + |
2791 | + """Tests for the actual work done in `create_region_obj`.""" |
2792 | + |
2793 | + def test__creates_obj(self): |
2794 | + region = start_up.create_region_obj() |
2795 | + self.assertIsNotNone(region) |
2796 | + self.assertIsNotNone( |
2797 | + RegionController.objects.get(system_id=region.system_id)) |
2798 | + |
2799 | + def test__doesnt_read_maas_id_from_cache(self): |
2800 | + set_maas_id(factory.make_string()) |
2801 | + os.unlink(get_path('/var/lib/maas/maas_id')) |
2802 | + region = start_up.create_region_obj() |
2803 | + self.assertIsNotNone(region) |
2804 | + self.assertIsNotNone( |
2805 | + RegionController.objects.get(system_id=region.system_id)) |
2806 | + |
2807 | + def test__finds_region_by_maas_id(self): |
2808 | + region = factory.make_RegionController() |
2809 | + self.useFixture(MAASIDFixture(region.system_id)) |
2810 | + self.assertEquals(region, start_up.create_region_obj()) |
2811 | + |
2812 | + def test__finds_region_by_hostname(self): |
2813 | + region = factory.make_RegionController() |
2814 | + mock_gethostname = self.patch_autospec(start_up, "gethostname") |
2815 | + mock_gethostname.return_value = region.hostname |
2816 | + self.assertEquals(region, start_up.create_region_obj()) |
2817 | + |
2818 | + def test__finds_region_by_mac(self): |
2819 | + region = factory.make_RegionController() |
2820 | + factory.make_Interface(node=region) |
2821 | + mock_get_mac_addresses = self.patch_autospec( |
2822 | + start_up, "get_mac_addresses") |
2823 | + mock_get_mac_addresses.return_value = [ |
2824 | + nic.mac_address.raw |
2825 | + for nic in region.interface_set.all() |
2826 | + ] |
2827 | + self.assertEquals(region, start_up.create_region_obj()) |
2828 | + |
2829 | + def test__converts_rack_to_region_rack(self): |
2830 | + rack = factory.make_RackController() |
2831 | + self.useFixture(MAASIDFixture(rack.system_id)) |
2832 | + region_rack = start_up.create_region_obj() |
2833 | + self.assertEquals(rack, region_rack) |
2834 | + self.assertEquals( |
2835 | + region_rack.node_type, NODE_TYPE.REGION_AND_RACK_CONTROLLER) |
2836 | + |
2837 | + def test__converts_node_to_region_rack(self): |
2838 | + node = factory.make_Node( |
2839 | + node_type=factory.pick_choice( |
2840 | + NODE_TYPE_CHOICES, |
2841 | + but_not=[ |
2842 | + NODE_TYPE.REGION_CONTROLLER, |
2843 | + NODE_TYPE.RACK_CONTROLLER, |
2844 | + NODE_TYPE.REGION_AND_RACK_CONTROLLER, |
2845 | + ])) |
2846 | + self.useFixture(MAASIDFixture(node.system_id)) |
2847 | + region = start_up.create_region_obj() |
2848 | + self.assertEquals(node, region) |
2849 | + self.assertEquals(region.node_type, NODE_TYPE.REGION_CONTROLLER) |
2850 | + |
2851 | + def test__sets_owner_if_none(self): |
2852 | + region = factory.make_RegionController() |
2853 | + self.useFixture(MAASIDFixture(region.system_id)) |
2854 | + self.assertEquals( |
2855 | + get_worker_user(), start_up.create_region_obj().owner) |
2856 | + |
2857 | + def test__leaves_owner_if_set(self): |
2858 | + region = factory.make_RegionController() |
2859 | + self.useFixture(MAASIDFixture(region.system_id)) |
2860 | + user = factory.make_User() |
2861 | + region.owner = user |
2862 | + region.save() |
2863 | + self.assertEquals(user, start_up.create_region_obj().owner) |
2864 | +>>>>>>> MERGE-SOURCE |
2865 | |
2866 | === modified file 'src/maasserver/triggers/tests/test_websocket.py' |
2867 | === modified file 'src/maasserver/triggers/tests/test_websocket_listener.py' |
2868 | --- src/maasserver/triggers/tests/test_websocket_listener.py 2016-10-12 15:26:17 +0000 |
2869 | +++ src/maasserver/triggers/tests/test_websocket_listener.py 2016-10-26 23:39:26 +0000 |
2870 | @@ -2923,6 +2923,7 @@ |
2871 | yield listener.stopService() |
2872 | |
2873 | |
2874 | +<<<<<<< TREE |
2875 | class TestPackageRepositoryListener( |
2876 | MAASTransactionServerTestCase, TransactionalHelpersMixin): |
2877 | """End-to-end test of both the listeners code and the cluster |
2878 | @@ -3102,6 +3103,129 @@ |
2879 | yield listener.stopService() |
2880 | |
2881 | |
2882 | +======= |
2883 | +class TestIPRangeSubnetListener( |
2884 | + MAASTransactionServerTestCase, TransactionalHelpersMixin): |
2885 | + """End-to-end test of both the listeners code and the triggers on |
2886 | + maasserver_iprange tables that notifies affected subnets.""" |
2887 | + |
2888 | + @wait_for_reactor |
2889 | + @inlineCallbacks |
2890 | + def test__calls_handler_on_create_notification(self): |
2891 | + yield deferToDatabase(register_websocket_triggers) |
2892 | + subnet = yield deferToDatabase( |
2893 | + self.create_subnet, { |
2894 | + "cidr": '192.168.0.0/24', |
2895 | + "gateway_ip": '192.168.0.1', |
2896 | + "dns_servers": [], |
2897 | + }) |
2898 | + |
2899 | + listener = PostgresListenerService() |
2900 | + dv = DeferredValue() |
2901 | + listener.register("subnet", lambda *args: dv.set(args)) |
2902 | + yield listener.startService() |
2903 | + try: |
2904 | + iprange = yield deferToDatabase( |
2905 | + self.create_iprange, { |
2906 | + "type": IPRANGE_TYPE.DYNAMIC, |
2907 | + "subnet": subnet, |
2908 | + "start_ip": '192.168.0.100', |
2909 | + "end_ip": '192.168.0.110', |
2910 | + }) |
2911 | + yield dv.get(timeout=2) |
2912 | + self.assertEqual(('update', '%s' % iprange.subnet.id), dv.value) |
2913 | + finally: |
2914 | + yield listener.stopService() |
2915 | + |
2916 | + @wait_for_reactor |
2917 | + @inlineCallbacks |
2918 | + def test__calls_handler_on_update_notification(self): |
2919 | + yield deferToDatabase(register_websocket_triggers) |
2920 | + iprange = yield deferToDatabase(self.create_iprange) |
2921 | + new_end_ip = factory.pick_ip_in_IPRange(iprange) |
2922 | + |
2923 | + listener = PostgresListenerService() |
2924 | + dv = DeferredValue() |
2925 | + listener.register("subnet", lambda *args: dv.set(args)) |
2926 | + yield listener.startService() |
2927 | + try: |
2928 | + yield deferToDatabase( |
2929 | + self.update_iprange, |
2930 | + iprange.id, {"end_ip": new_end_ip}) |
2931 | + yield dv.get(timeout=2) |
2932 | + self.assertEqual(('update', '%s' % iprange.subnet.id), dv.value) |
2933 | + finally: |
2934 | + yield listener.stopService() |
2935 | + |
2936 | + @wait_for_reactor |
2937 | + @inlineCallbacks |
2938 | + def test__calls_handler_on_update_on_old_and_new_subnet_notification(self): |
2939 | + yield deferToDatabase(register_websocket_triggers) |
2940 | + old_subnet = yield deferToDatabase( |
2941 | + self.create_subnet, { |
2942 | + "cidr": '192.168.0.0/24', |
2943 | + "gateway_ip": '192.168.0.1', |
2944 | + "dns_servers": [], |
2945 | + }) |
2946 | + new_subnet = yield deferToDatabase( |
2947 | + self.create_subnet, { |
2948 | + "cidr": '192.168.1.0/24', |
2949 | + "gateway_ip": '192.168.1.1', |
2950 | + "dns_servers": [], |
2951 | + }) |
2952 | + iprange = yield deferToDatabase( |
2953 | + self.create_iprange, { |
2954 | + "type": IPRANGE_TYPE.DYNAMIC, |
2955 | + "subnet": old_subnet, |
2956 | + "start_ip": '192.168.0.100', |
2957 | + "end_ip": '192.168.0.110', |
2958 | + }) |
2959 | + dvs = [DeferredValue(), DeferredValue()] |
2960 | + |
2961 | + def set_defer_value(*args): |
2962 | + for dv in dvs: |
2963 | + if not dv.isSet: |
2964 | + dv.set(args) |
2965 | + break |
2966 | + |
2967 | + listener = PostgresListenerService() |
2968 | + listener.register("subnet", set_defer_value) |
2969 | + yield listener.startService() |
2970 | + try: |
2971 | + yield deferToDatabase(self.update_iprange, iprange.id, { |
2972 | + "type": IPRANGE_TYPE.DYNAMIC, |
2973 | + "subnet": new_subnet, |
2974 | + "start_ip": '192.168.1.10', |
2975 | + "end_ip": '192.168.1.150', |
2976 | + }) |
2977 | + yield dvs[0].get(timeout=2) |
2978 | + yield dvs[1].get(timeout=2) |
2979 | + self.assertItemsEqual([ |
2980 | + ('update', '%s' % old_subnet.id), |
2981 | + ('update', '%s' % new_subnet.id), |
2982 | + ], [dvs[0].value, dvs[1].value]) |
2983 | + finally: |
2984 | + yield listener.stopService() |
2985 | + |
2986 | + @wait_for_reactor |
2987 | + @inlineCallbacks |
2988 | + def test__calls_handler_on_delete_notification(self): |
2989 | + yield deferToDatabase(register_websocket_triggers) |
2990 | + iprange = yield deferToDatabase(self.create_iprange) |
2991 | + |
2992 | + listener = PostgresListenerService() |
2993 | + dv = DeferredValue() |
2994 | + listener.register("subnet", lambda *args: dv.set(args)) |
2995 | + yield listener.startService() |
2996 | + try: |
2997 | + yield deferToDatabase(self.delete_iprange, iprange.id) |
2998 | + yield dv.get(timeout=2) |
2999 | + self.assertEqual(('update', '%s' % iprange.subnet.id), dv.value) |
3000 | + finally: |
3001 | + yield listener.stopService() |
3002 | + |
3003 | + |
3004 | +>>>>>>> MERGE-SOURCE |
3005 | class TestNodeTypeChange( |
3006 | MAASTransactionServerTestCase, TransactionalHelpersMixin): |
3007 | """End-to-end test of node type change triggers code.""" |
3008 | |
3009 | === modified file 'src/maasserver/triggers/websocket.py' |
3010 | === added file 'src/maasserver/views/tests/test_images.py.OTHER' |
3011 | --- src/maasserver/views/tests/test_images.py.OTHER 1970-01-01 00:00:00 +0000 |
3012 | +++ src/maasserver/views/tests/test_images.py.OTHER 2016-10-26 23:39:26 +0000 |
3013 | @@ -0,0 +1,991 @@ |
3014 | +# Copyright 2014-2016 Canonical Ltd. This software is licensed under the |
3015 | +# GNU Affero General Public License version 3 (see the file LICENSE). |
3016 | + |
3017 | +"""Test maasserver images views.""" |
3018 | + |
3019 | +__all__ = [] |
3020 | + |
3021 | +import datetime |
3022 | +import http.client |
3023 | +import json |
3024 | +import random |
3025 | + |
3026 | +from django.conf import settings |
3027 | +from django.core.urlresolvers import reverse |
3028 | +from lxml.html import fromstring |
3029 | +from maasserver.enum import ( |
3030 | + BOOT_RESOURCE_TYPE, |
3031 | + NODE_STATUS, |
3032 | +) |
3033 | +from maasserver.models import ( |
3034 | + BootResource, |
3035 | + BootSourceCache, |
3036 | + BootSourceSelection, |
3037 | + Config, |
3038 | +) |
3039 | +from maasserver.models.signals import bootsources |
3040 | +from maasserver.testing import extract_redirect |
3041 | +from maasserver.testing.factory import factory |
3042 | +from maasserver.testing.testcase import MAASServerTestCase |
3043 | +from maasserver.utils.converters import human_readable_bytes |
3044 | +from maasserver.utils.orm import ( |
3045 | + get_one, |
3046 | + reload_object, |
3047 | +) |
3048 | +from maasserver.views import images as images_view |
3049 | +from maastesting.matchers import ( |
3050 | + MockCalledOnceWith, |
3051 | + MockCalledWith, |
3052 | +) |
3053 | +from requests import ConnectionError |
3054 | +from testtools.matchers import ( |
3055 | + ContainsAll, |
3056 | + HasLength, |
3057 | +) |
3058 | + |
3059 | + |
3060 | +class UbuntuImagesTest(MAASServerTestCase): |
3061 | + |
3062 | + def setUp(self): |
3063 | + super(UbuntuImagesTest, self).setUp() |
3064 | + # Disable boot source cache signals. |
3065 | + self.addCleanup(bootsources.signals.enable) |
3066 | + bootsources.signals.disable() |
3067 | + |
3068 | + def patch_get_os_info_from_boot_sources( |
3069 | + self, sources, releases=None, arches=None): |
3070 | + if releases is None: |
3071 | + releases = [factory.make_name('release') for _ in range(3)] |
3072 | + if arches is None: |
3073 | + arches = [factory.make_name('arch') for _ in range(3)] |
3074 | + mock_get_os_info = self.patch( |
3075 | + images_view, 'get_os_info_from_boot_sources') |
3076 | + mock_get_os_info.return_value = (sources, releases, arches) |
3077 | + return mock_get_os_info |
3078 | + |
3079 | + def test_shows_connection_error(self): |
3080 | + self.client_log_in(as_admin=True) |
3081 | + mock_get_os_info = self.patch( |
3082 | + images_view, 'get_os_info_from_boot_sources') |
3083 | + mock_get_os_info.side_effect = ConnectionError() |
3084 | + response = self.client.get(reverse('images')) |
3085 | + doc = fromstring(response.content) |
3086 | + warnings = doc.cssselect('div#connection-error') |
3087 | + self.assertEqual(1, len(warnings)) |
3088 | + |
3089 | + def test_shows_no_ubuntu_sources(self): |
3090 | + self.client_log_in(as_admin=True) |
3091 | + response = self.client.get(reverse('images')) |
3092 | + doc = fromstring(response.content) |
3093 | + warnings = doc.cssselect('div#no-ubuntu-sources') |
3094 | + self.assertEqual(1, len(warnings)) |
3095 | + |
3096 | + def test_shows_too_many_ubuntu_sources(self): |
3097 | + self.client_log_in(as_admin=True) |
3098 | + sources = [factory.make_BootSource() for _ in range(2)] |
3099 | + self.patch_get_os_info_from_boot_sources(sources) |
3100 | + response = self.client.get(reverse('images')) |
3101 | + doc = fromstring(response.content) |
3102 | + warnings = doc.cssselect('div#too-many-ubuntu-sources') |
3103 | + self.assertEqual(1, len(warnings)) |
3104 | + |
3105 | + def test_shows_release_options(self): |
3106 | + self.client_log_in(as_admin=True) |
3107 | + sources = [factory.make_BootSource()] |
3108 | + releases = [factory.make_name('release') for _ in range(3)] |
3109 | + self.patch_get_os_info_from_boot_sources(sources, releases=releases) |
3110 | + response = self.client.get(reverse('images')) |
3111 | + doc = fromstring(response.content) |
3112 | + releases_content = doc.cssselect( |
3113 | + 'ul#ubuntu-releases')[0].text_content() |
3114 | + self.assertThat(releases_content, ContainsAll(releases)) |
3115 | + |
3116 | + def test_shows_architecture_options(self): |
3117 | + self.client_log_in(as_admin=True) |
3118 | + sources = [factory.make_BootSource()] |
3119 | + arches = [factory.make_name('arch') for _ in range(3)] |
3120 | + self.patch_get_os_info_from_boot_sources(sources, arches=arches) |
3121 | + response = self.client.get(reverse('images')) |
3122 | + doc = fromstring(response.content) |
3123 | + arches_content = doc.cssselect( |
3124 | + 'ul#ubuntu-arches')[0].text_content() |
3125 | + self.assertThat(arches_content, ContainsAll(arches)) |
3126 | + |
3127 | + def test_shows_missing_images_warning_if_not_ubuntu_boot_resources(self): |
3128 | + self.client_log_in() |
3129 | + response = self.client.get(reverse('images')) |
3130 | + doc = fromstring(response.content) |
3131 | + warnings = doc.cssselect('div#missing-ubuntu-images') |
3132 | + self.assertEqual(1, len(warnings)) |
3133 | + |
3134 | + def test_hides_import_button_if_not_admin(self): |
3135 | + self.client_log_in() |
3136 | + sources = [factory.make_BootSource()] |
3137 | + self.patch_get_os_info_from_boot_sources(sources) |
3138 | + response = self.client.get(reverse('images')) |
3139 | + doc = fromstring(response.content) |
3140 | + import_button = doc.cssselect( |
3141 | + '#ubuntu-images')[0].cssselect('input[type="submit"]') |
3142 | + self.assertEqual(0, len(import_button)) |
3143 | + |
3144 | + def test_shows_import_button_if_admin(self): |
3145 | + self.client_log_in(as_admin=True) |
3146 | + sources = [factory.make_BootSource()] |
3147 | + self.patch_get_os_info_from_boot_sources(sources) |
3148 | + response = self.client.get(reverse('images')) |
3149 | + doc = fromstring(response.content) |
3150 | + import_button = doc.cssselect( |
3151 | + '#ubuntu-images')[0].cssselect('input[type="submit"]') |
3152 | + self.assertEqual(1, len(import_button)) |
3153 | + |
3154 | + def test_post_returns_forbidden_if_not_admin(self): |
3155 | + self.client_log_in() |
3156 | + response = self.client.post( |
3157 | + reverse('images'), {'ubuntu_images': 1}) |
3158 | + self.assertEqual(http.client.FORBIDDEN, response.status_code) |
3159 | + |
3160 | + def test_import_calls_import_resources(self): |
3161 | + self.client_log_in(as_admin=True) |
3162 | + sources = [factory.make_BootSource()] |
3163 | + self.patch_get_os_info_from_boot_sources(sources) |
3164 | + mock_import = self.patch(images_view, 'import_resources') |
3165 | + response = self.client.post( |
3166 | + reverse('images'), {'ubuntu_images': 1}) |
3167 | + self.assertEqual(http.client.FOUND, response.status_code) |
3168 | + self.assertThat(mock_import, MockCalledOnceWith()) |
3169 | + |
3170 | + def test_import_sets_empty_selections(self): |
3171 | + self.client_log_in(as_admin=True) |
3172 | + source = factory.make_BootSource() |
3173 | + self.patch_get_os_info_from_boot_sources([source]) |
3174 | + self.patch(images_view, 'import_resources') |
3175 | + response = self.client.post( |
3176 | + reverse('images'), {'ubuntu_images': 1}) |
3177 | + self.assertEqual(http.client.FOUND, response.status_code) |
3178 | + |
3179 | + selections = BootSourceSelection.objects.filter(boot_source=source) |
3180 | + self.assertThat(selections, HasLength(1)) |
3181 | + self.assertEqual( |
3182 | + (selections[0].os, selections[0].release, |
3183 | + selections[0].arches, selections[0].subarches, |
3184 | + selections[0].labels), |
3185 | + ("ubuntu", "", [], ["*"], ["*"])) |
3186 | + |
3187 | + def test_import_sets_release_selections(self): |
3188 | + self.client_log_in(as_admin=True) |
3189 | + source = factory.make_BootSource() |
3190 | + releases = [factory.make_name('release') for _ in range(3)] |
3191 | + self.patch_get_os_info_from_boot_sources([source]) |
3192 | + self.patch(images_view, 'import_resources') |
3193 | + response = self.client.post( |
3194 | + reverse('images'), {'ubuntu_images': 1, 'release': releases}) |
3195 | + self.assertEqual(http.client.FOUND, response.status_code) |
3196 | + |
3197 | + selections = BootSourceSelection.objects.filter(boot_source=source) |
3198 | + self.assertThat(selections, HasLength(len(releases))) |
3199 | + self.assertItemsEqual( |
3200 | + releases, |
3201 | + [selection.release for selection in selections]) |
3202 | + |
3203 | + def test_import_sets_arches_on_selections(self): |
3204 | + self.client_log_in(as_admin=True) |
3205 | + source = factory.make_BootSource() |
3206 | + releases = [factory.make_name('release') for _ in range(3)] |
3207 | + arches = [factory.make_name('arches') for _ in range(3)] |
3208 | + self.patch_get_os_info_from_boot_sources([source]) |
3209 | + self.patch(images_view, 'import_resources') |
3210 | + response = self.client.post( |
3211 | + reverse('images'), |
3212 | + {'ubuntu_images': 1, 'release': releases, 'arch': arches}) |
3213 | + self.assertEqual(http.client.FOUND, response.status_code) |
3214 | + |
3215 | + selections = BootSourceSelection.objects.filter(boot_source=source) |
3216 | + self.assertThat(selections, HasLength(len(releases))) |
3217 | + self.assertItemsEqual( |
3218 | + [arches, arches, arches], |
3219 | + [selection.arches for selection in selections]) |
3220 | + |
3221 | + def test_import_removes_old_selections(self): |
3222 | + self.client_log_in(as_admin=True) |
3223 | + source = factory.make_BootSource() |
3224 | + release = factory.make_name('release') |
3225 | + delete_selection = BootSourceSelection.objects.create( |
3226 | + boot_source=source, os='ubuntu', |
3227 | + release=factory.make_name('release')) |
3228 | + keep_selection = BootSourceSelection.objects.create( |
3229 | + boot_source=source, os='ubuntu', release=release) |
3230 | + self.patch_get_os_info_from_boot_sources([source]) |
3231 | + self.patch(images_view, 'import_resources') |
3232 | + response = self.client.post( |
3233 | + reverse('images'), {'ubuntu_images': 1, 'release': [release]}) |
3234 | + self.assertEqual(http.client.FOUND, response.status_code) |
3235 | + self.assertIsNone(reload_object(delete_selection)) |
3236 | + self.assertIsNotNone(reload_object(keep_selection)) |
3237 | + |
3238 | + |
3239 | +class OtherImagesTest(MAASServerTestCase): |
3240 | + |
3241 | + def setUp(self): |
3242 | + super(OtherImagesTest, self).setUp() |
3243 | + # Disable boot source cache signals. |
3244 | + self.addCleanup(bootsources.signals.enable) |
3245 | + bootsources.signals.disable() |
3246 | + |
3247 | + def make_other_resource(self, os=None, arch=None, subarch=None, |
3248 | + release=None): |
3249 | + if os is None: |
3250 | + os = factory.make_name('os') |
3251 | + if arch is None: |
3252 | + arch = factory.make_name('arch') |
3253 | + if subarch is None: |
3254 | + subarch = factory.make_name('subarch') |
3255 | + if release is None: |
3256 | + release = factory.make_name('release') |
3257 | + name = '%s/%s' % (os, release) |
3258 | + architecture = '%s/%s' % (arch, subarch) |
3259 | + resource = factory.make_BootResource( |
3260 | + rtype=BOOT_RESOURCE_TYPE.SYNCED, |
3261 | + name=name, architecture=architecture) |
3262 | + resource_set = factory.make_BootResourceSet(resource) |
3263 | + factory.make_boot_resource_file_with_content(resource_set) |
3264 | + return resource |
3265 | + |
3266 | + def test_hides_other_synced_images_section(self): |
3267 | + self.client_log_in() |
3268 | + BootSourceCache.objects.all().delete() |
3269 | + response = self.client.get(reverse('images')) |
3270 | + doc = fromstring(response.content) |
3271 | + section = doc.cssselect('div#other-sync-images') |
3272 | + self.assertEqual( |
3273 | + 0, len(section), "Didn't hide the other images section.") |
3274 | + |
3275 | + def test_shows_other_synced_images_section(self): |
3276 | + self.client_log_in(as_admin=True) |
3277 | + factory.make_BootSourceCache() |
3278 | + response = self.client.get(reverse('images')) |
3279 | + doc = fromstring(response.content) |
3280 | + section = doc.cssselect('div#other-sync-images') |
3281 | + self.assertEqual( |
3282 | + 1, len(section), "Didn't show the other images section.") |
3283 | + |
3284 | + def test_hides_image_from_boot_source_cache_without_admin(self): |
3285 | + self.client_log_in() |
3286 | + factory.make_BootSourceCache() |
3287 | + response = self.client.get(reverse('images')) |
3288 | + doc = fromstring(response.content) |
3289 | + rows = doc.cssselect('table#other-resources > tbody > tr') |
3290 | + self.assertEqual( |
3291 | + 0, len(rows), "Didn't hide unselected boot image from non-admin.") |
3292 | + |
3293 | + def test_shows_image_from_boot_source_cache_with_admin(self): |
3294 | + self.client_log_in(as_admin=True) |
3295 | + cache = factory.make_BootSourceCache() |
3296 | + response = self.client.get(reverse('images')) |
3297 | + doc = fromstring(response.content) |
3298 | + title = doc.cssselect( |
3299 | + 'table#other-resources > tbody > ' |
3300 | + 'tr > td')[1].text_content().strip() |
3301 | + self.assertEqual('%s/%s' % (cache.os, cache.release), title) |
3302 | + |
3303 | + def test_shows_checkbox_for_boot_source_cache(self): |
3304 | + self.client_log_in(as_admin=True) |
3305 | + factory.make_BootSourceCache() |
3306 | + response = self.client.get(reverse('images')) |
3307 | + doc = fromstring(response.content) |
3308 | + checkbox = doc.cssselect( |
3309 | + 'table#other-resources > tbody > tr > td > input') |
3310 | + self.assertEqual( |
3311 | + 1, len(checkbox), "Didn't show checkbox for boot image.") |
3312 | + |
3313 | + def test_shows_last_update_time_for_synced_resource(self): |
3314 | + self.client_log_in(as_admin=True) |
3315 | + cache = factory.make_BootSourceCache() |
3316 | + self.make_other_resource( |
3317 | + os=cache.os, arch=cache.arch, |
3318 | + subarch=cache.subarch, release=cache.release) |
3319 | + response = self.client.get(reverse('images')) |
3320 | + doc = fromstring(response.content) |
3321 | + last_update = doc.cssselect( |
3322 | + 'table#other-resources > tbody > ' |
3323 | + 'tr > td')[5].text_content().strip() |
3324 | + dt = datetime.datetime.strptime(last_update, '%a, %d %b. %Y %H:%M:%S') |
3325 | + # TimestampedModel includes microseconds which we don't print. To |
3326 | + # confirm the right time is returned its easiest to just compare the |
3327 | + # ctimes |
3328 | + self.assertEquals(cache.updated.ctime(), dt.ctime()) |
3329 | + |
3330 | + def test_shows_number_of_nodes_for_synced_resource(self): |
3331 | + self.client_log_in(as_admin=True) |
3332 | + cache = factory.make_BootSourceCache() |
3333 | + resource = self.make_other_resource( |
3334 | + os=cache.os, arch=cache.arch, |
3335 | + subarch=cache.subarch, release=cache.release) |
3336 | + factory.make_Node( |
3337 | + status=NODE_STATUS.DEPLOYED, |
3338 | + osystem=cache.os, distro_series=cache.release, |
3339 | + architecture=resource.architecture) |
3340 | + response = self.client.get(reverse('images')) |
3341 | + doc = fromstring(response.content) |
3342 | + number_of_nodes = doc.cssselect( |
3343 | + 'table#other-resources > tbody > ' |
3344 | + 'tr > td')[4].text_content().strip() |
3345 | + self.assertEqual( |
3346 | + 1, int(number_of_nodes), |
3347 | + "Incorrect number of deployed nodes for resource.") |
3348 | + |
3349 | + def test_shows_apply_button_if_admin(self): |
3350 | + self.client_log_in(as_admin=True) |
3351 | + factory.make_BootSourceCache() |
3352 | + response = self.client.get(reverse('images')) |
3353 | + doc = fromstring(response.content) |
3354 | + apply_button = doc.cssselect( |
3355 | + '#other-sync-images')[0].cssselect('input[type="submit"]') |
3356 | + self.assertEqual( |
3357 | + 1, len(apply_button), "Didn't show apply button for admin.") |
3358 | + |
3359 | + def test_hides_apply_button_if_import_running(self): |
3360 | + self.client_log_in(as_admin=True) |
3361 | + factory.make_BootSourceCache() |
3362 | + self.patch( |
3363 | + images_view, 'is_import_resources_running').return_value = True |
3364 | + response = self.client.get(reverse('images')) |
3365 | + doc = fromstring(response.content) |
3366 | + apply_button = doc.cssselect( |
3367 | + '#other-sync-images')[0].cssselect('input[type="submit"]') |
3368 | + self.assertEqual( |
3369 | + 0, len(apply_button), |
3370 | + "Didn't hide apply button when import running.") |
3371 | + |
3372 | + def test_calls_get_os_release_title_for_other_resource(self): |
3373 | + self.client_log_in() |
3374 | + title = factory.make_name('title') |
3375 | + cache = factory.make_BootSourceCache() |
3376 | + resource = self.make_other_resource( |
3377 | + os=cache.os, arch=cache.arch, |
3378 | + subarch=cache.subarch, release=cache.release) |
3379 | + mock_get_title = self.patch(images_view, 'get_os_release_title') |
3380 | + mock_get_title.return_value = title |
3381 | + response = self.client.get(reverse('images')) |
3382 | + doc = fromstring(response.content) |
3383 | + row_title = doc.cssselect( |
3384 | + 'table#other-resources > tbody > ' |
3385 | + 'tr > td')[1].text_content().strip() |
3386 | + self.assertEqual(title, row_title) |
3387 | + os, release = resource.name.split('/') |
3388 | + self.assertThat(mock_get_title, MockCalledWith(os, release)) |
3389 | + |
3390 | + def test_post_returns_forbidden_if_not_admin(self): |
3391 | + self.client_log_in() |
3392 | + response = self.client.post( |
3393 | + reverse('images'), {'other_images': 1}) |
3394 | + self.assertEqual(http.client.FORBIDDEN, response.status_code) |
3395 | + |
3396 | + def test_post_clears_all_other_os_selections(self): |
3397 | + self.client_log_in(as_admin=True) |
3398 | + source = factory.make_BootSource() |
3399 | + ubuntu_selection = BootSourceSelection.objects.create( |
3400 | + boot_source=source, os='ubuntu') |
3401 | + other_selection = BootSourceSelection.objects.create( |
3402 | + boot_source=source, os=factory.make_name('os')) |
3403 | + self.patch(images_view, 'import_resources') |
3404 | + response = self.client.post( |
3405 | + reverse('images'), {'other_images': 1, 'image': []}) |
3406 | + self.assertEqual(http.client.FOUND, response.status_code) |
3407 | + self.assertIsNotNone(reload_object(ubuntu_selection)) |
3408 | + self.assertIsNone(reload_object(other_selection)) |
3409 | + |
3410 | + def test_post_creates_selection_with_multiple_arches(self): |
3411 | + self.client_log_in(as_admin=True) |
3412 | + source = factory.make_BootSource() |
3413 | + os = factory.make_name('os') |
3414 | + release = factory.make_name('release') |
3415 | + arches = [factory.make_name('arch') for _ in range(3)] |
3416 | + images = [] |
3417 | + for arch in arches: |
3418 | + factory.make_BootSourceCache( |
3419 | + boot_source=source, os=os, release=release, arch=arch) |
3420 | + images.append('%s/%s/subarch/%s' % (os, arch, release)) |
3421 | + self.patch(images_view, 'import_resources') |
3422 | + response = self.client.post( |
3423 | + reverse('images'), {'other_images': 1, 'image': images}) |
3424 | + self.assertEqual(http.client.FOUND, response.status_code) |
3425 | + |
3426 | + selection = get_one(BootSourceSelection.objects.filter( |
3427 | + boot_source=source, os=os, release=release)) |
3428 | + self.assertIsNotNone(selection) |
3429 | + self.assertItemsEqual(arches, selection.arches) |
3430 | + |
3431 | + def test_post_calls_import_resources(self): |
3432 | + self.client_log_in(as_admin=True) |
3433 | + mock_import = self.patch(images_view, 'import_resources') |
3434 | + response = self.client.post( |
3435 | + reverse('images'), {'other_images': 1, 'image': []}) |
3436 | + self.assertEqual(http.client.FOUND, response.status_code) |
3437 | + self.assertThat(mock_import, MockCalledOnceWith()) |
3438 | + |
3439 | + |
3440 | +class GeneratedImagesTest(MAASServerTestCase): |
3441 | + |
3442 | + def make_generated_resource(self, os=None, arch=None, subarch=None, |
3443 | + release=None): |
3444 | + if os is None: |
3445 | + os = factory.make_name('os') |
3446 | + if arch is None: |
3447 | + arch = factory.make_name('arch') |
3448 | + if subarch is None: |
3449 | + subarch = factory.make_name('subarch') |
3450 | + if release is None: |
3451 | + release = factory.make_name('release') |
3452 | + name = '%s/%s' % (os, release) |
3453 | + architecture = '%s/%s' % (arch, subarch) |
3454 | + resource = factory.make_BootResource( |
3455 | + rtype=BOOT_RESOURCE_TYPE.GENERATED, |
3456 | + name=name, architecture=architecture) |
3457 | + resource_set = factory.make_BootResourceSet(resource) |
3458 | + factory.make_boot_resource_file_with_content(resource_set) |
3459 | + return resource |
3460 | + |
3461 | + def test_hides_generated_images_section(self): |
3462 | + self.client_log_in() |
3463 | + response = self.client.get(reverse('images')) |
3464 | + doc = fromstring(response.content) |
3465 | + section = doc.cssselect('div#generated-images') |
3466 | + self.assertEqual( |
3467 | + 0, len(section), "Didn't hide the generated images section.") |
3468 | + |
3469 | + def test_shows_generated_images_section(self): |
3470 | + self.client_log_in() |
3471 | + self.make_generated_resource() |
3472 | + response = self.client.get(reverse('images')) |
3473 | + doc = fromstring(response.content) |
3474 | + section = doc.cssselect('div#generated-images') |
3475 | + self.assertEqual( |
3476 | + 1, len(section), "Didn't show the generated images section.") |
3477 | + |
3478 | + def test_shows_generated_resources(self): |
3479 | + self.client_log_in() |
3480 | + resources = [self.make_generated_resource() for _ in range(3)] |
3481 | + names = [resource.name for resource in resources] |
3482 | + response = self.client.get(reverse('images')) |
3483 | + doc = fromstring(response.content) |
3484 | + table_content = doc.cssselect( |
3485 | + 'table#generated-resources')[0].text_content() |
3486 | + self.assertThat(table_content, ContainsAll(names)) |
3487 | + |
3488 | + def test_shows_delete_button_for_generated_resource(self): |
3489 | + self.client_log_in(as_admin=True) |
3490 | + self.make_generated_resource() |
3491 | + response = self.client.get(reverse('images')) |
3492 | + doc = fromstring(response.content) |
3493 | + delete_btn = doc.cssselect( |
3494 | + 'table#generated-resources > tbody > tr > td > ' |
3495 | + 'a[title="Delete image"]') |
3496 | + self.assertEqual( |
3497 | + 1, len(delete_btn), |
3498 | + "Didn't show delete button for generated image.") |
3499 | + |
3500 | + def test_shows_last_update_time_for_synced_resource(self): |
3501 | + self.client_log_in(as_admin=True) |
3502 | + resource = self.make_generated_resource() |
3503 | + response = self.client.get(reverse('images')) |
3504 | + doc = fromstring(response.content) |
3505 | + last_update = doc.cssselect( |
3506 | + 'table#generated-resources > tbody > ' |
3507 | + 'tr > td')[5].text_content().strip() |
3508 | + dt = datetime.datetime.strptime(last_update, '%a, %d %b. %Y %H:%M:%S') |
3509 | + # TimestampedModel includes microseconds which we don't print. To |
3510 | + # confirm the right time is returned its easiest to just compare the |
3511 | + # ctimes |
3512 | + self.assertEquals(resource.updated.ctime(), dt.ctime()) |
3513 | + |
3514 | + def test_hides_delete_button_for_generated_resource_when_not_admin(self): |
3515 | + self.client_log_in() |
3516 | + self.make_generated_resource() |
3517 | + response = self.client.get(reverse('images')) |
3518 | + doc = fromstring(response.content) |
3519 | + delete_btn = doc.cssselect( |
3520 | + 'table#generated-resources > tbody > tr > td > ' |
3521 | + 'a[title="Delete image"]') |
3522 | + self.assertEqual( |
3523 | + 0, len(delete_btn), |
3524 | + "Didn't hide delete button for generated image when not admin.") |
3525 | + |
3526 | + def test_calls_get_os_release_title_for_generated_resource(self): |
3527 | + self.client_log_in() |
3528 | + title = factory.make_name('title') |
3529 | + resource = self.make_generated_resource() |
3530 | + mock_get_title = self.patch(images_view, 'get_os_release_title') |
3531 | + mock_get_title.return_value = title |
3532 | + response = self.client.get(reverse('images')) |
3533 | + doc = fromstring(response.content) |
3534 | + row_title = doc.cssselect( |
3535 | + 'table#generated-resources > tbody > ' |
3536 | + 'tr > td')[1].text_content().strip() |
3537 | + self.assertEqual(title, row_title) |
3538 | + os, release = resource.name.split('/') |
3539 | + self.assertThat(mock_get_title, MockCalledOnceWith(os, release)) |
3540 | + |
3541 | + |
3542 | +class UploadedImagesTest(MAASServerTestCase): |
3543 | + |
3544 | + def make_uploaded_resource(self, name=None): |
3545 | + if name is None: |
3546 | + name = factory.make_name('name') |
3547 | + arch = factory.make_name('arch') |
3548 | + subarch = factory.make_name('subarch') |
3549 | + architecture = '%s/%s' % (arch, subarch) |
3550 | + resource = factory.make_BootResource( |
3551 | + rtype=BOOT_RESOURCE_TYPE.UPLOADED, |
3552 | + name=name, architecture=architecture) |
3553 | + resource_set = factory.make_BootResourceSet(resource) |
3554 | + factory.make_boot_resource_file_with_content(resource_set) |
3555 | + return resource |
3556 | + |
3557 | + def test_shows_no_custom_images_message(self): |
3558 | + self.client_log_in() |
3559 | + response = self.client.get(reverse('images')) |
3560 | + doc = fromstring(response.content) |
3561 | + warnings = doc.cssselect('div#no-custom-images') |
3562 | + self.assertEqual(1, len(warnings)) |
3563 | + |
3564 | + def test_shows_uploaded_resources(self): |
3565 | + self.client_log_in() |
3566 | + names = [factory.make_name('name') for _ in range(3)] |
3567 | + [self.make_uploaded_resource(name) for name in names] |
3568 | + response = self.client.get(reverse('images')) |
3569 | + doc = fromstring(response.content) |
3570 | + table_content = doc.cssselect( |
3571 | + 'table#uploaded-resources')[0].text_content() |
3572 | + self.assertThat(table_content, ContainsAll(names)) |
3573 | + |
3574 | + def test_shows_uploaded_resources_name_if_title_blank(self): |
3575 | + self.client_log_in() |
3576 | + name = factory.make_name('name') |
3577 | + resource = self.make_uploaded_resource(name) |
3578 | + resource.extra['title'] = '' |
3579 | + resource.save() |
3580 | + response = self.client.get(reverse('images')) |
3581 | + doc = fromstring(response.content) |
3582 | + name_col = doc.cssselect( |
3583 | + 'table#uploaded-resources > tbody > tr > td')[1].text_content() |
3584 | + self.assertEqual(name, name_col.strip()) |
3585 | + |
3586 | + def test_shows_last_update_time_for_synced_resource(self): |
3587 | + self.client_log_in(as_admin=True) |
3588 | + resource = self.make_uploaded_resource() |
3589 | + response = self.client.get(reverse('images')) |
3590 | + doc = fromstring(response.content) |
3591 | + last_update = doc.cssselect( |
3592 | + 'table#uploaded-resources > tbody > ' |
3593 | + 'tr > td')[5].text_content().strip() |
3594 | + dt = datetime.datetime.strptime(last_update, '%a, %d %b. %Y %H:%M:%S') |
3595 | + # TimestampedModel includes microseconds which we don't print. To |
3596 | + # confirm the right time is returned its easiest to just compare the |
3597 | + # ctimes |
3598 | + self.assertEquals(resource.updated.ctime(), dt.ctime()) |
3599 | + |
3600 | + def test_shows_delete_button_for_uploaded_resource(self): |
3601 | + self.client_log_in(as_admin=True) |
3602 | + self.make_uploaded_resource() |
3603 | + response = self.client.get(reverse('images')) |
3604 | + doc = fromstring(response.content) |
3605 | + delete_btn = doc.cssselect( |
3606 | + 'table#uploaded-resources > tbody > tr > td > ' |
3607 | + 'a[title="Delete image"]') |
3608 | + self.assertEqual(1, len(delete_btn)) |
3609 | + |
3610 | + def test_hides_delete_button_for_uploaded_resource_when_not_admin(self): |
3611 | + self.client_log_in() |
3612 | + self.make_uploaded_resource() |
3613 | + response = self.client.get(reverse('images')) |
3614 | + doc = fromstring(response.content) |
3615 | + delete_btn = doc.cssselect( |
3616 | + 'table#uploaded-resources > tbody > tr > td > ' |
3617 | + 'a[title="Delete image"]') |
3618 | + self.assertEqual(0, len(delete_btn)) |
3619 | + |
3620 | + |
3621 | +class TestImageAjax(MAASServerTestCase): |
3622 | + |
3623 | + def get_images_ajax(self): |
3624 | + return self.client.get( |
3625 | + reverse('images'), HTTP_X_REQUESTED_WITH='XMLHttpRequest') |
3626 | + |
3627 | + def test__returns_json(self): |
3628 | + self.client_log_in() |
3629 | + response = self.get_images_ajax() |
3630 | + self.assertEqual('application/json', response['Content-Type']) |
3631 | + |
3632 | + def test__returns_region_import_running_True(self): |
3633 | + self.client_log_in() |
3634 | + self.patch( |
3635 | + images_view, 'is_import_resources_running').return_value = True |
3636 | + response = self.get_images_ajax() |
3637 | + json_obj = json.loads( |
3638 | + response.content.decode(settings.DEFAULT_CHARSET)) |
3639 | + self.assertTrue(json_obj['region_import_running']) |
3640 | + |
3641 | + def test__returns_region_import_running_False(self): |
3642 | + self.client_log_in() |
3643 | + self.patch( |
3644 | + images_view, 'is_import_resources_running').return_value = False |
3645 | + response = self.get_images_ajax() |
3646 | + json_obj = json.loads( |
3647 | + response.content.decode(settings.DEFAULT_CHARSET)) |
3648 | + self.assertFalse(json_obj['region_import_running']) |
3649 | + |
3650 | + def test__returns_cluster_import_running_True(self): |
3651 | + self.client_log_in() |
3652 | + self.patch( |
3653 | + images_view, 'is_import_boot_images_running').return_value = True |
3654 | + response = self.get_images_ajax() |
3655 | + json_obj = json.loads( |
3656 | + response.content.decode(settings.DEFAULT_CHARSET)) |
3657 | + self.assertTrue(json_obj['cluster_import_running']) |
3658 | + |
3659 | + def test__returns_cluster_import_running_False(self): |
3660 | + self.client_log_in() |
3661 | + self.patch( |
3662 | + images_view, 'is_import_boot_images_running').return_value = False |
3663 | + response = self.get_images_ajax() |
3664 | + json_obj = json.loads( |
3665 | + response.content.decode(settings.DEFAULT_CHARSET)) |
3666 | + self.assertFalse(json_obj['cluster_import_running']) |
3667 | + |
3668 | + def test_returns_resources(self): |
3669 | + self.client_log_in() |
3670 | + resources = [factory.make_usable_boot_resource() for _ in range(3)] |
3671 | + resource_ids = [resource.id for resource in resources] |
3672 | + response = self.get_images_ajax() |
3673 | + json_obj = json.loads( |
3674 | + response.content.decode(settings.DEFAULT_CHARSET)) |
3675 | + json_ids = [ |
3676 | + json_resource['id'] |
3677 | + for json_resource in json_obj['resources'] |
3678 | + ] |
3679 | + self.assertItemsEqual(resource_ids, json_ids) |
3680 | + |
3681 | + def test_returns_resources_datetime_format(self): |
3682 | + """Ensure the date/time format is correct""" |
3683 | + self.client_log_in() |
3684 | + resource = factory.make_usable_boot_resource() |
3685 | + response = self.get_images_ajax() |
3686 | + json_obj = json.loads( |
3687 | + response.content.decode(settings.DEFAULT_CHARSET)) |
3688 | + json_updated = datetime.datetime.strptime( |
3689 | + json_obj['resources'][0]['lastUpdate'], "%a, %d %b. %Y %H:%M:%S") |
3690 | + self.assertEqual(resource.updated.timetuple(), |
3691 | + json_updated.timetuple()) |
3692 | + |
3693 | + def test_returns_resource_attributes(self): |
3694 | + self.client_log_in() |
3695 | + factory.make_usable_boot_resource() |
3696 | + response = self.get_images_ajax() |
3697 | + json_obj = json.loads( |
3698 | + response.content.decode(settings.DEFAULT_CHARSET)) |
3699 | + json_resource = json_obj['resources'][0] |
3700 | + self.assertThat( |
3701 | + json_resource, |
3702 | + ContainsAll([ |
3703 | + 'id', 'rtype', 'name', 'title', 'arch', 'size', |
3704 | + 'complete', 'status', 'downloading', |
3705 | + 'numberOfNodes', 'lastUpdate'])) |
3706 | + |
3707 | + def test_returns_ubuntu_release_version_name(self): |
3708 | + self.client_log_in() |
3709 | + # Use trusty as known to map to "14.04 LTS" |
3710 | + version = '14.04 LTS' |
3711 | + name = 'ubuntu/trusty' |
3712 | + factory.make_usable_boot_resource( |
3713 | + rtype=BOOT_RESOURCE_TYPE.SYNCED, name=name) |
3714 | + response = self.get_images_ajax() |
3715 | + json_obj = json.loads( |
3716 | + response.content.decode(settings.DEFAULT_CHARSET)) |
3717 | + json_resource = json_obj['resources'][0] |
3718 | + self.assertEqual(version, json_resource['title']) |
3719 | + |
3720 | + def test_shows_number_of_nodes_deployed_for_resource(self): |
3721 | + self.client_log_in() |
3722 | + resource = factory.make_usable_boot_resource( |
3723 | + rtype=BOOT_RESOURCE_TYPE.SYNCED) |
3724 | + os_name, series = resource.name.split('/') |
3725 | + number_of_nodes = random.randint(1, 4) |
3726 | + for _ in range(number_of_nodes): |
3727 | + factory.make_Node( |
3728 | + status=NODE_STATUS.DEPLOYED, |
3729 | + osystem=os_name, distro_series=series, |
3730 | + architecture=resource.architecture) |
3731 | + response = self.get_images_ajax() |
3732 | + json_obj = json.loads( |
3733 | + response.content.decode(settings.DEFAULT_CHARSET)) |
3734 | + json_resource = json_obj['resources'][0] |
3735 | + self.assertEqual(number_of_nodes, json_resource['numberOfNodes']) |
3736 | + |
3737 | + def test_shows_number_of_nodes_deployed_for_resource_with_defaults(self): |
3738 | + self.client_log_in() |
3739 | + resource = factory.make_usable_boot_resource( |
3740 | + rtype=BOOT_RESOURCE_TYPE.SYNCED) |
3741 | + os_name, series = resource.name.split('/') |
3742 | + Config.objects.set_config('default_osystem', os_name) |
3743 | + Config.objects.set_config('default_distro_series', series) |
3744 | + number_of_nodes = random.randint(1, 4) |
3745 | + for _ in range(number_of_nodes): |
3746 | + factory.make_Node( |
3747 | + status=NODE_STATUS.DEPLOYED, |
3748 | + architecture=resource.architecture) |
3749 | + response = self.get_images_ajax() |
3750 | + json_obj = json.loads( |
3751 | + response.content.decode(settings.DEFAULT_CHARSET)) |
3752 | + json_resource = json_obj['resources'][0] |
3753 | + self.assertEqual(number_of_nodes, json_resource['numberOfNodes']) |
3754 | + |
3755 | + def test_shows_number_of_nodes_deployed_for_ubuntu_subarch_resource(self): |
3756 | + self.client_log_in() |
3757 | + resource = factory.make_usable_boot_resource( |
3758 | + rtype=BOOT_RESOURCE_TYPE.SYNCED) |
3759 | + arch, subarch = resource.split_arch() |
3760 | + extra_subarch = factory.make_name('subarch') |
3761 | + resource.extra['subarches'] = ','.join([subarch, extra_subarch]) |
3762 | + resource.save() |
3763 | + |
3764 | + os_name, series = resource.name.split('/') |
3765 | + node_architecture = '%s/%s' % (arch, extra_subarch) |
3766 | + number_of_nodes = random.randint(1, 4) |
3767 | + for _ in range(number_of_nodes): |
3768 | + factory.make_Node( |
3769 | + status=NODE_STATUS.DEPLOYED, |
3770 | + osystem=os_name, distro_series=series, |
3771 | + architecture=node_architecture) |
3772 | + response = self.get_images_ajax() |
3773 | + json_obj = json.loads( |
3774 | + response.content.decode(settings.DEFAULT_CHARSET)) |
3775 | + json_resource = json_obj['resources'][0] |
3776 | + self.assertEqual(number_of_nodes, json_resource['numberOfNodes']) |
3777 | + |
3778 | + def test_combines_subarch_resources_into_one_resource(self): |
3779 | + self.client_log_in() |
3780 | + name = 'ubuntu/%s' % factory.make_name('series') |
3781 | + arch = factory.make_name('arch') |
3782 | + subarches = [factory.make_name('subarch') for _ in range(3)] |
3783 | + for subarch in subarches: |
3784 | + factory.make_usable_boot_resource( |
3785 | + rtype=BOOT_RESOURCE_TYPE.SYNCED, |
3786 | + name=name, architecture='%s/%s' % (arch, subarch)) |
3787 | + response = self.get_images_ajax() |
3788 | + json_obj = json.loads( |
3789 | + response.content.decode(settings.DEFAULT_CHARSET)) |
3790 | + self.assertEqual( |
3791 | + 1, len(json_obj['resources']), |
3792 | + 'More than one resource was returned.') |
3793 | + |
3794 | + def test_combined_subarch_resource_calculates_unique_size(self): |
3795 | + self.client_log_in() |
3796 | + name = 'ubuntu/%s' % factory.make_name('series') |
3797 | + arch = factory.make_name('arch') |
3798 | + subarches = [factory.make_name('subarch') for _ in range(3)] |
3799 | + largefile = factory.make_LargeFile() |
3800 | + for subarch in subarches: |
3801 | + resource = factory.make_BootResource( |
3802 | + rtype=BOOT_RESOURCE_TYPE.SYNCED, |
3803 | + name=name, architecture='%s/%s' % (arch, subarch)) |
3804 | + resource_set = factory.make_BootResourceSet(resource) |
3805 | + factory.make_BootResourceFile(resource_set, largefile) |
3806 | + response = self.get_images_ajax() |
3807 | + json_obj = json.loads( |
3808 | + response.content.decode(settings.DEFAULT_CHARSET)) |
3809 | + json_resource = json_obj['resources'][0] |
3810 | + self.assertEqual( |
3811 | + human_readable_bytes(largefile.total_size), json_resource['size']) |
3812 | + |
3813 | + def test_combined_subarch_resource_calculates_num_of_nodes_deployed(self): |
3814 | + self.client_log_in() |
3815 | + osystem = 'ubuntu' |
3816 | + series = factory.make_name('series') |
3817 | + name = '%s/%s' % (osystem, series) |
3818 | + arch = factory.make_name('arch') |
3819 | + subarches = [factory.make_name('subarch') for _ in range(3)] |
3820 | + for subarch in subarches: |
3821 | + factory.make_usable_boot_resource( |
3822 | + rtype=BOOT_RESOURCE_TYPE.SYNCED, |
3823 | + name=name, architecture='%s/%s' % (arch, subarch)) |
3824 | + |
3825 | + number_of_nodes = random.randint(1, 4) |
3826 | + for _ in range(number_of_nodes): |
3827 | + subarch = random.choice(subarches) |
3828 | + node_architecture = '%s/%s' % (arch, subarch) |
3829 | + factory.make_Node( |
3830 | + status=NODE_STATUS.DEPLOYED, |
3831 | + osystem=osystem, distro_series=series, |
3832 | + architecture=node_architecture) |
3833 | + |
3834 | + response = self.get_images_ajax() |
3835 | + json_obj = json.loads( |
3836 | + response.content.decode(settings.DEFAULT_CHARSET)) |
3837 | + json_resource = json_obj['resources'][0] |
3838 | + self.assertEqual(number_of_nodes, json_resource['numberOfNodes']) |
3839 | + |
3840 | + def test_combined_subarch_resource_calculates_complete_True(self): |
3841 | + self.client_log_in() |
3842 | + name = 'ubuntu/%s' % factory.make_name('series') |
3843 | + arch = factory.make_name('arch') |
3844 | + subarches = [factory.make_name('subarch') for _ in range(3)] |
3845 | + resources = [ |
3846 | + factory.make_usable_boot_resource( |
3847 | + rtype=BOOT_RESOURCE_TYPE.SYNCED, |
3848 | + name=name, architecture='%s/%s' % (arch, subarch)) |
3849 | + for subarch in subarches |
3850 | + ] |
3851 | + self.patch( |
3852 | + BootResource.objects, |
3853 | + 'get_resources_matching_boot_images').return_value = resources |
3854 | + response = self.get_images_ajax() |
3855 | + json_obj = json.loads( |
3856 | + response.content.decode(settings.DEFAULT_CHARSET)) |
3857 | + json_resource = json_obj['resources'][0] |
3858 | + self.assertTrue(json_resource['complete']) |
3859 | + |
3860 | + def test_combined_subarch_resource_calculates_complete_False(self): |
3861 | + self.client_log_in() |
3862 | + name = 'ubuntu/%s' % factory.make_name('series') |
3863 | + arch = factory.make_name('arch') |
3864 | + subarches = [factory.make_name('subarch') for _ in range(3)] |
3865 | + incomplete_subarch = subarches.pop() |
3866 | + factory.make_BootResource( |
3867 | + rtype=BOOT_RESOURCE_TYPE.SYNCED, |
3868 | + name=name, architecture='%s/%s' % (arch, incomplete_subarch)) |
3869 | + for subarch in subarches: |
3870 | + factory.make_usable_boot_resource( |
3871 | + rtype=BOOT_RESOURCE_TYPE.SYNCED, |
3872 | + name=name, architecture='%s/%s' % (arch, subarch)) |
3873 | + response = self.get_images_ajax() |
3874 | + json_obj = json.loads( |
3875 | + response.content.decode(settings.DEFAULT_CHARSET)) |
3876 | + json_resource = json_obj['resources'][0] |
3877 | + self.assertFalse(json_resource['complete']) |
3878 | + |
3879 | + def test_combined_subarch_resource_calculates_progress(self): |
3880 | + self.client_log_in() |
3881 | + name = 'ubuntu/%s' % factory.make_name('series') |
3882 | + arch = factory.make_name('arch') |
3883 | + subarches = [factory.make_name('subarch') for _ in range(3)] |
3884 | + largefile = factory.make_LargeFile() |
3885 | + largefile.total_size = largefile.total_size * 2 |
3886 | + largefile.save() |
3887 | + for subarch in subarches: |
3888 | + resource = factory.make_BootResource( |
3889 | + rtype=BOOT_RESOURCE_TYPE.SYNCED, |
3890 | + name=name, architecture='%s/%s' % (arch, subarch)) |
3891 | + resource_set = factory.make_BootResourceSet(resource) |
3892 | + factory.make_BootResourceFile(resource_set, largefile) |
3893 | + response = self.get_images_ajax() |
3894 | + json_obj = json.loads( |
3895 | + response.content.decode(settings.DEFAULT_CHARSET)) |
3896 | + json_resource = json_obj['resources'][0] |
3897 | + self.assertEqual("Downloading 50%", json_resource['status']) |
3898 | + |
3899 | + def test_combined_subarch_resource_shows_queued_if_no_progress(self): |
3900 | + self.client_log_in() |
3901 | + name = 'ubuntu/%s' % factory.make_name('series') |
3902 | + arch = factory.make_name('arch') |
3903 | + subarches = [factory.make_name('subarch') for _ in range(3)] |
3904 | + largefile = factory.make_LargeFile( |
3905 | + content="".encode(settings.DEFAULT_CHARSET)) |
3906 | + for subarch in subarches: |
3907 | + resource = factory.make_BootResource( |
3908 | + rtype=BOOT_RESOURCE_TYPE.SYNCED, |
3909 | + name=name, architecture='%s/%s' % (arch, subarch)) |
3910 | + resource_set = factory.make_BootResourceSet(resource) |
3911 | + factory.make_BootResourceFile(resource_set, largefile) |
3912 | + response = self.get_images_ajax() |
3913 | + json_obj = json.loads( |
3914 | + response.content.decode(settings.DEFAULT_CHARSET)) |
3915 | + json_resource = json_obj['resources'][0] |
3916 | + self.assertEqual("Queued for download", json_resource['status']) |
3917 | + |
3918 | + def test_combined_subarch_resource_shows_complete_status(self): |
3919 | + self.client_log_in() |
3920 | + name = 'ubuntu/%s' % factory.make_name('series') |
3921 | + arch = factory.make_name('arch') |
3922 | + subarches = [factory.make_name('subarch') for _ in range(3)] |
3923 | + resources = [ |
3924 | + factory.make_usable_boot_resource( |
3925 | + rtype=BOOT_RESOURCE_TYPE.SYNCED, |
3926 | + name=name, architecture='%s/%s' % (arch, subarch)) |
3927 | + for subarch in subarches |
3928 | + ] |
3929 | + self.patch( |
3930 | + BootResource.objects, |
3931 | + 'get_resources_matching_boot_images').return_value = resources |
3932 | + response = self.get_images_ajax() |
3933 | + json_obj = json.loads( |
3934 | + response.content.decode(settings.DEFAULT_CHARSET)) |
3935 | + json_resource = json_obj['resources'][0] |
3936 | + self.assertEqual("Complete", json_resource['status']) |
3937 | + |
3938 | + def test_combined_subarch_resource_shows_waiting_for_cluster_to_sync(self): |
3939 | + self.client_log_in() |
3940 | + name = 'ubuntu/%s' % factory.make_name('series') |
3941 | + arch = factory.make_name('arch') |
3942 | + subarches = [factory.make_name('subarch') for _ in range(3)] |
3943 | + for subarch in subarches: |
3944 | + factory.make_usable_boot_resource( |
3945 | + rtype=BOOT_RESOURCE_TYPE.SYNCED, |
3946 | + name=name, architecture='%s/%s' % (arch, subarch)) |
3947 | + self.patch( |
3948 | + BootResource.objects, |
3949 | + 'get_resources_matching_boot_images').return_value = [] |
3950 | + response = self.get_images_ajax() |
3951 | + json_obj = json.loads( |
3952 | + response.content.decode(settings.DEFAULT_CHARSET)) |
3953 | + json_resource = json_obj['resources'][0] |
3954 | + self.assertEqual( |
3955 | + "Waiting for rack controller(s) to sync", json_resource['status']) |
3956 | + |
3957 | + def test_combined_subarch_resource_shows_clusters_syncing(self): |
3958 | + self.client_log_in() |
3959 | + name = 'ubuntu/%s' % factory.make_name('series') |
3960 | + arch = factory.make_name('arch') |
3961 | + subarches = [factory.make_name('subarch') for _ in range(3)] |
3962 | + for subarch in subarches: |
3963 | + factory.make_usable_boot_resource( |
3964 | + rtype=BOOT_RESOURCE_TYPE.SYNCED, |
3965 | + name=name, architecture='%s/%s' % (arch, subarch)) |
3966 | + self.patch( |
3967 | + BootResource.objects, |
3968 | + 'get_resources_matching_boot_images').return_value = [] |
3969 | + self.patch( |
3970 | + images_view, 'is_import_boot_images_running').return_value = True |
3971 | + response = self.get_images_ajax() |
3972 | + json_obj = json.loads( |
3973 | + response.content.decode(settings.DEFAULT_CHARSET)) |
3974 | + json_resource = json_obj['resources'][0] |
3975 | + self.assertEqual( |
3976 | + "Syncing to rack controller(s)", json_resource['status']) |
3977 | + |
3978 | + |
3979 | +class TestImageDelete(MAASServerTestCase): |
3980 | + |
3981 | + def test_non_admin_cannot_delete(self): |
3982 | + self.client_log_in() |
3983 | + resource = factory.make_BootResource(rtype=BOOT_RESOURCE_TYPE.UPLOADED) |
3984 | + response = self.client.post( |
3985 | + reverse('image-delete', args=[resource.id])) |
3986 | + self.assertEqual(http.client.FORBIDDEN, response.status_code) |
3987 | + self.assertIsNotNone(reload_object(resource)) |
3988 | + |
3989 | + def test_deletes_resource(self): |
3990 | + self.client_log_in(as_admin=True) |
3991 | + resource = factory.make_BootResource(rtype=BOOT_RESOURCE_TYPE.UPLOADED) |
3992 | + response = self.client.post( |
3993 | + reverse('image-delete', args=[resource.id]), |
3994 | + {'post': 'yes'}) |
3995 | + self.assertEqual(http.client.FOUND, response.status_code) |
3996 | + self.assertIsNone(reload_object(resource)) |
3997 | + |
3998 | + def test_redirects_to_images(self): |
3999 | + self.client_log_in(as_admin=True) |
4000 | + resource = factory.make_BootResource(rtype=BOOT_RESOURCE_TYPE.UPLOADED) |
4001 | + response = self.client.post( |
4002 | + reverse('image-delete', args=[resource.id]), |
4003 | + {'post': 'yes'}) |
4004 | + self.assertEqual(reverse('images'), extract_redirect(response)) |
4005 | |
4006 | === modified file 'src/maasserver/websockets/handlers/machine.py' |
4007 | === modified file 'src/maasserver/websockets/handlers/subnet.py' |
4008 | === modified file 'src/maasserver/websockets/handlers/tests/test_machine.py' |
4009 | === modified file 'src/maasserver/websockets/handlers/tests/test_subnet.py' |
4010 | === modified file 'src/maasserver/websockets/tests/test_base.py' |
4011 | === modified file 'src/metadataserver/api.py' |
4012 | --- src/metadataserver/api.py 2016-09-13 20:58:15 +0000 |
4013 | +++ src/metadataserver/api.py 2016-10-26 23:39:26 +0000 |
4014 | @@ -168,6 +168,7 @@ |
4015 | else: |
4016 | type_name = EVENT_TYPES.NODE_COMMISSIONING_EVENT_FAILED |
4017 | elif node.status == NODE_STATUS.DEPLOYING: |
4018 | +<<<<<<< TREE |
4019 | if result in ['SUCCESS', None]: |
4020 | type_name = EVENT_TYPES.NODE_INSTALL_EVENT |
4021 | else: |
4022 | @@ -177,6 +178,12 @@ |
4023 | type_name = EVENT_TYPES.NODE_ENTERING_RESCUE_MODE_EVENT |
4024 | else: |
4025 | type_name = EVENT_TYPES.NODE_ENTERING_RESCUE_MODE_EVENT_FAILED |
4026 | +======= |
4027 | + if result in ['SUCCESS', None]: |
4028 | + type_name = EVENT_TYPES.NODE_INSTALL_EVENT |
4029 | + else: |
4030 | + type_name = EVENT_TYPES.NODE_INSTALL_EVENT_FAILED |
4031 | +>>>>>>> MERGE-SOURCE |
4032 | elif node.node_type in [ |
4033 | NODE_TYPE.RACK_CONTROLLER, |
4034 | NODE_TYPE.REGION_AND_RACK_CONTROLLER]: |
4035 | |
4036 | === modified file 'src/metadataserver/fields.py' |
4037 | === modified file 'src/metadataserver/tests/test_fields.py' |
4038 | === modified file 'src/provisioningserver/boot/powernv.py' |
4039 | === modified file 'src/provisioningserver/boot/tests/test_powernv.py' |
4040 | === modified file 'src/provisioningserver/dns/tests/test_zoneconfig.py' |
4041 | --- src/provisioningserver/dns/tests/test_zoneconfig.py 2016-09-12 19:33:29 +0000 |
4042 | +++ src/provisioningserver/dns/tests/test_zoneconfig.py 2016-10-26 23:39:26 +0000 |
4043 | @@ -210,6 +210,53 @@ |
4044 | ) |
4045 | ) |
4046 | |
4047 | + def test_handles_slash_32_dynamic_range(self): |
4048 | + target_dir = patch_dns_config_path(self) |
4049 | + domain = factory.make_string() |
4050 | + network = factory.make_ipv4_network() |
4051 | + dns_ip = factory.pick_ip_in_network(network) |
4052 | + ipv4_hostname = factory.make_name('host') |
4053 | + ipv4_ip = factory.pick_ip_in_network(network) |
4054 | + range_ip = factory.pick_ip_in_network(network, but_not={ipv4_ip}) |
4055 | + ipv6_hostname = factory.make_name('host') |
4056 | + ipv6_ip = factory.make_ipv6_address() |
4057 | + ttl = random.randint(10, 300) |
4058 | + mapping = { |
4059 | + ipv4_hostname: HostnameIPMapping(None, ttl, {ipv4_ip}), |
4060 | + ipv6_hostname: HostnameIPMapping(None, ttl, {ipv6_ip}), |
4061 | + } |
4062 | + dynamic_range = IPRange(IPAddress(range_ip), IPAddress(range_ip)) |
4063 | + expected_generate_directives = ( |
4064 | + DNSForwardZoneConfig.get_GENERATE_directives( |
4065 | + dynamic_range)) |
4066 | + other_mapping = {ipv4_hostname: HostnameRRsetMapping( |
4067 | + None, {(ttl, 'MX', '10 bar')})} |
4068 | + dns_zone_config = DNSForwardZoneConfig( |
4069 | + domain, serial=random.randint(1, 100), |
4070 | + other_mapping=other_mapping, default_ttl=ttl, |
4071 | + mapping=mapping, dns_ip=dns_ip, |
4072 | + dynamic_ranges=[dynamic_range]) |
4073 | + dns_zone_config.write_config() |
4074 | + self.assertThat( |
4075 | + os.path.join(target_dir, 'zone.%s' % domain), |
4076 | + FileContains( |
4077 | + matcher=ContainsAll( |
4078 | + [ |
4079 | + '$TTL %d' % ttl, |
4080 | + '%s %d IN A %s' % (ipv4_hostname, ttl, ipv4_ip), |
4081 | + '%s %d IN AAAA %s' % (ipv6_hostname, ttl, ipv6_ip), |
4082 | + '%s %d IN MX 10 bar' % (ipv4_hostname, ttl), |
4083 | + ] + |
4084 | + [ |
4085 | + '$GENERATE %s %s IN A %s' % ( |
4086 | + iterator_values, reverse_dns, hostname) |
4087 | + for iterator_values, reverse_dns, hostname in |
4088 | + expected_generate_directives |
4089 | + ] |
4090 | + ) |
4091 | + ) |
4092 | + ) |
4093 | + |
4094 | def test_writes_dns_zone_config(self): |
4095 | target_dir = patch_dns_config_path(self) |
4096 | domain = factory.make_string() |
4097 | |
4098 | === modified file 'src/provisioningserver/dns/zoneconfig.py' |
4099 | === modified file 'src/provisioningserver/events.py' |
4100 | --- src/provisioningserver/events.py 2016-10-25 13:57:02 +0000 |
4101 | +++ src/provisioningserver/events.py 2016-10-26 23:39:26 +0000 |
4102 | @@ -70,6 +70,7 @@ |
4103 | NODE_COMMISSIONING_EVENT = "NODE_COMMISSIONING_EVENT" |
4104 | NODE_COMMISSIONING_EVENT_FAILED = "NODE_COMMISSIONING_EVENT_FAILED" |
4105 | NODE_INSTALL_EVENT = "NODE_INSTALL_EVENT" |
4106 | +<<<<<<< TREE |
4107 | NODE_INSTALL_EVENT_FAILED = "NODE_INSTALL_EVENT_FAILED" |
4108 | NODE_ENTERING_RESCUE_MODE_EVENT = "NODE_ENTERING_RESCUE_MODE_EVENT" |
4109 | NODE_ENTERING_RESCUE_MODE_EVENT_FAILED = ( |
4110 | @@ -77,6 +78,9 @@ |
4111 | NODE_EXITING_RESCUE_MODE_EVENT = "NODE_EXITING_RESCUE_MODE_EVENT" |
4112 | NODE_EXITING_RESCUE_MODE_EVENT_FAILED = ( |
4113 | "NODE_EXITING_RESCUE_MODE_EVENT_FAILED") |
4114 | +======= |
4115 | + NODE_INSTALL_EVENT_FAILED = "NODE_INSTALL_EVENT_FAILED" |
4116 | +>>>>>>> MERGE-SOURCE |
4117 | # Node user request events |
4118 | REQUEST_NODE_START_COMMISSIONING = "REQUEST_NODE_START_COMMISSIONING" |
4119 | REQUEST_NODE_ABORT_COMMISSIONING = "REQUEST_NODE_ABORT_COMMISSIONING" |
4120 | @@ -180,6 +184,7 @@ |
4121 | description="Node installation", |
4122 | level=DEBUG, |
4123 | ), |
4124 | +<<<<<<< TREE |
4125 | EVENT_TYPES.NODE_INSTALL_EVENT_FAILED: EventDetail( |
4126 | description="Node installation failure", |
4127 | level=ERROR, |
4128 | @@ -200,6 +205,12 @@ |
4129 | description="Node exiting rescue mode failure", |
4130 | level=ERROR, |
4131 | ), |
4132 | +======= |
4133 | + EVENT_TYPES.NODE_INSTALL_EVENT_FAILED: EventDetail( |
4134 | + description="Node installation failure", |
4135 | + level=ERROR, |
4136 | + ), |
4137 | +>>>>>>> MERGE-SOURCE |
4138 | EVENT_TYPES.REQUEST_NODE_START_COMMISSIONING: EventDetail( |
4139 | description="User starting node commissioning", |
4140 | level=INFO, |
4141 | |
4142 | === modified file 'src/provisioningserver/import_images/boot_resources.py' |
4143 | === modified file 'src/provisioningserver/import_images/tests/test_boot_resources.py' |
4144 | === modified file 'src/provisioningserver/plugin.py' |
4145 | --- src/provisioningserver/plugin.py 2016-10-19 17:06:30 +0000 |
4146 | +++ src/provisioningserver/plugin.py 2016-10-26 23:39:26 +0000 |
4147 | @@ -179,10 +179,18 @@ |
4148 | import crochet |
4149 | crochet.no_setup() |
4150 | |
4151 | +<<<<<<< TREE |
4152 | def _configureLogging(self, verbosity: int): |
4153 | # Get something going with the logs. |
4154 | logger.configure(verbosity, logger.LoggingMode.TWISTD) |
4155 | |
4156 | +======= |
4157 | + def _configureLogging(self): |
4158 | + # Get something going with the logs. |
4159 | + from provisioningserver import logger |
4160 | + logger.basicConfig() |
4161 | + |
4162 | +>>>>>>> MERGE-SOURCE |
4163 | def makeService(self, options): |
4164 | """Construct the MAAS Cluster service.""" |
4165 | register_sigusr2_thread_dump_handler() |
4166 | @@ -190,7 +198,11 @@ |
4167 | add_patches_to_twisted() |
4168 | |
4169 | self._configureCrochet() |
4170 | +<<<<<<< TREE |
4171 | self._configureLogging(options["verbosity"]) |
4172 | +======= |
4173 | + self._configureLogging() |
4174 | +>>>>>>> MERGE-SOURCE |
4175 | |
4176 | with ClusterConfiguration.open() as config: |
4177 | tftp_root = config.tftp_root |
4178 | |
4179 | === modified file 'src/provisioningserver/power/query.py' |
4180 | === modified file 'src/provisioningserver/power/schema.py' |
4181 | --- src/provisioningserver/power/schema.py 2016-10-20 20:55:30 +0000 |
4182 | +++ src/provisioningserver/power/schema.py 2016-10-26 23:39:26 +0000 |
4183 | @@ -349,6 +349,7 @@ |
4184 | 'description': 'Digital Loggers, Inc. PDU', |
4185 | 'fields': [ |
4186 | make_json_field( |
4187 | +<<<<<<< TREE |
4188 | 'outlet_id', "Outlet ID", scope=POWER_PARAMETER_SCOPE.NODE, |
4189 | required=True), |
4190 | make_json_field('power_address', "Power address", required=True), |
4191 | @@ -363,6 +364,11 @@ |
4192 | 'description': "Facebook's Wedge", |
4193 | 'fields': [ |
4194 | make_json_field('power_address', "IP address", required=True), |
4195 | +======= |
4196 | + 'outlet_id', "Outlet ID", scope=POWER_PARAMETER_SCOPE.NODE, |
4197 | + required=True), |
4198 | + make_json_field('power_address', "Power address", required=True), |
4199 | +>>>>>>> MERGE-SOURCE |
4200 | make_json_field('power_user', "Power user"), |
4201 | make_json_field( |
4202 | 'power_pass', "Power password", field_type='password'), |
4203 | |
4204 | === modified file 'src/provisioningserver/power/tests/test_query.py' |
4205 | === modified file 'src/provisioningserver/rackdservices/tests/test_tftp.py' |
4206 | === modified file 'src/provisioningserver/rackdservices/tftp.py' |
4207 | === modified file 'src/provisioningserver/refresh/tests/test_node_info_scripts.py' |
4208 | === modified file 'src/provisioningserver/templates/commissioning-user-data/snippets/maas_enlist.sh' |
4209 | === modified file 'src/provisioningserver/tests/test_plugin.py' |
4210 | --- src/provisioningserver/tests/test_plugin.py 2016-10-19 21:08:54 +0000 |
4211 | +++ src/provisioningserver/tests/test_plugin.py 2016-10-26 23:39:26 +0000 |
4212 | @@ -81,7 +81,11 @@ |
4213 | self.useFixture(ClusterConfigurationFixture()) |
4214 | self.patch(provisioningserver, "services", MultiService()) |
4215 | self.patch_autospec(crochet, "no_setup") |
4216 | +<<<<<<< TREE |
4217 | self.patch_autospec(logger, "configure") |
4218 | +======= |
4219 | + self.patch_autospec(logger, "basicConfig") |
4220 | +>>>>>>> MERGE-SOURCE |
4221 | self.tempdir = self.make_dir() |
4222 | |
4223 | def test_init(self): |
4224 | @@ -108,9 +112,13 @@ |
4225 | "Not all services are named.") |
4226 | self.assertEqual(service, provisioningserver.services) |
4227 | self.assertThat(crochet.no_setup, MockCalledOnceWith()) |
4228 | +<<<<<<< TREE |
4229 | self.assertThat( |
4230 | logger.configure, MockCalledOnceWith( |
4231 | options["verbosity"], logger.LoggingMode.TWISTD)) |
4232 | +======= |
4233 | + self.assertThat(logger.basicConfig, MockCalledOnceWith()) |
4234 | +>>>>>>> MERGE-SOURCE |
4235 | |
4236 | def test_makeService_patches_tftp_service(self): |
4237 | mock_tftp_patch = ( |
4238 | |
4239 | === modified file 'src/provisioningserver/utils/tests/test_env.py' |