Merge lp:~ltrager/maas/lp1593991_2.0 into lp:~maas-committers/maas/trunk
- lp1593991_2.0
- Merge into trunk
Proposed by
Lee Trager
Status: | Superseded |
---|---|
Proposed branch: | lp:~ltrager/maas/lp1593991_2.0 |
Merge into: | lp:~maas-committers/maas/trunk |
Diff against target: |
4158 lines (+3178/-21) (has conflicts) 42 files modified
Makefile (+1/-1) docs/about.rst (+9/-0) docs/changelog.rst (+522/-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/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 (+101/-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 (+12/-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 (+4/-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/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/lp1593991_2.0 |
Related bugs: |
Reviewer | Review Type | Date Requested | Status |
---|---|---|---|
MAAS Maintainers | Pending | ||
Review via email: mp+309156@code.launchpad.net |
Commit message
Backport from 2.1.1 - Validate power parameters in the node form.
Description of the change
Backport from 2.1.1, reviewed in https:/
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-24 18:43:47 +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-24 18:43:47 +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-24 18:43:47 +0000 |
38 | @@ -2,6 +2,7 @@ |
39 | Changelog |
40 | ========= |
41 | |
42 | +<<<<<<< TREE |
43 | 2.1.0 Alpha 4 |
44 | ============= |
45 | |
46 | @@ -399,12 +400,530 @@ |
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: #1615686 Single-IP dynamic ranges resulted in internal errors. |
58 | + |
59 | +2.0.0 |
60 | +===== |
61 | + |
62 | +Important announcements |
63 | +---------------------- |
64 | + |
65 | +**MAAS 2.0 supported on Ubuntu 16.04 LTS (Xenial)** |
66 | + MAAS version 2.0 is supported on Ubuntu 16.04 LTS. MAAS 2.0 and greater |
67 | + will NOT be supported on Ubuntu 14.04 LTS. The latest MAAS 1.9 point release |
68 | + will continue to be supported on Ubuntu 14.04 LTS (Trusty) until it reaches |
69 | + end-of-life. |
70 | + |
71 | + Upgrades are supported for users running Ubuntu 14.04 systems running |
72 | + MAAS 1.9 or earlier. Upon upgrading to Ubuntu 16.04, the MAAS |
73 | + database and configuration will be seamlessly migrated to the supported |
74 | + MAAS version. |
75 | + |
76 | + Please see the “Other Notable Changes” section below for more details |
77 | + regarding the reasons for this change. |
78 | + |
79 | +**Terminology Changes** |
80 | + Cluster controllers have been renamed to rack controllers. |
81 | + |
82 | + Starting from MAAS 2.0, MAAS cluster controllers have been deprecated, |
83 | + along with the legacy ``nodegroups`` API. The new API endpoint is |
84 | + ``rackcontrollers``, which provides feature parity with earlier versions |
85 | + of MAAS. |
86 | + |
87 | + For more information on rack controllers, refer to the `Major new Features` |
88 | + section bellow or refer to :ref:`rack-configuration`. |
89 | + |
90 | +**API 1.0 has been deprecated, introducing API 2.0** |
91 | + Starting from MAAS 2.0, the MAAS REST API version 1.0 has been deprecated. |
92 | + MAAS 2.0 drops support for the legacy 1.0 API, in favor of API version 2.0. |
93 | + With the introduction of the new API version, various endpoints have now |
94 | + been deprecated, and new end-points have been introduced. API users will |
95 | + need to update their client tools to reflect the changes. |
96 | + |
97 | + For more information on API 2.0, refer to :ref:`API documentation <region-controller-api>`. |
98 | + |
99 | +**Static IP ranges have been deprecated** |
100 | + Starting from MAAS 2.0, static IP ranges (previously found on the cluster |
101 | + interface page) have been deprecated. MAAS now assumes total control of a |
102 | + subnet. MAAS will automatically assign IP addresses to deployed machines, |
103 | + as long as those IP addresses are not within a dynamic or a reserved |
104 | + IP range. Users are now only required to specify one or more dynamic ranges |
105 | + per subnet. Dynamic ranges are used for auto-enlistment, commissioning, and |
106 | + any other systems configured for DHCP. IP addresses in-use for purposes such |
107 | + as devices, default gateways, DNS servers, rack and region controllers, and |
108 | + BMCs are automatically avoided when assigning IP addresses. Reserved IP |
109 | + ranges may be added if MAAS should avoid certain ranges of IP addresses in |
110 | + the subnet. |
111 | + |
112 | +**maas-region-controller-min has been renamed to maas-region-api** |
113 | + The ``maas-region-controller-min`` package has been renamed to |
114 | + ``maas-region-api``. This package provides API services for MAAS |
115 | + (``maas-regiond``) and can be used to scale out the API front-end of |
116 | + a MAAS region. |
117 | + |
118 | +**MAAS user creation been moved to 'maas' command** |
119 | + Starting from MAAS 2.0, the ``maas`` command now provides the ability to |
120 | + create admin users. The ``maas-region createadmin`` command has been |
121 | + deprecated. New administrators should now be created with |
122 | + ``maas createadmin``. |
123 | + |
124 | +**maas-provision command has been replaced** |
125 | + The MAAS rack controller command-line interface (``maas-provision``) has |
126 | + been replaced by the ``maas-rack`` command. |
127 | + |
128 | +**maas-region-admin command has been replaced with maas-region** |
129 | + The MAAS region controller command-line interface (``maas-region-admin``) |
130 | + has been replaced by the ``maas-region`` command. Note that this command |
131 | + provides an interface to interact directly with Django, which should only be |
132 | + used for debugging purposes. |
133 | + |
134 | +**Debian Installer is no longer installed or supported** |
135 | + Because support for the Debian Installer (DI) has been dropped (as |
136 | + of MAAS 1.9), MAAS no longer downloads DI-related files from simplestreams. |
137 | + Upon upgrading to MAAS 2.0, DI-related files will be removed from the MAAS |
138 | + region (and all rack controllers). |
139 | + |
140 | + |
141 | +Major new features |
142 | +------------------ |
143 | + |
144 | +**MAAS Rack Controllers and High Availability** |
145 | + Starting from MAAS 2.0, MAAS **cluster controllers** have been renamed to |
146 | + **rack controllers**. |
147 | + |
148 | + * The ``nodegroups`` and ``nodegroups/(group)/interfaces`` API endpoints |
149 | + have been deprecated. In MAAS 2.0, the ``rackcontrollers`` interface |
150 | + partially replaces this functionality. For defining dynamic and reserved |
151 | + ranges, or specifying default gateways, use the ``subnets`` endpoint. For |
152 | + enabling or disabling DHCP, use the ``fabrics/(fabric)/vlans`` endpoint. |
153 | + |
154 | + * The **Clusters** tab is no longer available in the Web UI. |
155 | + Controllers are now found under the **Nodes** tab, where each region |
156 | + and/or rack controller can be found. Other cluster interface properties |
157 | + have been moved to the Subnet and VLAN details pages under the **Networks** |
158 | + tab. |
159 | + |
160 | + * Machines no longer belong to a specific controller. |
161 | + In earlier versions of MAAS, machines would directly assigned to a cluster |
162 | + controller. The cluster controller that the machine belonged to would not |
163 | + only perform DHCP for that machine, but also all the PXE booting and power |
164 | + management. |
165 | + |
166 | + In order to support high availability for rack controllers, (starting from |
167 | + MAAS 2.0) machines no longer belong to a specific rack controller. The best |
168 | + controller to manage a machine is now determined at runtime. |
169 | + |
170 | + * DHCP is now configured per-VLAN. |
171 | + In earlier versions of MAAS, DHCP was directly linked and configured |
172 | + per cluster interface. As of MAAS 2.0, DHCP is now configured and managed |
173 | + per-VLAN, which allows any rack controller to potentially provide DHCP in |
174 | + a high-availability environment. |
175 | + |
176 | + * Rack controllers have been enabled for high availability. |
177 | + Starting from MAAS 2.0, rack controllers in the same VLAN become |
178 | + candidates to manage DHCP, PXE/TFTP, and power control for machines on |
179 | + the VLAN. MAAS now supports the concept of a **primary** and a |
180 | + **secondary** rack controller. If a secondary controller determines that |
181 | + the primary controller is unavailable, it will assume control of those |
182 | + services. |
183 | + |
184 | + * Added ``maas-rack support-dump`` command. |
185 | + For increased support observability, users can now dump the contents of |
186 | + several internal MAAS data structures by executing ``sudo maas-rack |
187 | + support-dump``. This command will dump networking diagnostics, rack |
188 | + configuration, and image information. Information can be restricted to a |
189 | + particular category by using the ``--networking``, ``--config``, or |
190 | + ``--images`` options. |
191 | + |
192 | + * Rack controllers can now be found under the **Nodes** tab in the Web UI. |
193 | + MAAS 2.0 Adds a new **Controllers** section under thee **Nodes** tab. This |
194 | + section will now list all rack and region controllers. Under a rack |
195 | + controller, the user will be able to see service tracking, connected VLANs, |
196 | + rack interfaces and other relevant information. |
197 | + |
198 | +**Region Controller Redundancy (High Availability)** |
199 | + Starting from MAAS 2.0, MAAS provides the ability to scale out (provide |
200 | + redundancy for) the MAAS region controller API, HTTP server, and DNS. This |
201 | + will allow administrators to set up multiple MAAS region controllers |
202 | + (``maas-region-api``) against a common database, providing redundancy of |
203 | + services. With further manual configuration, users will be able to setup |
204 | + the MAAS region controller in high availability mode. |
205 | + |
206 | +**New Networks Web UI** |
207 | + MAAS 2.0 introduces a few new Web UI features that were not available in |
208 | + previous versions of MAAS. |
209 | + |
210 | + * Added fabric and space details pages. |
211 | + |
212 | + * Added the ability to add and remove fabrics, spaces, subnets and VLANs. |
213 | + This can be done using the actions menu on the **Networks** tab. |
214 | + |
215 | + The ability to delete fabrics, spaces, subnets and VLANs is also available |
216 | + from the details page for each respective object. |
217 | + |
218 | +**DNS Management** |
219 | + MAAS 2.0 extends DNS management by adding the following features: |
220 | + |
221 | + * Ability to create multiple DNS domains. |
222 | + * Ability to add multiple records (``CNAME``, ``TXT``, ``MX``, ``SRV``) per |
223 | + domain. (API only) |
224 | + * Ability to select the domain for machines and devices. |
225 | + * Ability to assign (additional) names to IP addresses. (API only) |
226 | + * For deployed machines, ``A`` records continue to be created for |
227 | + the IP of the PXE interface. |
228 | + * Additional PTR records are now created for all non-PXE interfaces in |
229 | + the form: ``<interface>.<machine fully-qualified-domain-name>`` |
230 | + * Reverse DNS is now generated for only the subnet specified, rather |
231 | + than the parent /24 or /16. By default, `RFC2137`_ glue is provided |
232 | + for networks smaller than /24. This can be disabled or changed |
233 | + on a per-subnet basis via the API. |
234 | + |
235 | +.. _RFC2137: |
236 | + https://tools.ietf.org/html/rfc2137 |
237 | + |
238 | +**IP Ranges** |
239 | + Previous versions of MAAS used the concepts of a **dynamic range** and |
240 | + **static range**, which were properties of each cluster interface. This |
241 | + has been redesigned for MAAS 2.0 as follows: |
242 | + |
243 | + * Dynamic ranges have been migrated from earlier MAAS releases as-is. |
244 | + |
245 | + * Because static ranges have been removed from MAAS, each static |
246 | + range has been migrated to one or more reserved ranges, which |
247 | + represent the opposite of the previous static range. (MAAS now |
248 | + assumes it has full control of each managed subnet, and is free |
249 | + to assign IP addresses as it sees fit, unless told otherwise.) |
250 | + |
251 | + For example, if in an earlier MAAS release a cluster interface was |
252 | + configured on 192.168.0.1/24, with a dynamic range of 192.168.0.2 |
253 | + through 192.168.0.99, and a static range of 192.168.0.100 through |
254 | + 192.168.0.199, this will be migrated to:: |
255 | + |
256 | + IP range #1 (dynamic): 192.168.0.2 - 192.168.0.99 |
257 | + IP range #2 (reserved): 192.168.0.200 - 192.168.0.254 |
258 | + |
259 | + Since 192.168.0.100 - 192.168.0.199 (the previous static range) |
260 | + is not accounted for, MAAS assumes it is free to allocate static |
261 | + IP addresses from that range. |
262 | + |
263 | + * Scalability is now possible by means of adding a second dynamic |
264 | + IP range to a VLAN. (To deal with IP address exhaustion, MAAS |
265 | + supports multiple dynamic ranges on one or more subnets within |
266 | + a DHCP-enabled VLAN.) |
267 | + |
268 | + * Reserved ranges can now be allocated to a particular MAAS user. |
269 | + |
270 | + * A comment field has been added, so that users can indicate why |
271 | + a particular range of IP addresses is reserved. |
272 | + |
273 | + * IP ranges can be configured in the Web UI via the Subnet |
274 | + details page, or using the ``subnets`` REST API endpoint. |
275 | + |
276 | +**API 2.0 and MAAS CLI Updates** |
277 | + Version 1.0 of the MAAS REST API has been removed and replaced with the 2.0 |
278 | + version of the REST API. As such, new endpoints and commands have been |
279 | + introduced: |
280 | + |
281 | + * RackControllers - This endpoint/command has the following operations |
282 | + in addition to the base operations provided by nodes: |
283 | + |
284 | + * ``import-boot-images`` - Import the boot images on all rack |
285 | + controllers |
286 | + * ``describe-power-types`` - Query all of the rack controllers for |
287 | + power information |
288 | + |
289 | + * RackController - This endpoint/command has the following operations |
290 | + in addition to the base operations provided by nodes |
291 | + |
292 | + * ``import-boot-images`` - Import boot images on the given rack |
293 | + controller |
294 | + * ``refresh`` - refresh the hardware information for the given rack |
295 | + controller |
296 | + |
297 | + * Machines - This endpoint/command replaces many of the operations |
298 | + previously found in the nodes endpoint/command. The machines |
299 | + endpoint/command has the following operations in addition to the |
300 | + base operations provided by nodes. |
301 | + |
302 | + * ``power-parameters`` - Retrieve power parameters for multiple |
303 | + machines |
304 | + * ``list-allocated`` - Fetch machines that were allocated to the |
305 | + user/oauth token. |
306 | + * ``allocate`` - Allocate an available machine for deployment. |
307 | + * ``accept`` - Accept declared machine into MAAS. |
308 | + * ``accept-all`` - Accept all declared machines into MAAS. |
309 | + * ``create`` - Create a new machine. |
310 | + * ``add-chassis`` - Add special hardware types. |
311 | + * ``release`` - Release multiple machines. |
312 | + |
313 | + * Machine - This endpoint/command replaces many of the operations |
314 | + previously found in the node endpoint/command. The machine |
315 | + endpoint/command has the following operations in addition to the |
316 | + base operations provided by node. |
317 | + |
318 | + * ``power-parameters`` - Obtain power parameters for the given machine. |
319 | + * ``deploy`` - Deploy an operating system to a given machine. |
320 | + * ``abort`` - Abort the machines current operation. |
321 | + * ``get-curtin-config`` - Return the rendered curtin configuration for |
322 | + the machine. |
323 | + * ``power-off`` - Power off the given machine. |
324 | + * ``set-storage-layout`` - Change the storage layout of the given |
325 | + machine. |
326 | + * ``power-on`` - Turn on the given machine. |
327 | + * ``release`` - Release a given machine. |
328 | + * ``clear-default-gateways`` - Clear any set default gateways on the |
329 | + machine. |
330 | + * ``update`` - Change machine configuration. |
331 | + * ``query-power-state`` - Query the power state of a machine. |
332 | + * ``commission`` - Begin commissioning process for a machine |
333 | + |
334 | + Other endpoints/commands have changed: |
335 | + |
336 | + * All list commands/operations have been converted to read |
337 | + * All new and add commands/operations have been converted to create |
338 | + * Nodes - The nodes endpoint/command is now a base endpoint/command |
339 | + for all other node types(devices, machines, and rack-controllers). |
340 | + As such most operations have been moved to the machines |
341 | + endpoint/command.The following operations remain as they can be |
342 | + used on all node types. |
343 | + |
344 | + * ``is-registered`` - Returns whether or not the given MAC address is |
345 | + registered with this MAAS. |
346 | + * ``set-zone`` - Assign multiple nodes to a physical zone at once. |
347 | + * ``read`` - List nodes visible to the user, optionally filtered by |
348 | + criteria. |
349 | + |
350 | + * Node - The node endpoint/command is now a base endpoint/command for |
351 | + all other node types(devices, machines, and rack-controllers). As |
352 | + such most operations have been moved to the machine endpoint/command. |
353 | + The following operations remain as they can be used on all node types. |
354 | + |
355 | + * ``read`` - Read information about a specific node |
356 | + * ``details`` - Obtain various system details. |
357 | + * ``delete`` - Delete a specific node. |
358 | + |
359 | + * With the migration of nodes to machines the following items previously |
360 | + outputted with the list command have been changed or removed from the |
361 | + machines read command: |
362 | + |
363 | + * ``status - Will now show all status types |
364 | + * ``substatus``, ``substatus_action``, ``substatus_message``, |
365 | + ``substatus_name`` - Replaced by ``status``, ``status_action``, |
366 | + ``status_message``, ``status_name``. |
367 | + * ``boot_type - Removed, MAAS 2.0 only supports fastpath. |
368 | + * ``pxe_mac`` - Replaced by ``boot_interface``. |
369 | + * ``hostname`` - Now only displays the hostname (without the domain) of |
370 | + the machine. ``fqdn`` and ``domain`` attributes can now be used instead. |
371 | + |
372 | + * And other endpoints/commands have been deprecated: |
373 | + |
374 | + * NodeGroups - Replacement operations are found in the |
375 | + RackControllers, Machines, and BootResources endpoints/commands. |
376 | + * NodeGroupInterfaces - replacement operations are found in the |
377 | + RackController, IPRanges, Subnets, and VLANS endpoints/commands. |
378 | + |
379 | +**Extended Storage Support** |
380 | + MAAS 2.0 Storage Model has been extended to support: |
381 | + |
382 | + * XFS as a filesystem. |
383 | + * Mount options. |
384 | + * Swap partitions. (MAAS 1.9 only supported the creation of a swap |
385 | + file in the filesystem.) |
386 | + * ``tmps``/``ramfs`` support. |
387 | + |
388 | + All of these options are currently available over the CLI. |
389 | + |
390 | +**DHCP Snippets** |
391 | + MAAS 2.0 introduces the ability to define DHCP snippets. This |
392 | + feature allows administrators to manage DHCP directly from MAAS, removing |
393 | + the need to manually modify template files. The following types of DHCP |
394 | + snippets can be defined: |
395 | + |
396 | + * **Host snippets** - used for configuration for a particular node in MAAS. |
397 | + * **Subnet snippets** - used for configuration for a specific subnet in MAAS. |
398 | + * **Global snippets** - used for configuration that will affect DHCP (isc-dhcp) |
399 | + as a whole. |
400 | + |
401 | + For more information, see :ref:`DHCP Snippets <dhcpsnippets>`. |
402 | + |
403 | +Minor new features |
404 | +------------------ |
405 | + |
406 | +**MAAS proxy is now managed** |
407 | + Starting from MAAS 2.0, MAAS now manages the configuration for |
408 | + ``maas-proxy``. This allows MAAS to lock down the proxy so that it only |
409 | + allows traffic from networks MAAS knows about. For more information, see |
410 | + :ref:`MAAS Proxy <proxy>`. |
411 | + |
412 | +**rsyslog during enlistment and commissioning** |
413 | + MAAS 2.0 now enables ``rsyslog`` for the enlistment and commissioning |
414 | + environment (when using Xenial as the commissioning image). This allows users |
415 | + to see ``cloud-init``'s syslog information in ``/var/log/maas/rsyslog/``. |
416 | + |
417 | +**Ability to change a machine’s domain name from the UI** |
418 | + MAAS 2.0 introduces the ability to change a machine’s DNS domain |
419 | + via the Web UI. It was previously supported on the API only. |
420 | + |
421 | +**Networks listing page** |
422 | + In the **Networks** tab, a new networks overview has been introduced, which |
423 | + provides a high-level view of the MAAS networking mode. The network model |
424 | + can be grouped by either fabrics or spaces. |
425 | + |
426 | +**Service Tracking** |
427 | + MAAS now tracks the status of the services required for its operation, such |
428 | + as ``bind``, ``maas-dhcpd``, ``maas-dhcpd6``, ``tgt``, and ``maas-proxy``. |
429 | + |
430 | + |
431 | +Other notable changes |
432 | +--------------------- |
433 | + |
434 | +**MAAS 2.0 requires Python 3.5** |
435 | + Starting with MAAS 2.0, MAAS has now been ported to Python 3.5 (the default |
436 | + version of Python in Ubuntu 16.04 "Xenial"). |
437 | + |
438 | +**MAAS 2.0 now fully supports native Django 1.8 migration system** |
439 | + MAAS is now based on Django 1.8. Django 1.8 has dropped support for the |
440 | + South migration system in favor of the native Django migration |
441 | + system, which breaks backwards compatibility with previous versions of |
442 | + Django. |
443 | + |
444 | + MAAS continues to support a full upgrade path. MAAS versions 1.5, 1.7, 1.8, |
445 | + and 1.9 have been tested and confirmed to upgrade seamlessly to MAAS 2.0. |
446 | + |
447 | +**Instant DHCP lease notifications** |
448 | + MAAS no longer scans the leases file every 5 minutes. ``isc-dhcp-server`` |
449 | + now directly notifies MAAS if a lease is committed, released, or expires. |
450 | + |
451 | +**Host entries in DHCP** |
452 | + Host entries are now rendered in the DHCP configuration instead |
453 | + of placed in the leases file. This removes any state that previously |
454 | + existed in the DHCP lease database on the cluster controller. |
455 | + |
456 | + Starting with MAAS 2.0, if the dhcpd.leases file is lost (such as during a |
457 | + failure scenario in a high availability environment), MAAS will be able to |
458 | + reconstruct it. |
459 | + |
460 | +**Power control is no longer specific to a rack controller** |
461 | + MAAS selects one of the available rack controllers to power control |
462 | + or query a BMC. The same rack controller that powers the BMC does |
463 | + not need to be the rack controller that the machine PXE boots from. |
464 | + |
465 | + |
466 | +2.0.0 (rc4) |
467 | +=========== |
468 | + |
469 | +Issues fixed in this release |
470 | +---------------------------- |
471 | + |
472 | +LP: #1592666 Mirror URL contains double slash (/) after hostname, impacting proxy cachaility. |
473 | + |
474 | +LP: #1604461 [2.0rc2] Static IP address are allowed to be created in a dynamic range. |
475 | + |
476 | +LP: #1610397 When juju adds containers to MAAS, ensure they inherit the parent machine domain name, if none is passed by Juju. |
477 | + |
478 | +LP: #1611342 UI error while generating a MAAS key (token). |
479 | + |
480 | +LP: #1610414 apiclient.maas_client.MAASClient.post() always sets an op in the query string |
481 | + |
482 | + |
483 | +2.0.0 (rc3) |
484 | +=========== |
485 | + |
486 | +Issues fixed in this release |
487 | +---------------------------- |
488 | + |
489 | +LP: #1557434 For the MAAS CLI, mimic the error behaviour provided by argparse 1.1 on PyPI when insufficient arguments are given. |
490 | + |
491 | +LP: #1594991 MAAS displays every power query on the summarized view of node event log. |
492 | + |
493 | +LP: #1603147 Commissioning dropdown is grey and checkmarks are missing. |
494 | + |
495 | +LP: #1576116 [2.0rc1] MAAS does not respect default subnet's DNS server when choosing default DNS |
496 | + |
497 | +LP: #1600720 [2.0rc1] MAAS doesn't honor DNS settings for a subnet for DHCP |
498 | + |
499 | +LP: #1598028 [2.0] Loading latest machine events can make web browser unresponsive |
500 | + |
501 | +LP: #1604128 [2.0rc2] Unable to add a public SSH Key due to lp1604147 |
502 | + |
503 | +LP: #1604169 [2.0] maas login yields "ImportError: No module named 'maasserver'" |
504 | + |
505 | +LP: #1604962 Fixes to correctly log cloud-init/curtin FAIL events in the node event log. |
506 | + |
507 | +LP: #1604987 Fixes to correctly log "mark_failed" events. |
508 | + |
509 | +LP: #1602721 [2.0rc2] Can't get node-results via cli/api |
510 | + |
511 | +LP: #1604465 [2.0] RackController.get_image_sync_status causes huge load on regiond process |
512 | + |
513 | +LP: #1598149 [2.0rc2] MAAS is not automatically monitoring timeouts for commissioning. |
514 | + |
515 | +LP: #1605252 [2.0] Error messaging about monitor expiry has been dropped |
516 | + |
517 | + |
518 | +2.0.0 (rc2) |
519 | +=========== |
520 | + |
521 | +Issues fixed in this release |
522 | +---------------------------- |
523 | + |
524 | +LP: #1582070 Pick up wrong grub.cfg if another filesystem exists |
525 | + |
526 | +LP: #1599223 [2.0] confusing reverse DNS lookups because MAAS creates multiple PTR records |
527 | + |
528 | +LP: #1600259 [2.0] reverse DNS sometimes assigns FQDN where it should assign IFACE.FQDN |
529 | + |
530 | +LP: #1599997 [2.0rc1] after upgrade from 2.0b3, Error on request (13) subnet.list: 'NoneType' object is not iterable |
531 | + |
532 | +LP: #1598461 [2.0rc1] Image import dates are inconsistent |
533 | + |
534 | +LP: #1598937 [2.0rc1] Following fresh install maas command fails - PermissionError: [Errno 13] Permission denied: '/home/ubuntu/.maascli.db' |
535 | + |
536 | +LP: #1597787 [1.9.3,2.0] cannot create more than 4 partitions when disk is configured with mbr |
537 | + |
538 | +LP: #1600267 [1.9,2.0,UX] Can't add aliases when parent interface is set to 'DCHP' |
539 | + |
540 | +LP: #1600198 [1.9,2.0,UX] Creating a Bcache disk is not prevented when is not created in partition |
541 | + |
542 | + |
543 | +>>>>>>> MERGE-SOURCE |
544 | 2.0.0 (rc1) |
545 | =========== |
546 | |
547 | Issues fixed in this release |
548 | ---------------------------- |
549 | |
550 | +LP: #1576357 Determine a method for how to reconnect a deleted rack controller |
551 | + |
552 | +LP: #1592246 [2.0b7, regression] maas-rack register makes up a new hostname |
553 | + |
554 | +LP: #1595753 [beta8] HMC power driver regression -- Not able to connect via SSH. |
555 | + |
556 | +LP: #1592885 [2.0b7] Date and time format should be consistent accross logs |
557 | + |
558 | +LP: #1597324 [2.0b8] Unable to set default gateway interface |
559 | + |
560 | +LP: #1515188 [1.9] VMware power management fails when VMs are organized in nested subfolders |
561 | + |
562 | +LP: #1596046 [2.0] maas 2.0 pxeboot fails on PowerNV |
563 | + |
564 | +LP: #1600267 [1.9,2.0,UX] Can't add aliases when parent interface is set to 'DCHP' |
565 | + |
566 | +LP: #1598937 [2.0 rc1] Following fresh install maas command fails - PermissionError: [Errno 13] Permission denied: '/home/ubuntu/.maascli.db' |
567 | + |
568 | +2.0.0 (beta8) |
569 | +============= |
570 | + |
571 | +Issues fixed in this release |
572 | +---------------------------- |
573 | + |
574 | LP: #1590081 Allow ed25519 and ecdsa ssh keys |
575 | |
576 | LP: #1462078 [2.0b2, UI] Can't add a device and it does not show why |
577 | @@ -1344,6 +1863,7 @@ |
578 | |
579 | .. _1553617: |
580 | https://launchpad.net/bugs/1553617 |
581 | +<<<<<<< TREE |
582 | |
583 | |
584 | 1.9.1 |
585 | @@ -3978,3 +4498,5 @@ |
586 | #1185897 expose ability to re-commission node in api and cli |
587 | |
588 | #997092 Can't delete allocated node even if owned by self |
589 | +======= |
590 | +>>>>>>> MERGE-SOURCE |
591 | |
592 | === modified file 'docs/index.rst' |
593 | --- docs/index.rst 2016-08-15 09:54:43 +0000 |
594 | +++ docs/index.rst 2016-10-24 18:43:47 +0000 |
595 | @@ -6,6 +6,15 @@ |
596 | |
597 | This is the documentation for the `MAAS project`_. |
598 | |
599 | +This is API and developer documentation only. MAAS 2.0 user documentation is |
600 | +published on `http://maas.io_` and its source is found on `GitHub_`. |
601 | + |
602 | +.. _http://maas.io: |
603 | + http://maas.io/docs/ |
604 | + |
605 | +.. _GitHub: |
606 | + https://github.com/CanonicalLtd/maas-docs |
607 | + |
608 | Metal as a Service -- MAAS -- lets you treat physical servers like |
609 | virtual machines in the cloud. Rather than having to manage each |
610 | server individually, MAAS turns your bare metal into an elastic |
611 | |
612 | === modified file 'src/apiclient/tests/test_maas_client.py' |
613 | === modified file 'src/maas/settings.py' |
614 | === modified file 'src/maasserver/api/account.py' |
615 | --- src/maasserver/api/account.py 2016-10-12 15:26:17 +0000 |
616 | +++ src/maasserver/api/account.py 2016-10-24 18:43:47 +0000 |
617 | @@ -5,12 +5,21 @@ |
618 | |
619 | __all__ = [ |
620 | 'AccountHandler', |
621 | +<<<<<<< TREE |
622 | ] |
623 | |
624 | import http.client |
625 | import json |
626 | |
627 | from django.http import HttpResponse |
628 | +======= |
629 | + ] |
630 | + |
631 | +import http.client |
632 | +import json |
633 | + |
634 | +from django.http import HttpResponse |
635 | +>>>>>>> MERGE-SOURCE |
636 | from maasserver.api.support import ( |
637 | operation, |
638 | OperationsHandler, |
639 | @@ -55,16 +64,30 @@ |
640 | |
641 | """ |
642 | profile = request.user.userprofile |
643 | +<<<<<<< TREE |
644 | consumer_name = get_optional_param(request.data, 'name') |
645 | consumer, token = profile.create_authorisation_token(consumer_name) |
646 | auth_info = { |
647 | +======= |
648 | + consumer, token = profile.create_authorisation_token() |
649 | + auth_info = { |
650 | +>>>>>>> MERGE-SOURCE |
651 | 'token_key': token.key, 'token_secret': token.secret, |
652 | +<<<<<<< TREE |
653 | 'consumer_key': consumer.key, 'name': consumer.name |
654 | } |
655 | return HttpResponse( |
656 | json.dumps(auth_info), |
657 | content_type='application/json; charset=utf-8', |
658 | status=int(http.client.OK)) |
659 | +======= |
660 | + 'consumer_key': consumer.key, |
661 | + } |
662 | + return HttpResponse( |
663 | + json.dumps(auth_info), |
664 | + content_type='application/json; charset=utf-8', |
665 | + status=int(http.client.OK)) |
666 | +>>>>>>> MERGE-SOURCE |
667 | |
668 | @operation(idempotent=False) |
669 | def delete_authorisation_token(self, request): |
670 | |
671 | === modified file 'src/maasserver/api/blockdevices.py' |
672 | === modified file 'src/maasserver/api/boot_resources.py' |
673 | === modified file 'src/maasserver/api/events.py' |
674 | === modified file 'src/maasserver/api/interfaces.py' |
675 | --- src/maasserver/api/interfaces.py 2016-10-20 16:04:24 +0000 |
676 | +++ src/maasserver/api/interfaces.py 2016-10-24 18:43:47 +0000 |
677 | @@ -415,6 +415,7 @@ |
678 | :param parents: Parent interfaces that make this bond. |
679 | |
680 | Fields for VLAN interface: |
681 | +<<<<<<< TREE |
682 | |
683 | :param tags: Tags for the interface. |
684 | :param vlan: Tagged VLAN the interface is connected to. |
685 | @@ -424,6 +425,9 @@ |
686 | |
687 | :param name: Name of the interface. |
688 | :param mac_address: MAC address of the interface. |
689 | +======= |
690 | + |
691 | +>>>>>>> MERGE-SOURCE |
692 | :param tags: Tags for the interface. |
693 | :param vlan: VLAN the interface is connected to. |
694 | :param parent: Parent interface for this bridge interface. |
695 | |
696 | === modified file 'src/maasserver/api/ip_addresses.py' |
697 | === modified file 'src/maasserver/api/machines.py' |
698 | === modified file 'src/maasserver/api/tests/test_api.py' |
699 | === modified file 'src/maasserver/api/tests/test_blockdevice.py' |
700 | === modified file 'src/maasserver/api/tests/test_ipaddresses.py' |
701 | --- src/maasserver/api/tests/test_ipaddresses.py 2016-10-18 16:31:31 +0000 |
702 | +++ src/maasserver/api/tests/test_ipaddresses.py 2016-10-24 18:43:47 +0000 |
703 | @@ -25,6 +25,7 @@ |
704 | ) |
705 | from maasserver.testing.factory import factory |
706 | from maasserver.utils.converters import json_load_bytes |
707 | +<<<<<<< TREE |
708 | from maasserver.utils.orm import ( |
709 | reload_object, |
710 | transactional, |
711 | @@ -375,6 +376,14 @@ |
712 | def setUp(self): |
713 | register_view("maasserver_discovery") |
714 | return super().setUp() |
715 | +======= |
716 | +from maasserver.utils.orm import reload_object |
717 | +from netaddr import IPAddress |
718 | +from testtools.matchers import Equals |
719 | + |
720 | + |
721 | +class TestIPAddressesAPI(APITestCase.ForUser): |
722 | +>>>>>>> MERGE-SOURCE |
723 | |
724 | def post_reservation_request( |
725 | self, subnet=None, ip_address=None, network=None, mac=None, |
726 | |
727 | === modified file 'src/maasserver/api/tests/test_machine.py' |
728 | --- src/maasserver/api/tests/test_machine.py 2016-10-20 19:43:37 +0000 |
729 | +++ src/maasserver/api/tests/test_machine.py 2016-10-24 18:43:47 +0000 |
730 | @@ -1231,6 +1231,43 @@ |
731 | machine = reload_object(machine) |
732 | self.assertEqual(old_zone, machine.zone) |
733 | |
734 | +<<<<<<< TREE |
735 | +======= |
736 | + def test_PUT_sets_disable_ipv4(self): |
737 | + self.become_admin() |
738 | + original_setting = factory.pick_bool() |
739 | + machine = factory.make_Node( |
740 | + owner=self.user, |
741 | + architecture=make_usable_architecture(self), |
742 | + power_type='manual', |
743 | + disable_ipv4=original_setting) |
744 | + new_setting = not original_setting |
745 | + |
746 | + response = self.client.put( |
747 | + self.get_machine_uri(machine), {'disable_ipv4': new_setting}) |
748 | + self.assertEqual(http.client.OK, response.status_code) |
749 | + |
750 | + machine = reload_object(machine) |
751 | + self.assertEqual(new_setting, machine.disable_ipv4) |
752 | + |
753 | + def test_PUT_leaves_disable_ipv4_unchanged_by_default(self): |
754 | + self.become_admin() |
755 | + original_setting = factory.pick_bool() |
756 | + machine = factory.make_Node( |
757 | + owner=self.user, |
758 | + architecture=make_usable_architecture(self), |
759 | + power_type='manual', |
760 | + disable_ipv4=original_setting) |
761 | + self.assertEqual(original_setting, machine.disable_ipv4) |
762 | + |
763 | + response = self.client.put( |
764 | + self.get_machine_uri(machine), {'zone': factory.make_Zone()}) |
765 | + self.assertEqual(http.client.OK, response.status_code) |
766 | + |
767 | + machine = reload_object(machine) |
768 | + self.assertEqual(original_setting, machine.disable_ipv4) |
769 | + |
770 | +>>>>>>> MERGE-SOURCE |
771 | def test_PUT_updates_swap_size(self): |
772 | self.become_admin() |
773 | machine = factory.make_Node( |
774 | |
775 | === modified file 'src/maasserver/api/tests/test_machines.py' |
776 | === modified file 'src/maasserver/api/tests/test_node.py' |
777 | === modified file 'src/maasserver/api/utils.py' |
778 | === modified file 'src/maasserver/bootresources.py' |
779 | --- src/maasserver/bootresources.py 2016-10-12 15:26:17 +0000 |
780 | +++ src/maasserver/bootresources.py 2016-10-24 18:43:47 +0000 |
781 | @@ -733,6 +733,7 @@ |
782 | {'sha256': rfile.largefile.sha256}) |
783 | maaslog.debug("Finalizing boot image %s.", ident) |
784 | |
785 | +<<<<<<< TREE |
786 | # Ensure that the size of the largefile starts at zero. |
787 | rfile.largefile.size = 0 |
788 | transactional(rfile.largefile.save)(update_fields=['size']) |
789 | @@ -745,10 +746,21 @@ |
790 | database per chunk. This makes the process be reported correctly. |
791 | """ |
792 | with rfile.largefile.content.open('wb') as stream: |
793 | +======= |
794 | + # Ensure that the size of the largefile starts at zero. |
795 | + rfile.largefile.size = 0 |
796 | + rfile.largefile.save(update_fields=['size']) |
797 | + |
798 | + # Write the contents into the database, while calculating the sha256 |
799 | + # hash for the read data. |
800 | + with rfile.largefile.content.open('wb') as stream: |
801 | + while True: |
802 | +>>>>>>> MERGE-SOURCE |
803 | buf = reader.read(self.read_size) |
804 | stream.seek(0, 2) |
805 | stream.write(buf) |
806 | cksummer.update(buf) |
807 | +<<<<<<< TREE |
808 | buf_len = len(buf) |
809 | rfile.largefile.size += buf_len |
810 | rfile.largefile.save(update_fields=['size']) |
811 | @@ -765,6 +777,13 @@ |
812 | # Don't check the checksum if finalization was cancelled. |
813 | if self._cancel_finalize: |
814 | return |
815 | +======= |
816 | + buf_len = len(buf) |
817 | + rfile.largefile.size += buf_len |
818 | + rfile.largefile.save(update_fields=['size']) |
819 | + if buf_len != self.read_size: |
820 | + break |
821 | +>>>>>>> MERGE-SOURCE |
822 | |
823 | if not cksummer.check(): |
824 | # Calculated sha256 hash from the data does not match, what |
825 | @@ -1091,6 +1110,7 @@ |
826 | def insert_item(self, data, src, target, pedigree, contentsource): |
827 | """Overridable from `BasicMirrorWriter`.""" |
828 | item = sutil.products_exdata(src, pedigree) |
829 | +<<<<<<< TREE |
830 | product_name = pedigree[0] |
831 | version_name = pedigree[1] |
832 | versions = src['products'][product_name]['versions'] |
833 | @@ -1110,6 +1130,23 @@ |
834 | return |
835 | else: |
836 | self.store.insert(item, contentsource) |
837 | +======= |
838 | + product_name = pedigree[0] |
839 | + version_name = pedigree[1] |
840 | + versions = src['products'][product_name]['versions'] |
841 | + items = versions[version_name]['items'] |
842 | + if ( |
843 | + item['ftype'] == BOOT_RESOURCE_FILE_TYPE.ROOT_IMAGE and |
844 | + BOOT_RESOURCE_FILE_TYPE.SQUASHFS_IMAGE in items.keys()): |
845 | + # If both a SquashFS and root-image.gz are available only insert |
846 | + # the SquashFS image. |
847 | + return |
848 | + elif item['ftype'] not in dict(BOOT_RESOURCE_FILE_TYPE_CHOICES).keys(): |
849 | + # Skip filetypes that we don't know about. |
850 | + return |
851 | + else: |
852 | + self.store.insert(item, contentsource) |
853 | +>>>>>>> MERGE-SOURCE |
854 | |
855 | |
856 | def download_boot_resources(path, store, product_mapping, |
857 | |
858 | === modified file 'src/maasserver/dhcp.py' |
859 | --- src/maasserver/dhcp.py 2016-10-12 15:26:17 +0000 |
860 | +++ src/maasserver/dhcp.py 2016-10-24 18:43:47 +0000 |
861 | @@ -372,6 +372,7 @@ |
862 | |
863 | @typed |
864 | def make_subnet_config( |
865 | +<<<<<<< TREE |
866 | rack_controller, subnet, maas_dns_server, |
867 | ntp_servers: Union[list, dict], default_domain, |
868 | failover_peer=None, subnets_dhcp_snippets: list=None): |
869 | @@ -381,7 +382,13 @@ |
870 | include in DHCP responses, or a dict; if the latter, it ought to match |
871 | the output from `get_ntp_server_addresses_for_rack`. |
872 | """ |
873 | +======= |
874 | + rack_controller, subnet, maas_dns_server, ntp_server, default_domain, |
875 | + failover_peer=None, subnets_dhcp_snippets=[]): |
876 | + """Return DHCP subnet configuration dict for a rack interface.""" |
877 | +>>>>>>> MERGE-SOURCE |
878 | ip_network = subnet.get_ipnetwork() |
879 | +<<<<<<< TREE |
880 | if subnet.dns_servers is not None and len(subnet.dns_servers) > 0: |
881 | # Replace MAAS DNS with the servers defined on the subnet. |
882 | dns_servers = [IPAddress(server) for server in subnet.dns_servers] |
883 | @@ -400,6 +407,15 @@ |
884 | else: |
885 | ntp_servers = [ntp_server] |
886 | |
887 | +======= |
888 | + if subnet.dns_servers is not None and len(subnet.dns_servers) > 0: |
889 | + # Replace MAAS DNS with the servers defined on the subnet. |
890 | + dns_servers = ", ".join(subnet.dns_servers) |
891 | + elif maas_dns_server is not None and len(maas_dns_server) > 0: |
892 | + dns_servers = maas_dns_server |
893 | + else: |
894 | + dns_servers = "" |
895 | +>>>>>>> MERGE-SOURCE |
896 | return { |
897 | 'subnet': str(ip_network.network), |
898 | 'subnet_mask': str(ip_network.netmask), |
899 | @@ -486,7 +502,11 @@ |
900 | for subnet in subnets: |
901 | subnet_configs.append( |
902 | make_subnet_config( |
903 | +<<<<<<< TREE |
904 | rack_controller, subnet, maas_dns_server, ntp_servers, |
905 | +======= |
906 | + rack_controller, subnet, maas_dns_server, ntp_server, |
907 | +>>>>>>> MERGE-SOURCE |
908 | domain, peer_name, subnets_dhcp_snippets)) |
909 | |
910 | # Generate the hosts for all subnets. |
911 | @@ -558,6 +578,7 @@ |
912 | for vlan, (subnets_v4, subnets_v6) in vlan_subnets.items(): |
913 | # IPv4 |
914 | if len(subnets_v4) > 0: |
915 | +<<<<<<< TREE |
916 | try: |
917 | config = get_dhcp_configure_for( |
918 | 4, rack_controller, vlan, subnets_v4, ntp_servers, |
919 | @@ -580,8 +601,33 @@ |
920 | }) |
921 | hosts_v4.extend(hosts) |
922 | interfaces_v4.add(interface) |
923 | +======= |
924 | + try: |
925 | + config = get_dhcp_configure_for( |
926 | + 4, rack_controller, vlan, subnets_v4, ntp_server, |
927 | + default_domain, dhcp_snippets) |
928 | + except DHCPConfigurationError as e: |
929 | + # XXX bug #1602412: this silently breaks DHCPv4, but we cannot |
930 | + # allow it to crash here since DHCPv6 might be able to run. |
931 | + # This error may be irrelevant if there is an IPv4 network in |
932 | + # the MAAS model which is not configured on the rack, and the |
933 | + # user only wants to serve DHCPv6. But it is still something |
934 | + # worth noting, so log it and continue. |
935 | + log.err(e) |
936 | + else: |
937 | + failover_peer, subnets, hosts, interface = config |
938 | + if failover_peer is not None: |
939 | + failover_peers_v4.append(failover_peer) |
940 | + shared_networks_v4.append({ |
941 | + "name": "vlan-%d" % vlan.id, |
942 | + "subnets": subnets, |
943 | + }) |
944 | + hosts_v4.extend(hosts) |
945 | + interfaces_v4.add(interface) |
946 | +>>>>>>> MERGE-SOURCE |
947 | # IPv6 |
948 | if len(subnets_v6) > 0: |
949 | +<<<<<<< TREE |
950 | try: |
951 | config = get_dhcp_configure_for( |
952 | 6, rack_controller, vlan, subnets_v6, |
953 | @@ -605,6 +651,32 @@ |
954 | hosts_v6.extend(hosts) |
955 | interfaces_v6.add(interface) |
956 | return DHCPConfigurationForRack( |
957 | +======= |
958 | + try: |
959 | + config = get_dhcp_configure_for( |
960 | + 6, rack_controller, vlan, subnets_v6, |
961 | + ntp_server, default_domain, dhcp_snippets) |
962 | + except DHCPConfigurationError as e: |
963 | + # XXX bug #1602412: this silently breaks DHCPv6, but we cannot |
964 | + # allow it to crash here since DHCPv4 might be able to run. |
965 | + # This error may be irrelevant if there is an IPv6 network in |
966 | + # the MAAS model which is not configured on the rack, and the |
967 | + # user only wants to serve DHCPv4. But it is still something |
968 | + # worth noting, so log it and continue. |
969 | + log.err(e) |
970 | + else: |
971 | + failover_peer, subnets, hosts, interface = config |
972 | + if failover_peer is not None: |
973 | + failover_peers_v6.append(failover_peer) |
974 | + shared_networks_v6.append({ |
975 | + "name": "vlan-%d" % vlan.id, |
976 | + "subnets": subnets, |
977 | + }) |
978 | + hosts_v6.extend(hosts) |
979 | + interfaces_v6.add(interface) |
980 | + return ( |
981 | + get_omapi_key(), |
982 | +>>>>>>> MERGE-SOURCE |
983 | failover_peers_v4, shared_networks_v4, hosts_v4, interfaces_v4, |
984 | failover_peers_v6, shared_networks_v6, hosts_v6, interfaces_v6, |
985 | get_omapi_key(), global_dhcp_snippets) |
986 | |
987 | === modified file 'src/maasserver/dns/tests/test_zonegenerator.py' |
988 | === modified file 'src/maasserver/dns/zonegenerator.py' |
989 | === modified file 'src/maasserver/enum.py' |
990 | --- src/maasserver/enum.py 2016-09-08 17:26:54 +0000 |
991 | +++ src/maasserver/enum.py 2016-10-24 18:43:47 +0000 |
992 | @@ -341,9 +341,15 @@ |
993 | #: Root Image (gets converted to root-image root-tgz, on the rack) |
994 | ROOT_IMAGE = 'root-image.gz' |
995 | |
996 | +<<<<<<< TREE |
997 | #: Root image in SquashFS form, does not need to be converted |
998 | SQUASHFS_IMAGE = 'squashfs' |
999 | |
1000 | +======= |
1001 | + # Root image in SquashFS form, does not need to be converted |
1002 | + SQUASHFS_IMAGE = 'squashfs' |
1003 | + |
1004 | +>>>>>>> MERGE-SOURCE |
1005 | #: Boot Kernel (ISCSI kernel) |
1006 | BOOT_KERNEL = 'boot-kernel' |
1007 | |
1008 | |
1009 | === modified file 'src/maasserver/forms.py' |
1010 | === modified file 'src/maasserver/forms_interface_link.py' |
1011 | === modified file 'src/maasserver/forms_settings.py' |
1012 | === added file 'src/maasserver/migrations/builtin/maasserver/0066_allow_squashfs.py' |
1013 | --- src/maasserver/migrations/builtin/maasserver/0066_allow_squashfs.py 1970-01-01 00:00:00 +0000 |
1014 | +++ src/maasserver/migrations/builtin/maasserver/0066_allow_squashfs.py 2016-10-24 18:43:47 +0000 |
1015 | @@ -0,0 +1,22 @@ |
1016 | +# -*- coding: utf-8 -*- |
1017 | +from __future__ import unicode_literals |
1018 | + |
1019 | +from django.db import ( |
1020 | + migrations, |
1021 | + models, |
1022 | +) |
1023 | + |
1024 | + |
1025 | +class Migration(migrations.Migration): |
1026 | + |
1027 | + dependencies = [ |
1028 | + ('maasserver', '0065_larger_osystem_and_distro_series'), |
1029 | + ] |
1030 | + |
1031 | + operations = [ |
1032 | + migrations.AlterField( |
1033 | + model_name='bootresourcefile', |
1034 | + name='filetype', |
1035 | + 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), |
1036 | + ), |
1037 | + ] |
1038 | |
1039 | === renamed file 'src/maasserver/migrations/builtin/maasserver/0066_allow_squashfs.py' => 'src/maasserver/migrations/builtin/maasserver/0066_allow_squashfs.py.moved' |
1040 | === modified file 'src/maasserver/models/bootresource.py' |
1041 | === modified file 'src/maasserver/models/node.py' |
1042 | --- src/maasserver/models/node.py 2016-10-22 05:40:08 +0000 |
1043 | +++ src/maasserver/models/node.py 2016-10-24 18:43:47 +0000 |
1044 | @@ -219,23 +219,57 @@ |
1045 | |
1046 | # Return type from `get_effective_power_info`. |
1047 | PowerInfo = namedtuple("PowerInfo", ( |
1048 | - "can_be_started", |
1049 | - "can_be_stopped", |
1050 | - "can_be_queried", |
1051 | - "power_type", |
1052 | - "power_parameters", |
1053 | -)) |
1054 | - |
1055 | -DefaultGateways = namedtuple("DefaultGateways", ( |
1056 | - "ipv4", |
1057 | - "ipv6", |
1058 | -)) |
1059 | - |
1060 | -GatewayDefinition = namedtuple("GatewayDefinition", ( |
1061 | - "interface_id", |
1062 | - "subnet_id", |
1063 | - "gateway_ip", |
1064 | -)) |
1065 | +<<<<<<< TREE |
1066 | + "can_be_started", |
1067 | + "can_be_stopped", |
1068 | + "can_be_queried", |
1069 | + "power_type", |
1070 | + "power_parameters", |
1071 | +)) |
1072 | + |
1073 | +DefaultGateways = namedtuple("DefaultGateways", ( |
1074 | + "ipv4", |
1075 | + "ipv6", |
1076 | +)) |
1077 | + |
1078 | +GatewayDefinition = namedtuple("GatewayDefinition", ( |
1079 | + "interface_id", |
1080 | + "subnet_id", |
1081 | + "gateway_ip", |
1082 | +)) |
1083 | +======= |
1084 | + "can_be_started", |
1085 | + "can_be_stopped", |
1086 | + "can_be_queried", |
1087 | + "power_type", |
1088 | + "power_parameters", |
1089 | +)) |
1090 | + |
1091 | +DefaultGateways = namedtuple("DefaultGateways", ( |
1092 | + "ipv4", |
1093 | + "ipv6", |
1094 | +)) |
1095 | + |
1096 | +GatewayDefinition = namedtuple("GatewayDefinition", ( |
1097 | + "interface_id", |
1098 | + "subnet_id", |
1099 | + "gateway_ip", |
1100 | +)) |
1101 | + |
1102 | +# The sequence from which the decimal form of node system IDs should be |
1103 | +# pulled. At the time of writing this should match the definition in migration |
1104 | +# 0021_create_node_system_id_sequence, and vice-versa. |
1105 | +node_system_id = Sequence( |
1106 | + "maasserver_node_system_id_seq", cycle=False, |
1107 | + minvalue=(24 ** 5), maxvalue=((24 ** 6) - 1), |
1108 | + start=15600471, owner="maasserver_node.system_id", |
1109 | +) |
1110 | + |
1111 | + |
1112 | +def generate_node_system_ids(): |
1113 | + """Return an iterable that yields short system IDs.""" |
1114 | + return map(znums.from_int, node_system_id) |
1115 | +>>>>>>> MERGE-SOURCE |
1116 | |
1117 | |
1118 | def generate_node_system_id(): |
1119 | @@ -1126,6 +1160,7 @@ |
1120 | This is the maximum time the commissioning is allowed to take. |
1121 | """ |
1122 | # Return a *very* conservative estimate for now. |
1123 | +<<<<<<< TREE |
1124 | return timedelta( |
1125 | minutes=NODE_FAILURE_MONITORED_STATUS_TIMEOUTS[ |
1126 | NODE_STATUS.COMMISSIONING]).total_seconds() |
1127 | @@ -1139,6 +1174,11 @@ |
1128 | return timedelta( |
1129 | minutes=NODE_FAILURE_MONITORED_STATUS_TIMEOUTS[ |
1130 | NODE_STATUS.ENTERING_RESCUE_MODE]).total_seconds() |
1131 | +======= |
1132 | + return timedelta( |
1133 | + minutes=NODE_FAILURE_MONITORED_STATUS_TIMEOUTS[ |
1134 | + NODE_STATUS.COMMISSIONING]).total_seconds() |
1135 | +>>>>>>> MERGE-SOURCE |
1136 | |
1137 | def get_releasing_time(self): |
1138 | """Return the releasing time of this node (in seconds). |
1139 | @@ -2856,6 +2896,7 @@ |
1140 | if not gateway_ipv6: |
1141 | gateway_ipv6 = self._get_gateway_tuple_by_family( |
1142 | found_gateways, IPADDRESS_FAMILY.IPv6) |
1143 | +<<<<<<< TREE |
1144 | return DefaultGateways._make((gateway_ipv4, gateway_ipv6)) |
1145 | |
1146 | def get_default_dns_servers(self, ipv4=True, ipv6=True): |
1147 | @@ -2898,6 +2939,49 @@ |
1148 | ipv4=(ipv4 and gateways.ipv4 is not None), |
1149 | ipv6=(ipv6 and gateways.ipv6 is not None)) |
1150 | return [maas_dns_server] |
1151 | +======= |
1152 | + return DefaultGateways._make((gateway_ipv4, gateway_ipv6)) |
1153 | + |
1154 | + def get_default_dns_servers(self): |
1155 | + """Return the default DNS servers for this node.""" |
1156 | + # Circular imports. |
1157 | + from maasserver.dns.zonegenerator import get_dns_server_address |
1158 | + |
1159 | + gateways = self.get_default_gateways() |
1160 | + |
1161 | + # Try first to use DNS servers from default gateway subnets. |
1162 | + if gateways.ipv4 is not None: |
1163 | + subnet = Subnet.objects.get(id=gateways.ipv4.subnet_id) |
1164 | + if subnet.dns_servers is not None and len(subnet.dns_servers) > 0: |
1165 | + # An IPv4 subnet is hosting the default gateway and has DNS |
1166 | + # servers defined. IPv4 DNS servers take first-priority. |
1167 | + return subnet.dns_servers |
1168 | + if gateways.ipv6 is not None: |
1169 | + subnet = Subnet.objects.get(id=gateways.ipv6.subnet_id) |
1170 | + if subnet.dns_servers is not None and len(subnet.dns_servers) > 0: |
1171 | + # An IPv6 subnet is hosting the default gateway and has DNS |
1172 | + # servers defined. IPv6 DNS servers take second-priority. |
1173 | + return subnet.dns_servers |
1174 | + |
1175 | + # No default gateway subnet has specific DNS servers defined, so |
1176 | + # use MAAS for the default DNS server. |
1177 | + if gateways.ipv4 is None and gateways.ipv6 is None: |
1178 | + # If there are no default gateways, the default is the MAAS |
1179 | + # region IP address. |
1180 | + maas_dns_server = get_dns_server_address( |
1181 | + rack_controller=self.get_boot_rack_controller()) |
1182 | + else: |
1183 | + # Choose an address consistent with the primary address-family |
1184 | + # in use, as indicated by the presence (or not) of a gateway. |
1185 | + # Note that this path is only taken if the MAAS URL is set to |
1186 | + # a hostname, and the hostname resolves to both an IPv4 and an |
1187 | + # IPv6 address. |
1188 | + maas_dns_server = get_dns_server_address( |
1189 | + rack_controller=self.get_boot_rack_controller(), |
1190 | + ipv4=(gateways.ipv4 is not None), |
1191 | + ipv6=(gateways.ipv6 is not None)) |
1192 | + return [maas_dns_server] |
1193 | +>>>>>>> MERGE-SOURCE |
1194 | |
1195 | def get_boot_purpose(self): |
1196 | """ |
1197 | |
1198 | === modified file 'src/maasserver/models/staticipaddress.py' |
1199 | --- src/maasserver/models/staticipaddress.py 2016-10-17 23:38:43 +0000 |
1200 | +++ src/maasserver/models/staticipaddress.py 2016-10-24 18:43:47 +0000 |
1201 | @@ -298,6 +298,11 @@ |
1202 | # DISTINCT ON returns the first matching row for any given |
1203 | # hostname, using the query's ordering. Here, we're trying to |
1204 | # return the IPs for the oldest Interface address. |
1205 | +<<<<<<< TREE |
1206 | +======= |
1207 | + # |
1208 | + # For nodes that have disable_ipv4 set, leave out any IPv4 address. |
1209 | +>>>>>>> MERGE-SOURCE |
1210 | default_ttl = "%d" % Config.objects.get_config('default_dns_ttl') |
1211 | if raw_ttl: |
1212 | ttl_clause = """node.address_ttl""" |
1213 | @@ -364,7 +369,15 @@ |
1214 | query_parms = [] |
1215 | sql_query += """ |
1216 | staticip.ip IS NOT NULL AND |
1217 | +<<<<<<< TREE |
1218 | host(staticip.ip) != '' |
1219 | +======= |
1220 | + host(staticip.ip) != '' AND |
1221 | + ( |
1222 | + node.disable_ipv4 IS FALSE OR |
1223 | + family(staticip.ip) <> 4 |
1224 | + ) |
1225 | +>>>>>>> MERGE-SOURCE |
1226 | ORDER BY |
1227 | node.hostname, |
1228 | is_boot DESC, |
1229 | @@ -400,6 +413,7 @@ |
1230 | END, |
1231 | interface.id |
1232 | """ |
1233 | +<<<<<<< TREE |
1234 | iface_sql_query = """ |
1235 | SELECT |
1236 | CONCAT(node.hostname, '.', domain.name) AS fqdn, |
1237 | @@ -448,6 +462,58 @@ |
1238 | assigned DESC, /* Return all assigned IPs for a node first. */ |
1239 | interface.id |
1240 | """ |
1241 | +======= |
1242 | + iface_sql_query = """ |
1243 | + SELECT |
1244 | + CONCAT(node.hostname, '.', domain.name) AS fqdn, |
1245 | + node.system_id, |
1246 | + node.node_type, |
1247 | + """ + ttl_clause + """ AS ttl, |
1248 | + staticip.ip, |
1249 | + interface.name |
1250 | + FROM |
1251 | + maasserver_interface AS interface |
1252 | + JOIN maasserver_node AS node ON |
1253 | + node.id = interface.node_id |
1254 | + JOIN maasserver_domain as domain ON |
1255 | + domain.id = node.domain_id |
1256 | + JOIN maasserver_interface_ip_addresses AS link ON |
1257 | + link.interface_id = interface.id |
1258 | + JOIN maasserver_staticipaddress AS staticip ON |
1259 | + staticip.id = link.staticipaddress_id |
1260 | + """ |
1261 | + if isinstance(domain_or_subnet, Domain): |
1262 | + # This logic is similar to the logic in sql_query above. |
1263 | + iface_sql_query += """ |
1264 | + LEFT JOIN maasserver_domain as domain2 ON |
1265 | + /* Pick up another copy of domain looking for instances of |
1266 | + * the name as the top of a domain. |
1267 | + */ |
1268 | + domain2.name = CONCAT( |
1269 | + interface.name, '.', node.hostname, '.', domain.name) |
1270 | + WHERE |
1271 | + (domain2.name IS NOT NULL OR node.domain_id = %s) AND |
1272 | + """ |
1273 | + else: |
1274 | + # For subnets, we need ALL the names, so that we can correctly |
1275 | + # identify which ones should have the FQDN. dns/zonegenerator.py |
1276 | + # optimizes based on this, and only calls once with a subnet, |
1277 | + # expecting to get all the subnets back in one table. |
1278 | + iface_sql_query += """ |
1279 | + WHERE |
1280 | + """ |
1281 | + iface_sql_query += """ |
1282 | + staticip.ip IS NOT NULL AND |
1283 | + host(staticip.ip) != '' AND |
1284 | + ( |
1285 | + node.disable_ipv4 IS FALSE OR |
1286 | + family(staticip.ip) <> 4 |
1287 | + ) |
1288 | + ORDER BY |
1289 | + node.hostname, |
1290 | + interface.id |
1291 | + """ |
1292 | +>>>>>>> MERGE-SOURCE |
1293 | # We get user reserved et al mappings first, so that we can overwrite |
1294 | # TTL as we process the return from the SQL horror above. |
1295 | mapping = self._get_user_reserved_mappings(domain_or_subnet) |
1296 | @@ -456,14 +522,19 @@ |
1297 | iface_is_boot = defaultdict(bool, { |
1298 | hostname: True for hostname in mapping.keys() |
1299 | }) |
1300 | +<<<<<<< TREE |
1301 | assigned_ips = defaultdict(bool) |
1302 | cursor.execute(sql_query, query_parms) |
1303 | +======= |
1304 | + cursor.execute(sql_query, query_parms) |
1305 | +>>>>>>> MERGE-SOURCE |
1306 | # The records from the query provide, for each hostname (after |
1307 | # stripping domain), the boot and non-boot interface ip address in ipv4 |
1308 | # and ipv6. Our task: if there are boot interace IPs, they win. If |
1309 | # there are none, then whatever we got wins. The ORDER BY means that |
1310 | # we will see all of the boot interfaces before we see any non-boot |
1311 | # interface IPs. See Bug#1584850 |
1312 | +<<<<<<< TREE |
1313 | for (fqdn, system_id, node_type, ttl, |
1314 | ip, is_boot) in cursor.fetchall(): |
1315 | mapping[fqdn].node_type = node_type |
1316 | @@ -491,6 +562,29 @@ |
1317 | mapping[name].system_id = system_id |
1318 | mapping[name].ttl = ttl |
1319 | mapping[name].ips.add(ip) |
1320 | +======= |
1321 | + for (fqdn, system_id, node_type, ttl, |
1322 | + ip, is_boot) in cursor.fetchall(): |
1323 | + mapping[fqdn].node_type = node_type |
1324 | + mapping[fqdn].system_id = system_id |
1325 | + mapping[fqdn].ttl = ttl |
1326 | + if is_boot: |
1327 | + iface_is_boot[fqdn] = True |
1328 | + # If we have an IP on the right interface type, save it. |
1329 | + if is_boot == iface_is_boot[fqdn]: |
1330 | + mapping[fqdn].ips.add(ip) |
1331 | + # Next, get all the addresses, on all the interfaces, and add the ones |
1332 | + # that are not already present on the FQDN as $IFACE.$FQDN. |
1333 | + cursor.execute(iface_sql_query, (domain_or_subnet.id,)) |
1334 | + for (fqdn, system_id, node_type, ttl, |
1335 | + ip, iface_name) in cursor.fetchall(): |
1336 | + if ip not in mapping[fqdn].ips: |
1337 | + name = "%s.%s" % (iface_name, fqdn) |
1338 | + mapping[name].node_type = node_type |
1339 | + mapping[name].system_id = system_id |
1340 | + mapping[name].ttl = ttl |
1341 | + mapping[name].ips.add(ip) |
1342 | +>>>>>>> MERGE-SOURCE |
1343 | return mapping |
1344 | |
1345 | def filter_by_ip_family(self, family): |
1346 | |
1347 | === modified file 'src/maasserver/models/subnet.py' |
1348 | === modified file 'src/maasserver/models/tests/test_largefile.py' |
1349 | === modified file 'src/maasserver/models/tests/test_node.py' |
1350 | --- src/maasserver/models/tests/test_node.py 2016-10-22 05:50:06 +0000 |
1351 | +++ src/maasserver/models/tests/test_node.py 2016-10-24 18:43:47 +0000 |
1352 | @@ -364,6 +364,27 @@ |
1353 | racks_and_regions.add(factory.make_Node(node_type=node_type)) |
1354 | self.assertItemsEqual(racks_and_regions, Controller.objects.all()) |
1355 | |
1356 | +<<<<<<< TREE |
1357 | +======= |
1358 | + def test_get_running_controller(self): |
1359 | + rack = factory.make_RackController() |
1360 | + self.useFixture(MAASIDFixture(rack.system_id)) |
1361 | + self.assertEquals(rack, Controller.objects.get_running_controller()) |
1362 | + |
1363 | + def test_get_running_controller_can_ignore_cache(self): |
1364 | + # Store invalid value in cache |
1365 | + self.useFixture(MAASIDFixture(factory.make_string())) |
1366 | + rack = factory.make_RackController() |
1367 | + # Write valid value to disk |
1368 | + maas_id_path = get_path('/var/lib/maas/maas_id') |
1369 | + os.unlink(maas_id_path) |
1370 | + with open(maas_id_path, 'w') as fd: |
1371 | + fd.write(rack.system_id) |
1372 | + self.assertEquals( |
1373 | + rack, Controller.objects.get_running_controller(read_cache=False)) |
1374 | + self.assertEquals(rack.system_id, get_maas_id()) |
1375 | + |
1376 | +>>>>>>> MERGE-SOURCE |
1377 | |
1378 | class TestRackControllerManager(MAASServerTestCase): |
1379 | |
1380 | @@ -4717,6 +4738,7 @@ |
1381 | ), node.get_default_gateways()) |
1382 | |
1383 | |
1384 | +<<<<<<< TREE |
1385 | class TestGetDefaultDNSServers(MAASServerTestCase): |
1386 | """Tests for `Node.get_default_dns_servers`.""" |
1387 | |
1388 | @@ -4892,6 +4914,161 @@ |
1389 | |
1390 | |
1391 | class TestNode_Start(MAASTransactionServerTestCase): |
1392 | +======= |
1393 | +class TestGetDefaultDNSServers(MAASServerTestCase): |
1394 | + """Tests for `Node.get_default_dns_servers`.""" |
1395 | + |
1396 | + def make_Node_with_RackController( |
1397 | + self, ipv4=True, ipv6=True, ipv4_gateway=True, ipv6_gateway=True, |
1398 | + ipv4_subnet_dns=None, ipv6_subnet_dns=None): |
1399 | + ipv4_subnet_dns = [] if ipv4_subnet_dns is None else ipv4_subnet_dns |
1400 | + ipv6_subnet_dns = [] if ipv6_subnet_dns is None else ipv6_subnet_dns |
1401 | + rack_v4 = None |
1402 | + rack_v6 = None |
1403 | + fabric = factory.make_Fabric() |
1404 | + vlan = fabric.get_default_vlan() |
1405 | + if ipv4: |
1406 | + gateway_ip = None if ipv4_gateway else "" |
1407 | + v4_subnet = factory.make_Subnet( |
1408 | + version=4, vlan=vlan, dns_servers=ipv4_subnet_dns, |
1409 | + gateway_ip=gateway_ip) |
1410 | + if ipv6: |
1411 | + gateway_ip = None if ipv6_gateway else "" |
1412 | + v6_subnet = factory.make_Subnet( |
1413 | + version=6, vlan=vlan, dns_servers=ipv6_subnet_dns, |
1414 | + gateway_ip=gateway_ip) |
1415 | + rack = factory.make_RegionRackController() |
1416 | + vlan.primary_rack = rack |
1417 | + vlan.dhcp_on = True |
1418 | + vlan.save() |
1419 | + # In order to determine the correct IP address per-address-family, |
1420 | + # a name lookup is performed on the hostname part of the URL. |
1421 | + # We need to mock that so we can return whatever IP addresses it |
1422 | + # resolves to. |
1423 | + rack.url = "http://region:5240/MAAS/" |
1424 | + if ipv4: |
1425 | + rack_v4 = factory.pick_ip_in_Subnet(v4_subnet) |
1426 | + if ipv6: |
1427 | + rack_v6 = factory.pick_ip_in_Subnet(v6_subnet) |
1428 | + |
1429 | + def get_address(hostname, ip_version=4): |
1430 | + """Mock function to return the IP address of the rack based on the |
1431 | + given address family. |
1432 | + """ |
1433 | + if ip_version == 4: |
1434 | + return {IPAddress(rack_v4)} if rack_v4 else set() |
1435 | + elif ip_version == 6: |
1436 | + return {IPAddress(rack_v6)} if rack_v6 else set() |
1437 | + |
1438 | + resolve_hostname = self.patch( |
1439 | + server_address_module, 'resolve_hostname') |
1440 | + resolve_hostname.side_effect = get_address |
1441 | + rack.interface_set.all().delete() |
1442 | + rackif = factory.make_Interface(vlan=vlan, node=rack) |
1443 | + if ipv4: |
1444 | + rackif.link_subnet(INTERFACE_LINK_TYPE.STATIC, v4_subnet, rack_v4) |
1445 | + if ipv6: |
1446 | + rackif.link_subnet(INTERFACE_LINK_TYPE.STATIC, v6_subnet, rack_v6) |
1447 | + rack.boot_interface = rackif |
1448 | + rack.save() |
1449 | + node = factory.make_Node(status=NODE_STATUS.READY, disable_ipv4=False) |
1450 | + nodeif = factory.make_Interface(vlan=vlan, node=node) |
1451 | + if ipv4: |
1452 | + nodeif.link_subnet(INTERFACE_LINK_TYPE.AUTO, v4_subnet) |
1453 | + if ipv6: |
1454 | + nodeif.link_subnet(INTERFACE_LINK_TYPE.AUTO, v6_subnet) |
1455 | + node.boot_interface = nodeif |
1456 | + node.save() |
1457 | + return rack_v4, rack_v6, node |
1458 | + |
1459 | + def test__uses_rack_ipv4_if_ipv4_only_with_no_gateway(self): |
1460 | + rack_v4, rack_v6, node = self.make_Node_with_RackController( |
1461 | + ipv4=True, ipv4_gateway=False, ipv6=False, ipv6_gateway=False) |
1462 | + self.assertThat(node.get_default_dns_servers(), Equals([rack_v4])) |
1463 | + |
1464 | + def test__uses_rack_ipv4_if_ipv4_only_with_no_gateway_v4_dns(self): |
1465 | + ipv4_subnet_dns = factory.make_ip_address(ipv6=False) |
1466 | + rack_v4, rack_v6, node = self.make_Node_with_RackController( |
1467 | + ipv4=True, ipv4_gateway=False, ipv6=False, ipv6_gateway=False, |
1468 | + ipv4_subnet_dns=[ipv4_subnet_dns]) |
1469 | + self.assertThat( |
1470 | + node.get_default_dns_servers(), Equals([rack_v4])) |
1471 | + |
1472 | + def test__uses_rack_ipv6_if_ipv6_only_with_no_gateway_v6_dns(self): |
1473 | + ipv6_subnet_dns = factory.make_ip_address(ipv6=True) |
1474 | + rack_v4, rack_v6, node = self.make_Node_with_RackController( |
1475 | + ipv4=False, ipv4_gateway=False, ipv6=True, ipv6_gateway=False, |
1476 | + ipv6_subnet_dns=[ipv6_subnet_dns]) |
1477 | + self.assertThat(node.get_default_dns_servers(), Equals([rack_v6])) |
1478 | + |
1479 | + def test__uses_rack_ipv4_if_dual_stack_with_no_gateway(self): |
1480 | + rack_v4, rack_v6, node = self.make_Node_with_RackController( |
1481 | + ipv4=True, ipv4_gateway=False, ipv6=True, ipv6_gateway=False) |
1482 | + self.assertThat(node.get_default_dns_servers(), Equals([rack_v4])) |
1483 | + |
1484 | + def test__uses_rack_ipv4_if_dual_stack_with_ipv4_gateway(self): |
1485 | + rack_v4, rack_v6, node = self.make_Node_with_RackController( |
1486 | + ipv4=True, ipv4_gateway=True, ipv6=True, ipv6_gateway=False) |
1487 | + self.assertThat(node.get_default_dns_servers(), Equals([rack_v4])) |
1488 | + |
1489 | + def test__uses_subnet_ipv4_if_dual_stack_with_ipv4_gateway_with_dns(self): |
1490 | + ipv4_subnet_dns = factory.make_ip_address(ipv6=False) |
1491 | + ipv6_subnet_dns = factory.make_ip_address(ipv6=True) |
1492 | + rack_v4, rack_v6, node = self.make_Node_with_RackController( |
1493 | + ipv4=True, ipv4_gateway=True, ipv6=True, ipv6_gateway=False, |
1494 | + ipv4_subnet_dns=[ipv4_subnet_dns], |
1495 | + ipv6_subnet_dns=[ipv6_subnet_dns]) |
1496 | + self.assertThat( |
1497 | + node.get_default_dns_servers(), Equals([ipv4_subnet_dns])) |
1498 | + |
1499 | + def test__uses_rack_ipv6_if_dual_stack_with_ipv6_gateway(self): |
1500 | + rack_v4, rack_v6, node = self.make_Node_with_RackController( |
1501 | + ipv4=True, ipv4_gateway=False, ipv6=True, ipv6_gateway=True) |
1502 | + self.assertThat(node.get_default_dns_servers(), Equals([rack_v6])) |
1503 | + |
1504 | + def test__uses_subnet_ipv6_if_dual_stack_with_ipv6_gateway(self): |
1505 | + ipv4_subnet_dns = factory.make_ip_address(ipv6=False) |
1506 | + ipv6_subnet_dns = factory.make_ip_address(ipv6=True) |
1507 | + rack_v4, rack_v6, node = self.make_Node_with_RackController( |
1508 | + ipv4=True, ipv4_gateway=False, ipv6=True, ipv6_gateway=True, |
1509 | + ipv4_subnet_dns=[ipv4_subnet_dns], |
1510 | + ipv6_subnet_dns=[ipv6_subnet_dns]) |
1511 | + self.assertThat( |
1512 | + node.get_default_dns_servers(), Equals([ipv6_subnet_dns])) |
1513 | + |
1514 | + def test__uses_rack_ipv4_if_ipv4_with_ipv4_gateway(self): |
1515 | + rack_v4, rack_v6, node = self.make_Node_with_RackController( |
1516 | + ipv4=True, ipv4_gateway=True, ipv6=False, ipv6_gateway=False) |
1517 | + self.assertThat(node.get_default_dns_servers(), Equals([rack_v4])) |
1518 | + |
1519 | + def test__uses_subnet_ipv4_if_ipv4_stack_with_ipv4_gateway_and_dns(self): |
1520 | + ipv4_subnet_dns = factory.make_ip_address(ipv6=False) |
1521 | + ipv6_subnet_dns = factory.make_ip_address(ipv6=True) |
1522 | + rack_v4, rack_v6, node = self.make_Node_with_RackController( |
1523 | + ipv4=True, ipv4_gateway=True, ipv6=False, ipv6_gateway=False, |
1524 | + ipv4_subnet_dns=[ipv4_subnet_dns], |
1525 | + ipv6_subnet_dns=[ipv6_subnet_dns]) |
1526 | + self.assertThat( |
1527 | + node.get_default_dns_servers(), Equals([ipv4_subnet_dns])) |
1528 | + |
1529 | + def test__uses_rack_ipv6_if_ipv6_with_ipv6_gateway(self): |
1530 | + rack_v4, rack_v6, node = self.make_Node_with_RackController( |
1531 | + ipv4=False, ipv4_gateway=False, ipv6=True, ipv6_gateway=True) |
1532 | + self.assertThat(node.get_default_dns_servers(), Equals([rack_v6])) |
1533 | + |
1534 | + def test__uses_subnet_ipv6_if_ipv6_with_ipv6_gateway_and_dns(self): |
1535 | + ipv4_subnet_dns = factory.make_ip_address(ipv6=False) |
1536 | + ipv6_subnet_dns = factory.make_ip_address(ipv6=True) |
1537 | + rack_v4, rack_v6, node = self.make_Node_with_RackController( |
1538 | + ipv4=False, ipv4_gateway=False, ipv6=True, ipv6_gateway=True, |
1539 | + ipv4_subnet_dns=[ipv4_subnet_dns], |
1540 | + ipv6_subnet_dns=[ipv6_subnet_dns]) |
1541 | + self.assertThat( |
1542 | + node.get_default_dns_servers(), Equals([ipv6_subnet_dns])) |
1543 | + |
1544 | + |
1545 | +class TestNode_Start(MAASServerTestCase): |
1546 | +>>>>>>> MERGE-SOURCE |
1547 | """Tests for Node.start().""" |
1548 | |
1549 | def setUp(self): |
1550 | |
1551 | === modified file 'src/maasserver/models/tests/test_staticipaddress.py' |
1552 | --- src/maasserver/models/tests/test_staticipaddress.py 2016-10-18 08:00:37 +0000 |
1553 | +++ src/maasserver/models/tests/test_staticipaddress.py 2016-10-24 18:43:47 +0000 |
1554 | @@ -363,6 +363,7 @@ |
1555 | mapping = StaticIPAddress.objects.get_hostname_ip_mapping(domain) |
1556 | self.assertEqual(expected_mapping, mapping) |
1557 | |
1558 | +<<<<<<< TREE |
1559 | def test_get_hostname_ip_mapping_returns_all_mappings_for_subnet(self): |
1560 | domain = Domain.objects.get_default_domain() |
1561 | expected_mapping = {} |
1562 | @@ -384,14 +385,42 @@ |
1563 | self.assertEqual(expected_mapping, mapping) |
1564 | |
1565 | def test_get_hostname_ip_mapping_returns_fqdn_and_other(self): |
1566 | +======= |
1567 | + def test_get_hostname_ip_mapping_returns_all_mappings_for_subnet(self): |
1568 | + domain = Domain.objects.get_default_domain() |
1569 | + expected_mapping = {} |
1570 | + for _ in range(3): |
1571 | + node = factory.make_Node(interface=True, disable_ipv4=False) |
1572 | + boot_interface = node.get_boot_interface() |
1573 | + subnet = factory.make_Subnet() |
1574 | + staticip = factory.make_StaticIPAddress( |
1575 | + alloc_type=IPADDRESS_TYPE.STICKY, |
1576 | + ip=factory.pick_ip_in_Subnet(subnet), |
1577 | + subnet=subnet, interface=boot_interface) |
1578 | + full_hostname = "%s.%s" % (node.hostname, domain.name) |
1579 | + expected_mapping[full_hostname] = HostnameIPMapping( |
1580 | + node.system_id, 30, {staticip.ip}, node.node_type) |
1581 | + # See also LP#1600259. It doesn't matter what subnet is passed in, you |
1582 | + # get all of them. |
1583 | + mapping = StaticIPAddress.objects.get_hostname_ip_mapping( |
1584 | + Subnet.objects.first()) |
1585 | + self.assertEqual(expected_mapping, mapping) |
1586 | + |
1587 | + def test_get_hostname_ip_mapping_returns_fqdn_and_other(self): |
1588 | +>>>>>>> MERGE-SOURCE |
1589 | hostname = factory.make_name('hostname') |
1590 | domainname = factory.make_name('domain') |
1591 | factory.make_Domain(name=domainname) |
1592 | full_hostname = "%s.%s" % (hostname, domainname) |
1593 | subnet = factory.make_Subnet() |
1594 | node = factory.make_Node_with_Interface_on_Subnet( |
1595 | +<<<<<<< TREE |
1596 | interface=True, hostname=full_hostname, interface_count=3, |
1597 | subnet=subnet) |
1598 | +======= |
1599 | + interface=True, hostname=full_hostname, interface_count=3, |
1600 | + subnet=subnet, disable_ipv4=False) |
1601 | +>>>>>>> MERGE-SOURCE |
1602 | boot_interface = node.get_boot_interface() |
1603 | staticip = factory.make_StaticIPAddress( |
1604 | alloc_type=IPADDRESS_TYPE.STICKY, |
1605 | @@ -516,9 +545,14 @@ |
1606 | alloc_type=IPADDRESS_TYPE.STICKY, |
1607 | subnet=subnet, interface=boot_interface) |
1608 | newer_nic = factory.make_Interface(INTERFACE_TYPE.PHYSICAL, node=node) |
1609 | +<<<<<<< TREE |
1610 | newer_ip = factory.make_StaticIPAddress( |
1611 | alloc_type=IPADDRESS_TYPE.STICKY, subnet=subnet, |
1612 | interface=newer_nic) |
1613 | +======= |
1614 | + newer_ip = factory.make_StaticIPAddress( |
1615 | + alloc_type=IPADDRESS_TYPE.STICKY, interface=newer_nic) |
1616 | +>>>>>>> MERGE-SOURCE |
1617 | mapping = StaticIPAddress.objects.get_hostname_ip_mapping( |
1618 | node.domain) |
1619 | expected_mapping = { |
1620 | @@ -535,11 +569,20 @@ |
1621 | interface=True, hostname=factory.make_name('host')) |
1622 | boot_interface = node.get_boot_interface() |
1623 | staticip = factory.make_StaticIPAddress( |
1624 | +<<<<<<< TREE |
1625 | alloc_type=IPADDRESS_TYPE.STICKY, subnet=subnet, |
1626 | interface=boot_interface) |
1627 | nic = node.get_boot_interface() # equals boot_interface |
1628 | auto_ip = factory.make_StaticIPAddress( |
1629 | alloc_type=IPADDRESS_TYPE.AUTO, subnet=subnet, interface=nic) |
1630 | +======= |
1631 | + alloc_type=IPADDRESS_TYPE.STICKY, |
1632 | + ip=factory.pick_ip_in_Subnet(subnet), |
1633 | + subnet=subnet, interface=boot_interface) |
1634 | + nic = node.get_boot_interface() |
1635 | + auto_ip = factory.make_StaticIPAddress( |
1636 | + alloc_type=IPADDRESS_TYPE.AUTO, interface=nic) |
1637 | +>>>>>>> MERGE-SOURCE |
1638 | mapping = StaticIPAddress.objects.get_hostname_ip_mapping( |
1639 | node.domain) |
1640 | expected_mapping = { |
1641 | @@ -605,15 +648,24 @@ |
1642 | staticip = factory.make_StaticIPAddress( |
1643 | alloc_type=IPADDRESS_TYPE.AUTO, interface=iface, |
1644 | subnet=subnet) |
1645 | - factory.make_StaticIPAddress( |
1646 | + discovered = factory.make_StaticIPAddress( |
1647 | alloc_type=IPADDRESS_TYPE.DISCOVERED, interface=iface, |
1648 | subnet=subnet) |
1649 | mapping = StaticIPAddress.objects.get_hostname_ip_mapping( |
1650 | node.domain) |
1651 | +<<<<<<< TREE |
1652 | expected_mapping = { |
1653 | node.fqdn: HostnameIPMapping( |
1654 | node.system_id, 30, {staticip.ip}, node.node_type)} |
1655 | self.assertEqual(expected_mapping, mapping) |
1656 | +======= |
1657 | + expected_mapping = { |
1658 | + node.fqdn: HostnameIPMapping( |
1659 | + node.system_id, 30, {staticip.ip}, node.node_type), |
1660 | + "%s.%s" % (iface.name, node.fqdn): HostnameIPMapping( |
1661 | + node.system_id, 30, {discovered.ip}, node.node_type)} |
1662 | + self.assertEqual(expected_mapping, mapping) |
1663 | +>>>>>>> MERGE-SOURCE |
1664 | |
1665 | def test_get_hostname_ip_mapping_prefers_bond_with_no_boot_interface(self): |
1666 | subnet = factory.make_Subnet( |
1667 | @@ -831,7 +883,13 @@ |
1668 | mapping = StaticIPAddress.objects.get_hostname_ip_mapping(domain) |
1669 | expected_mapping = { |
1670 | node.fqdn: HostnameIPMapping( |
1671 | +<<<<<<< TREE |
1672 | node.system_id, 30, {sip0.ip}, node.node_type)} |
1673 | +======= |
1674 | + node.system_id, 30, {sip0.ip}, node.node_type), |
1675 | + "%s.%s" % (iface1.name, node.fqdn): HostnameIPMapping( |
1676 | + node.system_id, 30, {sip1.ip}, node.node_type)} |
1677 | +>>>>>>> MERGE-SOURCE |
1678 | self.assertEqual(expected_mapping, mapping) |
1679 | |
1680 | def test_get_hostname_ip_mapping_returns_correct_bond_ip(self): |
1681 | @@ -877,8 +935,15 @@ |
1682 | mapping = StaticIPAddress.objects.get_hostname_ip_mapping(domain) |
1683 | expected_mapping = { |
1684 | node.fqdn: HostnameIPMapping( |
1685 | - node.system_id, 30, {bond_sip.ip}, node.node_type), |
1686 | - } |
1687 | +<<<<<<< TREE |
1688 | + node.system_id, 30, {bond_sip.ip}, node.node_type), |
1689 | + } |
1690 | +======= |
1691 | + node.system_id, 30, {bond_sip.ip}, node.node_type), |
1692 | + "%s.%s" % (iface1.name, node.fqdn): HostnameIPMapping( |
1693 | + node.system_id, 30, {sip1.ip}, node.node_type), |
1694 | + } |
1695 | +>>>>>>> MERGE-SOURCE |
1696 | self.assertEqual(expected_mapping, mapping) |
1697 | |
1698 | |
1699 | |
1700 | === modified file 'src/maasserver/models/tests/test_subnet.py' |
1701 | === modified file 'src/maasserver/node_status.py' |
1702 | --- src/maasserver/node_status.py 2016-10-03 22:13:11 +0000 |
1703 | +++ src/maasserver/node_status.py 2016-10-24 18:43:47 +0000 |
1704 | @@ -218,6 +218,22 @@ |
1705 | NODE_STATUS.EXITING_RESCUE_MODE: 5, |
1706 | } |
1707 | |
1708 | +# State transitions that are monitored for timeouts for when a node |
1709 | +# fails: |
1710 | +# Mapping between in-progress statuses and the corresponding failed |
1711 | +# statuses. |
1712 | +NODE_FAILURE_MONITORED_STATUS_TRANSITIONS = { |
1713 | + NODE_STATUS.COMMISSIONING: NODE_STATUS.FAILED_COMMISSIONING, |
1714 | + NODE_STATUS.DEPLOYING: NODE_STATUS.FAILED_DEPLOYMENT, |
1715 | + NODE_STATUS.RELEASING: NODE_STATUS.FAILED_RELEASING, |
1716 | +} |
1717 | + |
1718 | +NODE_FAILURE_MONITORED_STATUS_TIMEOUTS = { |
1719 | + NODE_STATUS.COMMISSIONING: 20, |
1720 | + NODE_STATUS.DEPLOYING: 40, |
1721 | + NODE_STATUS.RELEASING: 5, |
1722 | +} |
1723 | + |
1724 | # Statuses that correspond to managed steps for which MAAS actively |
1725 | # monitors that the status changes after a fixed period of time. |
1726 | MONITORED_STATUSES = list(NODE_FAILURE_STATUS_TRANSITIONS.keys()) |
1727 | |
1728 | === modified file 'src/maasserver/preseed.py' |
1729 | === modified file 'src/maasserver/preseed_network.py' |
1730 | --- src/maasserver/preseed_network.py 2016-08-25 14:19:40 +0000 |
1731 | +++ src/maasserver/preseed_network.py 2016-10-24 18:43:47 +0000 |
1732 | @@ -6,10 +6,14 @@ |
1733 | __all__ = [ |
1734 | ] |
1735 | |
1736 | +<<<<<<< TREE |
1737 | from collections import defaultdict |
1738 | from operator import attrgetter |
1739 | |
1740 | from maasserver.dns.zonegenerator import get_dns_search_paths |
1741 | +======= |
1742 | +from maasserver.dns.zonegenerator import get_dns_search_paths |
1743 | +>>>>>>> MERGE-SOURCE |
1744 | from maasserver.enum import ( |
1745 | INTERFACE_TYPE, |
1746 | IPADDRESS_FAMILY, |
1747 | @@ -26,7 +30,11 @@ |
1748 | def __init__(self, node): |
1749 | self.node = node |
1750 | self.gateways = node.get_default_gateways() |
1751 | +<<<<<<< TREE |
1752 | self.routes = StaticRoute.objects.all() |
1753 | +======= |
1754 | + self.dns_servers = node.get_default_dns_servers() |
1755 | +>>>>>>> MERGE-SOURCE |
1756 | self.gateway_ipv4_set = False |
1757 | self.gateway_ipv6_set = False |
1758 | # The default value is False: expected keys are 4 and 6. |
1759 | @@ -64,7 +72,11 @@ |
1760 | ipv4=self.addr_family_present[4], ipv6=self.addr_family_present[6]) |
1761 | self.network_config.append({ |
1762 | "type": "nameserver", |
1763 | +<<<<<<< TREE |
1764 | "address": default_dns_servers, |
1765 | +======= |
1766 | + "address": self.dns_servers, |
1767 | +>>>>>>> MERGE-SOURCE |
1768 | "search": sorted(get_dns_search_paths()), |
1769 | }) |
1770 | |
1771 | |
1772 | === modified file 'src/maasserver/preseed_storage.py' |
1773 | === modified file 'src/maasserver/rpc/nodes.py' |
1774 | === modified file 'src/maasserver/rpc/tests/test_nodes.py' |
1775 | === modified file 'src/maasserver/rpc/tests/test_regionservice.py' |
1776 | --- src/maasserver/rpc/tests/test_regionservice.py 2016-10-12 15:26:17 +0000 |
1777 | +++ src/maasserver/rpc/tests/test_regionservice.py 2016-10-24 18:43:47 +0000 |
1778 | @@ -77,6 +77,7 @@ |
1779 | TwistedLoggerFixture, |
1780 | ) |
1781 | import netaddr |
1782 | +from provisioningserver.path import get_path |
1783 | from provisioningserver.rpc import ( |
1784 | common, |
1785 | exceptions, |
1786 | @@ -87,6 +88,10 @@ |
1787 | from provisioningserver.rpc.testing import call_responder |
1788 | from provisioningserver.rpc.testing.doubles import DummyConnection |
1789 | from provisioningserver.utils import events |
1790 | +from provisioningserver.utils.env import ( |
1791 | + get_maas_id, |
1792 | + set_maas_id, |
1793 | +) |
1794 | from provisioningserver.utils.testing import MAASIDFixture |
1795 | from provisioningserver.utils.twisted import ( |
1796 | callInReactorWithTimeout, |
1797 | @@ -1204,6 +1209,15 @@ |
1798 | if len(exceptions) == 0: |
1799 | return original() |
1800 | else: |
1801 | + # Stick a bad value in maas_id cache to test that maas_id is |
1802 | + # being reread from disk each time. |
1803 | + good_maas_id = get_maas_id() |
1804 | + set_maas_id(factory.make_string()) |
1805 | + # Write the good value to disk |
1806 | + maas_id_path = get_path("/var/lib/maas/maas_id") |
1807 | + os.unlink(maas_id_path) |
1808 | + with open(maas_id_path, "w") as fd: |
1809 | + fd.write(good_maas_id) |
1810 | raise exceptions.pop(0) |
1811 | |
1812 | fake_promote = self.patch(regionservice.RegionAdvertising, "promote") |
1813 | |
1814 | === added file 'src/maasserver/static/css/maas-styles.css.OTHER' |
1815 | --- src/maasserver/static/css/maas-styles.css.OTHER 1970-01-01 00:00:00 +0000 |
1816 | +++ src/maasserver/static/css/maas-styles.css.OTHER 2016-10-24 18:43:47 +0000 |
1817 | @@ -0,0 +1,1 @@ |
1818 | +.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} |
1819 | |
1820 | === modified file 'src/maasserver/static/js/angular/controllers/node_details_networking.js' |
1821 | === modified file 'src/maasserver/static/js/angular/controllers/node_details_storage.js' |
1822 | === modified file 'src/maasserver/static/js/angular/controllers/node_events.js' |
1823 | === modified file 'src/maasserver/static/js/angular/controllers/tests/test_node_details_networking.js' |
1824 | === modified file 'src/maasserver/static/js/angular/controllers/tests/test_node_details_storage.js' |
1825 | --- src/maasserver/static/js/angular/controllers/tests/test_node_details_storage.js 2016-10-12 16:34:23 +0000 |
1826 | +++ src/maasserver/static/js/angular/controllers/tests/test_node_details_storage.js 2016-10-24 18:43:47 +0000 |
1827 | @@ -2810,6 +2810,7 @@ |
1828 | }); |
1829 | }); |
1830 | |
1831 | +<<<<<<< TREE |
1832 | describe("getCannotCreateBcacheMsg", function() { |
1833 | |
1834 | it("returns msg if no cachesets", |
1835 | @@ -3075,6 +3076,94 @@ |
1836 | |
1837 | }); |
1838 | |
1839 | +======= |
1840 | + describe("getCannotCreateBcacheMsg", function() { |
1841 | + |
1842 | + it("returns msg if no cachesets", |
1843 | + function() { |
1844 | + var controller = makeController(); |
1845 | + $scope.available = [ |
1846 | + { |
1847 | + fstype: null, |
1848 | + $selected: true, |
1849 | + has_partitions: false |
1850 | + } |
1851 | + ]; |
1852 | + $scope.cachesets = []; |
1853 | + expect($scope.getCannotCreateBcacheMsg()).toBe( |
1854 | + "Create at least one cache set to create bcache"); |
1855 | + }); |
1856 | + |
1857 | + it("returns msg if two selected", function() { |
1858 | + var controller = makeController(); |
1859 | + $scope.cachesets = [{}]; |
1860 | + $scope.available = [ { $selected: true }, { $selected: true }]; |
1861 | + expect($scope.getCannotCreateBcacheMsg()).toBe( |
1862 | + "Select only one available device to create bcache"); |
1863 | + }); |
1864 | + |
1865 | + it("returns msg if selected has fstype", function() { |
1866 | + var controller = makeController(); |
1867 | + $scope.available = [ |
1868 | + { |
1869 | + fstype: "ext4", |
1870 | + $selected: true, |
1871 | + has_partitions: false |
1872 | + } |
1873 | + ]; |
1874 | + $scope.cachesets = [{}]; |
1875 | + |
1876 | + expect($scope.getCannotCreateBcacheMsg()).toBe( |
1877 | + "Device is formatted; unformat the device to create bcache"); |
1878 | + }); |
1879 | + |
1880 | + it("returns msg if selected is volume group", function() { |
1881 | + var controller = makeController(); |
1882 | + $scope.available = [ |
1883 | + { |
1884 | + type: "lvm-vg", |
1885 | + fstype: null, |
1886 | + $selected: true, |
1887 | + has_partitions: false |
1888 | + } |
1889 | + ]; |
1890 | + $scope.cachesets = [{}]; |
1891 | + expect($scope.getCannotCreateBcacheMsg()).toBe( |
1892 | + "Cannot use a logical volume as a backing device for bcache."); |
1893 | + }); |
1894 | + |
1895 | + it("returns msg if selected has partitions", function() { |
1896 | + var controller = makeController(); |
1897 | + $scope.available = [ |
1898 | + { |
1899 | + fstype: null, |
1900 | + $selected: true, |
1901 | + has_partitions: true |
1902 | + } |
1903 | + ]; |
1904 | + $scope.cachesets = [{}]; |
1905 | + expect($scope.getCannotCreateBcacheMsg()).toBe( |
1906 | + "Device has already been partitioned; create a " + |
1907 | + "new partition to use as the bcache backing " + |
1908 | + "device"); |
1909 | + }); |
1910 | + |
1911 | + it("returns null if selected is valid", |
1912 | + function() { |
1913 | + var controller = makeController(); |
1914 | + $scope.available = [ |
1915 | + { |
1916 | + fstype: null, |
1917 | + $selected: true, |
1918 | + has_partitions: false |
1919 | + } |
1920 | + ]; |
1921 | + $scope.cachesets = [{}]; |
1922 | + expect($scope.getCannotCreateBcacheMsg()).toBeNull(); |
1923 | + }); |
1924 | + }); |
1925 | + |
1926 | +>>>>>>> MERGE-SOURCE |
1927 | describe("canCreateBcache", function() { |
1928 | |
1929 | it("returns false when isAvailableDisabled is true", function() { |
1930 | |
1931 | === modified file 'src/maasserver/static/js/angular/controllers/tests/test_node_events.js' |
1932 | === modified file 'src/maasserver/static/js/angular/directives/controller_image_status.js' |
1933 | === modified file 'src/maasserver/static/partials/node-details.html' |
1934 | --- src/maasserver/static/partials/node-details.html 2016-10-20 16:04:24 +0000 |
1935 | +++ src/maasserver/static/partials/node-details.html 2016-10-24 18:43:47 +0000 |
1936 | @@ -1936,9 +1936,15 @@ |
1937 | data-ng-disabled="!canCreateCacheSet()" |
1938 | data-ng-hide="isAllStorageDisabled() || !isSuperUser()" |
1939 | data-ng-click="createCacheSet()">Create cache Set</a> |
1940 | +<<<<<<< TREE |
1941 | <a class="button--secondary button--inline tooltip" |
1942 | aria-label="{$ getCannotCreateBcacheMsg() $}" |
1943 | data-ng-class="{ tooltip: !canCreateBcache() }" |
1944 | +======= |
1945 | + <a class="link-cta-ubuntu secondary margin-right" |
1946 | + data-tooltip="{$ getCannotCreateBcacheMsg() $}" |
1947 | + data-ng-class="{ tooltip: !canCreateBcache() }" |
1948 | +>>>>>>> MERGE-SOURCE |
1949 | data-ng-disabled="!canCreateBcache()" |
1950 | data-ng-hide="isAllStorageDisabled() || !isSuperUser()" |
1951 | data-ng-click="createBcache()">Create bcache</a> |
1952 | |
1953 | === modified file 'src/maasserver/static/partials/node-events.html' |
1954 | --- src/maasserver/static/partials/node-events.html 2016-08-25 20:45:36 +0000 |
1955 | +++ src/maasserver/static/partials/node-events.html 2016-10-24 18:43:47 +0000 |
1956 | @@ -6,6 +6,7 @@ |
1957 | </header> |
1958 | </div> |
1959 | <div class="ng-hide" data-ng-show="loaded"> |
1960 | +<<<<<<< TREE |
1961 | <header class="page-header u-margin--bottom"> |
1962 | <div class="wrapper--inner"> |
1963 | <h1 class="page-header__title">{$ node.fqdn $}</h1> |
1964 | @@ -15,6 +16,19 @@ |
1965 | </p> |
1966 | <div class="page-header__controls u-float--right"> |
1967 | <a class="button--base button--inline" href="#/node/{$ node.system_id $}">‹ Back to machine details</a> |
1968 | +======= |
1969 | + <header class="page-header margin-bottom"> |
1970 | + <div class="inner-wrapper"> |
1971 | + <h1 class="page-header__title eight-col"> |
1972 | + {$ node.fqdn $} |
1973 | + <span class="page-header__title--identicator"> |
1974 | + {$ events.length $} machine events in the past {$ days $} day(s) |
1975 | + <a href="" class="page-header__title-loadmore" data-ng-click="loadMore()">load 1 more day</a> |
1976 | + </span> |
1977 | + </h1> |
1978 | + <div class="page-header__actions four-col last-col"> |
1979 | + <a class="right link-cta-ubuntu text-button" href="#/node/{$ node.system_id $}">‹ Back to machine details</a> |
1980 | +>>>>>>> MERGE-SOURCE |
1981 | </div> |
1982 | </div> |
1983 | </header> |
1984 | |
1985 | === modified file 'src/maasserver/static/partials/nodes-list.html' |
1986 | --- src/maasserver/static/partials/nodes-list.html 2016-10-13 23:43:14 +0000 |
1987 | +++ src/maasserver/static/partials/nodes-list.html 2016-10-24 18:43:47 +0000 |
1988 | @@ -1,3 +1,4 @@ |
1989 | +<<<<<<< TREE |
1990 | <header class="page-header"> |
1991 | <div class="wrapper--inner"> |
1992 | <!-- XXX ricgard 2016-06-16 - Need to add e2e test. --> |
1993 | @@ -232,6 +233,178 @@ |
1994 | </div> |
1995 | <div class="form__group"> |
1996 | <label for="zone" class="form__group-label two-col">Zone</label> |
1997 | +======= |
1998 | +<header class="page-header margin-bottom" data-maas-sticky-header> |
1999 | + <div class="inner-wrapper"> |
2000 | + <h1 class="page-header__title eight-col">{$ $parent.site $} MAAS |
2001 | + <span class="page-header__title--identicator" id="bulk-actions"> |
2002 | + <!-- XXX blake_r 2015-02-19 - Need to add e2e test. --> |
2003 | + <a href="" |
2004 | + class="tooltip" |
2005 | + data-tooltip="A deployable node managed by MAAS." |
2006 | + data-ng-class="{ active: currentpage === 'nodes' }" |
2007 | + data-ng-click="toggleTab('nodes')">{$ nodes.length $} <ng-pluralize count="nodes.length" when="{'one': 'Machine', 'other': 'Machines'}"></ng-pluralize></a> |
2008 | + <span class="divide"></span> |
2009 | + <a href="" |
2010 | + class="tooltip" |
2011 | + data-tooltip="A node known to MAAS, but is not deployable." |
2012 | + data-ng-class="{ active: currentpage === 'devices' }" |
2013 | + data-ng-click="toggleTab('devices')">{$ devices.length $} <ng-pluralize count="devices.length" when="{'one': 'Device', 'other': 'Devices'}"></ng-pluralize> |
2014 | + </a> |
2015 | + <span data-ng-show="isSuperUser()" class="divide"></span> |
2016 | + <a href="" data-ng-show="isSuperUser()" |
2017 | + class="tooltip" |
2018 | + data-tooltip="A node that provides MAAS services." |
2019 | + data-ng-class="{ active: currentpage === 'controllers' }" |
2020 | + data-ng-click="toggleTab('controllers')">{$ controllers.length $} <ng-pluralize count="controllers.length" when="{'one': 'Controller', 'other': 'Controllers'}"></ng-pluralize> |
2021 | + </a> |
2022 | + <span class="power-status--power" data-ng-show="loading"> |
2023 | + <span class="loader"></span> |
2024 | + Loading... |
2025 | + </span> |
2026 | + </span> |
2027 | + </h1> |
2028 | + <!-- XXX blake_r 2015-02-19 - Need to add e2e test. --> |
2029 | + <div data-ng-show="currentpage === 'nodes'"> |
2030 | + <div class="page-header__actions"> |
2031 | + <div class="page-header__cta" data-ng-hide="tabs.nodes.selectedItems.length"> |
2032 | + <div class="right" data-maas-cta="addHardwareOptions" |
2033 | + data-ng-model="addHardwareOption" |
2034 | + data-ng-change="addHardwareOptionChanged()" data-default-title="Add hardware"> |
2035 | + </div> |
2036 | + </div> |
2037 | + <div class="page-header__cta ng-hide" data-ng-show="tabs.nodes.selectedItems.length"> |
2038 | + <span class="page-header__cta-feedback" data-ng-click="showSelected('nodes')"> |
2039 | + {$ tabs.nodes.selectedItems.length $} Selected |
2040 | + </span> |
2041 | + <div data-maas-cta="tabs.nodes.takeActionOptions" |
2042 | + data-ng-model="tabs.nodes.actionOption" |
2043 | + data-ng-change="actionOptionSelected('nodes')"> |
2044 | + </div> |
2045 | + </div> |
2046 | + </div> |
2047 | + <!-- XXX blake_r 2015-02-19 - Need to add e2e test. --> |
2048 | + <div class="page-header__dropdown ng-hide" data-ng-show="tabs.nodes.actionOption"> |
2049 | + <!-- XXX blake_r 2015-02-19 - Need to add e2e test. --> |
2050 | + <div class="page-header__feedback ng-hide" data-ng-hide="isActionError('nodes') || hasActionsInProgress('nodes')"> |
2051 | + <form class="form form--inline"> |
2052 | + <div class="nine-col no-margin-bottom ng-hide" data-ng-show="tabs.nodes.actionOption.name === 'commission'"> |
2053 | + <div class="form__group u-margin--right u-margin--top-tiny"> |
2054 | + <input class="checkbox margin-right" id="enableSSH" type="checkbox" |
2055 | + data-ng-model="tabs.nodes.commissionOptions.enableSSH"> |
2056 | + <label class="checkbox-label" for="enableSSH">Allow SSH access and prevent machine from powering off</label> |
2057 | + </div> |
2058 | + <div class="form__group u-margin--right u-margin--top-tiny"> |
2059 | + <input class="checkbox margin-right" id="skipNetworking" type="checkbox" |
2060 | + data-ng-model="tabs.nodes.commissionOptions.skipNetworking"> |
2061 | + <label class="checkbox-label" for="skipNetworking">Retain network configuration</label> |
2062 | + </div> |
2063 | + <div class="form__group u-margin--top-tiny"> |
2064 | + <input class="checkbox" id="skipStorage" type="checkbox" |
2065 | + data-ng-model="tabs.nodes.commissionOptions.skipStorage"> |
2066 | + <label class="checkbox-label" for="skipStorage">Retain storage configuration</label> |
2067 | + </div> |
2068 | + </div> |
2069 | + <span class="form__group ng-hide" data-ng-show="tabs.nodes.actionOption.name === 'deploy'"> |
2070 | + <label for="image" class="u-margin--right">Choose your image</label> |
2071 | + <span data-maas-os-select="osinfo" data-ng-model="tabs.nodes.osSelection"></span> |
2072 | + </span> |
2073 | + <!-- XXX rbanffy 2015-03-23 - Need to add e2e test. --> |
2074 | + <span class="form__group ng-hide" data-ng-show="tabs.nodes.actionOption.name === 'set-zone'"> |
2075 | + <label for="zone" class="u-margin--right">Select Zone</label> |
2076 | + <select name="zone" id="zone" placeholder="Choose a zone" |
2077 | + data-ng-model="tabs.nodes.zoneSelection" |
2078 | + data-ng-options="zone as zone.name for zone in zones"> |
2079 | + <option value="" disabled="disabled">Choose a zone</option> |
2080 | + </select> |
2081 | + </span> |
2082 | + <div class="right"> |
2083 | + <a href="" class="link-cta-ubuntu text-button" data-ng-click="actionCancel('nodes')">Cancel</a> |
2084 | + <button class="cta-ubuntu" data-ng-click="actionGo('nodes')" data-ng-hide="hasActionsFailed('nodes')">Go</button> |
2085 | + <button class="cta-ubuntu" data-ng-click="actionGo('nodes')" data-ng-show="hasActionsFailed('nodes')">Retry</button> |
2086 | + </div> |
2087 | + </form> |
2088 | + </div> |
2089 | + |
2090 | + <!-- XXX blake_r 2015-02-19 - Need to add e2e test. --> |
2091 | + <div class="page-header__feedback ng-hide" data-ng-show="isActionError('nodes')"> |
2092 | + <!-- XXX blake_r 2015-02-19 - Need to add e2e test. --> |
2093 | + <p class="page-header__feedback-message info" data-ng-hide="isDeployError('nodes') || isSSHKeyError('nodes')"> |
2094 | + {$ tabs.nodes.actionErrorCount $} |
2095 | + <span data-ng-pluralize count="tabs.nodes.selectedItems.length" when="{'one': 'node', 'other': 'nodes'}"></span> |
2096 | + cannot be {$ tabs.nodes.actionOption.sentence $}. To proceed, update your selection. |
2097 | + </p> |
2098 | + <!-- XXX blake_r 2015-02-19 - Need to add e2e test. --> |
2099 | + <p class="page-header__feedback-message info ng-hide" data-ng-show="isDeployError('nodes')"> |
2100 | + {$ tabs.nodes.selectedItems.length $} |
2101 | + <span data-ng-pluralize count="tabs.nodes.selectedItems.length" when="{'one': 'node', 'other': 'nodes'}"></span> |
2102 | + 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>. |
2103 | + </p> |
2104 | + <p class="page-header__feedback-message info ng-hide" data-ng-show="isSSHKeyError('nodes')"> |
2105 | + {$ tabs.nodes.selectedItems.length $} |
2106 | + <span data-ng-pluralize count="tabs.nodes.selectedItems.length" when="{'one': 'node', 'other': 'nodes'}"></span> |
2107 | + 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>. |
2108 | + </p> |
2109 | + </div> |
2110 | + |
2111 | + <!-- XXX blake_r 2015-05-07 - Need to add e2e test. --> |
2112 | + <div class="page-header__feedback ng-hide" data-ng-show="hasActionsInProgress('nodes') || hasActionsFailed('nodes')"> |
2113 | + <p class="page-header__feedback-message progress" data-ng-show="hasActionsInProgress('nodes')"> |
2114 | + <span class="loader"></span> |
2115 | + {$ tabs.nodes.actionProgress.completed $} of {$ tabs.nodes.actionProgress.total $} |
2116 | + nodes have been {$ tabs.nodes.actionOption.sentence $}. |
2117 | + </p> |
2118 | + <p class="page-header__feedback-message error" |
2119 | + data-ng-repeat="(error, nodes) in tabs.nodes.actionProgress.errors"> |
2120 | + The {$ tabs.nodes.actionOption.title.toLowerCase() $} action for {$ nodes.length $} |
2121 | + <span data-ng-pluralize count="nodes.length" when="{'one': 'node', 'other': 'nodes'}"></span> |
2122 | + failed with error: {$ error $} |
2123 | + </p> |
2124 | + </div> |
2125 | + </div> |
2126 | + <div class="page-header__dropdown border ng-hide" data-ng-show="addHardwareScope.viewable" data-ng-controller="AddHardwareController"> |
2127 | + <h3 class="u-padding--top" data-ng-if="showMachine()">Add machine</h3> |
2128 | + <h3 class="u-padding--top" data-ng-if="showChassis()">Add chassis</h3> |
2129 | + <div class="page-header__feedback" data-ng-hide="architectures.length"> |
2130 | + <!-- XXX blake_r 2015-02-24 - Need to add e2e test. --> |
2131 | + <p class="page-header__feedback-message info"> |
2132 | + Cannot add {$ mode $} until boot images have been imported. To fix, visit the <a href="images">images page</a>. |
2133 | + </p> |
2134 | + </div> |
2135 | + <form class="twelve-col ng-hide" data-ng-show="showMachine()"> |
2136 | + <fieldset class="six-col no-padding no-margin-bottom"> |
2137 | + <div class="inline"> |
2138 | + <label for="machine-name" class="two-col">Machine name</label> |
2139 | + <input type="text" id="machine-name" class="three-col" placeholder="Choose a machine name (optional)" |
2140 | + data-ng-model="machine.name"> |
2141 | + </div> |
2142 | + <div class="inline"> |
2143 | + <label for="domain" class="two-col">Domain</label> |
2144 | + <select name="domain" id="domain" class="three-col" |
2145 | + data-ng-model="machine.domain" |
2146 | + data-ng-options="domain as domain.name for domain in domains"> |
2147 | + <option value="" disabled>Choose a domain</option> |
2148 | + </select> |
2149 | + </div> |
2150 | + <div class="inline"> |
2151 | + <label for="architecture" class="two-col">Architecture</label> |
2152 | + <select name="architecture" id="architecture" class="three-col" |
2153 | + data-ng-model="machine.architecture" |
2154 | + data-ng-options="arch for arch in architectures"> |
2155 | + <option value="" disabled>Choose an architecture</option> |
2156 | + </select> |
2157 | + </div> |
2158 | + <div class="inline"> |
2159 | + <label for="min_hwe_kernel" class="two-col">Minimum Kernel</label> |
2160 | + <select name="min_hwe_kernel" id="min_hwe_kernel" class="three-col" placeholder="No minimum kernel" |
2161 | + data-ng-model="machine.min_hwe_kernel" |
2162 | + data-ng-options="hwe_kernel[0] as hwe_kernel[1] for hwe_kernel in hwe_kernels"> |
2163 | + <option value="">No minimum kernel</option> |
2164 | + </select> |
2165 | + </div> |
2166 | + <div class="inline"> |
2167 | + <label for="zone" class="two-col">Zone</label> |
2168 | +>>>>>>> MERGE-SOURCE |
2169 | <select name="zone" id="zone" class="three-col" placeholder="Choose a zone" |
2170 | data-ng-model="machine.zone" |
2171 | data-ng-options="zone as zone.name for zone in zones"> |
2172 | |
2173 | === added directory 'src/maasserver/static/scss/maas' |
2174 | === added directory 'src/maasserver/static/scss/maas/components' |
2175 | === added file 'src/maasserver/static/scss/maas/components/_accordion.scss.OTHER' |
2176 | --- src/maasserver/static/scss/maas/components/_accordion.scss.OTHER 1970-01-01 00:00:00 +0000 |
2177 | +++ src/maasserver/static/scss/maas/components/_accordion.scss.OTHER 2016-10-24 18:43:47 +0000 |
2178 | @@ -0,0 +1,112 @@ |
2179 | +@charset "UTF-8"; |
2180 | + |
2181 | +//// |
2182 | +/// MAAS accordion styles |
2183 | +/// |
2184 | +/// @project MAAS |
2185 | +/// @author Web Team at Canonical Ltd |
2186 | +/// @copyright 2015 Canonical Ltd |
2187 | +/// |
2188 | +//// |
2189 | + |
2190 | +.accordion { |
2191 | + @include box-sizing(); |
2192 | + @include rounded-corners(2px); |
2193 | + list-style: none; |
2194 | + background:#FFF; |
2195 | + box-shadow: 0 1px 1px rgba(0, 0, 0, .1); |
2196 | + margin-bottom: 40px; |
2197 | + |
2198 | + .disabled & { |
2199 | + opacity: .5; |
2200 | + pointer-events: none; |
2201 | + } |
2202 | + |
2203 | + // accordion main sidebar title |
2204 | + .accordion__title { |
2205 | + border-bottom: 1px dotted #B2B2B2; |
2206 | + padding: 13px 20px 12px; |
2207 | + margin: 0; |
2208 | + font-size: 1.3em; |
2209 | + } |
2210 | + |
2211 | + // accordion data block, contains all filter links and controls. |
2212 | + .accordion__tab { |
2213 | + border-bottom: 1px dotted #B2B2B2; |
2214 | + |
2215 | + &:last-of-type { |
2216 | + border: none; |
2217 | + } |
2218 | + |
2219 | + // Block level title |
2220 | + .accordion__tab-title { |
2221 | + padding: 12px 20px; |
2222 | + margin: 0; |
2223 | + color: #888; |
2224 | + cursor: pointer; |
2225 | + background: transparent url('../img/icons/accordion-open.svg') top 20px right 20px no-repeat; |
2226 | + |
2227 | + &.active { |
2228 | + background-image: url('../img/icons/accordion-close.svg'); |
2229 | + |
2230 | + + .accordion__tab-content { |
2231 | + max-height: 400px; |
2232 | + transition: max-height .5s ease-in; |
2233 | + overflow-y: auto; |
2234 | + } |
2235 | + } |
2236 | + } |
2237 | + |
2238 | + // Filter list |
2239 | + .accordion__tab-content { |
2240 | + max-height: 0; |
2241 | + transition: max-height .5s ease-out; |
2242 | + overflow: hidden; |
2243 | + |
2244 | + .accordion__tab-list { |
2245 | + list-style-type: none; |
2246 | + padding: 0 20px 14px; |
2247 | + margin: 0; |
2248 | + |
2249 | + .accordion__tab-item { |
2250 | + margin-bottom: 0.15em; |
2251 | + |
2252 | + .accordion__tab-link { |
2253 | + @include box-sizing(); |
2254 | + color: #333; |
2255 | + width: 100%; |
2256 | + display: inline-block; |
2257 | + padding-right: 20px; |
2258 | + border-bottom: 0; |
2259 | + |
2260 | + &:hover { |
2261 | + color: $ubuntu-orange; |
2262 | + text-decoration: none; |
2263 | + } |
2264 | + |
2265 | + .disabled & { |
2266 | + color: #333; |
2267 | + } |
2268 | + } |
2269 | + |
2270 | + &.active { |
2271 | + font-weight: 400; |
2272 | + |
2273 | + .accordion__tab-link { |
2274 | + background: transparent url('../img/icons/cross.svg') top 7px right 0px no-repeat; |
2275 | + } |
2276 | + |
2277 | + &:hover { |
2278 | + color: $ubuntu-orange; |
2279 | + |
2280 | + .accordion__tab-link { |
2281 | + color: $ubuntu-orange; |
2282 | + background-image: url('../img/icons/cross-orange.svg'); |
2283 | + } |
2284 | + } |
2285 | + } |
2286 | + } |
2287 | + } |
2288 | + } |
2289 | + } |
2290 | +} |
2291 | |
2292 | === modified file 'src/maasserver/templates/maasserver/base.html' |
2293 | --- src/maasserver/templates/maasserver/base.html 2016-10-05 15:01:48 +0000 |
2294 | +++ src/maasserver/templates/maasserver/base.html 2016-10-24 18:43:47 +0000 |
2295 | @@ -196,6 +196,7 @@ |
2296 | <div class="twelve-col"> |
2297 | <div class="eight-col"> |
2298 | {% block footer-copyright %} |
2299 | +<<<<<<< TREE |
2300 | <p> |
2301 | <small> |
2302 | <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> |
2303 | @@ -206,6 +207,12 @@ |
2304 | © 2016 Canonical Ltd. Ubuntu and Canonical are registered trademarks of Canonical Ltd. |
2305 | </small> |
2306 | </p> |
2307 | +======= |
2308 | + <p class="twelve-col"> |
2309 | + <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> |
2310 | + </p> |
2311 | + <p class="twelve-col copy">© 2016 Canonical Ltd. Ubuntu and Canonical are registered trademarks of Canonical Ltd.</p> |
2312 | +>>>>>>> MERGE-SOURCE |
2313 | {% endblock %} |
2314 | </div> |
2315 | <div class="four-col last-col"> |
2316 | |
2317 | === modified file 'src/maasserver/templates/maasserver/index.html' |
2318 | --- src/maasserver/templates/maasserver/index.html 2016-10-05 15:01:48 +0000 |
2319 | +++ src/maasserver/templates/maasserver/index.html 2016-10-24 18:43:47 +0000 |
2320 | @@ -146,6 +146,7 @@ |
2321 | <li{% if message.tags %} class="flash-messages__item flash-messages__item--{{ message.tags }}" {% endif %}>{{ message }}</li> |
2322 | {% endfor %} |
2323 | {% endif %} |
2324 | +<<<<<<< TREE |
2325 | </ul> |
2326 | </div> |
2327 | {% endif %} |
2328 | @@ -231,5 +232,25 @@ |
2329 | </div> |
2330 | </div> |
2331 | </footer> |
2332 | +======= |
2333 | + <div id="content" data-ng-view> |
2334 | + </div> |
2335 | + </div> |
2336 | + <div class="push"></div> |
2337 | + </main> |
2338 | + </div> |
2339 | + <div class="footer-wrapper" data-maas-error-toggle> |
2340 | + <footer class="global inner-wrapper clearfix"> |
2341 | + <div class="legal clearfix"> |
2342 | + <div class="legal-inner"> |
2343 | + <p class="twelve-col"> |
2344 | + <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> |
2345 | + </p> |
2346 | + <p class="twelve-col copy">© 2016 Canonical Ltd. Ubuntu and Canonical are registered trademarks of Canonical Ltd.</p> |
2347 | + </div> |
2348 | + </div> |
2349 | + </footer> |
2350 | + </div> |
2351 | +>>>>>>> MERGE-SOURCE |
2352 | </body> |
2353 | </html> |
2354 | |
2355 | === modified file 'src/maasserver/templates/maasserver/prefs.html' |
2356 | --- src/maasserver/templates/maasserver/prefs.html 2016-10-13 13:13:12 +0000 |
2357 | +++ src/maasserver/templates/maasserver/prefs.html 2016-10-24 18:43:47 +0000 |
2358 | @@ -29,8 +29,13 @@ |
2359 | <ul class="no-bullets"> |
2360 | {% for token in user.userprofile.get_authorisation_tokens %} |
2361 | <li class="bundle"> |
2362 | +<<<<<<< TREE |
2363 | <a href="#" class="delete-link icon right"> |
2364 | <img title="Delete MAAS key" class="left" src="{{ STATIC_URL }}assets/images/icons/delete.png" /> |
2365 | +======= |
2366 | + <a href="#" class="u-margin--top-tiny delete-link icon right"> |
2367 | + <img title="Delete MAAS key" class="left" src="{{ STATIC_URL }}img/delete.png" /> |
2368 | +>>>>>>> MERGE-SOURCE |
2369 | </a> |
2370 | <input type="text" value="{{ token.consumer.key }}:{{ token.key }}:{{ token.secret }}" id="{{ token.key }}" class="disabled" /> |
2371 | </li> |
2372 | |
2373 | === modified file 'src/maasserver/testing/factory.py' |
2374 | --- src/maasserver/testing/factory.py 2016-10-20 19:43:37 +0000 |
2375 | +++ src/maasserver/testing/factory.py 2016-10-24 18:43:47 +0000 |
2376 | @@ -440,9 +440,14 @@ |
2377 | ip_address = existing_static_ips[0] |
2378 | bmc_ip_address = self.pick_ip_in_Subnet(ip_address.subnet) |
2379 | node.power_parameters = { |
2380 | +<<<<<<< TREE |
2381 | "power_address": "qemu+ssh://user@%s/system" % ( |
2382 | factory.ip_to_url_format(bmc_ip_address)), |
2383 | "power_id": factory.make_name("power_id"), |
2384 | +======= |
2385 | + "power_address": "qemu+ssh://user@%s/system" % bmc_ip_address, |
2386 | + "power_id": factory.make_name("power_id"), |
2387 | +>>>>>>> MERGE-SOURCE |
2388 | } |
2389 | node.save() |
2390 | |
2391 | |
2392 | === modified file 'src/maasserver/tests/test_bootresources.py' |
2393 | --- src/maasserver/tests/test_bootresources.py 2016-10-12 15:26:17 +0000 |
2394 | +++ src/maasserver/tests/test_bootresources.py 2016-10-24 18:43:47 +0000 |
2395 | @@ -932,6 +932,7 @@ |
2396 | with rfile.largefile.content.open('rb') as stream: |
2397 | written_data = stream.read() |
2398 | self.assertEqual(content, written_data) |
2399 | +<<<<<<< TREE |
2400 | rfile.largefile = reload_object(rfile.largefile) |
2401 | self.assertEqual(rfile.largefile.size, len(written_data)) |
2402 | self.assertEqual(rfile.largefile.size, rfile.largefile.total_size) |
2403 | @@ -948,6 +949,11 @@ |
2404 | self.assertEqual(b'', written_data) |
2405 | rfile.largefile = reload_object(rfile.largefile) |
2406 | self.assertEqual(rfile.largefile.size, 0) |
2407 | +======= |
2408 | + rfile.largefile = reload_object(rfile.largefile) |
2409 | + self.assertEqual(rfile.largefile.size, len(written_data)) |
2410 | + self.assertEqual(rfile.largefile.size, rfile.largefile.total_size) |
2411 | +>>>>>>> MERGE-SOURCE |
2412 | |
2413 | @skip( |
2414 | "XXX blake_r: Skipped because it causes the test that runs after this " |
2415 | @@ -2098,6 +2104,7 @@ |
2416 | response = {"images": [make_rpc_boot_image()]} |
2417 | cluster_rpc.ListBootImages.return_value = succeed(response) |
2418 | self.assertTrue(service.are_boot_images_available_in_any_rack()) |
2419 | +<<<<<<< TREE |
2420 | |
2421 | |
2422 | class TestBootResourceRepoWriter(MAASServerTestCase): |
2423 | @@ -2335,3 +2342,104 @@ |
2424 | mock_insert = self.patch(boot_resource_repo_writer.store, 'insert') |
2425 | boot_resource_repo_writer.insert_item(data, src, None, pedigree, None) |
2426 | self.assertThat(mock_insert, MockNotCalled()) |
2427 | +======= |
2428 | + |
2429 | + |
2430 | +class TestBootResourceRepoWriter(MAASServerTestCase): |
2431 | + """Tests for `BootResourceRepoWriter`.""" |
2432 | + |
2433 | + def create_simplestream(self, ftypes): |
2434 | + version = '16.04' |
2435 | + arch = 'amd64' |
2436 | + subarch = 'hwe-x' |
2437 | + product = "com.ubuntu.maas.daily:v2:boot:%s:%s:%s" % ( |
2438 | + version, arch, subarch) |
2439 | + version = datetime.now().date().strftime('%Y%m%d.0') |
2440 | + versions = { |
2441 | + version: { |
2442 | + 'items': { |
2443 | + ftype: { |
2444 | + 'sha256': factory.make_name('sha256'), |
2445 | + 'path': factory.make_name('path'), |
2446 | + 'ftype': ftype, |
2447 | + 'size': random.randint(0, 2**64), |
2448 | + } for ftype in ftypes |
2449 | + } |
2450 | + } |
2451 | + } |
2452 | + products = { |
2453 | + product: { |
2454 | + 'krel': 'xenial', |
2455 | + 'subarch': subarch, |
2456 | + 'label': 'daily', |
2457 | + 'os': 'ubuntu', |
2458 | + 'arch': arch, |
2459 | + 'subarches': 'generic,%s' % subarch, |
2460 | + 'kflavor': 'generic', |
2461 | + 'version': version, |
2462 | + 'versions': versions, |
2463 | + } |
2464 | + } |
2465 | + src = { |
2466 | + 'datatype': 'image-downloads', |
2467 | + 'format': 'products:1.0', |
2468 | + 'updated': format_datetime(datetime.now()), |
2469 | + 'products': products, |
2470 | + 'content_id': 'com.ubuntu.maas:daily:v2:download' |
2471 | + } |
2472 | + return src, product, version |
2473 | + |
2474 | + def test_insert_prefers_squashfs_over_root_image(self): |
2475 | + boot_resource_repo_writer = BootResourceRepoWriter( |
2476 | + BootResourceStore(), None) |
2477 | + src, product, version = self.create_simplestream([ |
2478 | + BOOT_RESOURCE_FILE_TYPE.ROOT_IMAGE, |
2479 | + BOOT_RESOURCE_FILE_TYPE.SQUASHFS_IMAGE, |
2480 | + ]) |
2481 | + data = src['products'][product]['versions'][version]['items'][ |
2482 | + BOOT_RESOURCE_FILE_TYPE.ROOT_IMAGE] |
2483 | + pedigree = (product, version, BOOT_RESOURCE_FILE_TYPE.ROOT_IMAGE) |
2484 | + mock_insert = self.patch(boot_resource_repo_writer.store, 'insert') |
2485 | + boot_resource_repo_writer.insert_item(data, src, None, pedigree, None) |
2486 | + self.assertThat(mock_insert, MockNotCalled()) |
2487 | + |
2488 | + def test_insert_allows_squashfs(self): |
2489 | + boot_resource_repo_writer = BootResourceRepoWriter( |
2490 | + BootResourceStore(), None) |
2491 | + src, product, version = self.create_simplestream([ |
2492 | + BOOT_RESOURCE_FILE_TYPE.SQUASHFS_IMAGE, |
2493 | + ]) |
2494 | + data = src['products'][product]['versions'][version]['items'][ |
2495 | + BOOT_RESOURCE_FILE_TYPE.SQUASHFS_IMAGE] |
2496 | + pedigree = (product, version, BOOT_RESOURCE_FILE_TYPE.SQUASHFS_IMAGE) |
2497 | + mock_insert = self.patch(boot_resource_repo_writer.store, 'insert') |
2498 | + boot_resource_repo_writer.insert_item(data, src, None, pedigree, None) |
2499 | + self.assertThat(mock_insert, MockCalledOnce()) |
2500 | + |
2501 | + def test_insert_allows_root_image(self): |
2502 | + boot_resource_repo_writer = BootResourceRepoWriter( |
2503 | + BootResourceStore(), None) |
2504 | + src, product, version = self.create_simplestream([ |
2505 | + BOOT_RESOURCE_FILE_TYPE.ROOT_IMAGE, |
2506 | + ]) |
2507 | + data = src['products'][product]['versions'][version]['items'][ |
2508 | + BOOT_RESOURCE_FILE_TYPE.ROOT_IMAGE] |
2509 | + pedigree = (product, version, BOOT_RESOURCE_FILE_TYPE.ROOT_IMAGE) |
2510 | + mock_insert = self.patch(boot_resource_repo_writer.store, 'insert') |
2511 | + boot_resource_repo_writer.insert_item(data, src, None, pedigree, None) |
2512 | + self.assertThat(mock_insert, MockCalledOnce()) |
2513 | + |
2514 | + def test_insert_ignores_unknown_ftypes(self): |
2515 | + boot_resource_repo_writer = BootResourceRepoWriter( |
2516 | + BootResourceStore(), None) |
2517 | + unknown_ftype = factory.make_name('ftype') |
2518 | + src, product, version = self.create_simplestream([ |
2519 | + unknown_ftype, |
2520 | + ]) |
2521 | + data = src['products'][product]['versions'][version]['items'][ |
2522 | + unknown_ftype] |
2523 | + pedigree = (product, version, unknown_ftype) |
2524 | + mock_insert = self.patch(boot_resource_repo_writer.store, 'insert') |
2525 | + boot_resource_repo_writer.insert_item(data, src, None, pedigree, None) |
2526 | + self.assertThat(mock_insert, MockNotCalled()) |
2527 | +>>>>>>> MERGE-SOURCE |
2528 | |
2529 | === modified file 'src/maasserver/tests/test_config_forms.py' |
2530 | === modified file 'src/maasserver/tests/test_dhcp.py' |
2531 | --- src/maasserver/tests/test_dhcp.py 2016-09-30 18:18:11 +0000 |
2532 | +++ src/maasserver/tests/test_dhcp.py 2016-10-24 18:43:47 +0000 |
2533 | @@ -736,6 +736,7 @@ |
2534 | 'dhcp_snippets', |
2535 | ])) |
2536 | |
2537 | +<<<<<<< TREE |
2538 | def test__sets_ipv4_dns_from_arguments(self): |
2539 | rack_controller = factory.make_RackController(interface=False) |
2540 | vlan = factory.make_VLAN() |
2541 | @@ -837,6 +838,79 @@ |
2542 | self.assertThat( |
2543 | config['dns_servers'], |
2544 | Equals([IPAddress(addr) for addr in subnet_dns_servers])) |
2545 | +======= |
2546 | + def test__sets_ipv4_dns_from_arguments(self): |
2547 | + rack_controller = factory.make_RackController(interface=False) |
2548 | + vlan = factory.make_VLAN() |
2549 | + subnet = factory.make_Subnet(vlan=vlan, dns_servers=[], version=4) |
2550 | + factory.make_Interface( |
2551 | + INTERFACE_TYPE.PHYSICAL, vlan=vlan, node=rack_controller) |
2552 | + maas_dns = factory.make_ipv4_address() |
2553 | + ntp = factory.make_name('ntp') |
2554 | + default_domain = Domain.objects.get_default_domain() |
2555 | + config = dhcp.make_subnet_config( |
2556 | + rack_controller, subnet, maas_dns, ntp, default_domain) |
2557 | + self.expectThat(config['dns_servers'], Equals(maas_dns)) |
2558 | + |
2559 | + def test__sets_ipv6_dns_from_arguments(self): |
2560 | + rack_controller = factory.make_RackController(interface=False) |
2561 | + vlan = factory.make_VLAN() |
2562 | + subnet = factory.make_Subnet(vlan=vlan, dns_servers=[], version=6) |
2563 | + factory.make_Interface( |
2564 | + INTERFACE_TYPE.PHYSICAL, vlan=vlan, node=rack_controller) |
2565 | + maas_dns = factory.make_ipv6_address() |
2566 | + ntp = factory.make_name('ntp') |
2567 | + default_domain = Domain.objects.get_default_domain() |
2568 | + config = dhcp.make_subnet_config( |
2569 | + rack_controller, subnet, maas_dns, ntp, default_domain) |
2570 | + self.expectThat(config['dns_servers'], Equals(maas_dns)) |
2571 | + |
2572 | + def test__sets_ntp_from_arguments(self): |
2573 | + rack_controller = factory.make_RackController(interface=False) |
2574 | + vlan = factory.make_VLAN() |
2575 | + subnet = factory.make_Subnet(vlan=vlan, dns_servers=[]) |
2576 | + factory.make_Interface( |
2577 | + INTERFACE_TYPE.PHYSICAL, vlan=vlan, node=rack_controller) |
2578 | + ntp = factory.make_name('ntp') |
2579 | + default_domain = Domain.objects.get_default_domain() |
2580 | + config = dhcp.make_subnet_config( |
2581 | + rack_controller, subnet, "", ntp, default_domain) |
2582 | + self.expectThat(config['ntp_server'], Equals(ntp)) |
2583 | +>>>>>>> MERGE-SOURCE |
2584 | + |
2585 | + def test__overrides_ipv4_dns_from_subnet(self): |
2586 | + rack_controller = factory.make_RackController(interface=False) |
2587 | + vlan = factory.make_VLAN() |
2588 | + subnet = factory.make_Subnet(vlan=vlan, version=4) |
2589 | + maas_dns = factory.make_ipv4_address() |
2590 | + subnet_dns_servers = ["8.8.8.8", "8.8.4.4"] |
2591 | + subnet.dns_servers = subnet_dns_servers |
2592 | + subnet.save() |
2593 | + factory.make_Interface( |
2594 | + INTERFACE_TYPE.PHYSICAL, vlan=vlan, node=rack_controller) |
2595 | + ntp = factory.make_name('ntp') |
2596 | + default_domain = Domain.objects.get_default_domain() |
2597 | + config = dhcp.make_subnet_config( |
2598 | + rack_controller, subnet, maas_dns, ntp, default_domain) |
2599 | + self.expectThat( |
2600 | + config['dns_servers'], Equals(", ".join(subnet_dns_servers))) |
2601 | + |
2602 | + def test__overrides_ipv6_dns_from_subnet(self): |
2603 | + rack_controller = factory.make_RackController(interface=False) |
2604 | + vlan = factory.make_VLAN() |
2605 | + subnet = factory.make_Subnet(vlan=vlan, version=6) |
2606 | + maas_dns = factory.make_ipv6_address() |
2607 | + subnet_dns_servers = ["2001:db8::1", "2001:db8::2"] |
2608 | + subnet.dns_servers = subnet_dns_servers |
2609 | + subnet.save() |
2610 | + factory.make_Interface( |
2611 | + INTERFACE_TYPE.PHYSICAL, vlan=vlan, node=rack_controller) |
2612 | + ntp = factory.make_name('ntp') |
2613 | + default_domain = Domain.objects.get_default_domain() |
2614 | + config = dhcp.make_subnet_config( |
2615 | + rack_controller, subnet, maas_dns, ntp, default_domain) |
2616 | + self.expectThat( |
2617 | + config['dns_servers'], Equals(", ".join(subnet_dns_servers))) |
2618 | |
2619 | def test__sets_domain_name_from_passed_domain(self): |
2620 | rack_controller = factory.make_RackController(interface=False) |
2621 | |
2622 | === modified file 'src/maasserver/tests/test_forms_interface_link.py' |
2623 | === modified file 'src/maasserver/tests/test_preseed.py' |
2624 | --- src/maasserver/tests/test_preseed.py 2016-10-13 16:32:38 +0000 |
2625 | +++ src/maasserver/tests/test_preseed.py 2016-10-24 18:43:47 +0000 |
2626 | @@ -457,6 +457,33 @@ |
2627 | 'server_url', 'syslog_host_port'], |
2628 | context) |
2629 | |
2630 | +<<<<<<< TREE |
2631 | +======= |
2632 | + def test_get_preseed_context_archive_refs(self): |
2633 | + # urlparse lowercases the hostnames. That should not have any |
2634 | + # impact but for testing, create lower-case hostnames. |
2635 | + main_archive = factory.make_url(netloc="main-archive.example.com") |
2636 | + ports_archive = factory.make_url(netloc="ports-archive.example.com") |
2637 | + Config.objects.set_config('main_archive', main_archive) |
2638 | + Config.objects.set_config('ports_archive', ports_archive) |
2639 | + context = get_preseed_context() |
2640 | + parsed_main_archive = urlparse(main_archive) |
2641 | + parsed_ports_archive = urlparse(ports_archive) |
2642 | + self.assertEqual( |
2643 | + ( |
2644 | + parsed_main_archive.hostname, |
2645 | + parsed_main_archive.path.lstrip('/'), |
2646 | + parsed_ports_archive.hostname, |
2647 | + parsed_ports_archive.path.lstrip('/'), |
2648 | + ), |
2649 | + ( |
2650 | + context['main_archive_hostname'], |
2651 | + context['main_archive_directory'], |
2652 | + context['ports_archive_hostname'], |
2653 | + context['ports_archive_directory'], |
2654 | + )) |
2655 | + |
2656 | +>>>>>>> MERGE-SOURCE |
2657 | |
2658 | class TestNodePreseedContext( |
2659 | PreseedRPCMixin, BootImageHelperMixin, MAASTransactionServerTestCase): |
2660 | |
2661 | === modified file 'src/maasserver/tests/test_preseed_network.py' |
2662 | --- src/maasserver/tests/test_preseed_network.py 2016-08-24 21:04:59 +0000 |
2663 | +++ src/maasserver/tests/test_preseed_network.py 2016-10-24 18:43:47 +0000 |
2664 | @@ -243,7 +243,11 @@ |
2665 | |
2666 | def collectDNSConfig(self, node, ipv4=True, ipv6=True): |
2667 | config = "- type: nameserver\n address: %s\n search:\n" % ( |
2668 | +<<<<<<< TREE |
2669 | repr(node.get_default_dns_servers(ipv4=ipv4, ipv6=ipv6))) |
2670 | +======= |
2671 | + repr(node.get_default_dns_servers())) |
2672 | +>>>>>>> MERGE-SOURCE |
2673 | dns_searches = sorted(get_dns_search_paths()) |
2674 | for dns_name in dns_searches: |
2675 | config += " - %s\n" % dns_name |
2676 | |
2677 | === modified file 'src/maasserver/tests/test_preseed_storage.py' |
2678 | === modified file 'src/maasserver/tests/test_start_up.py' |
2679 | --- src/maasserver/tests/test_start_up.py 2016-06-29 10:00:16 +0000 |
2680 | +++ src/maasserver/tests/test_start_up.py 2016-10-24 18:43:47 +0000 |
2681 | @@ -145,6 +145,7 @@ |
2682 | self.assertThat(RegionController.objects.all(), HasLength(1)) |
2683 | |
2684 | def test__creates_maas_id_file(self): |
2685 | +<<<<<<< TREE |
2686 | start_up.is_master_process.return_value = False |
2687 | self.assertThat(get_maas_id(), Is(None)) |
2688 | with post_commit_hooks: |
2689 | @@ -173,3 +174,95 @@ |
2690 | Traceback (most recent call last):... |
2691 | Failure: maastesting.factory.TestException#...: boom |
2692 | """)) |
2693 | +======= |
2694 | + self.patch(start_up, "is_master_process").return_value = True |
2695 | + mock_set_maas_id = self.patch_autospec(start_up, "set_maas_id") |
2696 | + self.patch(start_up.RegionController, 'refresh') |
2697 | + with post_commit_hooks: |
2698 | + start_up.inner_start_up() |
2699 | + self.assertThat(mock_set_maas_id, MockCalledOnce()) |
2700 | + |
2701 | + def test__doesnt_create_maas_id_file_if_not_master(self): |
2702 | + self.patch(start_up, "is_master_process").return_value = False |
2703 | + mock_set_maas_id = self.patch_autospec(start_up, "set_maas_id") |
2704 | + with post_commit_hooks: |
2705 | + start_up.inner_start_up() |
2706 | + self.assertThat(mock_set_maas_id, MockNotCalled()) |
2707 | + |
2708 | + |
2709 | +class TestCreateRegionObj(MAASServerTestCase): |
2710 | + |
2711 | + """Tests for the actual work done in `create_region_obj`.""" |
2712 | + |
2713 | + def test__creates_obj(self): |
2714 | + region = start_up.create_region_obj() |
2715 | + self.assertIsNotNone(region) |
2716 | + self.assertIsNotNone( |
2717 | + RegionController.objects.get(system_id=region.system_id)) |
2718 | + |
2719 | + def test__doesnt_read_maas_id_from_cache(self): |
2720 | + set_maas_id(factory.make_string()) |
2721 | + os.unlink(get_path('/var/lib/maas/maas_id')) |
2722 | + region = start_up.create_region_obj() |
2723 | + self.assertIsNotNone(region) |
2724 | + self.assertIsNotNone( |
2725 | + RegionController.objects.get(system_id=region.system_id)) |
2726 | + |
2727 | + def test__finds_region_by_maas_id(self): |
2728 | + region = factory.make_RegionController() |
2729 | + self.useFixture(MAASIDFixture(region.system_id)) |
2730 | + self.assertEquals(region, start_up.create_region_obj()) |
2731 | + |
2732 | + def test__finds_region_by_hostname(self): |
2733 | + region = factory.make_RegionController() |
2734 | + mock_gethostname = self.patch_autospec(start_up, "gethostname") |
2735 | + mock_gethostname.return_value = region.hostname |
2736 | + self.assertEquals(region, start_up.create_region_obj()) |
2737 | + |
2738 | + def test__finds_region_by_mac(self): |
2739 | + region = factory.make_RegionController() |
2740 | + factory.make_Interface(node=region) |
2741 | + mock_get_mac_addresses = self.patch_autospec( |
2742 | + start_up, "get_mac_addresses") |
2743 | + mock_get_mac_addresses.return_value = [ |
2744 | + nic.mac_address.raw |
2745 | + for nic in region.interface_set.all() |
2746 | + ] |
2747 | + self.assertEquals(region, start_up.create_region_obj()) |
2748 | + |
2749 | + def test__converts_rack_to_region_rack(self): |
2750 | + rack = factory.make_RackController() |
2751 | + self.useFixture(MAASIDFixture(rack.system_id)) |
2752 | + region_rack = start_up.create_region_obj() |
2753 | + self.assertEquals(rack, region_rack) |
2754 | + self.assertEquals( |
2755 | + region_rack.node_type, NODE_TYPE.REGION_AND_RACK_CONTROLLER) |
2756 | + |
2757 | + def test__converts_node_to_region_rack(self): |
2758 | + node = factory.make_Node( |
2759 | + node_type=factory.pick_choice( |
2760 | + NODE_TYPE_CHOICES, |
2761 | + but_not=[ |
2762 | + NODE_TYPE.REGION_CONTROLLER, |
2763 | + NODE_TYPE.RACK_CONTROLLER, |
2764 | + NODE_TYPE.REGION_AND_RACK_CONTROLLER, |
2765 | + ])) |
2766 | + self.useFixture(MAASIDFixture(node.system_id)) |
2767 | + region = start_up.create_region_obj() |
2768 | + self.assertEquals(node, region) |
2769 | + self.assertEquals(region.node_type, NODE_TYPE.REGION_CONTROLLER) |
2770 | + |
2771 | + def test__sets_owner_if_none(self): |
2772 | + region = factory.make_RegionController() |
2773 | + self.useFixture(MAASIDFixture(region.system_id)) |
2774 | + self.assertEquals( |
2775 | + get_worker_user(), start_up.create_region_obj().owner) |
2776 | + |
2777 | + def test__leaves_owner_if_set(self): |
2778 | + region = factory.make_RegionController() |
2779 | + self.useFixture(MAASIDFixture(region.system_id)) |
2780 | + user = factory.make_User() |
2781 | + region.owner = user |
2782 | + region.save() |
2783 | + self.assertEquals(user, start_up.create_region_obj().owner) |
2784 | +>>>>>>> MERGE-SOURCE |
2785 | |
2786 | === modified file 'src/maasserver/triggers/tests/test_websocket.py' |
2787 | === modified file 'src/maasserver/triggers/tests/test_websocket_listener.py' |
2788 | --- src/maasserver/triggers/tests/test_websocket_listener.py 2016-10-12 15:26:17 +0000 |
2789 | +++ src/maasserver/triggers/tests/test_websocket_listener.py 2016-10-24 18:43:47 +0000 |
2790 | @@ -2923,6 +2923,7 @@ |
2791 | yield listener.stopService() |
2792 | |
2793 | |
2794 | +<<<<<<< TREE |
2795 | class TestPackageRepositoryListener( |
2796 | MAASTransactionServerTestCase, TransactionalHelpersMixin): |
2797 | """End-to-end test of both the listeners code and the cluster |
2798 | @@ -3102,6 +3103,129 @@ |
2799 | yield listener.stopService() |
2800 | |
2801 | |
2802 | +======= |
2803 | +class TestIPRangeSubnetListener( |
2804 | + MAASTransactionServerTestCase, TransactionalHelpersMixin): |
2805 | + """End-to-end test of both the listeners code and the triggers on |
2806 | + maasserver_iprange tables that notifies affected subnets.""" |
2807 | + |
2808 | + @wait_for_reactor |
2809 | + @inlineCallbacks |
2810 | + def test__calls_handler_on_create_notification(self): |
2811 | + yield deferToDatabase(register_websocket_triggers) |
2812 | + subnet = yield deferToDatabase( |
2813 | + self.create_subnet, { |
2814 | + "cidr": '192.168.0.0/24', |
2815 | + "gateway_ip": '192.168.0.1', |
2816 | + "dns_servers": [], |
2817 | + }) |
2818 | + |
2819 | + listener = PostgresListenerService() |
2820 | + dv = DeferredValue() |
2821 | + listener.register("subnet", lambda *args: dv.set(args)) |
2822 | + yield listener.startService() |
2823 | + try: |
2824 | + iprange = yield deferToDatabase( |
2825 | + self.create_iprange, { |
2826 | + "type": IPRANGE_TYPE.DYNAMIC, |
2827 | + "subnet": subnet, |
2828 | + "start_ip": '192.168.0.100', |
2829 | + "end_ip": '192.168.0.110', |
2830 | + }) |
2831 | + yield dv.get(timeout=2) |
2832 | + self.assertEqual(('update', '%s' % iprange.subnet.id), dv.value) |
2833 | + finally: |
2834 | + yield listener.stopService() |
2835 | + |
2836 | + @wait_for_reactor |
2837 | + @inlineCallbacks |
2838 | + def test__calls_handler_on_update_notification(self): |
2839 | + yield deferToDatabase(register_websocket_triggers) |
2840 | + iprange = yield deferToDatabase(self.create_iprange) |
2841 | + new_end_ip = factory.pick_ip_in_IPRange(iprange) |
2842 | + |
2843 | + listener = PostgresListenerService() |
2844 | + dv = DeferredValue() |
2845 | + listener.register("subnet", lambda *args: dv.set(args)) |
2846 | + yield listener.startService() |
2847 | + try: |
2848 | + yield deferToDatabase( |
2849 | + self.update_iprange, |
2850 | + iprange.id, {"end_ip": new_end_ip}) |
2851 | + yield dv.get(timeout=2) |
2852 | + self.assertEqual(('update', '%s' % iprange.subnet.id), dv.value) |
2853 | + finally: |
2854 | + yield listener.stopService() |
2855 | + |
2856 | + @wait_for_reactor |
2857 | + @inlineCallbacks |
2858 | + def test__calls_handler_on_update_on_old_and_new_subnet_notification(self): |
2859 | + yield deferToDatabase(register_websocket_triggers) |
2860 | + old_subnet = yield deferToDatabase( |
2861 | + self.create_subnet, { |
2862 | + "cidr": '192.168.0.0/24', |
2863 | + "gateway_ip": '192.168.0.1', |
2864 | + "dns_servers": [], |
2865 | + }) |
2866 | + new_subnet = yield deferToDatabase( |
2867 | + self.create_subnet, { |
2868 | + "cidr": '192.168.1.0/24', |
2869 | + "gateway_ip": '192.168.1.1', |
2870 | + "dns_servers": [], |
2871 | + }) |
2872 | + iprange = yield deferToDatabase( |
2873 | + self.create_iprange, { |
2874 | + "type": IPRANGE_TYPE.DYNAMIC, |
2875 | + "subnet": old_subnet, |
2876 | + "start_ip": '192.168.0.100', |
2877 | + "end_ip": '192.168.0.110', |
2878 | + }) |
2879 | + dvs = [DeferredValue(), DeferredValue()] |
2880 | + |
2881 | + def set_defer_value(*args): |
2882 | + for dv in dvs: |
2883 | + if not dv.isSet: |
2884 | + dv.set(args) |
2885 | + break |
2886 | + |
2887 | + listener = PostgresListenerService() |
2888 | + listener.register("subnet", set_defer_value) |
2889 | + yield listener.startService() |
2890 | + try: |
2891 | + yield deferToDatabase(self.update_iprange, iprange.id, { |
2892 | + "type": IPRANGE_TYPE.DYNAMIC, |
2893 | + "subnet": new_subnet, |
2894 | + "start_ip": '192.168.1.10', |
2895 | + "end_ip": '192.168.1.150', |
2896 | + }) |
2897 | + yield dvs[0].get(timeout=2) |
2898 | + yield dvs[1].get(timeout=2) |
2899 | + self.assertItemsEqual([ |
2900 | + ('update', '%s' % old_subnet.id), |
2901 | + ('update', '%s' % new_subnet.id), |
2902 | + ], [dvs[0].value, dvs[1].value]) |
2903 | + finally: |
2904 | + yield listener.stopService() |
2905 | + |
2906 | + @wait_for_reactor |
2907 | + @inlineCallbacks |
2908 | + def test__calls_handler_on_delete_notification(self): |
2909 | + yield deferToDatabase(register_websocket_triggers) |
2910 | + iprange = yield deferToDatabase(self.create_iprange) |
2911 | + |
2912 | + listener = PostgresListenerService() |
2913 | + dv = DeferredValue() |
2914 | + listener.register("subnet", lambda *args: dv.set(args)) |
2915 | + yield listener.startService() |
2916 | + try: |
2917 | + yield deferToDatabase(self.delete_iprange, iprange.id) |
2918 | + yield dv.get(timeout=2) |
2919 | + self.assertEqual(('update', '%s' % iprange.subnet.id), dv.value) |
2920 | + finally: |
2921 | + yield listener.stopService() |
2922 | + |
2923 | + |
2924 | +>>>>>>> MERGE-SOURCE |
2925 | class TestNodeTypeChange( |
2926 | MAASTransactionServerTestCase, TransactionalHelpersMixin): |
2927 | """End-to-end test of node type change triggers code.""" |
2928 | |
2929 | === modified file 'src/maasserver/triggers/websocket.py' |
2930 | === added file 'src/maasserver/views/tests/test_images.py.OTHER' |
2931 | --- src/maasserver/views/tests/test_images.py.OTHER 1970-01-01 00:00:00 +0000 |
2932 | +++ src/maasserver/views/tests/test_images.py.OTHER 2016-10-24 18:43:47 +0000 |
2933 | @@ -0,0 +1,991 @@ |
2934 | +# Copyright 2014-2016 Canonical Ltd. This software is licensed under the |
2935 | +# GNU Affero General Public License version 3 (see the file LICENSE). |
2936 | + |
2937 | +"""Test maasserver images views.""" |
2938 | + |
2939 | +__all__ = [] |
2940 | + |
2941 | +import datetime |
2942 | +import http.client |
2943 | +import json |
2944 | +import random |
2945 | + |
2946 | +from django.conf import settings |
2947 | +from django.core.urlresolvers import reverse |
2948 | +from lxml.html import fromstring |
2949 | +from maasserver.enum import ( |
2950 | + BOOT_RESOURCE_TYPE, |
2951 | + NODE_STATUS, |
2952 | +) |
2953 | +from maasserver.models import ( |
2954 | + BootResource, |
2955 | + BootSourceCache, |
2956 | + BootSourceSelection, |
2957 | + Config, |
2958 | +) |
2959 | +from maasserver.models.signals import bootsources |
2960 | +from maasserver.testing import extract_redirect |
2961 | +from maasserver.testing.factory import factory |
2962 | +from maasserver.testing.testcase import MAASServerTestCase |
2963 | +from maasserver.utils.converters import human_readable_bytes |
2964 | +from maasserver.utils.orm import ( |
2965 | + get_one, |
2966 | + reload_object, |
2967 | +) |
2968 | +from maasserver.views import images as images_view |
2969 | +from maastesting.matchers import ( |
2970 | + MockCalledOnceWith, |
2971 | + MockCalledWith, |
2972 | +) |
2973 | +from requests import ConnectionError |
2974 | +from testtools.matchers import ( |
2975 | + ContainsAll, |
2976 | + HasLength, |
2977 | +) |
2978 | + |
2979 | + |
2980 | +class UbuntuImagesTest(MAASServerTestCase): |
2981 | + |
2982 | + def setUp(self): |
2983 | + super(UbuntuImagesTest, self).setUp() |
2984 | + # Disable boot source cache signals. |
2985 | + self.addCleanup(bootsources.signals.enable) |
2986 | + bootsources.signals.disable() |
2987 | + |
2988 | + def patch_get_os_info_from_boot_sources( |
2989 | + self, sources, releases=None, arches=None): |
2990 | + if releases is None: |
2991 | + releases = [factory.make_name('release') for _ in range(3)] |
2992 | + if arches is None: |
2993 | + arches = [factory.make_name('arch') for _ in range(3)] |
2994 | + mock_get_os_info = self.patch( |
2995 | + images_view, 'get_os_info_from_boot_sources') |
2996 | + mock_get_os_info.return_value = (sources, releases, arches) |
2997 | + return mock_get_os_info |
2998 | + |
2999 | + def test_shows_connection_error(self): |
3000 | + self.client_log_in(as_admin=True) |
3001 | + mock_get_os_info = self.patch( |
3002 | + images_view, 'get_os_info_from_boot_sources') |
3003 | + mock_get_os_info.side_effect = ConnectionError() |
3004 | + response = self.client.get(reverse('images')) |
3005 | + doc = fromstring(response.content) |
3006 | + warnings = doc.cssselect('div#connection-error') |
3007 | + self.assertEqual(1, len(warnings)) |
3008 | + |
3009 | + def test_shows_no_ubuntu_sources(self): |
3010 | + self.client_log_in(as_admin=True) |
3011 | + response = self.client.get(reverse('images')) |
3012 | + doc = fromstring(response.content) |
3013 | + warnings = doc.cssselect('div#no-ubuntu-sources') |
3014 | + self.assertEqual(1, len(warnings)) |
3015 | + |
3016 | + def test_shows_too_many_ubuntu_sources(self): |
3017 | + self.client_log_in(as_admin=True) |
3018 | + sources = [factory.make_BootSource() for _ in range(2)] |
3019 | + self.patch_get_os_info_from_boot_sources(sources) |
3020 | + response = self.client.get(reverse('images')) |
3021 | + doc = fromstring(response.content) |
3022 | + warnings = doc.cssselect('div#too-many-ubuntu-sources') |
3023 | + self.assertEqual(1, len(warnings)) |
3024 | + |
3025 | + def test_shows_release_options(self): |
3026 | + self.client_log_in(as_admin=True) |
3027 | + sources = [factory.make_BootSource()] |
3028 | + releases = [factory.make_name('release') for _ in range(3)] |
3029 | + self.patch_get_os_info_from_boot_sources(sources, releases=releases) |
3030 | + response = self.client.get(reverse('images')) |
3031 | + doc = fromstring(response.content) |
3032 | + releases_content = doc.cssselect( |
3033 | + 'ul#ubuntu-releases')[0].text_content() |
3034 | + self.assertThat(releases_content, ContainsAll(releases)) |
3035 | + |
3036 | + def test_shows_architecture_options(self): |
3037 | + self.client_log_in(as_admin=True) |
3038 | + sources = [factory.make_BootSource()] |
3039 | + arches = [factory.make_name('arch') for _ in range(3)] |
3040 | + self.patch_get_os_info_from_boot_sources(sources, arches=arches) |
3041 | + response = self.client.get(reverse('images')) |
3042 | + doc = fromstring(response.content) |
3043 | + arches_content = doc.cssselect( |
3044 | + 'ul#ubuntu-arches')[0].text_content() |
3045 | + self.assertThat(arches_content, ContainsAll(arches)) |
3046 | + |
3047 | + def test_shows_missing_images_warning_if_not_ubuntu_boot_resources(self): |
3048 | + self.client_log_in() |
3049 | + response = self.client.get(reverse('images')) |
3050 | + doc = fromstring(response.content) |
3051 | + warnings = doc.cssselect('div#missing-ubuntu-images') |
3052 | + self.assertEqual(1, len(warnings)) |
3053 | + |
3054 | + def test_hides_import_button_if_not_admin(self): |
3055 | + self.client_log_in() |
3056 | + sources = [factory.make_BootSource()] |
3057 | + self.patch_get_os_info_from_boot_sources(sources) |
3058 | + response = self.client.get(reverse('images')) |
3059 | + doc = fromstring(response.content) |
3060 | + import_button = doc.cssselect( |
3061 | + '#ubuntu-images')[0].cssselect('input[type="submit"]') |
3062 | + self.assertEqual(0, len(import_button)) |
3063 | + |
3064 | + def test_shows_import_button_if_admin(self): |
3065 | + self.client_log_in(as_admin=True) |
3066 | + sources = [factory.make_BootSource()] |
3067 | + self.patch_get_os_info_from_boot_sources(sources) |
3068 | + response = self.client.get(reverse('images')) |
3069 | + doc = fromstring(response.content) |
3070 | + import_button = doc.cssselect( |
3071 | + '#ubuntu-images')[0].cssselect('input[type="submit"]') |
3072 | + self.assertEqual(1, len(import_button)) |
3073 | + |
3074 | + def test_post_returns_forbidden_if_not_admin(self): |
3075 | + self.client_log_in() |
3076 | + response = self.client.post( |
3077 | + reverse('images'), {'ubuntu_images': 1}) |
3078 | + self.assertEqual(http.client.FORBIDDEN, response.status_code) |
3079 | + |
3080 | + def test_import_calls_import_resources(self): |
3081 | + self.client_log_in(as_admin=True) |
3082 | + sources = [factory.make_BootSource()] |
3083 | + self.patch_get_os_info_from_boot_sources(sources) |
3084 | + mock_import = self.patch(images_view, 'import_resources') |
3085 | + response = self.client.post( |
3086 | + reverse('images'), {'ubuntu_images': 1}) |
3087 | + self.assertEqual(http.client.FOUND, response.status_code) |
3088 | + self.assertThat(mock_import, MockCalledOnceWith()) |
3089 | + |
3090 | + def test_import_sets_empty_selections(self): |
3091 | + self.client_log_in(as_admin=True) |
3092 | + source = factory.make_BootSource() |
3093 | + self.patch_get_os_info_from_boot_sources([source]) |
3094 | + self.patch(images_view, 'import_resources') |
3095 | + response = self.client.post( |
3096 | + reverse('images'), {'ubuntu_images': 1}) |
3097 | + self.assertEqual(http.client.FOUND, response.status_code) |
3098 | + |
3099 | + selections = BootSourceSelection.objects.filter(boot_source=source) |
3100 | + self.assertThat(selections, HasLength(1)) |
3101 | + self.assertEqual( |
3102 | + (selections[0].os, selections[0].release, |
3103 | + selections[0].arches, selections[0].subarches, |
3104 | + selections[0].labels), |
3105 | + ("ubuntu", "", [], ["*"], ["*"])) |
3106 | + |
3107 | + def test_import_sets_release_selections(self): |
3108 | + self.client_log_in(as_admin=True) |
3109 | + source = factory.make_BootSource() |
3110 | + releases = [factory.make_name('release') for _ in range(3)] |
3111 | + self.patch_get_os_info_from_boot_sources([source]) |
3112 | + self.patch(images_view, 'import_resources') |
3113 | + response = self.client.post( |
3114 | + reverse('images'), {'ubuntu_images': 1, 'release': releases}) |
3115 | + self.assertEqual(http.client.FOUND, response.status_code) |
3116 | + |
3117 | + selections = BootSourceSelection.objects.filter(boot_source=source) |
3118 | + self.assertThat(selections, HasLength(len(releases))) |
3119 | + self.assertItemsEqual( |
3120 | + releases, |
3121 | + [selection.release for selection in selections]) |
3122 | + |
3123 | + def test_import_sets_arches_on_selections(self): |
3124 | + self.client_log_in(as_admin=True) |
3125 | + source = factory.make_BootSource() |
3126 | + releases = [factory.make_name('release') for _ in range(3)] |
3127 | + arches = [factory.make_name('arches') for _ in range(3)] |
3128 | + self.patch_get_os_info_from_boot_sources([source]) |
3129 | + self.patch(images_view, 'import_resources') |
3130 | + response = self.client.post( |
3131 | + reverse('images'), |
3132 | + {'ubuntu_images': 1, 'release': releases, 'arch': arches}) |
3133 | + self.assertEqual(http.client.FOUND, response.status_code) |
3134 | + |
3135 | + selections = BootSourceSelection.objects.filter(boot_source=source) |
3136 | + self.assertThat(selections, HasLength(len(releases))) |
3137 | + self.assertItemsEqual( |
3138 | + [arches, arches, arches], |
3139 | + [selection.arches for selection in selections]) |
3140 | + |
3141 | + def test_import_removes_old_selections(self): |
3142 | + self.client_log_in(as_admin=True) |
3143 | + source = factory.make_BootSource() |
3144 | + release = factory.make_name('release') |
3145 | + delete_selection = BootSourceSelection.objects.create( |
3146 | + boot_source=source, os='ubuntu', |
3147 | + release=factory.make_name('release')) |
3148 | + keep_selection = BootSourceSelection.objects.create( |
3149 | + boot_source=source, os='ubuntu', release=release) |
3150 | + self.patch_get_os_info_from_boot_sources([source]) |
3151 | + self.patch(images_view, 'import_resources') |
3152 | + response = self.client.post( |
3153 | + reverse('images'), {'ubuntu_images': 1, 'release': [release]}) |
3154 | + self.assertEqual(http.client.FOUND, response.status_code) |
3155 | + self.assertIsNone(reload_object(delete_selection)) |
3156 | + self.assertIsNotNone(reload_object(keep_selection)) |
3157 | + |
3158 | + |
3159 | +class OtherImagesTest(MAASServerTestCase): |
3160 | + |
3161 | + def setUp(self): |
3162 | + super(OtherImagesTest, self).setUp() |
3163 | + # Disable boot source cache signals. |
3164 | + self.addCleanup(bootsources.signals.enable) |
3165 | + bootsources.signals.disable() |
3166 | + |
3167 | + def make_other_resource(self, os=None, arch=None, subarch=None, |
3168 | + release=None): |
3169 | + if os is None: |
3170 | + os = factory.make_name('os') |
3171 | + if arch is None: |
3172 | + arch = factory.make_name('arch') |
3173 | + if subarch is None: |
3174 | + subarch = factory.make_name('subarch') |
3175 | + if release is None: |
3176 | + release = factory.make_name('release') |
3177 | + name = '%s/%s' % (os, release) |
3178 | + architecture = '%s/%s' % (arch, subarch) |
3179 | + resource = factory.make_BootResource( |
3180 | + rtype=BOOT_RESOURCE_TYPE.SYNCED, |
3181 | + name=name, architecture=architecture) |
3182 | + resource_set = factory.make_BootResourceSet(resource) |
3183 | + factory.make_boot_resource_file_with_content(resource_set) |
3184 | + return resource |
3185 | + |
3186 | + def test_hides_other_synced_images_section(self): |
3187 | + self.client_log_in() |
3188 | + BootSourceCache.objects.all().delete() |
3189 | + response = self.client.get(reverse('images')) |
3190 | + doc = fromstring(response.content) |
3191 | + section = doc.cssselect('div#other-sync-images') |
3192 | + self.assertEqual( |
3193 | + 0, len(section), "Didn't hide the other images section.") |
3194 | + |
3195 | + def test_shows_other_synced_images_section(self): |
3196 | + self.client_log_in(as_admin=True) |
3197 | + factory.make_BootSourceCache() |
3198 | + response = self.client.get(reverse('images')) |
3199 | + doc = fromstring(response.content) |
3200 | + section = doc.cssselect('div#other-sync-images') |
3201 | + self.assertEqual( |
3202 | + 1, len(section), "Didn't show the other images section.") |
3203 | + |
3204 | + def test_hides_image_from_boot_source_cache_without_admin(self): |
3205 | + self.client_log_in() |
3206 | + factory.make_BootSourceCache() |
3207 | + response = self.client.get(reverse('images')) |
3208 | + doc = fromstring(response.content) |
3209 | + rows = doc.cssselect('table#other-resources > tbody > tr') |
3210 | + self.assertEqual( |
3211 | + 0, len(rows), "Didn't hide unselected boot image from non-admin.") |
3212 | + |
3213 | + def test_shows_image_from_boot_source_cache_with_admin(self): |
3214 | + self.client_log_in(as_admin=True) |
3215 | + cache = factory.make_BootSourceCache() |
3216 | + response = self.client.get(reverse('images')) |
3217 | + doc = fromstring(response.content) |
3218 | + title = doc.cssselect( |
3219 | + 'table#other-resources > tbody > ' |
3220 | + 'tr > td')[1].text_content().strip() |
3221 | + self.assertEqual('%s/%s' % (cache.os, cache.release), title) |
3222 | + |
3223 | + def test_shows_checkbox_for_boot_source_cache(self): |
3224 | + self.client_log_in(as_admin=True) |
3225 | + factory.make_BootSourceCache() |
3226 | + response = self.client.get(reverse('images')) |
3227 | + doc = fromstring(response.content) |
3228 | + checkbox = doc.cssselect( |
3229 | + 'table#other-resources > tbody > tr > td > input') |
3230 | + self.assertEqual( |
3231 | + 1, len(checkbox), "Didn't show checkbox for boot image.") |
3232 | + |
3233 | + def test_shows_last_update_time_for_synced_resource(self): |
3234 | + self.client_log_in(as_admin=True) |
3235 | + cache = factory.make_BootSourceCache() |
3236 | + self.make_other_resource( |
3237 | + os=cache.os, arch=cache.arch, |
3238 | + subarch=cache.subarch, release=cache.release) |
3239 | + response = self.client.get(reverse('images')) |
3240 | + doc = fromstring(response.content) |
3241 | + last_update = doc.cssselect( |
3242 | + 'table#other-resources > tbody > ' |
3243 | + 'tr > td')[5].text_content().strip() |
3244 | + dt = datetime.datetime.strptime(last_update, '%a, %d %b. %Y %H:%M:%S') |
3245 | + # TimestampedModel includes microseconds which we don't print. To |
3246 | + # confirm the right time is returned its easiest to just compare the |
3247 | + # ctimes |
3248 | + self.assertEquals(cache.updated.ctime(), dt.ctime()) |
3249 | + |
3250 | + def test_shows_number_of_nodes_for_synced_resource(self): |
3251 | + self.client_log_in(as_admin=True) |
3252 | + cache = factory.make_BootSourceCache() |
3253 | + resource = self.make_other_resource( |
3254 | + os=cache.os, arch=cache.arch, |
3255 | + subarch=cache.subarch, release=cache.release) |
3256 | + factory.make_Node( |
3257 | + status=NODE_STATUS.DEPLOYED, |
3258 | + osystem=cache.os, distro_series=cache.release, |
3259 | + architecture=resource.architecture) |
3260 | + response = self.client.get(reverse('images')) |
3261 | + doc = fromstring(response.content) |
3262 | + number_of_nodes = doc.cssselect( |
3263 | + 'table#other-resources > tbody > ' |
3264 | + 'tr > td')[4].text_content().strip() |
3265 | + self.assertEqual( |
3266 | + 1, int(number_of_nodes), |
3267 | + "Incorrect number of deployed nodes for resource.") |
3268 | + |
3269 | + def test_shows_apply_button_if_admin(self): |
3270 | + self.client_log_in(as_admin=True) |
3271 | + factory.make_BootSourceCache() |
3272 | + response = self.client.get(reverse('images')) |
3273 | + doc = fromstring(response.content) |
3274 | + apply_button = doc.cssselect( |
3275 | + '#other-sync-images')[0].cssselect('input[type="submit"]') |
3276 | + self.assertEqual( |
3277 | + 1, len(apply_button), "Didn't show apply button for admin.") |
3278 | + |
3279 | + def test_hides_apply_button_if_import_running(self): |
3280 | + self.client_log_in(as_admin=True) |
3281 | + factory.make_BootSourceCache() |
3282 | + self.patch( |
3283 | + images_view, 'is_import_resources_running').return_value = True |
3284 | + response = self.client.get(reverse('images')) |
3285 | + doc = fromstring(response.content) |
3286 | + apply_button = doc.cssselect( |
3287 | + '#other-sync-images')[0].cssselect('input[type="submit"]') |
3288 | + self.assertEqual( |
3289 | + 0, len(apply_button), |
3290 | + "Didn't hide apply button when import running.") |
3291 | + |
3292 | + def test_calls_get_os_release_title_for_other_resource(self): |
3293 | + self.client_log_in() |
3294 | + title = factory.make_name('title') |
3295 | + cache = factory.make_BootSourceCache() |
3296 | + resource = self.make_other_resource( |
3297 | + os=cache.os, arch=cache.arch, |
3298 | + subarch=cache.subarch, release=cache.release) |
3299 | + mock_get_title = self.patch(images_view, 'get_os_release_title') |
3300 | + mock_get_title.return_value = title |
3301 | + response = self.client.get(reverse('images')) |
3302 | + doc = fromstring(response.content) |
3303 | + row_title = doc.cssselect( |
3304 | + 'table#other-resources > tbody > ' |
3305 | + 'tr > td')[1].text_content().strip() |
3306 | + self.assertEqual(title, row_title) |
3307 | + os, release = resource.name.split('/') |
3308 | + self.assertThat(mock_get_title, MockCalledWith(os, release)) |
3309 | + |
3310 | + def test_post_returns_forbidden_if_not_admin(self): |
3311 | + self.client_log_in() |
3312 | + response = self.client.post( |
3313 | + reverse('images'), {'other_images': 1}) |
3314 | + self.assertEqual(http.client.FORBIDDEN, response.status_code) |
3315 | + |
3316 | + def test_post_clears_all_other_os_selections(self): |
3317 | + self.client_log_in(as_admin=True) |
3318 | + source = factory.make_BootSource() |
3319 | + ubuntu_selection = BootSourceSelection.objects.create( |
3320 | + boot_source=source, os='ubuntu') |
3321 | + other_selection = BootSourceSelection.objects.create( |
3322 | + boot_source=source, os=factory.make_name('os')) |
3323 | + self.patch(images_view, 'import_resources') |
3324 | + response = self.client.post( |
3325 | + reverse('images'), {'other_images': 1, 'image': []}) |
3326 | + self.assertEqual(http.client.FOUND, response.status_code) |
3327 | + self.assertIsNotNone(reload_object(ubuntu_selection)) |
3328 | + self.assertIsNone(reload_object(other_selection)) |
3329 | + |
3330 | + def test_post_creates_selection_with_multiple_arches(self): |
3331 | + self.client_log_in(as_admin=True) |
3332 | + source = factory.make_BootSource() |
3333 | + os = factory.make_name('os') |
3334 | + release = factory.make_name('release') |
3335 | + arches = [factory.make_name('arch') for _ in range(3)] |
3336 | + images = [] |
3337 | + for arch in arches: |
3338 | + factory.make_BootSourceCache( |
3339 | + boot_source=source, os=os, release=release, arch=arch) |
3340 | + images.append('%s/%s/subarch/%s' % (os, arch, release)) |
3341 | + self.patch(images_view, 'import_resources') |
3342 | + response = self.client.post( |
3343 | + reverse('images'), {'other_images': 1, 'image': images}) |
3344 | + self.assertEqual(http.client.FOUND, response.status_code) |
3345 | + |
3346 | + selection = get_one(BootSourceSelection.objects.filter( |
3347 | + boot_source=source, os=os, release=release)) |
3348 | + self.assertIsNotNone(selection) |
3349 | + self.assertItemsEqual(arches, selection.arches) |
3350 | + |
3351 | + def test_post_calls_import_resources(self): |
3352 | + self.client_log_in(as_admin=True) |
3353 | + mock_import = self.patch(images_view, 'import_resources') |
3354 | + response = self.client.post( |
3355 | + reverse('images'), {'other_images': 1, 'image': []}) |
3356 | + self.assertEqual(http.client.FOUND, response.status_code) |
3357 | + self.assertThat(mock_import, MockCalledOnceWith()) |
3358 | + |
3359 | + |
3360 | +class GeneratedImagesTest(MAASServerTestCase): |
3361 | + |
3362 | + def make_generated_resource(self, os=None, arch=None, subarch=None, |
3363 | + release=None): |
3364 | + if os is None: |
3365 | + os = factory.make_name('os') |
3366 | + if arch is None: |
3367 | + arch = factory.make_name('arch') |
3368 | + if subarch is None: |
3369 | + subarch = factory.make_name('subarch') |
3370 | + if release is None: |
3371 | + release = factory.make_name('release') |
3372 | + name = '%s/%s' % (os, release) |
3373 | + architecture = '%s/%s' % (arch, subarch) |
3374 | + resource = factory.make_BootResource( |
3375 | + rtype=BOOT_RESOURCE_TYPE.GENERATED, |
3376 | + name=name, architecture=architecture) |
3377 | + resource_set = factory.make_BootResourceSet(resource) |
3378 | + factory.make_boot_resource_file_with_content(resource_set) |
3379 | + return resource |
3380 | + |
3381 | + def test_hides_generated_images_section(self): |
3382 | + self.client_log_in() |
3383 | + response = self.client.get(reverse('images')) |
3384 | + doc = fromstring(response.content) |
3385 | + section = doc.cssselect('div#generated-images') |
3386 | + self.assertEqual( |
3387 | + 0, len(section), "Didn't hide the generated images section.") |
3388 | + |
3389 | + def test_shows_generated_images_section(self): |
3390 | + self.client_log_in() |
3391 | + self.make_generated_resource() |
3392 | + response = self.client.get(reverse('images')) |
3393 | + doc = fromstring(response.content) |
3394 | + section = doc.cssselect('div#generated-images') |
3395 | + self.assertEqual( |
3396 | + 1, len(section), "Didn't show the generated images section.") |
3397 | + |
3398 | + def test_shows_generated_resources(self): |
3399 | + self.client_log_in() |
3400 | + resources = [self.make_generated_resource() for _ in range(3)] |
3401 | + names = [resource.name for resource in resources] |
3402 | + response = self.client.get(reverse('images')) |
3403 | + doc = fromstring(response.content) |
3404 | + table_content = doc.cssselect( |
3405 | + 'table#generated-resources')[0].text_content() |
3406 | + self.assertThat(table_content, ContainsAll(names)) |
3407 | + |
3408 | + def test_shows_delete_button_for_generated_resource(self): |
3409 | + self.client_log_in(as_admin=True) |
3410 | + self.make_generated_resource() |
3411 | + response = self.client.get(reverse('images')) |
3412 | + doc = fromstring(response.content) |
3413 | + delete_btn = doc.cssselect( |
3414 | + 'table#generated-resources > tbody > tr > td > ' |
3415 | + 'a[title="Delete image"]') |
3416 | + self.assertEqual( |
3417 | + 1, len(delete_btn), |
3418 | + "Didn't show delete button for generated image.") |
3419 | + |
3420 | + def test_shows_last_update_time_for_synced_resource(self): |
3421 | + self.client_log_in(as_admin=True) |
3422 | + resource = self.make_generated_resource() |
3423 | + response = self.client.get(reverse('images')) |
3424 | + doc = fromstring(response.content) |
3425 | + last_update = doc.cssselect( |
3426 | + 'table#generated-resources > tbody > ' |
3427 | + 'tr > td')[5].text_content().strip() |
3428 | + dt = datetime.datetime.strptime(last_update, '%a, %d %b. %Y %H:%M:%S') |
3429 | + # TimestampedModel includes microseconds which we don't print. To |
3430 | + # confirm the right time is returned its easiest to just compare the |
3431 | + # ctimes |
3432 | + self.assertEquals(resource.updated.ctime(), dt.ctime()) |
3433 | + |
3434 | + def test_hides_delete_button_for_generated_resource_when_not_admin(self): |
3435 | + self.client_log_in() |
3436 | + self.make_generated_resource() |
3437 | + response = self.client.get(reverse('images')) |
3438 | + doc = fromstring(response.content) |
3439 | + delete_btn = doc.cssselect( |
3440 | + 'table#generated-resources > tbody > tr > td > ' |
3441 | + 'a[title="Delete image"]') |
3442 | + self.assertEqual( |
3443 | + 0, len(delete_btn), |
3444 | + "Didn't hide delete button for generated image when not admin.") |
3445 | + |
3446 | + def test_calls_get_os_release_title_for_generated_resource(self): |
3447 | + self.client_log_in() |
3448 | + title = factory.make_name('title') |
3449 | + resource = self.make_generated_resource() |
3450 | + mock_get_title = self.patch(images_view, 'get_os_release_title') |
3451 | + mock_get_title.return_value = title |
3452 | + response = self.client.get(reverse('images')) |
3453 | + doc = fromstring(response.content) |
3454 | + row_title = doc.cssselect( |
3455 | + 'table#generated-resources > tbody > ' |
3456 | + 'tr > td')[1].text_content().strip() |
3457 | + self.assertEqual(title, row_title) |
3458 | + os, release = resource.name.split('/') |
3459 | + self.assertThat(mock_get_title, MockCalledOnceWith(os, release)) |
3460 | + |
3461 | + |
3462 | +class UploadedImagesTest(MAASServerTestCase): |
3463 | + |
3464 | + def make_uploaded_resource(self, name=None): |
3465 | + if name is None: |
3466 | + name = factory.make_name('name') |
3467 | + arch = factory.make_name('arch') |
3468 | + subarch = factory.make_name('subarch') |
3469 | + architecture = '%s/%s' % (arch, subarch) |
3470 | + resource = factory.make_BootResource( |
3471 | + rtype=BOOT_RESOURCE_TYPE.UPLOADED, |
3472 | + name=name, architecture=architecture) |
3473 | + resource_set = factory.make_BootResourceSet(resource) |
3474 | + factory.make_boot_resource_file_with_content(resource_set) |
3475 | + return resource |
3476 | + |
3477 | + def test_shows_no_custom_images_message(self): |
3478 | + self.client_log_in() |
3479 | + response = self.client.get(reverse('images')) |
3480 | + doc = fromstring(response.content) |
3481 | + warnings = doc.cssselect('div#no-custom-images') |
3482 | + self.assertEqual(1, len(warnings)) |
3483 | + |
3484 | + def test_shows_uploaded_resources(self): |
3485 | + self.client_log_in() |
3486 | + names = [factory.make_name('name') for _ in range(3)] |
3487 | + [self.make_uploaded_resource(name) for name in names] |
3488 | + response = self.client.get(reverse('images')) |
3489 | + doc = fromstring(response.content) |
3490 | + table_content = doc.cssselect( |
3491 | + 'table#uploaded-resources')[0].text_content() |
3492 | + self.assertThat(table_content, ContainsAll(names)) |
3493 | + |
3494 | + def test_shows_uploaded_resources_name_if_title_blank(self): |
3495 | + self.client_log_in() |
3496 | + name = factory.make_name('name') |
3497 | + resource = self.make_uploaded_resource(name) |
3498 | + resource.extra['title'] = '' |
3499 | + resource.save() |
3500 | + response = self.client.get(reverse('images')) |
3501 | + doc = fromstring(response.content) |
3502 | + name_col = doc.cssselect( |
3503 | + 'table#uploaded-resources > tbody > tr > td')[1].text_content() |
3504 | + self.assertEqual(name, name_col.strip()) |
3505 | + |
3506 | + def test_shows_last_update_time_for_synced_resource(self): |
3507 | + self.client_log_in(as_admin=True) |
3508 | + resource = self.make_uploaded_resource() |
3509 | + response = self.client.get(reverse('images')) |
3510 | + doc = fromstring(response.content) |
3511 | + last_update = doc.cssselect( |
3512 | + 'table#uploaded-resources > tbody > ' |
3513 | + 'tr > td')[5].text_content().strip() |
3514 | + dt = datetime.datetime.strptime(last_update, '%a, %d %b. %Y %H:%M:%S') |
3515 | + # TimestampedModel includes microseconds which we don't print. To |
3516 | + # confirm the right time is returned its easiest to just compare the |
3517 | + # ctimes |
3518 | + self.assertEquals(resource.updated.ctime(), dt.ctime()) |
3519 | + |
3520 | + def test_shows_delete_button_for_uploaded_resource(self): |
3521 | + self.client_log_in(as_admin=True) |
3522 | + self.make_uploaded_resource() |
3523 | + response = self.client.get(reverse('images')) |
3524 | + doc = fromstring(response.content) |
3525 | + delete_btn = doc.cssselect( |
3526 | + 'table#uploaded-resources > tbody > tr > td > ' |
3527 | + 'a[title="Delete image"]') |
3528 | + self.assertEqual(1, len(delete_btn)) |
3529 | + |
3530 | + def test_hides_delete_button_for_uploaded_resource_when_not_admin(self): |
3531 | + self.client_log_in() |
3532 | + self.make_uploaded_resource() |
3533 | + response = self.client.get(reverse('images')) |
3534 | + doc = fromstring(response.content) |
3535 | + delete_btn = doc.cssselect( |
3536 | + 'table#uploaded-resources > tbody > tr > td > ' |
3537 | + 'a[title="Delete image"]') |
3538 | + self.assertEqual(0, len(delete_btn)) |
3539 | + |
3540 | + |
3541 | +class TestImageAjax(MAASServerTestCase): |
3542 | + |
3543 | + def get_images_ajax(self): |
3544 | + return self.client.get( |
3545 | + reverse('images'), HTTP_X_REQUESTED_WITH='XMLHttpRequest') |
3546 | + |
3547 | + def test__returns_json(self): |
3548 | + self.client_log_in() |
3549 | + response = self.get_images_ajax() |
3550 | + self.assertEqual('application/json', response['Content-Type']) |
3551 | + |
3552 | + def test__returns_region_import_running_True(self): |
3553 | + self.client_log_in() |
3554 | + self.patch( |
3555 | + images_view, 'is_import_resources_running').return_value = True |
3556 | + response = self.get_images_ajax() |
3557 | + json_obj = json.loads( |
3558 | + response.content.decode(settings.DEFAULT_CHARSET)) |
3559 | + self.assertTrue(json_obj['region_import_running']) |
3560 | + |
3561 | + def test__returns_region_import_running_False(self): |
3562 | + self.client_log_in() |
3563 | + self.patch( |
3564 | + images_view, 'is_import_resources_running').return_value = False |
3565 | + response = self.get_images_ajax() |
3566 | + json_obj = json.loads( |
3567 | + response.content.decode(settings.DEFAULT_CHARSET)) |
3568 | + self.assertFalse(json_obj['region_import_running']) |
3569 | + |
3570 | + def test__returns_cluster_import_running_True(self): |
3571 | + self.client_log_in() |
3572 | + self.patch( |
3573 | + images_view, 'is_import_boot_images_running').return_value = True |
3574 | + response = self.get_images_ajax() |
3575 | + json_obj = json.loads( |
3576 | + response.content.decode(settings.DEFAULT_CHARSET)) |
3577 | + self.assertTrue(json_obj['cluster_import_running']) |
3578 | + |
3579 | + def test__returns_cluster_import_running_False(self): |
3580 | + self.client_log_in() |
3581 | + self.patch( |
3582 | + images_view, 'is_import_boot_images_running').return_value = False |
3583 | + response = self.get_images_ajax() |
3584 | + json_obj = json.loads( |
3585 | + response.content.decode(settings.DEFAULT_CHARSET)) |
3586 | + self.assertFalse(json_obj['cluster_import_running']) |
3587 | + |
3588 | + def test_returns_resources(self): |
3589 | + self.client_log_in() |
3590 | + resources = [factory.make_usable_boot_resource() for _ in range(3)] |
3591 | + resource_ids = [resource.id for resource in resources] |
3592 | + response = self.get_images_ajax() |
3593 | + json_obj = json.loads( |
3594 | + response.content.decode(settings.DEFAULT_CHARSET)) |
3595 | + json_ids = [ |
3596 | + json_resource['id'] |
3597 | + for json_resource in json_obj['resources'] |
3598 | + ] |
3599 | + self.assertItemsEqual(resource_ids, json_ids) |
3600 | + |
3601 | + def test_returns_resources_datetime_format(self): |
3602 | + """Ensure the date/time format is correct""" |
3603 | + self.client_log_in() |
3604 | + resource = factory.make_usable_boot_resource() |
3605 | + response = self.get_images_ajax() |
3606 | + json_obj = json.loads( |
3607 | + response.content.decode(settings.DEFAULT_CHARSET)) |
3608 | + json_updated = datetime.datetime.strptime( |
3609 | + json_obj['resources'][0]['lastUpdate'], "%a, %d %b. %Y %H:%M:%S") |
3610 | + self.assertEqual(resource.updated.timetuple(), |
3611 | + json_updated.timetuple()) |
3612 | + |
3613 | + def test_returns_resource_attributes(self): |
3614 | + self.client_log_in() |
3615 | + factory.make_usable_boot_resource() |
3616 | + response = self.get_images_ajax() |
3617 | + json_obj = json.loads( |
3618 | + response.content.decode(settings.DEFAULT_CHARSET)) |
3619 | + json_resource = json_obj['resources'][0] |
3620 | + self.assertThat( |
3621 | + json_resource, |
3622 | + ContainsAll([ |
3623 | + 'id', 'rtype', 'name', 'title', 'arch', 'size', |
3624 | + 'complete', 'status', 'downloading', |
3625 | + 'numberOfNodes', 'lastUpdate'])) |
3626 | + |
3627 | + def test_returns_ubuntu_release_version_name(self): |
3628 | + self.client_log_in() |
3629 | + # Use trusty as known to map to "14.04 LTS" |
3630 | + version = '14.04 LTS' |
3631 | + name = 'ubuntu/trusty' |
3632 | + factory.make_usable_boot_resource( |
3633 | + rtype=BOOT_RESOURCE_TYPE.SYNCED, name=name) |
3634 | + response = self.get_images_ajax() |
3635 | + json_obj = json.loads( |
3636 | + response.content.decode(settings.DEFAULT_CHARSET)) |
3637 | + json_resource = json_obj['resources'][0] |
3638 | + self.assertEqual(version, json_resource['title']) |
3639 | + |
3640 | + def test_shows_number_of_nodes_deployed_for_resource(self): |
3641 | + self.client_log_in() |
3642 | + resource = factory.make_usable_boot_resource( |
3643 | + rtype=BOOT_RESOURCE_TYPE.SYNCED) |
3644 | + os_name, series = resource.name.split('/') |
3645 | + number_of_nodes = random.randint(1, 4) |
3646 | + for _ in range(number_of_nodes): |
3647 | + factory.make_Node( |
3648 | + status=NODE_STATUS.DEPLOYED, |
3649 | + osystem=os_name, distro_series=series, |
3650 | + architecture=resource.architecture) |
3651 | + response = self.get_images_ajax() |
3652 | + json_obj = json.loads( |
3653 | + response.content.decode(settings.DEFAULT_CHARSET)) |
3654 | + json_resource = json_obj['resources'][0] |
3655 | + self.assertEqual(number_of_nodes, json_resource['numberOfNodes']) |
3656 | + |
3657 | + def test_shows_number_of_nodes_deployed_for_resource_with_defaults(self): |
3658 | + self.client_log_in() |
3659 | + resource = factory.make_usable_boot_resource( |
3660 | + rtype=BOOT_RESOURCE_TYPE.SYNCED) |
3661 | + os_name, series = resource.name.split('/') |
3662 | + Config.objects.set_config('default_osystem', os_name) |
3663 | + Config.objects.set_config('default_distro_series', series) |
3664 | + number_of_nodes = random.randint(1, 4) |
3665 | + for _ in range(number_of_nodes): |
3666 | + factory.make_Node( |
3667 | + status=NODE_STATUS.DEPLOYED, |
3668 | + architecture=resource.architecture) |
3669 | + response = self.get_images_ajax() |
3670 | + json_obj = json.loads( |
3671 | + response.content.decode(settings.DEFAULT_CHARSET)) |
3672 | + json_resource = json_obj['resources'][0] |
3673 | + self.assertEqual(number_of_nodes, json_resource['numberOfNodes']) |
3674 | + |
3675 | + def test_shows_number_of_nodes_deployed_for_ubuntu_subarch_resource(self): |
3676 | + self.client_log_in() |
3677 | + resource = factory.make_usable_boot_resource( |
3678 | + rtype=BOOT_RESOURCE_TYPE.SYNCED) |
3679 | + arch, subarch = resource.split_arch() |
3680 | + extra_subarch = factory.make_name('subarch') |
3681 | + resource.extra['subarches'] = ','.join([subarch, extra_subarch]) |
3682 | + resource.save() |
3683 | + |
3684 | + os_name, series = resource.name.split('/') |
3685 | + node_architecture = '%s/%s' % (arch, extra_subarch) |
3686 | + number_of_nodes = random.randint(1, 4) |
3687 | + for _ in range(number_of_nodes): |
3688 | + factory.make_Node( |
3689 | + status=NODE_STATUS.DEPLOYED, |
3690 | + osystem=os_name, distro_series=series, |
3691 | + architecture=node_architecture) |
3692 | + response = self.get_images_ajax() |
3693 | + json_obj = json.loads( |
3694 | + response.content.decode(settings.DEFAULT_CHARSET)) |
3695 | + json_resource = json_obj['resources'][0] |
3696 | + self.assertEqual(number_of_nodes, json_resource['numberOfNodes']) |
3697 | + |
3698 | + def test_combines_subarch_resources_into_one_resource(self): |
3699 | + self.client_log_in() |
3700 | + name = 'ubuntu/%s' % factory.make_name('series') |
3701 | + arch = factory.make_name('arch') |
3702 | + subarches = [factory.make_name('subarch') for _ in range(3)] |
3703 | + for subarch in subarches: |
3704 | + factory.make_usable_boot_resource( |
3705 | + rtype=BOOT_RESOURCE_TYPE.SYNCED, |
3706 | + name=name, architecture='%s/%s' % (arch, subarch)) |
3707 | + response = self.get_images_ajax() |
3708 | + json_obj = json.loads( |
3709 | + response.content.decode(settings.DEFAULT_CHARSET)) |
3710 | + self.assertEqual( |
3711 | + 1, len(json_obj['resources']), |
3712 | + 'More than one resource was returned.') |
3713 | + |
3714 | + def test_combined_subarch_resource_calculates_unique_size(self): |
3715 | + self.client_log_in() |
3716 | + name = 'ubuntu/%s' % factory.make_name('series') |
3717 | + arch = factory.make_name('arch') |
3718 | + subarches = [factory.make_name('subarch') for _ in range(3)] |
3719 | + largefile = factory.make_LargeFile() |
3720 | + for subarch in subarches: |
3721 | + resource = factory.make_BootResource( |
3722 | + rtype=BOOT_RESOURCE_TYPE.SYNCED, |
3723 | + name=name, architecture='%s/%s' % (arch, subarch)) |
3724 | + resource_set = factory.make_BootResourceSet(resource) |
3725 | + factory.make_BootResourceFile(resource_set, largefile) |
3726 | + response = self.get_images_ajax() |
3727 | + json_obj = json.loads( |
3728 | + response.content.decode(settings.DEFAULT_CHARSET)) |
3729 | + json_resource = json_obj['resources'][0] |
3730 | + self.assertEqual( |
3731 | + human_readable_bytes(largefile.total_size), json_resource['size']) |
3732 | + |
3733 | + def test_combined_subarch_resource_calculates_num_of_nodes_deployed(self): |
3734 | + self.client_log_in() |
3735 | + osystem = 'ubuntu' |
3736 | + series = factory.make_name('series') |
3737 | + name = '%s/%s' % (osystem, series) |
3738 | + arch = factory.make_name('arch') |
3739 | + subarches = [factory.make_name('subarch') for _ in range(3)] |
3740 | + for subarch in subarches: |
3741 | + factory.make_usable_boot_resource( |
3742 | + rtype=BOOT_RESOURCE_TYPE.SYNCED, |
3743 | + name=name, architecture='%s/%s' % (arch, subarch)) |
3744 | + |
3745 | + number_of_nodes = random.randint(1, 4) |
3746 | + for _ in range(number_of_nodes): |
3747 | + subarch = random.choice(subarches) |
3748 | + node_architecture = '%s/%s' % (arch, subarch) |
3749 | + factory.make_Node( |
3750 | + status=NODE_STATUS.DEPLOYED, |
3751 | + osystem=osystem, distro_series=series, |
3752 | + architecture=node_architecture) |
3753 | + |
3754 | + response = self.get_images_ajax() |
3755 | + json_obj = json.loads( |
3756 | + response.content.decode(settings.DEFAULT_CHARSET)) |
3757 | + json_resource = json_obj['resources'][0] |
3758 | + self.assertEqual(number_of_nodes, json_resource['numberOfNodes']) |
3759 | + |
3760 | + def test_combined_subarch_resource_calculates_complete_True(self): |
3761 | + self.client_log_in() |
3762 | + name = 'ubuntu/%s' % factory.make_name('series') |
3763 | + arch = factory.make_name('arch') |
3764 | + subarches = [factory.make_name('subarch') for _ in range(3)] |
3765 | + resources = [ |
3766 | + factory.make_usable_boot_resource( |
3767 | + rtype=BOOT_RESOURCE_TYPE.SYNCED, |
3768 | + name=name, architecture='%s/%s' % (arch, subarch)) |
3769 | + for subarch in subarches |
3770 | + ] |
3771 | + self.patch( |
3772 | + BootResource.objects, |
3773 | + 'get_resources_matching_boot_images').return_value = resources |
3774 | + response = self.get_images_ajax() |
3775 | + json_obj = json.loads( |
3776 | + response.content.decode(settings.DEFAULT_CHARSET)) |
3777 | + json_resource = json_obj['resources'][0] |
3778 | + self.assertTrue(json_resource['complete']) |
3779 | + |
3780 | + def test_combined_subarch_resource_calculates_complete_False(self): |
3781 | + self.client_log_in() |
3782 | + name = 'ubuntu/%s' % factory.make_name('series') |
3783 | + arch = factory.make_name('arch') |
3784 | + subarches = [factory.make_name('subarch') for _ in range(3)] |
3785 | + incomplete_subarch = subarches.pop() |
3786 | + factory.make_BootResource( |
3787 | + rtype=BOOT_RESOURCE_TYPE.SYNCED, |
3788 | + name=name, architecture='%s/%s' % (arch, incomplete_subarch)) |
3789 | + for subarch in subarches: |
3790 | + factory.make_usable_boot_resource( |
3791 | + rtype=BOOT_RESOURCE_TYPE.SYNCED, |
3792 | + name=name, architecture='%s/%s' % (arch, subarch)) |
3793 | + response = self.get_images_ajax() |
3794 | + json_obj = json.loads( |
3795 | + response.content.decode(settings.DEFAULT_CHARSET)) |
3796 | + json_resource = json_obj['resources'][0] |
3797 | + self.assertFalse(json_resource['complete']) |
3798 | + |
3799 | + def test_combined_subarch_resource_calculates_progress(self): |
3800 | + self.client_log_in() |
3801 | + name = 'ubuntu/%s' % factory.make_name('series') |
3802 | + arch = factory.make_name('arch') |
3803 | + subarches = [factory.make_name('subarch') for _ in range(3)] |
3804 | + largefile = factory.make_LargeFile() |
3805 | + largefile.total_size = largefile.total_size * 2 |
3806 | + largefile.save() |
3807 | + for subarch in subarches: |
3808 | + resource = factory.make_BootResource( |
3809 | + rtype=BOOT_RESOURCE_TYPE.SYNCED, |
3810 | + name=name, architecture='%s/%s' % (arch, subarch)) |
3811 | + resource_set = factory.make_BootResourceSet(resource) |
3812 | + factory.make_BootResourceFile(resource_set, largefile) |
3813 | + response = self.get_images_ajax() |
3814 | + json_obj = json.loads( |
3815 | + response.content.decode(settings.DEFAULT_CHARSET)) |
3816 | + json_resource = json_obj['resources'][0] |
3817 | + self.assertEqual("Downloading 50%", json_resource['status']) |
3818 | + |
3819 | + def test_combined_subarch_resource_shows_queued_if_no_progress(self): |
3820 | + self.client_log_in() |
3821 | + name = 'ubuntu/%s' % factory.make_name('series') |
3822 | + arch = factory.make_name('arch') |
3823 | + subarches = [factory.make_name('subarch') for _ in range(3)] |
3824 | + largefile = factory.make_LargeFile( |
3825 | + content="".encode(settings.DEFAULT_CHARSET)) |
3826 | + for subarch in subarches: |
3827 | + resource = factory.make_BootResource( |
3828 | + rtype=BOOT_RESOURCE_TYPE.SYNCED, |
3829 | + name=name, architecture='%s/%s' % (arch, subarch)) |
3830 | + resource_set = factory.make_BootResourceSet(resource) |
3831 | + factory.make_BootResourceFile(resource_set, largefile) |
3832 | + response = self.get_images_ajax() |
3833 | + json_obj = json.loads( |
3834 | + response.content.decode(settings.DEFAULT_CHARSET)) |
3835 | + json_resource = json_obj['resources'][0] |
3836 | + self.assertEqual("Queued for download", json_resource['status']) |
3837 | + |
3838 | + def test_combined_subarch_resource_shows_complete_status(self): |
3839 | + self.client_log_in() |
3840 | + name = 'ubuntu/%s' % factory.make_name('series') |
3841 | + arch = factory.make_name('arch') |
3842 | + subarches = [factory.make_name('subarch') for _ in range(3)] |
3843 | + resources = [ |
3844 | + factory.make_usable_boot_resource( |
3845 | + rtype=BOOT_RESOURCE_TYPE.SYNCED, |
3846 | + name=name, architecture='%s/%s' % (arch, subarch)) |
3847 | + for subarch in subarches |
3848 | + ] |
3849 | + self.patch( |
3850 | + BootResource.objects, |
3851 | + 'get_resources_matching_boot_images').return_value = resources |
3852 | + response = self.get_images_ajax() |
3853 | + json_obj = json.loads( |
3854 | + response.content.decode(settings.DEFAULT_CHARSET)) |
3855 | + json_resource = json_obj['resources'][0] |
3856 | + self.assertEqual("Complete", json_resource['status']) |
3857 | + |
3858 | + def test_combined_subarch_resource_shows_waiting_for_cluster_to_sync(self): |
3859 | + self.client_log_in() |
3860 | + name = 'ubuntu/%s' % factory.make_name('series') |
3861 | + arch = factory.make_name('arch') |
3862 | + subarches = [factory.make_name('subarch') for _ in range(3)] |
3863 | + for subarch in subarches: |
3864 | + factory.make_usable_boot_resource( |
3865 | + rtype=BOOT_RESOURCE_TYPE.SYNCED, |
3866 | + name=name, architecture='%s/%s' % (arch, subarch)) |
3867 | + self.patch( |
3868 | + BootResource.objects, |
3869 | + 'get_resources_matching_boot_images').return_value = [] |
3870 | + response = self.get_images_ajax() |
3871 | + json_obj = json.loads( |
3872 | + response.content.decode(settings.DEFAULT_CHARSET)) |
3873 | + json_resource = json_obj['resources'][0] |
3874 | + self.assertEqual( |
3875 | + "Waiting for rack controller(s) to sync", json_resource['status']) |
3876 | + |
3877 | + def test_combined_subarch_resource_shows_clusters_syncing(self): |
3878 | + self.client_log_in() |
3879 | + name = 'ubuntu/%s' % factory.make_name('series') |
3880 | + arch = factory.make_name('arch') |
3881 | + subarches = [factory.make_name('subarch') for _ in range(3)] |
3882 | + for subarch in subarches: |
3883 | + factory.make_usable_boot_resource( |
3884 | + rtype=BOOT_RESOURCE_TYPE.SYNCED, |
3885 | + name=name, architecture='%s/%s' % (arch, subarch)) |
3886 | + self.patch( |
3887 | + BootResource.objects, |
3888 | + 'get_resources_matching_boot_images').return_value = [] |
3889 | + self.patch( |
3890 | + images_view, 'is_import_boot_images_running').return_value = True |
3891 | + response = self.get_images_ajax() |
3892 | + json_obj = json.loads( |
3893 | + response.content.decode(settings.DEFAULT_CHARSET)) |
3894 | + json_resource = json_obj['resources'][0] |
3895 | + self.assertEqual( |
3896 | + "Syncing to rack controller(s)", json_resource['status']) |
3897 | + |
3898 | + |
3899 | +class TestImageDelete(MAASServerTestCase): |
3900 | + |
3901 | + def test_non_admin_cannot_delete(self): |
3902 | + self.client_log_in() |
3903 | + resource = factory.make_BootResource(rtype=BOOT_RESOURCE_TYPE.UPLOADED) |
3904 | + response = self.client.post( |
3905 | + reverse('image-delete', args=[resource.id])) |
3906 | + self.assertEqual(http.client.FORBIDDEN, response.status_code) |
3907 | + self.assertIsNotNone(reload_object(resource)) |
3908 | + |
3909 | + def test_deletes_resource(self): |
3910 | + self.client_log_in(as_admin=True) |
3911 | + resource = factory.make_BootResource(rtype=BOOT_RESOURCE_TYPE.UPLOADED) |
3912 | + response = self.client.post( |
3913 | + reverse('image-delete', args=[resource.id]), |
3914 | + {'post': 'yes'}) |
3915 | + self.assertEqual(http.client.FOUND, response.status_code) |
3916 | + self.assertIsNone(reload_object(resource)) |
3917 | + |
3918 | + def test_redirects_to_images(self): |
3919 | + self.client_log_in(as_admin=True) |
3920 | + resource = factory.make_BootResource(rtype=BOOT_RESOURCE_TYPE.UPLOADED) |
3921 | + response = self.client.post( |
3922 | + reverse('image-delete', args=[resource.id]), |
3923 | + {'post': 'yes'}) |
3924 | + self.assertEqual(reverse('images'), extract_redirect(response)) |
3925 | |
3926 | === modified file 'src/maasserver/websockets/handlers/machine.py' |
3927 | === modified file 'src/maasserver/websockets/handlers/subnet.py' |
3928 | === modified file 'src/maasserver/websockets/handlers/tests/test_machine.py' |
3929 | === modified file 'src/maasserver/websockets/handlers/tests/test_subnet.py' |
3930 | === modified file 'src/maasserver/websockets/tests/test_base.py' |
3931 | === modified file 'src/metadataserver/api.py' |
3932 | --- src/metadataserver/api.py 2016-09-13 20:58:15 +0000 |
3933 | +++ src/metadataserver/api.py 2016-10-24 18:43:47 +0000 |
3934 | @@ -168,6 +168,7 @@ |
3935 | else: |
3936 | type_name = EVENT_TYPES.NODE_COMMISSIONING_EVENT_FAILED |
3937 | elif node.status == NODE_STATUS.DEPLOYING: |
3938 | +<<<<<<< TREE |
3939 | if result in ['SUCCESS', None]: |
3940 | type_name = EVENT_TYPES.NODE_INSTALL_EVENT |
3941 | else: |
3942 | @@ -177,6 +178,12 @@ |
3943 | type_name = EVENT_TYPES.NODE_ENTERING_RESCUE_MODE_EVENT |
3944 | else: |
3945 | type_name = EVENT_TYPES.NODE_ENTERING_RESCUE_MODE_EVENT_FAILED |
3946 | +======= |
3947 | + if result in ['SUCCESS', None]: |
3948 | + type_name = EVENT_TYPES.NODE_INSTALL_EVENT |
3949 | + else: |
3950 | + type_name = EVENT_TYPES.NODE_INSTALL_EVENT_FAILED |
3951 | +>>>>>>> MERGE-SOURCE |
3952 | elif node.node_type in [ |
3953 | NODE_TYPE.RACK_CONTROLLER, |
3954 | NODE_TYPE.REGION_AND_RACK_CONTROLLER]: |
3955 | |
3956 | === modified file 'src/metadataserver/fields.py' |
3957 | === modified file 'src/metadataserver/tests/test_fields.py' |
3958 | === modified file 'src/provisioningserver/boot/powernv.py' |
3959 | === modified file 'src/provisioningserver/boot/tests/test_powernv.py' |
3960 | === modified file 'src/provisioningserver/dns/tests/test_zoneconfig.py' |
3961 | --- src/provisioningserver/dns/tests/test_zoneconfig.py 2016-09-12 19:33:29 +0000 |
3962 | +++ src/provisioningserver/dns/tests/test_zoneconfig.py 2016-10-24 18:43:47 +0000 |
3963 | @@ -210,6 +210,53 @@ |
3964 | ) |
3965 | ) |
3966 | |
3967 | + def test_handles_slash_32_dynamic_range(self): |
3968 | + target_dir = patch_dns_config_path(self) |
3969 | + domain = factory.make_string() |
3970 | + network = factory.make_ipv4_network() |
3971 | + dns_ip = factory.pick_ip_in_network(network) |
3972 | + ipv4_hostname = factory.make_name('host') |
3973 | + ipv4_ip = factory.pick_ip_in_network(network) |
3974 | + range_ip = factory.pick_ip_in_network(network, but_not={ipv4_ip}) |
3975 | + ipv6_hostname = factory.make_name('host') |
3976 | + ipv6_ip = factory.make_ipv6_address() |
3977 | + ttl = random.randint(10, 300) |
3978 | + mapping = { |
3979 | + ipv4_hostname: HostnameIPMapping(None, ttl, {ipv4_ip}), |
3980 | + ipv6_hostname: HostnameIPMapping(None, ttl, {ipv6_ip}), |
3981 | + } |
3982 | + dynamic_range = IPRange(IPAddress(range_ip), IPAddress(range_ip)) |
3983 | + expected_generate_directives = ( |
3984 | + DNSForwardZoneConfig.get_GENERATE_directives( |
3985 | + dynamic_range)) |
3986 | + other_mapping = {ipv4_hostname: HostnameRRsetMapping( |
3987 | + None, {(ttl, 'MX', '10 bar')})} |
3988 | + dns_zone_config = DNSForwardZoneConfig( |
3989 | + domain, serial=random.randint(1, 100), |
3990 | + other_mapping=other_mapping, default_ttl=ttl, |
3991 | + mapping=mapping, dns_ip=dns_ip, |
3992 | + dynamic_ranges=[dynamic_range]) |
3993 | + dns_zone_config.write_config() |
3994 | + self.assertThat( |
3995 | + os.path.join(target_dir, 'zone.%s' % domain), |
3996 | + FileContains( |
3997 | + matcher=ContainsAll( |
3998 | + [ |
3999 | + '$TTL %d' % ttl, |
4000 | + '%s %d IN A %s' % (ipv4_hostname, ttl, ipv4_ip), |
4001 | + '%s %d IN AAAA %s' % (ipv6_hostname, ttl, ipv6_ip), |
4002 | + '%s %d IN MX 10 bar' % (ipv4_hostname, ttl), |
4003 | + ] + |
4004 | + [ |
4005 | + '$GENERATE %s %s IN A %s' % ( |
4006 | + iterator_values, reverse_dns, hostname) |
4007 | + for iterator_values, reverse_dns, hostname in |
4008 | + expected_generate_directives |
4009 | + ] |
4010 | + ) |
4011 | + ) |
4012 | + ) |
4013 | + |
4014 | def test_writes_dns_zone_config(self): |
4015 | target_dir = patch_dns_config_path(self) |
4016 | domain = factory.make_string() |
4017 | |
4018 | === modified file 'src/provisioningserver/dns/zoneconfig.py' |
4019 | === modified file 'src/provisioningserver/events.py' |
4020 | --- src/provisioningserver/events.py 2016-08-25 20:11:43 +0000 |
4021 | +++ src/provisioningserver/events.py 2016-10-24 18:43:47 +0000 |
4022 | @@ -70,6 +70,7 @@ |
4023 | NODE_COMMISSIONING_EVENT = "NODE_COMMISSIONING_EVENT" |
4024 | NODE_COMMISSIONING_EVENT_FAILED = "NODE_COMMISSIONING_EVENT_FAILED" |
4025 | NODE_INSTALL_EVENT = "NODE_INSTALL_EVENT" |
4026 | +<<<<<<< TREE |
4027 | NODE_INSTALL_EVENT_FAILED = "NODE_INSTALL_EVENT_FAILED" |
4028 | NODE_ENTERING_RESCUE_MODE_EVENT = "NODE_ENTERING_RESCUE_MODE_EVENT" |
4029 | NODE_ENTERING_RESCUE_MODE_EVENT_FAILED = ( |
4030 | @@ -77,6 +78,9 @@ |
4031 | NODE_EXITING_RESCUE_MODE_EVENT = "NODE_EXITING_RESCUE_MODE_EVENT" |
4032 | NODE_EXITING_RESCUE_MODE_EVENT_FAILED = ( |
4033 | "NODE_EXITING_RESCUE_MODE_EVENT_FAILED") |
4034 | +======= |
4035 | + NODE_INSTALL_EVENT_FAILED = "NODE_INSTALL_EVENT_FAILED" |
4036 | +>>>>>>> MERGE-SOURCE |
4037 | # Node user request events |
4038 | REQUEST_NODE_START_COMMISSIONING = "REQUEST_NODE_START_COMMISSIONING" |
4039 | REQUEST_NODE_ABORT_COMMISSIONING = "REQUEST_NODE_ABORT_COMMISSIONING" |
4040 | @@ -180,6 +184,7 @@ |
4041 | description="Node installation", |
4042 | level=DEBUG, |
4043 | ), |
4044 | +<<<<<<< TREE |
4045 | EVENT_TYPES.NODE_INSTALL_EVENT_FAILED: EventDetail( |
4046 | description="Node installation failure", |
4047 | level=ERROR, |
4048 | @@ -200,6 +205,12 @@ |
4049 | description="Node exiting rescue mode failure", |
4050 | level=ERROR, |
4051 | ), |
4052 | +======= |
4053 | + EVENT_TYPES.NODE_INSTALL_EVENT_FAILED: EventDetail( |
4054 | + description="Node installation failure", |
4055 | + level=ERROR, |
4056 | + ), |
4057 | +>>>>>>> MERGE-SOURCE |
4058 | EVENT_TYPES.REQUEST_NODE_START_COMMISSIONING: EventDetail( |
4059 | description="User starting node commissioning", |
4060 | level=INFO, |
4061 | |
4062 | === modified file 'src/provisioningserver/import_images/boot_resources.py' |
4063 | === modified file 'src/provisioningserver/import_images/tests/test_boot_resources.py' |
4064 | === modified file 'src/provisioningserver/plugin.py' |
4065 | --- src/provisioningserver/plugin.py 2016-10-19 17:06:30 +0000 |
4066 | +++ src/provisioningserver/plugin.py 2016-10-24 18:43:47 +0000 |
4067 | @@ -179,10 +179,18 @@ |
4068 | import crochet |
4069 | crochet.no_setup() |
4070 | |
4071 | +<<<<<<< TREE |
4072 | def _configureLogging(self, verbosity: int): |
4073 | # Get something going with the logs. |
4074 | logger.configure(verbosity, logger.LoggingMode.TWISTD) |
4075 | |
4076 | +======= |
4077 | + def _configureLogging(self): |
4078 | + # Get something going with the logs. |
4079 | + from provisioningserver import logger |
4080 | + logger.basicConfig() |
4081 | + |
4082 | +>>>>>>> MERGE-SOURCE |
4083 | def makeService(self, options): |
4084 | """Construct the MAAS Cluster service.""" |
4085 | register_sigusr2_thread_dump_handler() |
4086 | @@ -190,7 +198,11 @@ |
4087 | add_patches_to_twisted() |
4088 | |
4089 | self._configureCrochet() |
4090 | +<<<<<<< TREE |
4091 | self._configureLogging(options["verbosity"]) |
4092 | +======= |
4093 | + self._configureLogging() |
4094 | +>>>>>>> MERGE-SOURCE |
4095 | |
4096 | with ClusterConfiguration.open() as config: |
4097 | tftp_root = config.tftp_root |
4098 | |
4099 | === modified file 'src/provisioningserver/power/query.py' |
4100 | === modified file 'src/provisioningserver/power/schema.py' |
4101 | --- src/provisioningserver/power/schema.py 2016-10-20 20:55:30 +0000 |
4102 | +++ src/provisioningserver/power/schema.py 2016-10-24 18:43:47 +0000 |
4103 | @@ -349,6 +349,7 @@ |
4104 | 'description': 'Digital Loggers, Inc. PDU', |
4105 | 'fields': [ |
4106 | make_json_field( |
4107 | +<<<<<<< TREE |
4108 | 'outlet_id', "Outlet ID", scope=POWER_PARAMETER_SCOPE.NODE, |
4109 | required=True), |
4110 | make_json_field('power_address', "Power address", required=True), |
4111 | @@ -363,6 +364,11 @@ |
4112 | 'description': "Facebook's Wedge", |
4113 | 'fields': [ |
4114 | make_json_field('power_address', "IP address", required=True), |
4115 | +======= |
4116 | + 'outlet_id', "Outlet ID", scope=POWER_PARAMETER_SCOPE.NODE, |
4117 | + required=True), |
4118 | + make_json_field('power_address', "Power address", required=True), |
4119 | +>>>>>>> MERGE-SOURCE |
4120 | make_json_field('power_user', "Power user"), |
4121 | make_json_field( |
4122 | 'power_pass', "Power password", field_type='password'), |
4123 | |
4124 | === modified file 'src/provisioningserver/power/tests/test_query.py' |
4125 | === modified file 'src/provisioningserver/rackdservices/tests/test_tftp.py' |
4126 | === modified file 'src/provisioningserver/rackdservices/tftp.py' |
4127 | === modified file 'src/provisioningserver/refresh/tests/test_node_info_scripts.py' |
4128 | === modified file 'src/provisioningserver/tests/test_plugin.py' |
4129 | --- src/provisioningserver/tests/test_plugin.py 2016-10-19 21:08:54 +0000 |
4130 | +++ src/provisioningserver/tests/test_plugin.py 2016-10-24 18:43:47 +0000 |
4131 | @@ -81,7 +81,11 @@ |
4132 | self.useFixture(ClusterConfigurationFixture()) |
4133 | self.patch(provisioningserver, "services", MultiService()) |
4134 | self.patch_autospec(crochet, "no_setup") |
4135 | +<<<<<<< TREE |
4136 | self.patch_autospec(logger, "configure") |
4137 | +======= |
4138 | + self.patch_autospec(logger, "basicConfig") |
4139 | +>>>>>>> MERGE-SOURCE |
4140 | self.tempdir = self.make_dir() |
4141 | |
4142 | def test_init(self): |
4143 | @@ -108,9 +112,13 @@ |
4144 | "Not all services are named.") |
4145 | self.assertEqual(service, provisioningserver.services) |
4146 | self.assertThat(crochet.no_setup, MockCalledOnceWith()) |
4147 | +<<<<<<< TREE |
4148 | self.assertThat( |
4149 | logger.configure, MockCalledOnceWith( |
4150 | options["verbosity"], logger.LoggingMode.TWISTD)) |
4151 | +======= |
4152 | + self.assertThat(logger.basicConfig, MockCalledOnceWith()) |
4153 | +>>>>>>> MERGE-SOURCE |
4154 | |
4155 | def test_makeService_patches_tftp_service(self): |
4156 | mock_tftp_patch = ( |
4157 | |
4158 | === modified file 'src/provisioningserver/utils/tests/test_env.py' |