Merge lp:~ltrager/maas/lp1593991_2.0 into lp:~maas-committers/maas/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
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

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 $}">&lsaquo;&nbsp;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 $}">&lsaquo;&nbsp;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>&nbsp;<a href="http://maas.ubuntu.com/{{doc_version}}/changelog.html#id1">View release notes</a>&nbsp;&nbsp;|&nbsp;&nbsp;<a href="https://maas.ubuntu.com/{{doc_version}}/">View documentation</a>
2303@@ -206,6 +207,12 @@
2304 &copy; 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">&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">&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'