Merge lp:~ltrager/maas/lp1636858_2.0 into lp:~maas-committers/maas/trunk

Proposed by Lee Trager
Status: Superseded
Proposed branch: lp:~ltrager/maas/lp1636858_2.0
Merge into: lp:~maas-committers/maas/trunk
Diff against target: 4239 lines (+3217/-21) (has conflicts)
43 files modified
Makefile (+1/-1)
docs/about.rst (+9/-0)
docs/changelog.rst (+524/-0)
docs/index.rst (+9/-0)
src/maasserver/api/account.py (+23/-0)
src/maasserver/api/interfaces.py (+4/-0)
src/maasserver/api/tests/test_ipaddresses.py (+9/-0)
src/maasserver/api/tests/test_machine.py (+37/-0)
src/maasserver/api/tests/test_machines.py (+20/-0)
src/maasserver/bootresources.py (+37/-0)
src/maasserver/dhcp.py (+72/-0)
src/maasserver/enum.py (+6/-0)
src/maasserver/migrations/builtin/maasserver/0066_allow_squashfs.py (+22/-0)
src/maasserver/models/node.py (+105/-17)
src/maasserver/models/staticipaddress.py (+94/-0)
src/maasserver/models/tests/test_node.py (+177/-0)
src/maasserver/models/tests/test_staticipaddress.py (+68/-3)
src/maasserver/node_status.py (+16/-0)
src/maasserver/preseed_network.py (+20/-0)
src/maasserver/rpc/tests/test_regionservice.py (+14/-0)
src/maasserver/static/css/maas-styles.css.OTHER (+1/-0)
src/maasserver/static/js/angular/controllers/tests/test_node_details_storage.js (+89/-0)
src/maasserver/static/partials/node-details.html (+6/-0)
src/maasserver/static/partials/node-events.html (+14/-0)
src/maasserver/static/partials/nodes-list.html (+173/-0)
src/maasserver/static/scss/maas/components/_accordion.scss.OTHER (+112/-0)
src/maasserver/templates/maasserver/base.html (+7/-0)
src/maasserver/templates/maasserver/index.html (+21/-0)
src/maasserver/templates/maasserver/prefs.html (+5/-0)
src/maasserver/testing/factory.py (+5/-0)
src/maasserver/tests/test_bootresources.py (+108/-0)
src/maasserver/tests/test_dhcp.py (+74/-0)
src/maasserver/tests/test_preseed.py (+27/-0)
src/maasserver/tests/test_preseed_network.py (+9/-0)
src/maasserver/tests/test_start_up.py (+93/-0)
src/maasserver/triggers/tests/test_websocket_listener.py (+124/-0)
src/maasserver/views/tests/test_images.py.OTHER (+991/-0)
src/metadataserver/api.py (+7/-0)
src/provisioningserver/dns/tests/test_zoneconfig.py (+47/-0)
src/provisioningserver/events.py (+11/-0)
src/provisioningserver/plugin.py (+12/-0)
src/provisioningserver/power/schema.py (+6/-0)
src/provisioningserver/tests/test_plugin.py (+8/-0)
Text conflict in docs/changelog.rst
Text conflict in src/maasserver/api/account.py
Text conflict in src/maasserver/api/interfaces.py
Text conflict in src/maasserver/api/tests/test_ipaddresses.py
Text conflict in src/maasserver/api/tests/test_machine.py
Text conflict in src/maasserver/api/tests/test_machines.py
Text conflict in src/maasserver/bootresources.py
Text conflict in src/maasserver/dhcp.py
Text conflict in src/maasserver/enum.py
Conflict adding file src/maasserver/migrations/builtin/maasserver/0066_allow_squashfs.py.  Moved existing file to src/maasserver/migrations/builtin/maasserver/0066_allow_squashfs.py.moved.
Text conflict in src/maasserver/models/node.py
Text conflict in src/maasserver/models/staticipaddress.py
Text conflict in src/maasserver/models/tests/test_node.py
Text conflict in src/maasserver/models/tests/test_staticipaddress.py
Text conflict in src/maasserver/preseed_network.py
Contents conflict in src/maasserver/static/css/maas-styles.css
Text conflict in src/maasserver/static/js/angular/controllers/tests/test_node_details_storage.js
Text conflict in src/maasserver/static/partials/node-details.html
Text conflict in src/maasserver/static/partials/node-events.html
Text conflict in src/maasserver/static/partials/nodes-list.html
Conflict adding files to src/maasserver/static/scss/maas.  Created directory.
Conflict because src/maasserver/static/scss/maas is not versioned, but has versioned children.  Versioned directory.
Conflict adding files to src/maasserver/static/scss/maas/components.  Created directory.
Conflict because src/maasserver/static/scss/maas/components is not versioned, but has versioned children.  Versioned directory.
Contents conflict in src/maasserver/static/scss/maas/components/_accordion.scss
Text conflict in src/maasserver/templates/maasserver/base.html
Text conflict in src/maasserver/templates/maasserver/index.html
Text conflict in src/maasserver/templates/maasserver/prefs.html
Text conflict in src/maasserver/testing/factory.py
Text conflict in src/maasserver/tests/test_bootresources.py
Text conflict in src/maasserver/tests/test_dhcp.py
Text conflict in src/maasserver/tests/test_preseed.py
Text conflict in src/maasserver/tests/test_preseed_network.py
Text conflict in src/maasserver/tests/test_start_up.py
Text conflict in src/maasserver/triggers/tests/test_websocket_listener.py
Contents conflict in src/maasserver/views/tests/test_images.py
Text conflict in src/metadataserver/api.py
Text conflict in src/provisioningserver/events.py
Text conflict in src/provisioningserver/plugin.py
Text conflict in src/provisioningserver/power/schema.py
Text conflict in src/provisioningserver/tests/test_plugin.py
To merge this branch: bzr merge lp:~ltrager/maas/lp1636858_2.0
Reviewer Review Type Date Requested Status
MAAS Maintainers Pending
Review via email: mp+309412@code.launchpad.net

Commit message

Backport from 2.1.1 - If the power_parameters are the empty string set them to an empty dictionary, don't use json.loads

To post a comment you must log in.

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
1=== modified file 'Makefile'
2--- Makefile 2016-10-17 06:38:56 +0000
3+++ Makefile 2016-10-26 23:39:26 +0000
4@@ -588,7 +588,7 @@
5
6 # Old names.
7 PACKAGING := $(abspath ../packaging.trunk)
8-PACKAGING_BRANCH := lp:~maas-maintainers/maas/packaging
9+PACKAGING_BRANCH := lp:~maas-maintainers/maas/packaging-2.0
10
11 packaging-tree = $(PACKAGING)
12 packaging-branch = $(PACKAGING_BRANCH)
13
14=== modified file 'buildout.cfg'
15=== modified file 'docs/about.rst'
16--- docs/about.rst 2016-04-11 16:23:26 +0000
17+++ docs/about.rst 2016-10-26 23:39:26 +0000
18@@ -1,6 +1,15 @@
19 About this documentation
20 ========================
21
22+This is API and developer documentation only. MAAS 2.0 user documentation is
23+published on `http://maas.io_` and its source is found on `GitHub_`.
24+
25+.. _http://maas.io:
26+ http://maas.io/docs/
27+
28+.. _GitHub:
29+ https://github.com/CanonicalLtd/maas-docs
30+
31 This is the documentation for Canonical's MAAS software. If you aren't
32 sure what that is, you should probably skip everything else and head
33 straight to the :ref:`orientation` section where it is explained.
34
35=== modified file 'docs/changelog.rst'
36--- docs/changelog.rst 2016-09-27 22:26:08 +0000
37+++ docs/changelog.rst 2016-10-26 23:39:26 +0000
38@@ -2,6 +2,7 @@
39 Changelog
40 =========
41
42+<<<<<<< TREE
43 2.1.0 Alpha 4
44 =============
45
46@@ -399,12 +400,532 @@
47 http://launchpad.net/bugs/1618572
48
49
50+=======
51+2.0.1
52+=====
53+
54+Issues fixed in this release
55+----------------------------
56+
57+LP: #1636251 resolv.conf search path doesn't match the domain for the host.
58+
59+LP: #1615686 Single-IP dynamic ranges resulted in internal errors.
60+
61+2.0.0
62+=====
63+
64+Important announcements
65+----------------------
66+
67+**MAAS 2.0 supported on Ubuntu 16.04 LTS (Xenial)**
68+ MAAS version 2.0 is supported on Ubuntu 16.04 LTS. MAAS 2.0 and greater
69+ will NOT be supported on Ubuntu 14.04 LTS. The latest MAAS 1.9 point release
70+ will continue to be supported on Ubuntu 14.04 LTS (Trusty) until it reaches
71+ end-of-life.
72+
73+ Upgrades are supported for users running Ubuntu 14.04 systems running
74+ MAAS 1.9 or earlier. Upon upgrading to Ubuntu 16.04, the MAAS
75+ database and configuration will be seamlessly migrated to the supported
76+ MAAS version.
77+
78+ Please see the “Other Notable Changes” section below for more details
79+ regarding the reasons for this change.
80+
81+**Terminology Changes**
82+ Cluster controllers have been renamed to rack controllers.
83+
84+ Starting from MAAS 2.0, MAAS cluster controllers have been deprecated,
85+ along with the legacy ``nodegroups`` API. The new API endpoint is
86+ ``rackcontrollers``, which provides feature parity with earlier versions
87+ of MAAS.
88+
89+ For more information on rack controllers, refer to the `Major new Features`
90+ section bellow or refer to :ref:`rack-configuration`.
91+
92+**API 1.0 has been deprecated, introducing API 2.0**
93+ Starting from MAAS 2.0, the MAAS REST API version 1.0 has been deprecated.
94+ MAAS 2.0 drops support for the legacy 1.0 API, in favor of API version 2.0.
95+ With the introduction of the new API version, various endpoints have now
96+ been deprecated, and new end-points have been introduced. API users will
97+ need to update their client tools to reflect the changes.
98+
99+ For more information on API 2.0, refer to :ref:`API documentation <region-controller-api>`.
100+
101+**Static IP ranges have been deprecated**
102+ Starting from MAAS 2.0, static IP ranges (previously found on the cluster
103+ interface page) have been deprecated. MAAS now assumes total control of a
104+ subnet. MAAS will automatically assign IP addresses to deployed machines,
105+ as long as those IP addresses are not within a dynamic or a reserved
106+ IP range. Users are now only required to specify one or more dynamic ranges
107+ per subnet. Dynamic ranges are used for auto-enlistment, commissioning, and
108+ any other systems configured for DHCP. IP addresses in-use for purposes such
109+ as devices, default gateways, DNS servers, rack and region controllers, and
110+ BMCs are automatically avoided when assigning IP addresses. Reserved IP
111+ ranges may be added if MAAS should avoid certain ranges of IP addresses in
112+ the subnet.
113+
114+**maas-region-controller-min has been renamed to maas-region-api**
115+ The ``maas-region-controller-min`` package has been renamed to
116+ ``maas-region-api``. This package provides API services for MAAS
117+ (``maas-regiond``) and can be used to scale out the API front-end of
118+ a MAAS region.
119+
120+**MAAS user creation been moved to 'maas' command**
121+ Starting from MAAS 2.0, the ``maas`` command now provides the ability to
122+ create admin users. The ``maas-region createadmin`` command has been
123+ deprecated. New administrators should now be created with
124+ ``maas createadmin``.
125+
126+**maas-provision command has been replaced**
127+ The MAAS rack controller command-line interface (``maas-provision``) has
128+ been replaced by the ``maas-rack`` command.
129+
130+**maas-region-admin command has been replaced with maas-region**
131+ The MAAS region controller command-line interface (``maas-region-admin``)
132+ has been replaced by the ``maas-region`` command. Note that this command
133+ provides an interface to interact directly with Django, which should only be
134+ used for debugging purposes.
135+
136+**Debian Installer is no longer installed or supported**
137+ Because support for the Debian Installer (DI) has been dropped (as
138+ of MAAS 1.9), MAAS no longer downloads DI-related files from simplestreams.
139+ Upon upgrading to MAAS 2.0, DI-related files will be removed from the MAAS
140+ region (and all rack controllers).
141+
142+
143+Major new features
144+------------------
145+
146+**MAAS Rack Controllers and High Availability**
147+ Starting from MAAS 2.0, MAAS **cluster controllers** have been renamed to
148+ **rack controllers**.
149+
150+ * The ``nodegroups`` and ``nodegroups/(group)/interfaces`` API endpoints
151+ have been deprecated. In MAAS 2.0, the ``rackcontrollers`` interface
152+ partially replaces this functionality. For defining dynamic and reserved
153+ ranges, or specifying default gateways, use the ``subnets`` endpoint. For
154+ enabling or disabling DHCP, use the ``fabrics/(fabric)/vlans`` endpoint.
155+
156+ * The **Clusters** tab is no longer available in the Web UI.
157+ Controllers are now found under the **Nodes** tab, where each region
158+ and/or rack controller can be found. Other cluster interface properties
159+ have been moved to the Subnet and VLAN details pages under the **Networks**
160+ tab.
161+
162+ * Machines no longer belong to a specific controller.
163+ In earlier versions of MAAS, machines would directly assigned to a cluster
164+ controller. The cluster controller that the machine belonged to would not
165+ only perform DHCP for that machine, but also all the PXE booting and power
166+ management.
167+
168+ In order to support high availability for rack controllers, (starting from
169+ MAAS 2.0) machines no longer belong to a specific rack controller. The best
170+ controller to manage a machine is now determined at runtime.
171+
172+ * DHCP is now configured per-VLAN.
173+ In earlier versions of MAAS, DHCP was directly linked and configured
174+ per cluster interface. As of MAAS 2.0, DHCP is now configured and managed
175+ per-VLAN, which allows any rack controller to potentially provide DHCP in
176+ a high-availability environment.
177+
178+ * Rack controllers have been enabled for high availability.
179+ Starting from MAAS 2.0, rack controllers in the same VLAN become
180+ candidates to manage DHCP, PXE/TFTP, and power control for machines on
181+ the VLAN. MAAS now supports the concept of a **primary** and a
182+ **secondary** rack controller. If a secondary controller determines that
183+ the primary controller is unavailable, it will assume control of those
184+ services.
185+
186+ * Added ``maas-rack support-dump`` command.
187+ For increased support observability, users can now dump the contents of
188+ several internal MAAS data structures by executing ``sudo maas-rack
189+ support-dump``. This command will dump networking diagnostics, rack
190+ configuration, and image information. Information can be restricted to a
191+ particular category by using the ``--networking``, ``--config``, or
192+ ``--images`` options.
193+
194+ * Rack controllers can now be found under the **Nodes** tab in the Web UI.
195+ MAAS 2.0 Adds a new **Controllers** section under thee **Nodes** tab. This
196+ section will now list all rack and region controllers. Under a rack
197+ controller, the user will be able to see service tracking, connected VLANs,
198+ rack interfaces and other relevant information.
199+
200+**Region Controller Redundancy (High Availability)**
201+ Starting from MAAS 2.0, MAAS provides the ability to scale out (provide
202+ redundancy for) the MAAS region controller API, HTTP server, and DNS. This
203+ will allow administrators to set up multiple MAAS region controllers
204+ (``maas-region-api``) against a common database, providing redundancy of
205+ services. With further manual configuration, users will be able to setup
206+ the MAAS region controller in high availability mode.
207+
208+**New Networks Web UI**
209+ MAAS 2.0 introduces a few new Web UI features that were not available in
210+ previous versions of MAAS.
211+
212+ * Added fabric and space details pages.
213+
214+ * Added the ability to add and remove fabrics, spaces, subnets and VLANs.
215+ This can be done using the actions menu on the **Networks** tab.
216+
217+ The ability to delete fabrics, spaces, subnets and VLANs is also available
218+ from the details page for each respective object.
219+
220+**DNS Management**
221+ MAAS 2.0 extends DNS management by adding the following features:
222+
223+ * Ability to create multiple DNS domains.
224+ * Ability to add multiple records (``CNAME``, ``TXT``, ``MX``, ``SRV``) per
225+ domain. (API only)
226+ * Ability to select the domain for machines and devices.
227+ * Ability to assign (additional) names to IP addresses. (API only)
228+ * For deployed machines, ``A`` records continue to be created for
229+ the IP of the PXE interface.
230+ * Additional PTR records are now created for all non-PXE interfaces in
231+ the form: ``<interface>.<machine fully-qualified-domain-name>``
232+ * Reverse DNS is now generated for only the subnet specified, rather
233+ than the parent /24 or /16. By default, `RFC2137`_ glue is provided
234+ for networks smaller than /24. This can be disabled or changed
235+ on a per-subnet basis via the API.
236+
237+.. _RFC2137:
238+ https://tools.ietf.org/html/rfc2137
239+
240+**IP Ranges**
241+ Previous versions of MAAS used the concepts of a **dynamic range** and
242+ **static range**, which were properties of each cluster interface. This
243+ has been redesigned for MAAS 2.0 as follows:
244+
245+ * Dynamic ranges have been migrated from earlier MAAS releases as-is.
246+
247+ * Because static ranges have been removed from MAAS, each static
248+ range has been migrated to one or more reserved ranges, which
249+ represent the opposite of the previous static range. (MAAS now
250+ assumes it has full control of each managed subnet, and is free
251+ to assign IP addresses as it sees fit, unless told otherwise.)
252+
253+ For example, if in an earlier MAAS release a cluster interface was
254+ configured on 192.168.0.1/24, with a dynamic range of 192.168.0.2
255+ through 192.168.0.99, and a static range of 192.168.0.100 through
256+ 192.168.0.199, this will be migrated to::
257+
258+ IP range #1 (dynamic): 192.168.0.2 - 192.168.0.99
259+ IP range #2 (reserved): 192.168.0.200 - 192.168.0.254
260+
261+ Since 192.168.0.100 - 192.168.0.199 (the previous static range)
262+ is not accounted for, MAAS assumes it is free to allocate static
263+ IP addresses from that range.
264+
265+ * Scalability is now possible by means of adding a second dynamic
266+ IP range to a VLAN. (To deal with IP address exhaustion, MAAS
267+ supports multiple dynamic ranges on one or more subnets within
268+ a DHCP-enabled VLAN.)
269+
270+ * Reserved ranges can now be allocated to a particular MAAS user.
271+
272+ * A comment field has been added, so that users can indicate why
273+ a particular range of IP addresses is reserved.
274+
275+ * IP ranges can be configured in the Web UI via the Subnet
276+ details page, or using the ``subnets`` REST API endpoint.
277+
278+**API 2.0 and MAAS CLI Updates**
279+ Version 1.0 of the MAAS REST API has been removed and replaced with the 2.0
280+ version of the REST API. As such, new endpoints and commands have been
281+ introduced:
282+
283+ * RackControllers - This endpoint/command has the following operations
284+ in addition to the base operations provided by nodes:
285+
286+ * ``import-boot-images`` - Import the boot images on all rack
287+ controllers
288+ * ``describe-power-types`` - Query all of the rack controllers for
289+ power information
290+
291+ * RackController - This endpoint/command has the following operations
292+ in addition to the base operations provided by nodes
293+
294+ * ``import-boot-images`` - Import boot images on the given rack
295+ controller
296+ * ``refresh`` - refresh the hardware information for the given rack
297+ controller
298+
299+ * Machines - This endpoint/command replaces many of the operations
300+ previously found in the nodes endpoint/command. The machines
301+ endpoint/command has the following operations in addition to the
302+ base operations provided by nodes.
303+
304+ * ``power-parameters`` - Retrieve power parameters for multiple
305+ machines
306+ * ``list-allocated`` - Fetch machines that were allocated to the
307+ user/oauth token.
308+ * ``allocate`` - Allocate an available machine for deployment.
309+ * ``accept`` - Accept declared machine into MAAS.
310+ * ``accept-all`` - Accept all declared machines into MAAS.
311+ * ``create`` - Create a new machine.
312+ * ``add-chassis`` - Add special hardware types.
313+ * ``release`` - Release multiple machines.
314+
315+ * Machine - This endpoint/command replaces many of the operations
316+ previously found in the node endpoint/command. The machine
317+ endpoint/command has the following operations in addition to the
318+ base operations provided by node.
319+
320+ * ``power-parameters`` - Obtain power parameters for the given machine.
321+ * ``deploy`` - Deploy an operating system to a given machine.
322+ * ``abort`` - Abort the machines current operation.
323+ * ``get-curtin-config`` - Return the rendered curtin configuration for
324+ the machine.
325+ * ``power-off`` - Power off the given machine.
326+ * ``set-storage-layout`` - Change the storage layout of the given
327+ machine.
328+ * ``power-on`` - Turn on the given machine.
329+ * ``release`` - Release a given machine.
330+ * ``clear-default-gateways`` - Clear any set default gateways on the
331+ machine.
332+ * ``update`` - Change machine configuration.
333+ * ``query-power-state`` - Query the power state of a machine.
334+ * ``commission`` - Begin commissioning process for a machine
335+
336+ Other endpoints/commands have changed:
337+
338+ * All list commands/operations have been converted to read
339+ * All new and add commands/operations have been converted to create
340+ * Nodes - The nodes endpoint/command is now a base endpoint/command
341+ for all other node types(devices, machines, and rack-controllers).
342+ As such most operations have been moved to the machines
343+ endpoint/command.The following operations remain as they can be
344+ used on all node types.
345+
346+ * ``is-registered`` - Returns whether or not the given MAC address is
347+ registered with this MAAS.
348+ * ``set-zone`` - Assign multiple nodes to a physical zone at once.
349+ * ``read`` - List nodes visible to the user, optionally filtered by
350+ criteria.
351+
352+ * Node - The node endpoint/command is now a base endpoint/command for
353+ all other node types(devices, machines, and rack-controllers). As
354+ such most operations have been moved to the machine endpoint/command.
355+ The following operations remain as they can be used on all node types.
356+
357+ * ``read`` - Read information about a specific node
358+ * ``details`` - Obtain various system details.
359+ * ``delete`` - Delete a specific node.
360+
361+ * With the migration of nodes to machines the following items previously
362+ outputted with the list command have been changed or removed from the
363+ machines read command:
364+
365+ * ``status - Will now show all status types
366+ * ``substatus``, ``substatus_action``, ``substatus_message``,
367+ ``substatus_name`` - Replaced by ``status``, ``status_action``,
368+ ``status_message``, ``status_name``.
369+ * ``boot_type - Removed, MAAS 2.0 only supports fastpath.
370+ * ``pxe_mac`` - Replaced by ``boot_interface``.
371+ * ``hostname`` - Now only displays the hostname (without the domain) of
372+ the machine. ``fqdn`` and ``domain`` attributes can now be used instead.
373+
374+ * And other endpoints/commands have been deprecated:
375+
376+ * NodeGroups - Replacement operations are found in the
377+ RackControllers, Machines, and BootResources endpoints/commands.
378+ * NodeGroupInterfaces - replacement operations are found in the
379+ RackController, IPRanges, Subnets, and VLANS endpoints/commands.
380+
381+**Extended Storage Support**
382+ MAAS 2.0 Storage Model has been extended to support:
383+
384+ * XFS as a filesystem.
385+ * Mount options.
386+ * Swap partitions. (MAAS 1.9 only supported the creation of a swap
387+ file in the filesystem.)
388+ * ``tmps``/``ramfs`` support.
389+
390+ All of these options are currently available over the CLI.
391+
392+**DHCP Snippets**
393+ MAAS 2.0 introduces the ability to define DHCP snippets. This
394+ feature allows administrators to manage DHCP directly from MAAS, removing
395+ the need to manually modify template files. The following types of DHCP
396+ snippets can be defined:
397+
398+ * **Host snippets** - used for configuration for a particular node in MAAS.
399+ * **Subnet snippets** - used for configuration for a specific subnet in MAAS.
400+ * **Global snippets** - used for configuration that will affect DHCP (isc-dhcp)
401+ as a whole.
402+
403+ For more information, see :ref:`DHCP Snippets <dhcpsnippets>`.
404+
405+Minor new features
406+------------------
407+
408+**MAAS proxy is now managed**
409+ Starting from MAAS 2.0, MAAS now manages the configuration for
410+ ``maas-proxy``. This allows MAAS to lock down the proxy so that it only
411+ allows traffic from networks MAAS knows about. For more information, see
412+ :ref:`MAAS Proxy <proxy>`.
413+
414+**rsyslog during enlistment and commissioning**
415+ MAAS 2.0 now enables ``rsyslog`` for the enlistment and commissioning
416+ environment (when using Xenial as the commissioning image). This allows users
417+ to see ``cloud-init``'s syslog information in ``/var/log/maas/rsyslog/``.
418+
419+**Ability to change a machine’s domain name from the UI**
420+ MAAS 2.0 introduces the ability to change a machine’s DNS domain
421+ via the Web UI. It was previously supported on the API only.
422+
423+**Networks listing page**
424+ In the **Networks** tab, a new networks overview has been introduced, which
425+ provides a high-level view of the MAAS networking mode. The network model
426+ can be grouped by either fabrics or spaces.
427+
428+**Service Tracking**
429+ MAAS now tracks the status of the services required for its operation, such
430+ as ``bind``, ``maas-dhcpd``, ``maas-dhcpd6``, ``tgt``, and ``maas-proxy``.
431+
432+
433+Other notable changes
434+---------------------
435+
436+**MAAS 2.0 requires Python 3.5**
437+ Starting with MAAS 2.0, MAAS has now been ported to Python 3.5 (the default
438+ version of Python in Ubuntu 16.04 "Xenial").
439+
440+**MAAS 2.0 now fully supports native Django 1.8 migration system**
441+ MAAS is now based on Django 1.8. Django 1.8 has dropped support for the
442+ South migration system in favor of the native Django migration
443+ system, which breaks backwards compatibility with previous versions of
444+ Django.
445+
446+ MAAS continues to support a full upgrade path. MAAS versions 1.5, 1.7, 1.8,
447+ and 1.9 have been tested and confirmed to upgrade seamlessly to MAAS 2.0.
448+
449+**Instant DHCP lease notifications**
450+ MAAS no longer scans the leases file every 5 minutes. ``isc-dhcp-server``
451+ now directly notifies MAAS if a lease is committed, released, or expires.
452+
453+**Host entries in DHCP**
454+ Host entries are now rendered in the DHCP configuration instead
455+ of placed in the leases file. This removes any state that previously
456+ existed in the DHCP lease database on the cluster controller.
457+
458+ Starting with MAAS 2.0, if the dhcpd.leases file is lost (such as during a
459+ failure scenario in a high availability environment), MAAS will be able to
460+ reconstruct it.
461+
462+**Power control is no longer specific to a rack controller**
463+ MAAS selects one of the available rack controllers to power control
464+ or query a BMC. The same rack controller that powers the BMC does
465+ not need to be the rack controller that the machine PXE boots from.
466+
467+
468+2.0.0 (rc4)
469+===========
470+
471+Issues fixed in this release
472+----------------------------
473+
474+LP: #1592666 Mirror URL contains double slash (/) after hostname, impacting proxy cachaility.
475+
476+LP: #1604461 [2.0rc2] Static IP address are allowed to be created in a dynamic range.
477+
478+LP: #1610397 When juju adds containers to MAAS, ensure they inherit the parent machine domain name, if none is passed by Juju.
479+
480+LP: #1611342 UI error while generating a MAAS key (token).
481+
482+LP: #1610414 apiclient.maas_client.MAASClient.post() always sets an op in the query string
483+
484+
485+2.0.0 (rc3)
486+===========
487+
488+Issues fixed in this release
489+----------------------------
490+
491+LP: #1557434 For the MAAS CLI, mimic the error behaviour provided by argparse 1.1 on PyPI when insufficient arguments are given.
492+
493+LP: #1594991 MAAS displays every power query on the summarized view of node event log.
494+
495+LP: #1603147 Commissioning dropdown is grey and checkmarks are missing.
496+
497+LP: #1576116 [2.0rc1] MAAS does not respect default subnet's DNS server when choosing default DNS
498+
499+LP: #1600720 [2.0rc1] MAAS doesn't honor DNS settings for a subnet for DHCP
500+
501+LP: #1598028 [2.0] Loading latest machine events can make web browser unresponsive
502+
503+LP: #1604128 [2.0rc2] Unable to add a public SSH Key due to lp1604147
504+
505+LP: #1604169 [2.0] maas login yields "ImportError: No module named 'maasserver'"
506+
507+LP: #1604962 Fixes to correctly log cloud-init/curtin FAIL events in the node event log.
508+
509+LP: #1604987 Fixes to correctly log "mark_failed" events.
510+
511+LP: #1602721 [2.0rc2] Can't get node-results via cli/api
512+
513+LP: #1604465 [2.0] RackController.get_image_sync_status causes huge load on regiond process
514+
515+LP: #1598149 [2.0rc2] MAAS is not automatically monitoring timeouts for commissioning.
516+
517+LP: #1605252 [2.0] Error messaging about monitor expiry has been dropped
518+
519+
520+2.0.0 (rc2)
521+===========
522+
523+Issues fixed in this release
524+----------------------------
525+
526+LP: #1582070 Pick up wrong grub.cfg if another filesystem exists
527+
528+LP: #1599223 [2.0] confusing reverse DNS lookups because MAAS creates multiple PTR records
529+
530+LP: #1600259 [2.0] reverse DNS sometimes assigns FQDN where it should assign IFACE.FQDN
531+
532+LP: #1599997 [2.0rc1] after upgrade from 2.0b3, Error on request (13) subnet.list: 'NoneType' object is not iterable
533+
534+LP: #1598461 [2.0rc1] Image import dates are inconsistent
535+
536+LP: #1598937 [2.0rc1] Following fresh install maas command fails - PermissionError: [Errno 13] Permission denied: '/home/ubuntu/.maascli.db'
537+
538+LP: #1597787 [1.9.3,2.0] cannot create more than 4 partitions when disk is configured with mbr
539+
540+LP: #1600267 [1.9,2.0,UX] Can't add aliases when parent interface is set to 'DCHP'
541+
542+LP: #1600198 [1.9,2.0,UX] Creating a Bcache disk is not prevented when is not created in partition
543+
544+
545+>>>>>>> MERGE-SOURCE
546 2.0.0 (rc1)
547 ===========
548
549 Issues fixed in this release
550 ----------------------------
551
552+LP: #1576357 Determine a method for how to reconnect a deleted rack controller
553+
554+LP: #1592246 [2.0b7, regression] maas-rack register makes up a new hostname
555+
556+LP: #1595753 [beta8] HMC power driver regression -- Not able to connect via SSH.
557+
558+LP: #1592885 [2.0b7] Date and time format should be consistent accross logs
559+
560+LP: #1597324 [2.0b8] Unable to set default gateway interface
561+
562+LP: #1515188 [1.9] VMware power management fails when VMs are organized in nested subfolders
563+
564+LP: #1596046 [2.0] maas 2.0 pxeboot fails on PowerNV
565+
566+LP: #1600267 [1.9,2.0,UX] Can't add aliases when parent interface is set to 'DCHP'
567+
568+LP: #1598937 [2.0 rc1] Following fresh install maas command fails - PermissionError: [Errno 13] Permission denied: '/home/ubuntu/.maascli.db'
569+
570+2.0.0 (beta8)
571+=============
572+
573+Issues fixed in this release
574+----------------------------
575+
576 LP: #1590081 Allow ed25519 and ecdsa ssh keys
577
578 LP: #1462078 [2.0b2, UI] Can't add a device and it does not show why
579@@ -1344,6 +1865,7 @@
580
581 .. _1553617:
582 https://launchpad.net/bugs/1553617
583+<<<<<<< TREE
584
585
586 1.9.1
587@@ -3978,3 +4500,5 @@
588 #1185897 expose ability to re-commission node in api and cli
589
590 #997092 Can't delete allocated node even if owned by self
591+=======
592+>>>>>>> MERGE-SOURCE
593
594=== modified file 'docs/index.rst'
595--- docs/index.rst 2016-08-15 09:54:43 +0000
596+++ docs/index.rst 2016-10-26 23:39:26 +0000
597@@ -6,6 +6,15 @@
598
599 This is the documentation for the `MAAS project`_.
600
601+This is API and developer documentation only. MAAS 2.0 user documentation is
602+published on `http://maas.io_` and its source is found on `GitHub_`.
603+
604+.. _http://maas.io:
605+ http://maas.io/docs/
606+
607+.. _GitHub:
608+ https://github.com/CanonicalLtd/maas-docs
609+
610 Metal as a Service -- MAAS -- lets you treat physical servers like
611 virtual machines in the cloud. Rather than having to manage each
612 server individually, MAAS turns your bare metal into an elastic
613
614=== modified file 'src/apiclient/tests/test_maas_client.py'
615=== modified file 'src/maas/settings.py'
616=== modified file 'src/maasserver/api/account.py'
617--- src/maasserver/api/account.py 2016-10-12 15:26:17 +0000
618+++ src/maasserver/api/account.py 2016-10-26 23:39:26 +0000
619@@ -5,12 +5,21 @@
620
621 __all__ = [
622 'AccountHandler',
623+<<<<<<< TREE
624 ]
625
626 import http.client
627 import json
628
629 from django.http import HttpResponse
630+=======
631+ ]
632+
633+import http.client
634+import json
635+
636+from django.http import HttpResponse
637+>>>>>>> MERGE-SOURCE
638 from maasserver.api.support import (
639 operation,
640 OperationsHandler,
641@@ -55,16 +64,30 @@
642
643 """
644 profile = request.user.userprofile
645+<<<<<<< TREE
646 consumer_name = get_optional_param(request.data, 'name')
647 consumer, token = profile.create_authorisation_token(consumer_name)
648 auth_info = {
649+=======
650+ consumer, token = profile.create_authorisation_token()
651+ auth_info = {
652+>>>>>>> MERGE-SOURCE
653 'token_key': token.key, 'token_secret': token.secret,
654+<<<<<<< TREE
655 'consumer_key': consumer.key, 'name': consumer.name
656 }
657 return HttpResponse(
658 json.dumps(auth_info),
659 content_type='application/json; charset=utf-8',
660 status=int(http.client.OK))
661+=======
662+ 'consumer_key': consumer.key,
663+ }
664+ return HttpResponse(
665+ json.dumps(auth_info),
666+ content_type='application/json; charset=utf-8',
667+ status=int(http.client.OK))
668+>>>>>>> MERGE-SOURCE
669
670 @operation(idempotent=False)
671 def delete_authorisation_token(self, request):
672
673=== modified file 'src/maasserver/api/blockdevices.py'
674=== modified file 'src/maasserver/api/boot_resources.py'
675=== modified file 'src/maasserver/api/events.py'
676=== modified file 'src/maasserver/api/interfaces.py'
677--- src/maasserver/api/interfaces.py 2016-10-20 16:04:24 +0000
678+++ src/maasserver/api/interfaces.py 2016-10-26 23:39:26 +0000
679@@ -415,6 +415,7 @@
680 :param parents: Parent interfaces that make this bond.
681
682 Fields for VLAN interface:
683+<<<<<<< TREE
684
685 :param tags: Tags for the interface.
686 :param vlan: Tagged VLAN the interface is connected to.
687@@ -424,6 +425,9 @@
688
689 :param name: Name of the interface.
690 :param mac_address: MAC address of the interface.
691+=======
692+
693+>>>>>>> MERGE-SOURCE
694 :param tags: Tags for the interface.
695 :param vlan: VLAN the interface is connected to.
696 :param parent: Parent interface for this bridge interface.
697
698=== modified file 'src/maasserver/api/ip_addresses.py'
699=== modified file 'src/maasserver/api/machines.py'
700=== modified file 'src/maasserver/api/tests/test_api.py'
701=== modified file 'src/maasserver/api/tests/test_blockdevice.py'
702=== modified file 'src/maasserver/api/tests/test_ipaddresses.py'
703--- src/maasserver/api/tests/test_ipaddresses.py 2016-10-18 16:31:31 +0000
704+++ src/maasserver/api/tests/test_ipaddresses.py 2016-10-26 23:39:26 +0000
705@@ -25,6 +25,7 @@
706 )
707 from maasserver.testing.factory import factory
708 from maasserver.utils.converters import json_load_bytes
709+<<<<<<< TREE
710 from maasserver.utils.orm import (
711 reload_object,
712 transactional,
713@@ -375,6 +376,14 @@
714 def setUp(self):
715 register_view("maasserver_discovery")
716 return super().setUp()
717+=======
718+from maasserver.utils.orm import reload_object
719+from netaddr import IPAddress
720+from testtools.matchers import Equals
721+
722+
723+class TestIPAddressesAPI(APITestCase.ForUser):
724+>>>>>>> MERGE-SOURCE
725
726 def post_reservation_request(
727 self, subnet=None, ip_address=None, network=None, mac=None,
728
729=== modified file 'src/maasserver/api/tests/test_machine.py'
730--- src/maasserver/api/tests/test_machine.py 2016-10-20 19:43:37 +0000
731+++ src/maasserver/api/tests/test_machine.py 2016-10-26 23:39:26 +0000
732@@ -1231,6 +1231,43 @@
733 machine = reload_object(machine)
734 self.assertEqual(old_zone, machine.zone)
735
736+<<<<<<< TREE
737+=======
738+ def test_PUT_sets_disable_ipv4(self):
739+ self.become_admin()
740+ original_setting = factory.pick_bool()
741+ machine = factory.make_Node(
742+ owner=self.user,
743+ architecture=make_usable_architecture(self),
744+ power_type='manual',
745+ disable_ipv4=original_setting)
746+ new_setting = not original_setting
747+
748+ response = self.client.put(
749+ self.get_machine_uri(machine), {'disable_ipv4': new_setting})
750+ self.assertEqual(http.client.OK, response.status_code)
751+
752+ machine = reload_object(machine)
753+ self.assertEqual(new_setting, machine.disable_ipv4)
754+
755+ def test_PUT_leaves_disable_ipv4_unchanged_by_default(self):
756+ self.become_admin()
757+ original_setting = factory.pick_bool()
758+ machine = factory.make_Node(
759+ owner=self.user,
760+ architecture=make_usable_architecture(self),
761+ power_type='manual',
762+ disable_ipv4=original_setting)
763+ self.assertEqual(original_setting, machine.disable_ipv4)
764+
765+ response = self.client.put(
766+ self.get_machine_uri(machine), {'zone': factory.make_Zone()})
767+ self.assertEqual(http.client.OK, response.status_code)
768+
769+ machine = reload_object(machine)
770+ self.assertEqual(original_setting, machine.disable_ipv4)
771+
772+>>>>>>> MERGE-SOURCE
773 def test_PUT_updates_swap_size(self):
774 self.become_admin()
775 machine = factory.make_Node(
776
777=== modified file 'src/maasserver/api/tests/test_machines.py'
778--- src/maasserver/api/tests/test_machines.py 2016-10-26 21:16:16 +0000
779+++ src/maasserver/api/tests/test_machines.py 2016-10-26 23:39:26 +0000
780@@ -204,6 +204,7 @@
781 "Select a valid choice. %s is not one of the "
782 "available choices." % power_type, validation_errors[0])
783
784+<<<<<<< TREE
785 def test_POST_new_handles_empty_str_power_parameters(self):
786 # Regression test for LP:1636858
787 response = self.client.post(
788@@ -243,6 +244,25 @@
789 power_address, machine.power_parameters['power_address'])
790 self.assertEqual(power_id, machine.power_parameters['power_id'])
791
792+=======
793+ def test_POST_new_handles_empty_str_power_parameters(self):
794+ # Regression test for LP:1636858
795+ response = self.client.post(
796+ reverse('machines_handler'),
797+ {
798+ 'architecture': make_usable_architecture(self),
799+ 'mac_addresses': ['aa:bb:cc:dd:ee:ff'],
800+ 'power_type': '',
801+ 'power_parameters': '',
802+ })
803+ self.assertEqual(http.client.OK, response.status_code)
804+ system_id = json.loads(
805+ response.content.decode(settings.DEFAULT_CHARSET))['system_id']
806+ machine = Machine.objects.get(system_id=system_id)
807+ self.assertEquals('', machine.power_type)
808+ self.assertItemsEqual({}, machine.power_parameters)
809+
810+>>>>>>> MERGE-SOURCE
811 def test_GET_lists_machines(self):
812 # The api allows for fetching the list of Machines.
813 machine1 = factory.make_Node()
814
815=== modified file 'src/maasserver/api/tests/test_node.py'
816=== modified file 'src/maasserver/api/utils.py'
817=== modified file 'src/maasserver/bootresources.py'
818--- src/maasserver/bootresources.py 2016-10-12 15:26:17 +0000
819+++ src/maasserver/bootresources.py 2016-10-26 23:39:26 +0000
820@@ -733,6 +733,7 @@
821 {'sha256': rfile.largefile.sha256})
822 maaslog.debug("Finalizing boot image %s.", ident)
823
824+<<<<<<< TREE
825 # Ensure that the size of the largefile starts at zero.
826 rfile.largefile.size = 0
827 transactional(rfile.largefile.save)(update_fields=['size'])
828@@ -745,10 +746,21 @@
829 database per chunk. This makes the process be reported correctly.
830 """
831 with rfile.largefile.content.open('wb') as stream:
832+=======
833+ # Ensure that the size of the largefile starts at zero.
834+ rfile.largefile.size = 0
835+ rfile.largefile.save(update_fields=['size'])
836+
837+ # Write the contents into the database, while calculating the sha256
838+ # hash for the read data.
839+ with rfile.largefile.content.open('wb') as stream:
840+ while True:
841+>>>>>>> MERGE-SOURCE
842 buf = reader.read(self.read_size)
843 stream.seek(0, 2)
844 stream.write(buf)
845 cksummer.update(buf)
846+<<<<<<< TREE
847 buf_len = len(buf)
848 rfile.largefile.size += buf_len
849 rfile.largefile.save(update_fields=['size'])
850@@ -765,6 +777,13 @@
851 # Don't check the checksum if finalization was cancelled.
852 if self._cancel_finalize:
853 return
854+=======
855+ buf_len = len(buf)
856+ rfile.largefile.size += buf_len
857+ rfile.largefile.save(update_fields=['size'])
858+ if buf_len != self.read_size:
859+ break
860+>>>>>>> MERGE-SOURCE
861
862 if not cksummer.check():
863 # Calculated sha256 hash from the data does not match, what
864@@ -1091,6 +1110,7 @@
865 def insert_item(self, data, src, target, pedigree, contentsource):
866 """Overridable from `BasicMirrorWriter`."""
867 item = sutil.products_exdata(src, pedigree)
868+<<<<<<< TREE
869 product_name = pedigree[0]
870 version_name = pedigree[1]
871 versions = src['products'][product_name]['versions']
872@@ -1110,6 +1130,23 @@
873 return
874 else:
875 self.store.insert(item, contentsource)
876+=======
877+ product_name = pedigree[0]
878+ version_name = pedigree[1]
879+ versions = src['products'][product_name]['versions']
880+ items = versions[version_name]['items']
881+ if (
882+ item['ftype'] == BOOT_RESOURCE_FILE_TYPE.ROOT_IMAGE and
883+ BOOT_RESOURCE_FILE_TYPE.SQUASHFS_IMAGE in items.keys()):
884+ # If both a SquashFS and root-image.gz are available only insert
885+ # the SquashFS image.
886+ return
887+ elif item['ftype'] not in dict(BOOT_RESOURCE_FILE_TYPE_CHOICES).keys():
888+ # Skip filetypes that we don't know about.
889+ return
890+ else:
891+ self.store.insert(item, contentsource)
892+>>>>>>> MERGE-SOURCE
893
894
895 def download_boot_resources(path, store, product_mapping,
896
897=== modified file 'src/maasserver/dhcp.py'
898--- src/maasserver/dhcp.py 2016-10-12 15:26:17 +0000
899+++ src/maasserver/dhcp.py 2016-10-26 23:39:26 +0000
900@@ -372,6 +372,7 @@
901
902 @typed
903 def make_subnet_config(
904+<<<<<<< TREE
905 rack_controller, subnet, maas_dns_server,
906 ntp_servers: Union[list, dict], default_domain,
907 failover_peer=None, subnets_dhcp_snippets: list=None):
908@@ -381,7 +382,13 @@
909 include in DHCP responses, or a dict; if the latter, it ought to match
910 the output from `get_ntp_server_addresses_for_rack`.
911 """
912+=======
913+ rack_controller, subnet, maas_dns_server, ntp_server, default_domain,
914+ failover_peer=None, subnets_dhcp_snippets=[]):
915+ """Return DHCP subnet configuration dict for a rack interface."""
916+>>>>>>> MERGE-SOURCE
917 ip_network = subnet.get_ipnetwork()
918+<<<<<<< TREE
919 if subnet.dns_servers is not None and len(subnet.dns_servers) > 0:
920 # Replace MAAS DNS with the servers defined on the subnet.
921 dns_servers = [IPAddress(server) for server in subnet.dns_servers]
922@@ -400,6 +407,15 @@
923 else:
924 ntp_servers = [ntp_server]
925
926+=======
927+ if subnet.dns_servers is not None and len(subnet.dns_servers) > 0:
928+ # Replace MAAS DNS with the servers defined on the subnet.
929+ dns_servers = ", ".join(subnet.dns_servers)
930+ elif maas_dns_server is not None and len(maas_dns_server) > 0:
931+ dns_servers = maas_dns_server
932+ else:
933+ dns_servers = ""
934+>>>>>>> MERGE-SOURCE
935 return {
936 'subnet': str(ip_network.network),
937 'subnet_mask': str(ip_network.netmask),
938@@ -486,7 +502,11 @@
939 for subnet in subnets:
940 subnet_configs.append(
941 make_subnet_config(
942+<<<<<<< TREE
943 rack_controller, subnet, maas_dns_server, ntp_servers,
944+=======
945+ rack_controller, subnet, maas_dns_server, ntp_server,
946+>>>>>>> MERGE-SOURCE
947 domain, peer_name, subnets_dhcp_snippets))
948
949 # Generate the hosts for all subnets.
950@@ -558,6 +578,7 @@
951 for vlan, (subnets_v4, subnets_v6) in vlan_subnets.items():
952 # IPv4
953 if len(subnets_v4) > 0:
954+<<<<<<< TREE
955 try:
956 config = get_dhcp_configure_for(
957 4, rack_controller, vlan, subnets_v4, ntp_servers,
958@@ -580,8 +601,33 @@
959 })
960 hosts_v4.extend(hosts)
961 interfaces_v4.add(interface)
962+=======
963+ try:
964+ config = get_dhcp_configure_for(
965+ 4, rack_controller, vlan, subnets_v4, ntp_server,
966+ default_domain, dhcp_snippets)
967+ except DHCPConfigurationError as e:
968+ # XXX bug #1602412: this silently breaks DHCPv4, but we cannot
969+ # allow it to crash here since DHCPv6 might be able to run.
970+ # This error may be irrelevant if there is an IPv4 network in
971+ # the MAAS model which is not configured on the rack, and the
972+ # user only wants to serve DHCPv6. But it is still something
973+ # worth noting, so log it and continue.
974+ log.err(e)
975+ else:
976+ failover_peer, subnets, hosts, interface = config
977+ if failover_peer is not None:
978+ failover_peers_v4.append(failover_peer)
979+ shared_networks_v4.append({
980+ "name": "vlan-%d" % vlan.id,
981+ "subnets": subnets,
982+ })
983+ hosts_v4.extend(hosts)
984+ interfaces_v4.add(interface)
985+>>>>>>> MERGE-SOURCE
986 # IPv6
987 if len(subnets_v6) > 0:
988+<<<<<<< TREE
989 try:
990 config = get_dhcp_configure_for(
991 6, rack_controller, vlan, subnets_v6,
992@@ -605,6 +651,32 @@
993 hosts_v6.extend(hosts)
994 interfaces_v6.add(interface)
995 return DHCPConfigurationForRack(
996+=======
997+ try:
998+ config = get_dhcp_configure_for(
999+ 6, rack_controller, vlan, subnets_v6,
1000+ ntp_server, default_domain, dhcp_snippets)
1001+ except DHCPConfigurationError as e:
1002+ # XXX bug #1602412: this silently breaks DHCPv6, but we cannot
1003+ # allow it to crash here since DHCPv4 might be able to run.
1004+ # This error may be irrelevant if there is an IPv6 network in
1005+ # the MAAS model which is not configured on the rack, and the
1006+ # user only wants to serve DHCPv4. But it is still something
1007+ # worth noting, so log it and continue.
1008+ log.err(e)
1009+ else:
1010+ failover_peer, subnets, hosts, interface = config
1011+ if failover_peer is not None:
1012+ failover_peers_v6.append(failover_peer)
1013+ shared_networks_v6.append({
1014+ "name": "vlan-%d" % vlan.id,
1015+ "subnets": subnets,
1016+ })
1017+ hosts_v6.extend(hosts)
1018+ interfaces_v6.add(interface)
1019+ return (
1020+ get_omapi_key(),
1021+>>>>>>> MERGE-SOURCE
1022 failover_peers_v4, shared_networks_v4, hosts_v4, interfaces_v4,
1023 failover_peers_v6, shared_networks_v6, hosts_v6, interfaces_v6,
1024 get_omapi_key(), global_dhcp_snippets)
1025
1026=== modified file 'src/maasserver/dns/tests/test_zonegenerator.py'
1027=== modified file 'src/maasserver/dns/zonegenerator.py'
1028=== modified file 'src/maasserver/enum.py'
1029--- src/maasserver/enum.py 2016-09-08 17:26:54 +0000
1030+++ src/maasserver/enum.py 2016-10-26 23:39:26 +0000
1031@@ -341,9 +341,15 @@
1032 #: Root Image (gets converted to root-image root-tgz, on the rack)
1033 ROOT_IMAGE = 'root-image.gz'
1034
1035+<<<<<<< TREE
1036 #: Root image in SquashFS form, does not need to be converted
1037 SQUASHFS_IMAGE = 'squashfs'
1038
1039+=======
1040+ # Root image in SquashFS form, does not need to be converted
1041+ SQUASHFS_IMAGE = 'squashfs'
1042+
1043+>>>>>>> MERGE-SOURCE
1044 #: Boot Kernel (ISCSI kernel)
1045 BOOT_KERNEL = 'boot-kernel'
1046
1047
1048=== modified file 'src/maasserver/forms.py'
1049=== modified file 'src/maasserver/forms_interface_link.py'
1050=== modified file 'src/maasserver/forms_settings.py'
1051=== added file 'src/maasserver/migrations/builtin/maasserver/0066_allow_squashfs.py'
1052--- src/maasserver/migrations/builtin/maasserver/0066_allow_squashfs.py 1970-01-01 00:00:00 +0000
1053+++ src/maasserver/migrations/builtin/maasserver/0066_allow_squashfs.py 2016-10-26 23:39:26 +0000
1054@@ -0,0 +1,22 @@
1055+# -*- coding: utf-8 -*-
1056+from __future__ import unicode_literals
1057+
1058+from django.db import (
1059+ migrations,
1060+ models,
1061+)
1062+
1063+
1064+class Migration(migrations.Migration):
1065+
1066+ dependencies = [
1067+ ('maasserver', '0065_larger_osystem_and_distro_series'),
1068+ ]
1069+
1070+ operations = [
1071+ migrations.AlterField(
1072+ model_name='bootresourcefile',
1073+ name='filetype',
1074+ field=models.CharField(choices=[('root-tgz', 'Root Image (tar.gz)'), ('root-dd', 'Root Compressed DD (dd -> tar.gz)'), ('root-image.gz', 'Compressed Root Image'), ('squashfs', 'SquashFS Root Image'), ('boot-kernel', 'Linux ISCSI Kernel'), ('boot-initrd', 'Initial ISCSI Ramdisk'), ('boot-dtb', 'ISCSI Device Tree Blob')], default='root-tgz', max_length=20, editable=False),
1075+ ),
1076+ ]
1077
1078=== renamed file 'src/maasserver/migrations/builtin/maasserver/0066_allow_squashfs.py' => 'src/maasserver/migrations/builtin/maasserver/0066_allow_squashfs.py.moved'
1079=== modified file 'src/maasserver/models/bootresource.py'
1080=== modified file 'src/maasserver/models/node.py'
1081--- src/maasserver/models/node.py 2016-10-26 18:28:12 +0000
1082+++ src/maasserver/models/node.py 2016-10-26 23:39:26 +0000
1083@@ -123,8 +123,12 @@
1084 COMMISSIONING_LIKE_STATUSES,
1085 get_failed_status,
1086 is_failed_status,
1087+<<<<<<< TREE
1088 MONITORED_STATUSES,
1089 NODE_FAILURE_MONITORED_STATUS_TIMEOUTS,
1090+=======
1091+ NODE_FAILURE_MONITORED_STATUS_TIMEOUTS,
1092+>>>>>>> MERGE-SOURCE
1093 NODE_TRANSITIONS,
1094 )
1095 from maasserver.rpc import (
1096@@ -220,23 +224,57 @@
1097
1098 # Return type from `get_effective_power_info`.
1099 PowerInfo = namedtuple("PowerInfo", (
1100- "can_be_started",
1101- "can_be_stopped",
1102- "can_be_queried",
1103- "power_type",
1104- "power_parameters",
1105-))
1106-
1107-DefaultGateways = namedtuple("DefaultGateways", (
1108- "ipv4",
1109- "ipv6",
1110-))
1111-
1112-GatewayDefinition = namedtuple("GatewayDefinition", (
1113- "interface_id",
1114- "subnet_id",
1115- "gateway_ip",
1116-))
1117+<<<<<<< TREE
1118+ "can_be_started",
1119+ "can_be_stopped",
1120+ "can_be_queried",
1121+ "power_type",
1122+ "power_parameters",
1123+))
1124+
1125+DefaultGateways = namedtuple("DefaultGateways", (
1126+ "ipv4",
1127+ "ipv6",
1128+))
1129+
1130+GatewayDefinition = namedtuple("GatewayDefinition", (
1131+ "interface_id",
1132+ "subnet_id",
1133+ "gateway_ip",
1134+))
1135+=======
1136+ "can_be_started",
1137+ "can_be_stopped",
1138+ "can_be_queried",
1139+ "power_type",
1140+ "power_parameters",
1141+))
1142+
1143+DefaultGateways = namedtuple("DefaultGateways", (
1144+ "ipv4",
1145+ "ipv6",
1146+))
1147+
1148+GatewayDefinition = namedtuple("GatewayDefinition", (
1149+ "interface_id",
1150+ "subnet_id",
1151+ "gateway_ip",
1152+))
1153+
1154+# The sequence from which the decimal form of node system IDs should be
1155+# pulled. At the time of writing this should match the definition in migration
1156+# 0021_create_node_system_id_sequence, and vice-versa.
1157+node_system_id = Sequence(
1158+ "maasserver_node_system_id_seq", cycle=False,
1159+ minvalue=(24 ** 5), maxvalue=((24 ** 6) - 1),
1160+ start=15600471, owner="maasserver_node.system_id",
1161+)
1162+
1163+
1164+def generate_node_system_ids():
1165+ """Return an iterable that yields short system IDs."""
1166+ return map(znums.from_int, node_system_id)
1167+>>>>>>> MERGE-SOURCE
1168
1169
1170 def generate_node_system_id():
1171@@ -1127,6 +1165,7 @@
1172 This is the maximum time the commissioning is allowed to take.
1173 """
1174 # Return a *very* conservative estimate for now.
1175+<<<<<<< TREE
1176 return timedelta(
1177 minutes=NODE_FAILURE_MONITORED_STATUS_TIMEOUTS[
1178 NODE_STATUS.COMMISSIONING]).total_seconds()
1179@@ -1140,6 +1179,11 @@
1180 return timedelta(
1181 minutes=NODE_FAILURE_MONITORED_STATUS_TIMEOUTS[
1182 NODE_STATUS.ENTERING_RESCUE_MODE]).total_seconds()
1183+=======
1184+ return timedelta(
1185+ minutes=NODE_FAILURE_MONITORED_STATUS_TIMEOUTS[
1186+ NODE_STATUS.COMMISSIONING]).total_seconds()
1187+>>>>>>> MERGE-SOURCE
1188
1189 def get_releasing_time(self):
1190 """Return the releasing time of this node (in seconds).
1191@@ -2865,6 +2909,7 @@
1192 if not gateway_ipv6:
1193 gateway_ipv6 = self._get_gateway_tuple_by_family(
1194 found_gateways, IPADDRESS_FAMILY.IPv6)
1195+<<<<<<< TREE
1196 return DefaultGateways._make((gateway_ipv4, gateway_ipv6))
1197
1198 def get_default_dns_servers(self, ipv4=True, ipv6=True):
1199@@ -2907,6 +2952,49 @@
1200 ipv4=(ipv4 and gateways.ipv4 is not None),
1201 ipv6=(ipv6 and gateways.ipv6 is not None))
1202 return [maas_dns_server]
1203+=======
1204+ return DefaultGateways._make((gateway_ipv4, gateway_ipv6))
1205+
1206+ def get_default_dns_servers(self):
1207+ """Return the default DNS servers for this node."""
1208+ # Circular imports.
1209+ from maasserver.dns.zonegenerator import get_dns_server_address
1210+
1211+ gateways = self.get_default_gateways()
1212+
1213+ # Try first to use DNS servers from default gateway subnets.
1214+ if gateways.ipv4 is not None:
1215+ subnet = Subnet.objects.get(id=gateways.ipv4.subnet_id)
1216+ if subnet.dns_servers is not None and len(subnet.dns_servers) > 0:
1217+ # An IPv4 subnet is hosting the default gateway and has DNS
1218+ # servers defined. IPv4 DNS servers take first-priority.
1219+ return subnet.dns_servers
1220+ if gateways.ipv6 is not None:
1221+ subnet = Subnet.objects.get(id=gateways.ipv6.subnet_id)
1222+ if subnet.dns_servers is not None and len(subnet.dns_servers) > 0:
1223+ # An IPv6 subnet is hosting the default gateway and has DNS
1224+ # servers defined. IPv6 DNS servers take second-priority.
1225+ return subnet.dns_servers
1226+
1227+ # No default gateway subnet has specific DNS servers defined, so
1228+ # use MAAS for the default DNS server.
1229+ if gateways.ipv4 is None and gateways.ipv6 is None:
1230+ # If there are no default gateways, the default is the MAAS
1231+ # region IP address.
1232+ maas_dns_server = get_dns_server_address(
1233+ rack_controller=self.get_boot_rack_controller())
1234+ else:
1235+ # Choose an address consistent with the primary address-family
1236+ # in use, as indicated by the presence (or not) of a gateway.
1237+ # Note that this path is only taken if the MAAS URL is set to
1238+ # a hostname, and the hostname resolves to both an IPv4 and an
1239+ # IPv6 address.
1240+ maas_dns_server = get_dns_server_address(
1241+ rack_controller=self.get_boot_rack_controller(),
1242+ ipv4=(gateways.ipv4 is not None),
1243+ ipv6=(gateways.ipv6 is not None))
1244+ return [maas_dns_server]
1245+>>>>>>> MERGE-SOURCE
1246
1247 def get_boot_purpose(self):
1248 """
1249
1250=== modified file 'src/maasserver/models/staticipaddress.py'
1251--- src/maasserver/models/staticipaddress.py 2016-10-17 23:38:43 +0000
1252+++ src/maasserver/models/staticipaddress.py 2016-10-26 23:39:26 +0000
1253@@ -298,6 +298,11 @@
1254 # DISTINCT ON returns the first matching row for any given
1255 # hostname, using the query's ordering. Here, we're trying to
1256 # return the IPs for the oldest Interface address.
1257+<<<<<<< TREE
1258+=======
1259+ #
1260+ # For nodes that have disable_ipv4 set, leave out any IPv4 address.
1261+>>>>>>> MERGE-SOURCE
1262 default_ttl = "%d" % Config.objects.get_config('default_dns_ttl')
1263 if raw_ttl:
1264 ttl_clause = """node.address_ttl"""
1265@@ -364,7 +369,15 @@
1266 query_parms = []
1267 sql_query += """
1268 staticip.ip IS NOT NULL AND
1269+<<<<<<< TREE
1270 host(staticip.ip) != ''
1271+=======
1272+ host(staticip.ip) != '' AND
1273+ (
1274+ node.disable_ipv4 IS FALSE OR
1275+ family(staticip.ip) <> 4
1276+ )
1277+>>>>>>> MERGE-SOURCE
1278 ORDER BY
1279 node.hostname,
1280 is_boot DESC,
1281@@ -400,6 +413,7 @@
1282 END,
1283 interface.id
1284 """
1285+<<<<<<< TREE
1286 iface_sql_query = """
1287 SELECT
1288 CONCAT(node.hostname, '.', domain.name) AS fqdn,
1289@@ -448,6 +462,58 @@
1290 assigned DESC, /* Return all assigned IPs for a node first. */
1291 interface.id
1292 """
1293+=======
1294+ iface_sql_query = """
1295+ SELECT
1296+ CONCAT(node.hostname, '.', domain.name) AS fqdn,
1297+ node.system_id,
1298+ node.node_type,
1299+ """ + ttl_clause + """ AS ttl,
1300+ staticip.ip,
1301+ interface.name
1302+ FROM
1303+ maasserver_interface AS interface
1304+ JOIN maasserver_node AS node ON
1305+ node.id = interface.node_id
1306+ JOIN maasserver_domain as domain ON
1307+ domain.id = node.domain_id
1308+ JOIN maasserver_interface_ip_addresses AS link ON
1309+ link.interface_id = interface.id
1310+ JOIN maasserver_staticipaddress AS staticip ON
1311+ staticip.id = link.staticipaddress_id
1312+ """
1313+ if isinstance(domain_or_subnet, Domain):
1314+ # This logic is similar to the logic in sql_query above.
1315+ iface_sql_query += """
1316+ LEFT JOIN maasserver_domain as domain2 ON
1317+ /* Pick up another copy of domain looking for instances of
1318+ * the name as the top of a domain.
1319+ */
1320+ domain2.name = CONCAT(
1321+ interface.name, '.', node.hostname, '.', domain.name)
1322+ WHERE
1323+ (domain2.name IS NOT NULL OR node.domain_id = %s) AND
1324+ """
1325+ else:
1326+ # For subnets, we need ALL the names, so that we can correctly
1327+ # identify which ones should have the FQDN. dns/zonegenerator.py
1328+ # optimizes based on this, and only calls once with a subnet,
1329+ # expecting to get all the subnets back in one table.
1330+ iface_sql_query += """
1331+ WHERE
1332+ """
1333+ iface_sql_query += """
1334+ staticip.ip IS NOT NULL AND
1335+ host(staticip.ip) != '' AND
1336+ (
1337+ node.disable_ipv4 IS FALSE OR
1338+ family(staticip.ip) <> 4
1339+ )
1340+ ORDER BY
1341+ node.hostname,
1342+ interface.id
1343+ """
1344+>>>>>>> MERGE-SOURCE
1345 # We get user reserved et al mappings first, so that we can overwrite
1346 # TTL as we process the return from the SQL horror above.
1347 mapping = self._get_user_reserved_mappings(domain_or_subnet)
1348@@ -456,14 +522,19 @@
1349 iface_is_boot = defaultdict(bool, {
1350 hostname: True for hostname in mapping.keys()
1351 })
1352+<<<<<<< TREE
1353 assigned_ips = defaultdict(bool)
1354 cursor.execute(sql_query, query_parms)
1355+=======
1356+ cursor.execute(sql_query, query_parms)
1357+>>>>>>> MERGE-SOURCE
1358 # The records from the query provide, for each hostname (after
1359 # stripping domain), the boot and non-boot interface ip address in ipv4
1360 # and ipv6. Our task: if there are boot interace IPs, they win. If
1361 # there are none, then whatever we got wins. The ORDER BY means that
1362 # we will see all of the boot interfaces before we see any non-boot
1363 # interface IPs. See Bug#1584850
1364+<<<<<<< TREE
1365 for (fqdn, system_id, node_type, ttl,
1366 ip, is_boot) in cursor.fetchall():
1367 mapping[fqdn].node_type = node_type
1368@@ -491,6 +562,29 @@
1369 mapping[name].system_id = system_id
1370 mapping[name].ttl = ttl
1371 mapping[name].ips.add(ip)
1372+=======
1373+ for (fqdn, system_id, node_type, ttl,
1374+ ip, is_boot) in cursor.fetchall():
1375+ mapping[fqdn].node_type = node_type
1376+ mapping[fqdn].system_id = system_id
1377+ mapping[fqdn].ttl = ttl
1378+ if is_boot:
1379+ iface_is_boot[fqdn] = True
1380+ # If we have an IP on the right interface type, save it.
1381+ if is_boot == iface_is_boot[fqdn]:
1382+ mapping[fqdn].ips.add(ip)
1383+ # Next, get all the addresses, on all the interfaces, and add the ones
1384+ # that are not already present on the FQDN as $IFACE.$FQDN.
1385+ cursor.execute(iface_sql_query, (domain_or_subnet.id,))
1386+ for (fqdn, system_id, node_type, ttl,
1387+ ip, iface_name) in cursor.fetchall():
1388+ if ip not in mapping[fqdn].ips:
1389+ name = "%s.%s" % (iface_name, fqdn)
1390+ mapping[name].node_type = node_type
1391+ mapping[name].system_id = system_id
1392+ mapping[name].ttl = ttl
1393+ mapping[name].ips.add(ip)
1394+>>>>>>> MERGE-SOURCE
1395 return mapping
1396
1397 def filter_by_ip_family(self, family):
1398
1399=== modified file 'src/maasserver/models/subnet.py'
1400=== modified file 'src/maasserver/models/tests/test_largefile.py'
1401=== modified file 'src/maasserver/models/tests/test_node.py'
1402--- src/maasserver/models/tests/test_node.py 2016-10-26 18:28:12 +0000
1403+++ src/maasserver/models/tests/test_node.py 2016-10-26 23:39:26 +0000
1404@@ -364,6 +364,27 @@
1405 racks_and_regions.add(factory.make_Node(node_type=node_type))
1406 self.assertItemsEqual(racks_and_regions, Controller.objects.all())
1407
1408+<<<<<<< TREE
1409+=======
1410+ def test_get_running_controller(self):
1411+ rack = factory.make_RackController()
1412+ self.useFixture(MAASIDFixture(rack.system_id))
1413+ self.assertEquals(rack, Controller.objects.get_running_controller())
1414+
1415+ def test_get_running_controller_can_ignore_cache(self):
1416+ # Store invalid value in cache
1417+ self.useFixture(MAASIDFixture(factory.make_string()))
1418+ rack = factory.make_RackController()
1419+ # Write valid value to disk
1420+ maas_id_path = get_path('/var/lib/maas/maas_id')
1421+ os.unlink(maas_id_path)
1422+ with open(maas_id_path, 'w') as fd:
1423+ fd.write(rack.system_id)
1424+ self.assertEquals(
1425+ rack, Controller.objects.get_running_controller(read_cache=False))
1426+ self.assertEquals(rack.system_id, get_maas_id())
1427+
1428+>>>>>>> MERGE-SOURCE
1429
1430 class TestRackControllerManager(MAASServerTestCase):
1431
1432@@ -4726,6 +4747,7 @@
1433 ), node.get_default_gateways())
1434
1435
1436+<<<<<<< TREE
1437 class TestGetDefaultDNSServers(MAASServerTestCase):
1438 """Tests for `Node.get_default_dns_servers`."""
1439
1440@@ -4901,6 +4923,161 @@
1441
1442
1443 class TestNode_Start(MAASTransactionServerTestCase):
1444+=======
1445+class TestGetDefaultDNSServers(MAASServerTestCase):
1446+ """Tests for `Node.get_default_dns_servers`."""
1447+
1448+ def make_Node_with_RackController(
1449+ self, ipv4=True, ipv6=True, ipv4_gateway=True, ipv6_gateway=True,
1450+ ipv4_subnet_dns=None, ipv6_subnet_dns=None):
1451+ ipv4_subnet_dns = [] if ipv4_subnet_dns is None else ipv4_subnet_dns
1452+ ipv6_subnet_dns = [] if ipv6_subnet_dns is None else ipv6_subnet_dns
1453+ rack_v4 = None
1454+ rack_v6 = None
1455+ fabric = factory.make_Fabric()
1456+ vlan = fabric.get_default_vlan()
1457+ if ipv4:
1458+ gateway_ip = None if ipv4_gateway else ""
1459+ v4_subnet = factory.make_Subnet(
1460+ version=4, vlan=vlan, dns_servers=ipv4_subnet_dns,
1461+ gateway_ip=gateway_ip)
1462+ if ipv6:
1463+ gateway_ip = None if ipv6_gateway else ""
1464+ v6_subnet = factory.make_Subnet(
1465+ version=6, vlan=vlan, dns_servers=ipv6_subnet_dns,
1466+ gateway_ip=gateway_ip)
1467+ rack = factory.make_RegionRackController()
1468+ vlan.primary_rack = rack
1469+ vlan.dhcp_on = True
1470+ vlan.save()
1471+ # In order to determine the correct IP address per-address-family,
1472+ # a name lookup is performed on the hostname part of the URL.
1473+ # We need to mock that so we can return whatever IP addresses it
1474+ # resolves to.
1475+ rack.url = "http://region:5240/MAAS/"
1476+ if ipv4:
1477+ rack_v4 = factory.pick_ip_in_Subnet(v4_subnet)
1478+ if ipv6:
1479+ rack_v6 = factory.pick_ip_in_Subnet(v6_subnet)
1480+
1481+ def get_address(hostname, ip_version=4):
1482+ """Mock function to return the IP address of the rack based on the
1483+ given address family.
1484+ """
1485+ if ip_version == 4:
1486+ return {IPAddress(rack_v4)} if rack_v4 else set()
1487+ elif ip_version == 6:
1488+ return {IPAddress(rack_v6)} if rack_v6 else set()
1489+
1490+ resolve_hostname = self.patch(
1491+ server_address_module, 'resolve_hostname')
1492+ resolve_hostname.side_effect = get_address
1493+ rack.interface_set.all().delete()
1494+ rackif = factory.make_Interface(vlan=vlan, node=rack)
1495+ if ipv4:
1496+ rackif.link_subnet(INTERFACE_LINK_TYPE.STATIC, v4_subnet, rack_v4)
1497+ if ipv6:
1498+ rackif.link_subnet(INTERFACE_LINK_TYPE.STATIC, v6_subnet, rack_v6)
1499+ rack.boot_interface = rackif
1500+ rack.save()
1501+ node = factory.make_Node(status=NODE_STATUS.READY, disable_ipv4=False)
1502+ nodeif = factory.make_Interface(vlan=vlan, node=node)
1503+ if ipv4:
1504+ nodeif.link_subnet(INTERFACE_LINK_TYPE.AUTO, v4_subnet)
1505+ if ipv6:
1506+ nodeif.link_subnet(INTERFACE_LINK_TYPE.AUTO, v6_subnet)
1507+ node.boot_interface = nodeif
1508+ node.save()
1509+ return rack_v4, rack_v6, node
1510+
1511+ def test__uses_rack_ipv4_if_ipv4_only_with_no_gateway(self):
1512+ rack_v4, rack_v6, node = self.make_Node_with_RackController(
1513+ ipv4=True, ipv4_gateway=False, ipv6=False, ipv6_gateway=False)
1514+ self.assertThat(node.get_default_dns_servers(), Equals([rack_v4]))
1515+
1516+ def test__uses_rack_ipv4_if_ipv4_only_with_no_gateway_v4_dns(self):
1517+ ipv4_subnet_dns = factory.make_ip_address(ipv6=False)
1518+ rack_v4, rack_v6, node = self.make_Node_with_RackController(
1519+ ipv4=True, ipv4_gateway=False, ipv6=False, ipv6_gateway=False,
1520+ ipv4_subnet_dns=[ipv4_subnet_dns])
1521+ self.assertThat(
1522+ node.get_default_dns_servers(), Equals([rack_v4]))
1523+
1524+ def test__uses_rack_ipv6_if_ipv6_only_with_no_gateway_v6_dns(self):
1525+ ipv6_subnet_dns = factory.make_ip_address(ipv6=True)
1526+ rack_v4, rack_v6, node = self.make_Node_with_RackController(
1527+ ipv4=False, ipv4_gateway=False, ipv6=True, ipv6_gateway=False,
1528+ ipv6_subnet_dns=[ipv6_subnet_dns])
1529+ self.assertThat(node.get_default_dns_servers(), Equals([rack_v6]))
1530+
1531+ def test__uses_rack_ipv4_if_dual_stack_with_no_gateway(self):
1532+ rack_v4, rack_v6, node = self.make_Node_with_RackController(
1533+ ipv4=True, ipv4_gateway=False, ipv6=True, ipv6_gateway=False)
1534+ self.assertThat(node.get_default_dns_servers(), Equals([rack_v4]))
1535+
1536+ def test__uses_rack_ipv4_if_dual_stack_with_ipv4_gateway(self):
1537+ rack_v4, rack_v6, node = self.make_Node_with_RackController(
1538+ ipv4=True, ipv4_gateway=True, ipv6=True, ipv6_gateway=False)
1539+ self.assertThat(node.get_default_dns_servers(), Equals([rack_v4]))
1540+
1541+ def test__uses_subnet_ipv4_if_dual_stack_with_ipv4_gateway_with_dns(self):
1542+ ipv4_subnet_dns = factory.make_ip_address(ipv6=False)
1543+ ipv6_subnet_dns = factory.make_ip_address(ipv6=True)
1544+ rack_v4, rack_v6, node = self.make_Node_with_RackController(
1545+ ipv4=True, ipv4_gateway=True, ipv6=True, ipv6_gateway=False,
1546+ ipv4_subnet_dns=[ipv4_subnet_dns],
1547+ ipv6_subnet_dns=[ipv6_subnet_dns])
1548+ self.assertThat(
1549+ node.get_default_dns_servers(), Equals([ipv4_subnet_dns]))
1550+
1551+ def test__uses_rack_ipv6_if_dual_stack_with_ipv6_gateway(self):
1552+ rack_v4, rack_v6, node = self.make_Node_with_RackController(
1553+ ipv4=True, ipv4_gateway=False, ipv6=True, ipv6_gateway=True)
1554+ self.assertThat(node.get_default_dns_servers(), Equals([rack_v6]))
1555+
1556+ def test__uses_subnet_ipv6_if_dual_stack_with_ipv6_gateway(self):
1557+ ipv4_subnet_dns = factory.make_ip_address(ipv6=False)
1558+ ipv6_subnet_dns = factory.make_ip_address(ipv6=True)
1559+ rack_v4, rack_v6, node = self.make_Node_with_RackController(
1560+ ipv4=True, ipv4_gateway=False, ipv6=True, ipv6_gateway=True,
1561+ ipv4_subnet_dns=[ipv4_subnet_dns],
1562+ ipv6_subnet_dns=[ipv6_subnet_dns])
1563+ self.assertThat(
1564+ node.get_default_dns_servers(), Equals([ipv6_subnet_dns]))
1565+
1566+ def test__uses_rack_ipv4_if_ipv4_with_ipv4_gateway(self):
1567+ rack_v4, rack_v6, node = self.make_Node_with_RackController(
1568+ ipv4=True, ipv4_gateway=True, ipv6=False, ipv6_gateway=False)
1569+ self.assertThat(node.get_default_dns_servers(), Equals([rack_v4]))
1570+
1571+ def test__uses_subnet_ipv4_if_ipv4_stack_with_ipv4_gateway_and_dns(self):
1572+ ipv4_subnet_dns = factory.make_ip_address(ipv6=False)
1573+ ipv6_subnet_dns = factory.make_ip_address(ipv6=True)
1574+ rack_v4, rack_v6, node = self.make_Node_with_RackController(
1575+ ipv4=True, ipv4_gateway=True, ipv6=False, ipv6_gateway=False,
1576+ ipv4_subnet_dns=[ipv4_subnet_dns],
1577+ ipv6_subnet_dns=[ipv6_subnet_dns])
1578+ self.assertThat(
1579+ node.get_default_dns_servers(), Equals([ipv4_subnet_dns]))
1580+
1581+ def test__uses_rack_ipv6_if_ipv6_with_ipv6_gateway(self):
1582+ rack_v4, rack_v6, node = self.make_Node_with_RackController(
1583+ ipv4=False, ipv4_gateway=False, ipv6=True, ipv6_gateway=True)
1584+ self.assertThat(node.get_default_dns_servers(), Equals([rack_v6]))
1585+
1586+ def test__uses_subnet_ipv6_if_ipv6_with_ipv6_gateway_and_dns(self):
1587+ ipv4_subnet_dns = factory.make_ip_address(ipv6=False)
1588+ ipv6_subnet_dns = factory.make_ip_address(ipv6=True)
1589+ rack_v4, rack_v6, node = self.make_Node_with_RackController(
1590+ ipv4=False, ipv4_gateway=False, ipv6=True, ipv6_gateway=True,
1591+ ipv4_subnet_dns=[ipv4_subnet_dns],
1592+ ipv6_subnet_dns=[ipv6_subnet_dns])
1593+ self.assertThat(
1594+ node.get_default_dns_servers(), Equals([ipv6_subnet_dns]))
1595+
1596+
1597+class TestNode_Start(MAASServerTestCase):
1598+>>>>>>> MERGE-SOURCE
1599 """Tests for Node.start()."""
1600
1601 def setUp(self):
1602
1603=== modified file 'src/maasserver/models/tests/test_staticipaddress.py'
1604--- src/maasserver/models/tests/test_staticipaddress.py 2016-10-18 08:00:37 +0000
1605+++ src/maasserver/models/tests/test_staticipaddress.py 2016-10-26 23:39:26 +0000
1606@@ -363,6 +363,7 @@
1607 mapping = StaticIPAddress.objects.get_hostname_ip_mapping(domain)
1608 self.assertEqual(expected_mapping, mapping)
1609
1610+<<<<<<< TREE
1611 def test_get_hostname_ip_mapping_returns_all_mappings_for_subnet(self):
1612 domain = Domain.objects.get_default_domain()
1613 expected_mapping = {}
1614@@ -384,14 +385,42 @@
1615 self.assertEqual(expected_mapping, mapping)
1616
1617 def test_get_hostname_ip_mapping_returns_fqdn_and_other(self):
1618+=======
1619+ def test_get_hostname_ip_mapping_returns_all_mappings_for_subnet(self):
1620+ domain = Domain.objects.get_default_domain()
1621+ expected_mapping = {}
1622+ for _ in range(3):
1623+ node = factory.make_Node(interface=True, disable_ipv4=False)
1624+ boot_interface = node.get_boot_interface()
1625+ subnet = factory.make_Subnet()
1626+ staticip = factory.make_StaticIPAddress(
1627+ alloc_type=IPADDRESS_TYPE.STICKY,
1628+ ip=factory.pick_ip_in_Subnet(subnet),
1629+ subnet=subnet, interface=boot_interface)
1630+ full_hostname = "%s.%s" % (node.hostname, domain.name)
1631+ expected_mapping[full_hostname] = HostnameIPMapping(
1632+ node.system_id, 30, {staticip.ip}, node.node_type)
1633+ # See also LP#1600259. It doesn't matter what subnet is passed in, you
1634+ # get all of them.
1635+ mapping = StaticIPAddress.objects.get_hostname_ip_mapping(
1636+ Subnet.objects.first())
1637+ self.assertEqual(expected_mapping, mapping)
1638+
1639+ def test_get_hostname_ip_mapping_returns_fqdn_and_other(self):
1640+>>>>>>> MERGE-SOURCE
1641 hostname = factory.make_name('hostname')
1642 domainname = factory.make_name('domain')
1643 factory.make_Domain(name=domainname)
1644 full_hostname = "%s.%s" % (hostname, domainname)
1645 subnet = factory.make_Subnet()
1646 node = factory.make_Node_with_Interface_on_Subnet(
1647+<<<<<<< TREE
1648 interface=True, hostname=full_hostname, interface_count=3,
1649 subnet=subnet)
1650+=======
1651+ interface=True, hostname=full_hostname, interface_count=3,
1652+ subnet=subnet, disable_ipv4=False)
1653+>>>>>>> MERGE-SOURCE
1654 boot_interface = node.get_boot_interface()
1655 staticip = factory.make_StaticIPAddress(
1656 alloc_type=IPADDRESS_TYPE.STICKY,
1657@@ -516,9 +545,14 @@
1658 alloc_type=IPADDRESS_TYPE.STICKY,
1659 subnet=subnet, interface=boot_interface)
1660 newer_nic = factory.make_Interface(INTERFACE_TYPE.PHYSICAL, node=node)
1661+<<<<<<< TREE
1662 newer_ip = factory.make_StaticIPAddress(
1663 alloc_type=IPADDRESS_TYPE.STICKY, subnet=subnet,
1664 interface=newer_nic)
1665+=======
1666+ newer_ip = factory.make_StaticIPAddress(
1667+ alloc_type=IPADDRESS_TYPE.STICKY, interface=newer_nic)
1668+>>>>>>> MERGE-SOURCE
1669 mapping = StaticIPAddress.objects.get_hostname_ip_mapping(
1670 node.domain)
1671 expected_mapping = {
1672@@ -535,11 +569,20 @@
1673 interface=True, hostname=factory.make_name('host'))
1674 boot_interface = node.get_boot_interface()
1675 staticip = factory.make_StaticIPAddress(
1676+<<<<<<< TREE
1677 alloc_type=IPADDRESS_TYPE.STICKY, subnet=subnet,
1678 interface=boot_interface)
1679 nic = node.get_boot_interface() # equals boot_interface
1680 auto_ip = factory.make_StaticIPAddress(
1681 alloc_type=IPADDRESS_TYPE.AUTO, subnet=subnet, interface=nic)
1682+=======
1683+ alloc_type=IPADDRESS_TYPE.STICKY,
1684+ ip=factory.pick_ip_in_Subnet(subnet),
1685+ subnet=subnet, interface=boot_interface)
1686+ nic = node.get_boot_interface()
1687+ auto_ip = factory.make_StaticIPAddress(
1688+ alloc_type=IPADDRESS_TYPE.AUTO, interface=nic)
1689+>>>>>>> MERGE-SOURCE
1690 mapping = StaticIPAddress.objects.get_hostname_ip_mapping(
1691 node.domain)
1692 expected_mapping = {
1693@@ -605,15 +648,24 @@
1694 staticip = factory.make_StaticIPAddress(
1695 alloc_type=IPADDRESS_TYPE.AUTO, interface=iface,
1696 subnet=subnet)
1697- factory.make_StaticIPAddress(
1698+ discovered = factory.make_StaticIPAddress(
1699 alloc_type=IPADDRESS_TYPE.DISCOVERED, interface=iface,
1700 subnet=subnet)
1701 mapping = StaticIPAddress.objects.get_hostname_ip_mapping(
1702 node.domain)
1703+<<<<<<< TREE
1704 expected_mapping = {
1705 node.fqdn: HostnameIPMapping(
1706 node.system_id, 30, {staticip.ip}, node.node_type)}
1707 self.assertEqual(expected_mapping, mapping)
1708+=======
1709+ expected_mapping = {
1710+ node.fqdn: HostnameIPMapping(
1711+ node.system_id, 30, {staticip.ip}, node.node_type),
1712+ "%s.%s" % (iface.name, node.fqdn): HostnameIPMapping(
1713+ node.system_id, 30, {discovered.ip}, node.node_type)}
1714+ self.assertEqual(expected_mapping, mapping)
1715+>>>>>>> MERGE-SOURCE
1716
1717 def test_get_hostname_ip_mapping_prefers_bond_with_no_boot_interface(self):
1718 subnet = factory.make_Subnet(
1719@@ -831,7 +883,13 @@
1720 mapping = StaticIPAddress.objects.get_hostname_ip_mapping(domain)
1721 expected_mapping = {
1722 node.fqdn: HostnameIPMapping(
1723+<<<<<<< TREE
1724 node.system_id, 30, {sip0.ip}, node.node_type)}
1725+=======
1726+ node.system_id, 30, {sip0.ip}, node.node_type),
1727+ "%s.%s" % (iface1.name, node.fqdn): HostnameIPMapping(
1728+ node.system_id, 30, {sip1.ip}, node.node_type)}
1729+>>>>>>> MERGE-SOURCE
1730 self.assertEqual(expected_mapping, mapping)
1731
1732 def test_get_hostname_ip_mapping_returns_correct_bond_ip(self):
1733@@ -877,8 +935,15 @@
1734 mapping = StaticIPAddress.objects.get_hostname_ip_mapping(domain)
1735 expected_mapping = {
1736 node.fqdn: HostnameIPMapping(
1737- node.system_id, 30, {bond_sip.ip}, node.node_type),
1738- }
1739+<<<<<<< TREE
1740+ node.system_id, 30, {bond_sip.ip}, node.node_type),
1741+ }
1742+=======
1743+ node.system_id, 30, {bond_sip.ip}, node.node_type),
1744+ "%s.%s" % (iface1.name, node.fqdn): HostnameIPMapping(
1745+ node.system_id, 30, {sip1.ip}, node.node_type),
1746+ }
1747+>>>>>>> MERGE-SOURCE
1748 self.assertEqual(expected_mapping, mapping)
1749
1750
1751
1752=== modified file 'src/maasserver/models/tests/test_subnet.py'
1753=== modified file 'src/maasserver/node_status.py'
1754--- src/maasserver/node_status.py 2016-10-03 22:13:11 +0000
1755+++ src/maasserver/node_status.py 2016-10-26 23:39:26 +0000
1756@@ -218,6 +218,22 @@
1757 NODE_STATUS.EXITING_RESCUE_MODE: 5,
1758 }
1759
1760+# State transitions that are monitored for timeouts for when a node
1761+# fails:
1762+# Mapping between in-progress statuses and the corresponding failed
1763+# statuses.
1764+NODE_FAILURE_MONITORED_STATUS_TRANSITIONS = {
1765+ NODE_STATUS.COMMISSIONING: NODE_STATUS.FAILED_COMMISSIONING,
1766+ NODE_STATUS.DEPLOYING: NODE_STATUS.FAILED_DEPLOYMENT,
1767+ NODE_STATUS.RELEASING: NODE_STATUS.FAILED_RELEASING,
1768+}
1769+
1770+NODE_FAILURE_MONITORED_STATUS_TIMEOUTS = {
1771+ NODE_STATUS.COMMISSIONING: 20,
1772+ NODE_STATUS.DEPLOYING: 40,
1773+ NODE_STATUS.RELEASING: 5,
1774+}
1775+
1776 # Statuses that correspond to managed steps for which MAAS actively
1777 # monitors that the status changes after a fixed period of time.
1778 MONITORED_STATUSES = list(NODE_FAILURE_STATUS_TRANSITIONS.keys())
1779
1780=== modified file 'src/maasserver/preseed.py'
1781=== modified file 'src/maasserver/preseed_network.py'
1782--- src/maasserver/preseed_network.py 2016-10-24 23:01:00 +0000
1783+++ src/maasserver/preseed_network.py 2016-10-26 23:39:26 +0000
1784@@ -6,10 +6,14 @@
1785 __all__ = [
1786 ]
1787
1788+<<<<<<< TREE
1789 from collections import defaultdict
1790 from operator import attrgetter
1791
1792 from maasserver.dns.zonegenerator import get_dns_search_paths
1793+=======
1794+from maasserver.dns.zonegenerator import get_dns_search_paths
1795+>>>>>>> MERGE-SOURCE
1796 from maasserver.enum import (
1797 INTERFACE_TYPE,
1798 IPADDRESS_FAMILY,
1799@@ -26,7 +30,11 @@
1800 def __init__(self, node):
1801 self.node = node
1802 self.gateways = node.get_default_gateways()
1803+<<<<<<< TREE
1804 self.routes = StaticRoute.objects.all()
1805+=======
1806+ self.dns_servers = node.get_default_dns_servers()
1807+>>>>>>> MERGE-SOURCE
1808 self.gateway_ipv4_set = False
1809 self.gateway_ipv6_set = False
1810 # The default value is False: expected keys are 4 and 6.
1811@@ -56,6 +64,7 @@
1812 # Order the network_config where dependencies come first.
1813 self._order_config_dependency()
1814
1815+<<<<<<< TREE
1816 # If we have no IPv6 addresses present, make sure we claim IPv4, so
1817 # that we at least get some address.
1818 if not self.addr_family_present[6]:
1819@@ -66,10 +75,21 @@
1820 name
1821 for name in sorted(get_dns_search_paths())
1822 if name != self.node.domain.name]
1823+=======
1824+ search_list = [self.node.domain.name] + [
1825+ name
1826+ for name in sorted(get_dns_search_paths())
1827+ if name != self.node.domain.name]
1828+>>>>>>> MERGE-SOURCE
1829 self.network_config.append({
1830 "type": "nameserver",
1831+<<<<<<< TREE
1832 "address": default_dns_servers,
1833 "search": search_list,
1834+=======
1835+ "address": self.dns_servers,
1836+ "search": search_list,
1837+>>>>>>> MERGE-SOURCE
1838 })
1839
1840 network_config = {
1841
1842=== modified file 'src/maasserver/preseed_storage.py'
1843=== modified file 'src/maasserver/rpc/nodes.py'
1844=== modified file 'src/maasserver/rpc/tests/test_nodes.py'
1845=== modified file 'src/maasserver/rpc/tests/test_regionservice.py'
1846--- src/maasserver/rpc/tests/test_regionservice.py 2016-10-12 15:26:17 +0000
1847+++ src/maasserver/rpc/tests/test_regionservice.py 2016-10-26 23:39:26 +0000
1848@@ -77,6 +77,7 @@
1849 TwistedLoggerFixture,
1850 )
1851 import netaddr
1852+from provisioningserver.path import get_path
1853 from provisioningserver.rpc import (
1854 common,
1855 exceptions,
1856@@ -87,6 +88,10 @@
1857 from provisioningserver.rpc.testing import call_responder
1858 from provisioningserver.rpc.testing.doubles import DummyConnection
1859 from provisioningserver.utils import events
1860+from provisioningserver.utils.env import (
1861+ get_maas_id,
1862+ set_maas_id,
1863+)
1864 from provisioningserver.utils.testing import MAASIDFixture
1865 from provisioningserver.utils.twisted import (
1866 callInReactorWithTimeout,
1867@@ -1204,6 +1209,15 @@
1868 if len(exceptions) == 0:
1869 return original()
1870 else:
1871+ # Stick a bad value in maas_id cache to test that maas_id is
1872+ # being reread from disk each time.
1873+ good_maas_id = get_maas_id()
1874+ set_maas_id(factory.make_string())
1875+ # Write the good value to disk
1876+ maas_id_path = get_path("/var/lib/maas/maas_id")
1877+ os.unlink(maas_id_path)
1878+ with open(maas_id_path, "w") as fd:
1879+ fd.write(good_maas_id)
1880 raise exceptions.pop(0)
1881
1882 fake_promote = self.patch(regionservice.RegionAdvertising, "promote")
1883
1884=== added file 'src/maasserver/static/css/maas-styles.css.OTHER'
1885--- src/maasserver/static/css/maas-styles.css.OTHER 1970-01-01 00:00:00 +0000
1886+++ src/maasserver/static/css/maas-styles.css.OTHER 2016-10-26 23:39:26 +0000
1887@@ -0,0 +1,1 @@
1888+.fake{display:none}body{font-size:14px}.one-col,.two-col,.three-col,.four-col,.five-col,.six-col,.seven-col,.eight-col,.nine-col,.ten-col,.eleven-col,.twelve-col,.col{-webkit-box-sizing:border-box;-moz-box-sizing:border-box;box-sizing:border-box;clear:none;display:inline-block;float:none;margin-right:2.12766%;margin-bottom:20px;position:relative;width:100%}.twelve-col .one-col,.twelve-col .two-col,.twelve-col .three-col,.twelve-col .four-col,.twelve-col .five-col,.twelve-col .six-col,.twelve-col .seven-col,.twelve-col .eight-col,.twelve-col .nine-col,.twelve-col .ten-col,.twelve-col .eleven-col{width:100%}.last-col,.last{margin-right:0}.clearfix:after,.container:after{clear:both;content:"\0020";display:block;height:0;overflow:hidden;visibility:hidden}.clear{clear:both}.clearfix{display:block}@media only screen and (min-width: 768px){body{font-size:15px}.one-col,.two-col,.three-col,.four-col,.five-col,.six-col,.seven-col,.eight-col,.nine-col,.ten-col,.eleven-col,.twelve-col,.col{float:left}.one-col{width:6.38297%}.two-col{width:14.89361%}.three-col{width:23.40425%}.four-col{width:31.91489%}.five-col{width:40.42553%}.six-col{width:48.93617%}.seven-col{width:57.4468%}.eight-col{width:65.95744%}.nine-col{width:74.46808%}.ten-col{width:82.97872%}.eleven-col{width:91.48936%}.twelve-col{width:100%;margin-right:0}.twelve-col .one-col{width:6.3053%;margin-right:2.21238%}.twelve-col .two-col{width:14.823%;margin-right:2.21238%}.twelve-col .three-col{width:23.3407%;margin-right:2.21238%}.twelve-col .three-col{width:48.8938%;margin-right:2.21238%}.twelve-col .four-col{width:31.8584%;margin-right:2.21238%}.twelve-col .five-col{width:40.3761%;margin-right:2.21238%}.twelve-col .six-col{width:48.8938%;margin-right:2.21238%}.twelve-col .seven-col{width:57.4115%;margin-right:2.21238%}.twelve-col .eight-col{width:65.9292%;margin-right:2.21238%}.twelve-col .nine-col{width:74.4469%;margin-right:2.21238%}.twelve-col .ten-col{width:82.9646%;margin-right:2.21238%}.twelve-col .eleven-col{width:91.4823%;margin-right:2.21238%}.twelve-col .twelve-col{width:100%;margin-right:0}.eleven-col .one-col{width:6.89238%;margin-right:2.41837%}.eleven-col .two-col{width:16.20314%;margin-right:2.41837%}.eleven-col .three-col{width:25.5139%;margin-right:2.41837%}.eleven-col .four-col{width:34.82466%;margin-right:2.41837%}.eleven-col .five-col{width:44.13542%;margin-right:2.41837%}.eleven-col .six-col{width:53.44619%;margin-right:2.41837%}.eleven-col .seven-col{width:62.75695%;margin-right:2.41837%}.eleven-col .eight-col{width:72.06771%;margin-right:2.41837%}.eleven-col .nine-col{width:81.37847%;margin-right:2.41837%}.eleven-col .ten-col{width:90.68923%;margin-right:2.41837%}.eleven-col .eleven-col{width:100%;margin-right:0}.ten-col .one-col{width:7.6%;margin-right:2.66666%}.ten-col .two-col{width:17.86666%;margin-right:2.66666%}.ten-col .three-col{width:28.13333%;margin-right:2.66666%}.ten-col .four-col{width:38.4%;margin-right:2.66666%}.ten-col .five-col{width:48.66666%;margin-right:2.66666%}.ten-col .six-col{width:58.93333%;margin-right:2.66666%}.ten-col .seven-col{width:69.19999%;margin-right:2.66666%}.ten-col .eight-col{width:79.46666%;margin-right:2.66666%}.ten-col .nine-col{width:89.73333%;margin-right:2.66666%}.ten-col .ten-col{width:100%;margin-right:0}.nine-col .one-col{width:8.46953%;margin-right:2.97176%}.nine-col .two-col{width:19.91084%;margin-right:2.97176%}.nine-col .three-col{width:31.35215%;margin-right:2.97176%}.nine-col .four-col{width:42.79346%;margin-right:2.97176%}.nine-col .five-col{width:54.23476%;margin-right:2.97176%}.nine-col .six-col{width:65.67607%;margin-right:2.97176%}.nine-col .seven-col{width:77.11738%;margin-right:2.97176%}.nine-col .eight-col{width:88.55869%;margin-right:2.97176%}.nine-col .nine-col{width:100%;margin-right:0}.eight-col .one-col{width:9.56375%;margin-right:3.3557%}.eight-col .two-col{width:22.48322%;margin-right:3.3557%}.eight-col .three-col{width:35.40268%;margin-right:3.3557%}.eight-col .four-col{width:48.32214%;margin-right:3.3557%}.eight-col .five-col{width:61.24161%;margin-right:3.3557%}.eight-col .six-col{width:74.16107%;margin-right:3.3557%}.eight-col .seven-col{width:87.08053%;margin-right:3.3557%}.eight-col .eight-col{width:100%;margin-right:0}.seven-col .one-col{width:10.98265%;margin-right:3.85356%}.seven-col .two-col{width:25.81888%;margin-right:3.85356%}.seven-col .three-col{width:40.6551%;margin-right:3.85356%}.seven-col .four-col{width:55.49132%;margin-right:3.85356%}.seven-col .five-col{width:70.32755%;margin-right:3.85356%}.seven-col .six-col{width:85.16377%;margin-right:3.85356%}.seven-col .seven-col{width:100%;margin-right:0}.six-col .one-col{width:12.89592%;margin-right:4.52488%}.six-col .two-col{width:30.31674%;margin-right:4.52488%}.six-col .three-col{width:47.73755%;margin-right:4.52488%}.six-col .four-col{width:65.15837%;margin-right:4.52488%}.six-col .five-col{width:82.57918%;margin-right:4.52488%}.six-col .six-col{width:100%;margin-right:0}.five-col .one-col{width:15.61643%;margin-right:5.47945%}.five-col .two-col{width:36.71232%;margin-right:5.47945%}.five-col .three-col{width:57.80821%;margin-right:5.47945%}.five-col .four-col{width:78.9041%;margin-right:5.47945%}.five-col .five-col{width:100%;margin-right:0}.four-col .one-col{width:19.79166%;margin-right:6.94444%}.four-col .two-col{width:46.52777%;margin-right:6.94444%}.four-col .three-col{width:73.26388%;margin-right:6.94444%}.four-col .four-col{width:100%;margin-right:0}.three-col .one-col{width:27.01421%;margin-right:9.47867%}.three-col .two-col{width:63.5071%;margin-right:9.47867%}.three-col .three-col{width:100%;margin-right:0}.two-col .one-col{width:42.53731%;margin-right:14.92537%}.two-col .two-col{width:100%;margin-right:0}.one-col .one-col{width:100%;margin-right:0}.twelve-col .last-col{margin-right:0}.eleven-col .last-col{margin-right:0}.ten-col .last-col{margin-right:0}.nine-col .last-col{margin-right:0}.eight-col .last-col{margin-right:0}.seven-col .last-col{margin-right:0}.six-col .last-col{margin-right:0}.five-col .last-col{margin-right:0}.four-col .last-col{margin-right:0}.three-col .last-col{margin-right:0}.two-col .last-col{margin-right:0}.one-col .last-col{margin-right:0}.row,#context-footer{-webkit-border-radius:0;-moz-border-radius:0;border-radius:0;margin:0;padding:40px 40px 20px}.row:after{content:".";visibility:hidden;display:block;height:0;clear:both}.row-feature{background:none}.container{-webkit-box-sizing:border-box;-moz-box-sizing:border-box;box-sizing:border-box;margin:20px 20px 0;width:100%}.append-one{margin-right:8.51063%}.append-two{margin-right:17.02127%}.append-three{margin-right:25.53191%}.append-four{margin-right:34.04255%}.append-five{margin-right:42.55319%}.append-six{margin-right:51.06383%}.append-seven{margin-right:59.57446%}.append-eight{margin-right:68.0851%}.append-nine{margin-right:76.59574%}.append-ten{margin-right:85.10638%}.append-eleven{margin-right:93.61702%}.prepend-one{margin-left:8.51063%}.prepend-two{margin-left:17.02127%}.prepend-three{margin-left:25.53191%}.prepend-four{margin-left:34.04255%}.prepend-five{margin-left:42.55319%}.prepend-six{margin-left:51.06383%}.prepend-seven{margin-left:59.57446%}.prepend-eight{margin-left:68.0851%}.prepend-nine{margin-left:76.59574%}.prepend-ten{margin-left:85.10638%}.prepend-eleven{margin-left:93.61702%}.push-one{margin-left:57px}.pull-one,.pull-two,.pull-three,.pull-four,.pull-five,.pull-six,.pull-seven,.pull-eight,.pull-nine,.pull-ten,.pull-eleven{float:left;position:relative}.pull-one{margin-left:-6.38297%}.pull-two{margin-left:-17.02127%}.pull-three{margin-left:-25.53191%}.pull-four{margin-left:-34.04255%}.pull-five{margin-left:-34.04255%}.pull-six{margin-left:-51.06383%}.pull-seven{margin-left:-59.57446%}.pull-eight{margin-left:-68.0851%}.pull-nine{margin-left:-76.59574%}.pull-ten{margin-left:-85.10638%}.pull-eleven{margin-left:-93.61702%}.push-1,.push-two,.push-three,.push-four,.push-five,.push-six,.push-seven,.push-eight,.push-nine,.push-ten,.push-eleven{float:left;position:relative}.push-one{margin:0 -8.51063% 0 8.51063%}.push-two{margin:0 -19.14893% 0 19.14893%}.push-three{margin:0 -27.65957% 0 27.65957%}.push-four{margin:0 -36.17021% 0 36.17021%}.push-five{margin:0 -36.17021% 0 36.17021%}.push-six{margin:0 -53.19149% 0 53.19149%}.push-seven{margin:0 -61.70212% 0 61.70212%}.push-eight{margin:0 -70.21276% 0 70.21276%}.push-nine{margin:0 -78.7234% 0 78.7234%}.push-ten{margin:0 -87.23404% 0 87.23404%}.push-eleven{margin:0 -95.74468% 0 95.74468%}}@media only screen and (min-width: 984px){body{font-size:16px}.one-col,.two-col,.three-col,.four-col,.five-col,.six-col,.seven-col,.eight-col,.nine-col,.ten-col,.eleven-col,.twelve-col,.col{float:left}.one-col{width:6.38297%}.two-col{width:14.89361%}.three-col{width:23.40425%}.four-col{width:31.91489%}.five-col{width:40.42553%}.six-col{width:48.93617%}.seven-col{width:57.4468%}.eight-col{width:65.95744%}.nine-col{width:74.46808%}.ten-col{width:82.97872%}.eleven-col{width:91.48936%}.three-col:nth-child(1):nth-last-child(4),.three-col:nth-child(2):nth-last-child(3),.three-col:nth-child(3):nth-last-child(2),.three-col:nth-child(4):nth-last-child(1){width:23.36%}.three-col:nth-of-type(2){margin-right:2.21238%}.twelve-col{width:100%;margin-right:0}.twelve-col .one-col{width:6.3053%;margin-right:2.21238%}.twelve-col .two-col{width:14.823%;margin-right:2.21238%}.twelve-col .three-col{width:23.3407%;margin-right:2.21238%}.twelve-col .three-col:nth-child(1):nth-last-child(4),.twelve-col .three-col:nth-child(2):nth-last-child(3),.twelve-col .three-col:nth-child(3):nth-last-child(2),.twelve-col .three-col:nth-child(4):nth-last-child(1){width:23.3407%}.twelve-col .three-col:nth-of-type(2){margin-right:2.21238%}.twelve-col .four-col{width:31.8584%;margin-right:2.21238%}.twelve-col .five-col{width:40.3761%;margin-right:2.21238%}.twelve-col .six-col{width:48.8938%;margin-right:2.21238%}.twelve-col .seven-col{width:57.4115%;margin-right:2.21238%}.twelve-col .eight-col{width:65.9292%;margin-right:2.21238%}.twelve-col .nine-col{width:74.4469%;margin-right:2.21238%}.twelve-col .ten-col{width:82.9646%;margin-right:2.21238%}.twelve-col .eleven-col{width:91.4823%;margin-right:2.21238%}.twelve-col .twelve-col{width:100%;margin-right:0}.eleven-col .one-col{width:6.89238%;margin-right:2.41837%}.eleven-col .two-col{width:16.20314%;margin-right:2.41837%}.eleven-col .three-col{width:25.5139%;margin-right:2.41837%}.eleven-col .four-col{width:34.82466%;margin-right:2.41837%}.eleven-col .five-col{width:44.13542%;margin-right:2.41837%}.eleven-col .six-col{width:53.44619%;margin-right:2.41837%}.eleven-col .seven-col{width:62.75695%;margin-right:2.41837%}.eleven-col .eight-col{width:72.06771%;margin-right:2.41837%}.eleven-col .nine-col{width:81.37847%;margin-right:2.41837%}.eleven-col .ten-col{width:90.68923%;margin-right:2.41837%}.eleven-col .eleven-col{width:100%;margin-right:0}.ten-col .one-col{width:7.6%;margin-right:2.66666%}.ten-col .two-col{width:17.86666%;margin-right:2.66666%}.ten-col .three-col{width:28.13333%;margin-right:2.66666%}.ten-col .four-col{width:38.4%;margin-right:2.66666%}.ten-col .five-col{width:48.66666%;margin-right:2.66666%}.ten-col .six-col{width:58.93333%;margin-right:2.66666%}.ten-col .seven-col{width:69.19999%;margin-right:2.66666%}.ten-col .eight-col{width:79.46666%;margin-right:2.66666%}.ten-col .nine-col{width:89.73333%;margin-right:2.66666%}.ten-col .ten-col{width:100%;margin-right:0}.nine-col .one-col{width:8.46953%;margin-right:2.97176%}.nine-col .two-col{width:19.91084%;margin-right:2.97176%}.nine-col .three-col{width:31.35215%;margin-right:2.97176%}.nine-col .four-col{width:42.79346%;margin-right:2.97176%}.nine-col .five-col{width:54.23476%;margin-right:2.97176%}.nine-col .six-col{width:65.67607%;margin-right:2.97176%}.nine-col .seven-col{width:77.11738%;margin-right:2.97176%}.nine-col .eight-col{width:88.55869%;margin-right:2.97176%}.nine-col .nine-col{width:100%;margin-right:0}.eight-col .one-col{width:9.56375%;margin-right:3.3557%}.eight-col .two-col{width:22.48322%;margin-right:3.3557%}.eight-col .three-col{width:35.40268%;margin-right:3.3557%}.eight-col .four-col{width:48.32214%;margin-right:3.3557%}.eight-col .five-col{width:61.24161%;margin-right:3.3557%}.eight-col .six-col{width:74.16107%;margin-right:3.3557%}.eight-col .seven-col{width:87.08053%;margin-right:3.3557%}.eight-col .eight-col{width:100%;margin-right:0}.seven-col .one-col{width:10.98265%;margin-right:3.85356%}.seven-col .two-col{width:25.81888%;margin-right:3.85356%}.seven-col .three-col{width:40.6551%;margin-right:3.85356%}.seven-col .four-col{width:55.49132%;margin-right:3.85356%}.seven-col .five-col{width:70.32755%;margin-right:3.85356%}.seven-col .six-col{width:85.16377%;margin-right:3.85356%}.seven-col .seven-col{width:100%;margin-right:0}.six-col .one-col{width:12.89592%;margin-right:4.52488%}.six-col .two-col{width:30.31674%;margin-right:4.52488%}.six-col .three-col{width:47.73755%;margin-right:4.52488%}.six-col .four-col{width:65.15837%;margin-right:4.52488%}.six-col .five-col{width:82.57918%;margin-right:4.52488%}.six-col .six-col{width:100%;margin-right:0}.five-col .one-col{width:15.61643%;margin-right:5.47945%}.five-col .two-col{width:36.71232%;margin-right:5.47945%}.five-col .three-col{width:57.80821%;margin-right:5.47945%}.five-col .four-col{width:78.9041%;margin-right:5.47945%}.five-col .five-col{width:100%;margin-right:0}.four-col .one-col{width:19.79166%;margin-right:6.94444%}.four-col .two-col{width:46.52777%;margin-right:6.94444%}.four-col .three-col{width:73.26388%;margin-right:6.94444%}.four-col .four-col{width:100%;margin-right:0}.three-col .one-col{width:27.01421%;margin-right:9.47867%}.three-col .two-col{width:63.5071%;margin-right:9.47867%}.three-col .three-col{width:100%;margin-right:0}.two-col .one-col{width:42.53731%;margin-right:14.92537%}.two-col .two-col{width:100%;margin-right:0}.one-col .one-col{width:100%;margin-right:0}.twelve-col .last-col{margin-right:0}.eleven-col .last-col{margin-right:0}.ten-col .last-col{margin-right:0}.nine-col .last-col{margin-right:0}.eight-col .last-col{margin-right:0}.seven-col .last-col{margin-right:0}.six-col .last-col{margin-right:0}.five-col .last-col{margin-right:0}.four-col .last-col{margin-right:0}.three-col .last-col{margin-right:0}.two-col .last-col{margin-right:0}.one-col .last-col{margin-right:0}.row,#context-footer{-webkit-border-radius:0;-moz-border-radius:0;border-radius:0;margin:0;padding:40px 40px 20px}.row:after{content:".";visibility:hidden;display:block;height:0;clear:both}.row-feature{background:none}.container{-webkit-box-sizing:border-box;-moz-box-sizing:border-box;box-sizing:border-box;margin:20px 20px 0;width:100%}.append-one{margin-right:8.51063%}.append-two{margin-right:17.02127%}.append-three{margin-right:25.53191%}.append-four{margin-right:34.04255%}.append-five{margin-right:42.55319%}.append-six{margin-right:51.06383%}.append-seven{margin-right:59.57446%}.append-eight{margin-right:68.0851%}.append-nine{margin-right:76.59574%}.append-ten{margin-right:85.10638%}.append-eleven{margin-right:93.61702%}.prepend-one{margin-left:8.51063%}.prepend-two{margin-left:17.02127%}.prepend-three{margin-left:25.53191%}.prepend-four{margin-left:34.04255%}.prepend-five{margin-left:42.55319%}.prepend-six{margin-left:51.06383%}.prepend-seven{margin-left:59.57446%}.prepend-eight{margin-left:68.0851%}.prepend-nine{margin-left:76.59574%}.prepend-ten{margin-left:85.10638%}.prepend-eleven{margin-left:93.61702%}.push-one{margin-left:57px}.pull-one,.pull-two,.pull-three,.pull-four,.pull-five,.pull-six,.pull-seven,.pull-eight,.pull-nine,.pull-ten,.pull-eleven{float:left;position:relative}.pull-one{margin-left:-6.38297%}.pull-two{margin-left:-17.02127%}.pull-three{margin-left:-25.53191%}.pull-four{margin-left:-34.04255%}.pull-five{margin-left:-34.04255%}.pull-six{margin-left:-51.06383%}.pull-seven{margin-left:-59.57446%}.pull-eight{margin-left:-68.0851%}.pull-nine{margin-left:-76.59574%}.pull-ten{margin-left:-85.10638%}.pull-eleven{margin-left:-93.61702%}.push-1,.push-two,.push-three,.push-four,.push-five,.push-six,.push-seven,.push-eight,.push-nine,.push-ten,.push-eleven{float:left;position:relative}.push-one{margin:0 -8.51063% 0 8.51063%}.push-two{margin:0 -19.14893% 0 19.14893%}.push-three{margin:0 -27.65957% 0 27.65957%}.push-four{margin:0 -36.17021% 0 36.17021%}.push-five{margin:0 -36.17021% 0 36.17021%}.push-six{margin:0 -53.19149% 0 53.19149%}.push-seven{margin:0 -61.70212% 0 61.70212%}.push-eight{margin:0 -70.21276% 0 70.21276%}.push-nine{margin:0 -78.7234% 0 78.7234%}.push-ten{margin:0 -87.23404% 0 87.23404%}.push-eleven{margin:0 -95.74468% 0 95.74468%}}html,body,div,span,applet,object,iframe,h1,h2,h3,h4,h5,h6,p,blockquote,pre,a,acronym,address,big,cite,code,del,dfn,em,img,ins,kbd,q,s,samp,small,strike,strong,sub,sup,tt,var,b,u,i,center,dl,ol,ul,li,form,label,legend,table,caption,tbody,tfoot,thead,tr,th,td,article,aside,canvas,details,embed,figure,figcaption,footer,header,menu,nav,output,ruby,section,summary,time,mark,audio,video{border:0;margin:0;padding:0;vertical-align:baseline}article,aside,details,figcaption,figure,footer,header,nav,section{display:block}audio,canvas,video{display:inline-block;*display:inline;*zoom:1}audio:not([controls]){display:none}[hidden]{display:none}@font-face{font-family:'Ubuntu';font-style:normal;font-weight:300;src:url("../fonts/ubuntu-l-webfont.eot");src:url("../fonts/ubuntu-l-webfont.eot?#iefix") format("embedded-opentype"),url("../fonts/ubuntu-l-webfont.woff") format("woff"),url("../fonts/ubuntu-l-webfont.ttf") format("truetype"),url("../fonts/ubuntu-l-webfont.svg#ubuntulight") format("svg")}@font-face{font-family:'Ubuntu';font-style:normal;font-weight:400;src:local("Ubuntu");src:url("../fonts/ubuntu-r-webfont.eot");src:url("../fonts/ubuntu-r-webfont.eot?#iefix") format("embedded-opentype"),url("../fonts/ubuntu-r-webfont.woff") format("woff"),url("../fonts/ubuntu-r-webfont.ttf") format("truetype"),url("../fonts/ubuntu-r-webfont.svg#ubunturegular") format("svg")}@font-face{font-family:'Ubuntu';font-style:normal;font-weight:500;src:url("../fonts/ubuntu-m-webfont.eot");src:local("Ubuntu Medium"),local("Ubuntu-Medium"),url("../fonts/ubuntu-m-webfont.eot?#iefix") format("embedded-opentype"),url("../fonts/ubuntu-m-webfont.woff") format("woff"),url("../fonts/ubuntu-m-webfont.ttf") format("truetype"),url("../fonts/ubuntu-m-webfont.svg#ubuntumedium") format("svg")}@font-face{font-family:'Ubuntu';font-style:normal;font-weight:700;src:url("../fonts/ubuntu-b-webfont.eot");src:local("Ubuntu Bold"),local("Ubuntu-Bold"),url("../fonts/ubuntu-b-webfont.eot?#iefix") format("embedded-opentype"),url("../fonts/ubuntu-b-webfont.woff") format("woff"),url("../fonts/ubuntu-b-webfont.ttf") format("truetype"),url("../fonts/ubuntu-b-webfont.svg#ubuntubold") format("svg")}@font-face{font-family:'Ubuntu';font-style:italic;font-weight:300;src:url("../fonts/ubuntu-li-webfont.eot");src:local("Ubuntu Light Italic"),local("Ubuntu-LightItalic"),url("../fonts/ubuntu-li-webfont.eot?#iefix") format("embedded-opentype"),url("../fonts/ubuntu-li-webfont.woff") format("woff"),url("../fonts/ubuntu-li-webfont.ttf") format("truetype"),url("../fonts/ubuntu-li-webfont.svg#ubuntulight_italic") format("svg")}@font-face{font-family:'Ubuntu';font-style:italic;font-weight:400;src:local("Ubuntu Italic"),local("Ubuntu-Italic"),url("https://themes.googleusercontent.com/static/fonts/ubuntu/v5/GZMdC02DTXXx8AdUvU2etw.woff") format("woff")}@font-face{font-family:'Ubuntu';font-style:italic;font-weight:500;src:url("../fonts/ubuntu-mi-webfont.eot");src:local("Ubuntu Medium Italic"),local("Ubuntu-MediumItalic"),url("../fonts/ubuntu-mi-webfont.eot?#iefix") format("embedded-opentype"),url("../fonts/ubuntu-mi-webfont.woff") format("woff"),url("../fonts/ubuntu-mi-webfont.ttf") format("truetype"),url("../fonts/ubuntu-mi-webfont.svg#ubuntumedium_italic") format("svg")}@font-face{font-family:'Ubuntu';font-style:italic;font-weight:700;src:local("Ubuntu Bold Italic"),local("Ubuntu-BoldItalic"),url("https://themes.googleusercontent.com/static/fonts/ubuntu/v5/pqisLQoeO9YTDCNnlQ9bfz8E0i7KZn-EPnyo3HZu7kw.woff") format("woff")}html{font-size:100%}body{color:#333;font-family:Ubuntu, Arial, "libra sans", sans-serif;font-weight:300}blockquote,q{quotes:none}blockquote{margin:28px 20px}blockquote:before,blockquote:after,q:before,q:after{content:"";content:none}legend{border:0;*margin-left:-7px}figure{margin:0}abbr,acronym{cursor:help}a:focus{outline:thin dotted}a:hover,a:active{outline:0}a:link,a:visited{color:#dd4814;text-decoration:none}a:hover,a:active,a:focus{text-decoration:underline}a.link-arrow:after{content:"\0000a0›"}nav ul li h2 a:after{content:"\0000a0›"}nav ul li a:after,.carousel ul li a:after,ul li p a:after{content:""}ol,ul{margin-left:20px;margin-bottom:20px}ol ol,ul ul,ol ul,ul ol{margin-bottom:0}nav ul,nav ol{list-style:none;list-style-image:none}svg:not(:root){overflow:hidden}img{border:0;height:auto;max-width:100%}img.left{margin-right:20px}img.right{margin-left:20px}.middle img{vertical-align:middle;margin-top:4em}h1,h2,h3,h4,h5,h6{font-weight:300;line-height:1.3}h1{font-size:1.625em;margin-bottom:.5em}h2{font-size:1.438em;margin-bottom:.5em}h3{font-size:1.219em;margin-bottom:.522em}h4{font-size:1.25em;font-weight:400;margin-bottom:.615em}h5{font-size:1em;font-weight:700;margin-bottom:1em}h6{font-size:.723em;font-weight:400;margin-bottom:1em;letter-spacing:.1em;text-transform:uppercase}p,li{font-size:1em;line-height:1.5;margin:0;margin-bottom:.75em;padding:0}h2 span,h1 span{display:block}p+h2,ul+h2,ol+h2,pre+h2{margin-top:0.5625em}header nav a:link{font-weight:normal}p+h3,ul+h3,ol+h3,pre+h3{margin-top:0.78261em}p+h4,ul+h4,ol+h4,pre+h4{margin-top:1.39286em}ol+h2,p+h2,pre+h2,ul+h2{margin-top:.563em}ol+h3,p+h3,pre+h3,ul+h3{margin-top:.783em}ol+h4,p+h4,pre+h4,ul+h4{margin-top:1.219em}li{margin-bottom:.4em}li:last-of-type{margin-bottom:0}ins{background:#fffbeb;text-decoration:none}small,.smaller{font-size:13px}sub,sup{font-size:75%;line-height:0;position:relative;vertical-align:baseline}sup{vertical-align:text-top}sub{vertical-align:text-bottom}dfn{font-style:italic}mark{background:#ff0;color:#000}code,pre{font-family:"Ubuntu Mono", "Consolas", "Monaco", "Lucida Console", "Courier New", Courier, monospace}pre{-webkit-border-radius:4px;-moz-border-radius:4px;border-radius:4px;background:#fdf6f2;padding:.6em 1em;white-space:pre-wrap;word-wrap:break-word}blockquote{margin:0}blockquote>p{font-size:0.92857em;font-weight:100;margin:0 0 .4em 0}blockquote small{font-size:.813em;line-height:1.4}button,input,select,textarea{font-family:Ubuntu,Arial,"libra sans",sans-serif;margin:0;vertical-align:baseline;*vertical-align:middle}select{font-size:1em;font-weight:300}button,input{line-height:normal}button,input[type="button"],input[type="reset"],input[type="submit"]{cursor:pointer;-webkit-appearance:button;*overflow:visible}input[type="checkbox"],input[type="radio"]{box-sizing:border-box;padding:0}input[type="search"]{-webkit-border-radius:2px;-moz-border-radius:2px;border-radius:2px;-moz-box-sizing:content-box;-webkit-appearance:none;-webkit-box-sizing:content-box;box-sizing:content-box;font-family:Ubuntu,Arial,"libra sans",sans-serif;font-weight:300;outline:none;padding:0.6956522em 0.869565em}input[type="search"]::-webkit-search-decoration{-webkit-appearance:none}button::-moz-focus-inner,input::-moz-focus-inner{border:0;padding:0}textarea{overflow:auto;vertical-align:top}form fieldset{-webkit-border-radius:4px;-moz-border-radius:4px;border-radius:4px;background-repeat:no-repeat;background-color:#EFEEEC;background-position:-15px -15px;border:0;margin-bottom:8px;padding:15px 20px}form fieldset h3{border-bottom:1px dotted #dfdcd9;margin-bottom:9px;padding-bottom:10px}form fieldset li:first-child{margin-top:0}form input[type="text"],form input[type="email"],form input[type="tel"],form textarea{-webkit-appearance:none;-webkit-border-radius:2px;-moz-border-radius:2px;border-radius:2px;background:#fff;border:1px solid #D2D2D2;display:block;font-family:Ubuntu,Arial,"libra sans",sans-serif;font-size:1em;font-weight:300;padding:0.6956522em 0.869565em}form input:focus,form textarea:focus{border:1px solid #dd4814}form textarea[readonly='readonly']{color:#999}form input[type="checkbox"],form input[type="radio"]{margin:0;width:auto}form input[type="checkbox"]+label,form input[type="radio"]+label{display:inline;margin-left:5px;vertical-align:middle;width:auto}form input[type="submit"]{font-size:1.14286em;-webkit-border-radius:4px;-moz-border-radius:4px;border-radius:4px;background-color:#dd4814;background-image:-moz-linear-gradient(#f26120, #dd4814);background-image:-webkit-gradient(linear, 0% 0%, 0% 100%, from(#f26120), to(#dd4814));background-image:-webkit-linear-gradient(#f26120, #dd4814);background-image:-o-linear-gradient(#f26120, #dd4814);-moz-box-shadow:none;-webkit-box-shadow:none;box-shadow:none;border:0;color:#fff;display:block;padding:10px 14px;text-shadow:none;width:auto;margin-bottom:0}form input[type="submit"]:hover{background:#dd4814}form label{cursor:pointer;display:block;margin-bottom:4px}form label span{color:#df382c}form ul{margin-left:0}form li{list-style:none outside none;margin-top:14px}form button[type="submit"]{border:0;display:inline-block;font-family:Ubuntu, Arial, "libra sans", sans-serif;text-decoration:none;font-weight:300}form input[type="reset"]{display:none}table{border-collapse:collapse;border-spacing:0;overflow-x:scroll;margin-bottom:20px;margin:0 0 2.85714em 0;width:100%}table th,table td{padding:15px 10px;background:#f0edea;border:1px dotted #888}table td{text-align:center;vertical-align:middle}table thead th{border-collapse:separate;border-spacing:0 10px;background:#fee3d2;color:#333333;font-weight:normal}table tbody th{text-align:left;font-weight:normal;font-weight:300}table th[scope="col"]{text-align:center}table thead th:first-of-type{text-align:left}@media only screen and (max-width: 768px){table{display:block}}@media only screen and (min-width: 984px){form fieldset{padding:15px 20px}img{max-width:none}}.audience-consumer{color:#333}.audience-consumer .row-box,.audience-consumer .main-content{color:#333}.audience-consumer .inner-wrapper{background:#fff}.audience-consumer .quote-right-top{padding:60px 60px 0 40px;background:url("/sites/ubuntu/latest/u/img/patterns/quote-orange-br-287x287.png") no-repeat;height:287px;position:absolute;right:-40px;text-align:left;top:-90px;width:31.91489%}.audience-consumer .quote-right-top p{font-size:1.14286em;margin:0.769em;padding-bottom:0;color:#fff}.audience-consumer .quote-right-top p cite{font-size:0.85714em;color:#fff;padding:0}.audience-consumer .quote-right-top p a,.audience-consumer .quote-right p a{color:#fff}.audience-consumer .quote-right{font-size:1.28571em;color:#fff;padding:50px 100px 0 50px;text-indent:-6px;background:url("/sites/ubuntu/latest/u/img/patterns/quote-orange-bl-287x287.png") no-repeat;min-height:287px;position:absolute;right:-20px;text-align:left;top:-90px;width:21.2006% em}.audience-consumer .quote-right cite{font-style:normal;margin-left:6px}.audience-consumer .quote-right-alt{background:url(/sites/ubuntu/latest/u/img/patterns/quote-white-br-360x360.png) 0 -100px no-repeat;color:#dd4814;padding:50px 50px 0 50px}.audience-consumer .quote-right-right{background:url("/sites/ubuntu/latest/u/img/patterns/quote-orange-br-287x287.png") no-repeat}.audience-enterprise h1{margin:0 0 18px 0}.audience-enterprise td{background:#fff}.audience-enterprise th,.audience-enterprise td{padding:6px 10px;background:#fff}.audience-enterprise th[scope="col"]{background:#E2D4DC;color:#772953}.audience-enterprise tbody th[rowspan]{background:#F7F2F6}.audience-enterprise tfoot th[rowspan]{background:#dfdcd9}.audience-enterprise tfoot td,.audience-enterprise tfoot th{font-weight:normal;background:#dfdcd9}.audience-enterprise .inner-wrapper{background:#2c001e;color:#fff}.audience-enterprise .row-box{background:#fff;color:#333}.row-enterprise{background:#772953;color:#fff;-webkit-border-radius:0;-moz-border-radius:0;border-radius:0}.row-enterprise .box,.row-enterprise div{background:#772953;color:#fff}.row-enterprise a{color:#fff}.enterprise-dot-pattern{background:url("/sites/ubuntu/latest/u/img/patterns/enterprise-dot-pattern.png")}.developer-dot-pattern{background:url("/sites/ubuntu/latest/u/img/patterns/developer-dot-pattern.png")}.wrapper,header.banner .nav-primary,nav div.footer-a div,.inline-lists ul,.legal{-webkit-box-sizing:border-box;-moz-box-sizing:border-box;box-sizing:border-box;width:auto}.inner-wrapper{-webkit-box-sizing:border-box;-moz-box-sizing:border-box;box-sizing:border-box;background:#fff;clear:both;display:block;float:left;width:100%;margin:0;padding-bottom:20px;position:relative;z-index:1}@media only screen and (min-width: 768px){.med-six-col .three-col{width:48%}.med-six-col .three-col:nth-of-type(2n){margin-right:0}}@media only screen and (min-width: 769px){.inner-wrapper{border-radius:4px;padding-bottom:20px}}@media only screen and (min-width: 984px){.wrapper{-webkit-box-sizing:border-box;-moz-box-sizing:border-box;box-sizing:border-box;background:#fff;margin:0 auto;position:relative;text-align:left;width:984px}.inner-wrapper{-moz-box-shadow:0 0 3px #c9c9c9;-webkit-box-shadow:0 0 3px #c9c9c9;box-shadow:0 0 3px #c9c9c9;margin:10px 0 30px}.three-col,.med-six-col .three-col{width:23.30%}.three-col.last-col:nth-of-type(2n){margin-right:0}.med-six-col .three-col:nth-of-type(2n){margin-right:20px}.med-six-col .three-col.last-col{margin-right:0}}.left{float:left}.right{float:right}.caps{text-transform:uppercase}img{border:0 none;height:auto;max-width:100%}img.left{margin-right:0}img.touch-border{margin-bottom:-3px}.accessibility-aid,.off-left{position:absolute;left:-999em}a.external{-moz-background-size:0.7em 0.7em;-webkit-background-size:0.7em 0.7em;-o-background-size:0.7em 0.7em;background-size:0.7em 0.7em;padding-right:.9em;background-image:url("//assets.ubuntu.com/sites/ubuntu/latest/u/img/external-link-orange.svg");background-position:right 1px;background-repeat:no-repeat}.opera-mini a.external,.no-svg a.external{background-image:url("//assets.ubuntu.com/sites/ubuntu/latest/u/img/external-link-orange.png")}.text-center,.align-center{text-align:center}.no-margin{margin:0}.no-margin-bottom{margin-bottom:0}.no-padding-bottom{padding-bottom:0}.pull-left-20{margin-left:-20px}.pull-right-20{margin-right:-20px}.pull-left-40{margin-left:-40px}.pull-right-40{margin-right:-41px}.no-border{border:0}.link-top{font-size:1em;clear:both;margin-bottom:40px;margin-top:-40px}.link-top a{background:#fff;margin-right:10px;margin-top:-17px;padding:5px;float:right}.pull-bottom-right{position:absolute;right:0;bottom:0;left:auto}.box .pull-bottom-right{-webkit-border-radius:0 0 4px 0;-moz-border-radius:0 0 4px 0;border-radius:0 0 4px 0}.pull-bottom-left{margin-left:-20px;margin-bottom:-21px}.pull-top-right{margin-left:-20px;margin-top:-21px}div.box-image-centered span img.priority-0,div.row-image-centered span img.priority-0,div.row.row-image-centered span img.priority-0,img.priority-0{position:absolute;left:-999em}.priority-0,.not-for-small{position:absolute;left:-999em}.video-container{position:relative;padding-bottom:56.25%;padding-top:30px;height:0;overflow:hidden}.video-container iframe{position:absolute;top:0;left:0;width:100%;height:100%}.video-container+h3,.video-container+.video-title{margin-top:20px}@media only screen and (max-width: 768px){.pull-right-40{margin-right:-30px}.pull-bottom-right,.pull-bottom-left{position:static}img.pull-bottom-left{margin-bottom:0;margin-left:0}}@media only screen and (min-width: 768px){div.box-image-centered span img.priority-0,div.row-image-centered span img.priority-0,div.row.row-image-centered span img.priority-0,img.priority-0{position:relative;left:auto}.priority-0,.not-for-small{position:relative;left:auto}.for-mobile,.for-small{position:absolute;left:-999em}.pull-right{float:right;margin-right:-30px}img.pull-left{margin-left:-30px}img.touch-border{float:left;margin-bottom:-30px}}@media only screen and (min-width: 769px){img.left{margin-right:20px}}@media only screen and (min-width: 984px){img.touch-border{float:left;margin-bottom:-40px}img.pull-left{margin-left:-40px}.pull-right{float:right;margin-right:-40px}.for-tablet,.for-medium{display:none}.no-border{border:0}}.caps-centered,.muted-heading{font-size:.875em;margin-bottom:20px;text-align:center;text-transform:uppercase}p.intro{font-size:1.14286em;line-height:1.4}.row div p:last-child,.row div ul:last-child{margin-bottom:0}.four-col p:last-child{margin-bottom:0}.note{color:#888;font-size:.813em}@media only screen and (min-width: 768px){p.intro{font-size:1.13333em}}@media only screen and (min-width: 984px){h1{font-size:2.8125em}h2{font-size:2em;margin-bottom:.375em}h3{font-size:1.438em;margin-bottom:.522em}h4{font-size:1em;margin-bottom:.75em}h5{font-size:1em}p,li,code,pre{font-size:16px;line-height:1.5;margin-bottom:.75em}p.intro{font-size:1.25em}}header.banner{border-top:0;min-width:100%;width:auto;background:#dd4814;display:block;position:relative;z-index:2}header.banner .nav-primary{border:0;margin:0 auto;overflow:hidden}header.banner .nav-primary ul{border-right:1px solid #ed6637;float:left;margin:0;position:relative}header.banner .nav-primary ul li{border-left:1px solid #c64012;float:left;list-style-image:none;margin:0;text-indent:0;vertical-align:bottom}header.banner .nav-primary ul li:last-child{border-right:1px solid #c64012}header.banner .nav-primary ul li a:link,header.banner .nav-primary ul li a:visited{font-size:14px;border-left:1px solid #ec5b29;color:#fff;display:block;margin-bottom:0;padding:14px 14px 13px;position:relative;text-align:center;text-decoration:none;-webkit-font-smoothing:subpixel-antialiased;-moz-font-smoothing:subpixel-antialiased;-o-font-smoothing:subpixel-antialiased;font-smoothing:subpixel-antialiased}header.banner .nav-primary ul a.active{background:#B83A10;border-left:1px solid #ec5b29}header.banner .nav-primary ul li a:hover{background:#e1662f;border-top:0;-moz-box-shadow:inset 0 2px 2px -2px #777;-webkit-box-shadow:inset 0 2px 2px -2px #777;box-shadow:inset 0 2px 2px -2px #777}#main-navigation-link{display:none}header.banner .nav-toggle{position:absolute;right:0;display:block;width:48px;height:48px;text-indent:-99999px;background-image:url(//assets.ubuntu.com/sites/ubuntu/latest/u/img/icons/navigation-menu-plain.svg);-moz-background-size:25px auto;-webkit-background-size:25px auto;-o-background-size:25px auto;background-size:25px auto;background-repeat:no-repeat;background-position:center center;cursor:pointer}header.banner .no-script{display:none}.opera-mini header.banner .nav-toggle,.no-svg header.banner .nav-toggle{background-image:url(//assets.ubuntu.com/sites/ubuntu/latest/u/img/icons/navigation-menu-plain.png)}header.banner nav ul{background-color:#f0f0f0;display:none;float:left}header.banner .nav-primary.active{-moz-box-shadow:0 1px 2px 1px rgba(120,120,120,0.2);-webkit-box-shadow:0 1px 2px 1px rgba(120,120,120,0.2);box-shadow:0 1px 2px 1px rgba(120,120,120,0.2);padding:0;border-bottom:1px solid #d4d7d4}header nav ul.active{display:block}header.banner .nav-primary ul li,header.banner .nav-primary ul li a:link,header.banner .nav-primary ul li a:visited,header.banner .nav-primary ul li a:active{display:block;padding:0;margin:0;border:none}header.banner .nav-primary ul li a:hover{-moz-box-shadow:none;-webkit-box-shadow:none;box-shadow:none;background-color:#d0d0d0}header.banner .nav-primary ul li a.active{background-color:#ddd}header.banner .nav-primary ul li{border-bottom:1px solid #F2F2F4;font-size:16px}header.banner .nav-primary ul li:last-child{border:0}header.banner nav.nav-primary ul li a:link,header.banner .nav-primary ul li a:visited,header.banner .nav-primary ul li a:hover,header.banner .nav-primary ul li a:active{padding:14px 14px 13px;text-align:left}header.banner nav.nav-primary ul.active li ul{display:none}#menu.active:after{background-image:url(//assets.ubuntu.com/sites/ubuntu/latest/u/img/patterns/nav-arrow.svg);background-repeat:no-repeat;background-position:50% 26px;content:"";display:block;height:23px;margin-left:0;padding-bottom:17px;position:relative;top:-3px;width:48px;z-index:999}html.no-svg #menu.active:after,.opera-mini #menu.active:after{background-image:url(//assets.ubuntu.com/sites/ubuntu/latest/u/img/patterns/nav-arrow.png)}.nav-secondary{border-bottom:1px solid #dfdcd9;margin-bottom:0}.nav-secondary ul{float:left;margin-bottom:10px;margin-left:2px}.nav-secondary ul li{float:left;margin-top:16px;font-size:14px;margin-right:15px}.nav-secondary ul li a:link,.nav-secondary ul li a:visited{color:#333;font-size:14px;float:left}.nav-secondary ul li a:hover,.nav-secondary ul li a:active{color:#dd4814;text-decoration:none}.nav-secondary ul li,.nav-secondary ul li.active a:link,.nav-secondary ul li.active a:visited{color:#dd4814;text-decoration:none}.nav-secondary ul.breadcrumb{margin-left:20px}.nav-secondary ul.breadcrumb li,.nav-secondary ul.breadcrumb li a:link,.nav-secondary ul.breadcrumb li a:visited{color:#888;margin-right:8px}.nav-secondary ul.breadcrumb li.active a:link,.nav-secondary ul.breadcrumb li.active a:visited{color:#dd4814}header.banner h2{font-size:1.78571em;display:block;left:4px;margin-bottom:0;position:relative;text-transform:lowercase;top:14px}header.banner h2 a:link,header.banner h2 a:visited,header.banner a{color:#fff;float:left;text-decoration:none}header.banner .logo{border-left:0;float:left;height:48px;overflow:hidden}header.banner .logo-ubuntu{background:url("//assets.ubuntu.com/sites/ubuntu/latest/u/img/ubuntu-logo.png") no-repeat scroll 0 10px transparent;font-size:18px;margin-bottom:0;position:relative;text-transform:lowercase;float:left;margin:0;display:inline-block;height:32px;min-width:128px;margin-right:-20px;margin-left:10px;padding:7px 14px 9px 0}header.banner .logo-ubuntu img{margin-right:8px;position:absolute;left:-999em}header.banner .logo-ubuntu span{float:left;font-size:23px;font-weight:300;padding-left:122px;padding-right:20px;position:relative;top:5px}header.banner .nav-primary.nav-left .logo-ubuntu{float:right}header.banner .nav-primary.nav-right .logo-ubuntu{background-image:url("//assets.ubuntu.com/sites/ubuntu/latest/u/img/logos/logo-ubuntu-white.svg");background-size:107px 25px;float:left}html.no-svg header.banner .nav-primary.nav-right .logo-ubuntu,.opera-mini header.banner .nav-primary.nav-right .logo-ubuntu{background-image:url("//assets.ubuntu.com/sites/ubuntu/latest/u/img/logos/logo-ubuntu-white.png")}@media only screen and (max-width: 295px){header.banner .nav-primary.nav-right .logo-ubuntu,header.banner .logo-ubuntu{-moz-background-size:20px 20px;-webkit-background-size:20px 20px;-o-background-size:20px 20px;background-size:20px 20px;background:url("//assets.ubuntu.com/sites/ubuntu/latest/u/img/logos/logo-ubuntu_cof-white_orange-hex.svg") 0 50% no-repeat;min-width:0;width:38px}header.banner .logo-ubuntu span{padding-left:38px}}html.no-svg header.banner .logo-ubuntu,.opera-mini header.banner .logo-ubuntu{background-image:url("//assets.ubuntu.com/sites/ubuntu/latest/u/img/logos/logo-ubuntu_cof-white_orange-hex.png")}@media only screen and (max-width: 768px){header.banner .nav-primary{-moz-box-shadow:0 1px 2px 1px rgba(0,0,0,0.2);-webkit-box-shadow:0 1px 2px 1px rgba(0,0,0,0.2);box-shadow:0 1px 2px 1px rgba(0,0,0,0.2)}header.banner .nav-primary.active{-moz-box-shadow:none;-webkit-box-shadow:none;box-shadow:none;padding:0}header nav ul.active{float:left}header nav ul.active li:last-child a:link,header nav ul.active li:last-child a:visited{border-bottom:0}header.banner .nav-primary ul{position:relative;width:100%}header.banner .nav-primary ul li.active a:link,header.banner .nav-primary ul li.active a:visited{color:#333;font-weight:700}header.banner .nav-primary ul li,header.banner .nav-primary ul li a:link,header.banner .nav-primary ul li a:visited,header.banner .nav-primary ul li a:active{-webkit-box-sizing:border-box;-moz-box-sizing:border-box;box-sizing:border-box;background:transparent;border:none;display:block;font-weight:300;margin:0;padding:0;width:100%}header.banner .nav-primary ul li a:link,header.banner .nav-primary ul li a:visited,header.banner .nav-primary ul li a:hover,header.banner .nav-primary ul li a:active{background-color:#f0f0f0;border-bottom:1px solid #d4d7d4;color:#333333;font-size:1em}header.banner .nav-primary ul li:nth-last-child(-n+2) a:link,header.banner .nav-primary ul li:nth-last-child(-n+2) a:visited{border:0}header.banner .nav-primary ul li a:hover{-moz-box-shadow:none;-webkit-box-shadow:none;box-shadow:none;background:#f8f8f8}header.banner .nav-primary ul li a.active{background-color:#ddd}header.banner nav.nav-primary ul li a:link,header.banner .nav-primary ul li a:visited,header.banner .nav-primary ul li a:hover,header.banner .nav-primary ul li a:active{padding:8px 10px;text-align:left}header.banner .nav-primary ul li{-webkit-box-sizing:border-box;-moz-box-sizing:border-box;box-sizing:border-box;background:transparent;border-bottom:0;border-right:1px solid #d4d7d4;float:left;width:50%}.nav-secondary{background:#fff}.nav-secondary ul.second-level-nav{border-top:1px solid #d4d7d4;display:none;margin-bottom:0;margin-left:0;padding-bottom:10px;padding-top:10px;width:100%}.nav-secondary ul.second-level-nav li{-webkit-box-sizing:border-box;-moz-box-sizing:border-box;box-sizing:border-box;width:50%;margin:0;float:left}.nav-secondary ul.second-level-nav li a,.nav-secondary ul.second-level-nav li a:link,.nav-secondary ul.second-level-nav li a:visited{-moz-box-sizing:border-box;-webkit-box-sizing:border-box;box-sizing:border-box;display:block;height:100%;padding:10px 10px 10px 20px;width:100%}.nav-secondary ul.second-level-nav li.active a,.nav-secondary ul.second-level-nav li.active a:link,.nav-secondary ul.second-level-nav li.active a:visited{color:#333;font-weight:700}.nav-secondary ul.third-level-nav{display:none;margin-bottom:0;width:100%;padding-bottom:20px}.nav-secondary ul.third-level-nav li{-webkit-box-sizing:border-box;-moz-box-sizing:border-box;box-sizing:border-box;width:50%;margin:0;float:left;padding-left:30px}.nav-secondary ul.third-level-nav li a,.nav-secondary ul.third-level-nav li a:link,.nav-secondary ul.third-level-nav li a:visited{-webkit-box-sizing:border-box;-moz-box-sizing:border-box;box-sizing:border-box;padding:10px 10px 10px 0;display:block;width:100%;height:100%}.nav-secondary ul.third-level-nav li.active a,.nav-secondary ul.third-level-nav li.active a:link,.nav-secondary ul.third-level-nav li.active a:visited{color:#333;font-weight:700}.nav-secondary ul.third-level-nav li.single-link{width:100%}.nav-secondary ul.third-level-nav li:only-child{width:100%}.nav-secondary ul.breadcrumb{-webkit-box-sizing:border-box;-moz-box-sizing:border-box;box-sizing:border-box;width:100%;margin-left:0;margin-bottom:0}.nav-secondary ul.breadcrumb li:first-of-type{border-bottom:1px solid #d4d7d4;margin-bottom:-1px}.nav-secondary ul.breadcrumb li{-webkit-box-sizing:border-box;-moz-box-sizing:border-box;box-sizing:border-box;color:#fff;width:100%;display:block;height:40px;margin:0}.nav-secondary ul.breadcrumb li a,.nav-secondary ul.breadcrumb li a:link,.nav-secondary ul.breadcrumb li a:visited{-webkit-box-sizing:border-box;-moz-box-sizing:border-box;box-sizing:border-box;font-size:16px;width:100%;color:#333333;display:block;margin-right:0;text-decoration:none;padding:8px 10px 0 10px}.nav-secondary ul.breadcrumb li.active{margin-top:12px}.nav-secondary ul.breadcrumb li.active a,.nav-secondary ul.breadcrumb li.active a:link,.nav-secondary ul.breadcrumb li.active a:visited{color:#333;font-weight:700}.nav-secondary ul.breadcrumb li:nth-of-type(2n){margin-top:12px}.nav-secondary ul.breadcrumb li .after{background-image:url(//assets.ubuntu.com/sites/ubuntu/latest/u/img/patterns/nav-down-arrow.svg);background-position:center center;background-repeat:no-repeat;background-size:18px;float:right;height:18px;margin-right:-5px;margin-top:-6px;padding:10px;position:relative;right:0;top:0;width:18px}.nav-secondary ul.breadcrumb li+li{display:none}.nav-secondary ul.breadcrumb li+li a:link,.nav-secondary ul.breadcrumb li+li a:active,.nav-secondary ul.breadcrumb li+li a:visited{padding-left:20px}.nav-secondary ul.breadcrumb li+li a.after{background-image:none}.nav-secondary.open ul.breadcrumb li a:after,.nav-secondary.open ul.breadcrumb li a:link:after,.nav-secondary.open ul.breadcrumb li a:visited:after{background-image:url(//assets.ubuntu.com/sites/ubuntu/latest/u/img/patterns/nav-up-arrow.svg)}.nav-secondary.open ul.breadcrumb li+li a.after{background-image:none}.nav-secondary.open ul.breadcrumb li .after{background-image:url(//assets.ubuntu.com/sites/ubuntu/latest/u/img/patterns/nav-up-arrow.svg);margin-top:-7px}.nav-secondary.open ul.second-level-nav,.nav-secondary.open ul.third-level-nav,.nav-secondary.open ul.breadcrumb li+li{display:block}.no-js .nav-secondary ul.second-level-nav{display:block}.no-js #main-navigation-link{position:absolute;right:10px;top:12px;width:20px;height:28px;z-index:999;text-indent:-999em;display:block}.no-js #main-navigation-link a{background-image:url("//assets.ubuntu.com/sites/ubuntu/latest/u/img/icons/navigation-menu-plain.svg");background-position:center center;background-repeat:no-repeat;background-size:25px auto;display:block;width:28px;height:28px;position:absolute}html.no-svg .nav-secondary ul.breadcrumb li .after,.opera-mini .nav-secondary ul.breadcrumb li .after{background-image:url(//assets.ubuntu.com/sites/ubuntu/latest/u/img/patterns/nav-down-arrow.png)}html.no-svg .nav-secondary ul.breadcrumb.open ul.breadcrumb li a:after,html.no-svg .nav-secondary ul.breadcrumb.open ul.breadcrumb li a:link:after,html.no-svg .nav-secondary ul.breadcrumb.open ul.breadcrumb li a:visited:after,.opera-mini .nav-secondary ul.breadcrumb.open ul.breadcrumb li a:after,.opera-mini .nav-secondary ul.breadcrumb.open ul.breadcrumb li a:link:after,.opera-mini .nav-secondary ul.breadcrumb.open ul.breadcrumb li a:visited:after{background-image:url(//assets.ubuntu.com/sites/ubuntu/latest/u/img/patterns/nav-up-arrow.png)}html.no-svg .nav-secondary ul.breadcrumb.open ul.breadcrumb li .after,.opera-mini .nav-secondary ul.breadcrumb.open ul.breadcrumb li .after{background-image:url(//assets.ubuntu.com/sites/ubuntu/latest/u/img/patterns/nav-up-arrow.png)}html.no-svg header.banner .nav-primary #google-appliance-search-form button[type="submit"],.opera-mini header.banner .nav-primary #google-appliance-search-form button[type="submit"]{background-image:url("//assets.ubuntu.com/sites/ubuntu/latest/u/img/search-black.png")}html.no-svg .nav-secondary ul.breadcrumb li .after,.opera-mini .nav-secondary ul.breadcrumb li .after{background-image:url(//assets.ubuntu.com/sites/ubuntu/latest/u/img/patterns/nav-down-arrow.png)}html.no-svg .nav-secondary ul.breadcrumb.open ul.breadcrumb li a:after,html.no-svg .nav-secondary ul.breadcrumb.open ul.breadcrumb li a:link:after,html.no-svg .nav-secondary ul.breadcrumb.open ul.breadcrumb li a:visited:after,.opera-mini .nav-secondary ul.breadcrumb.open ul.breadcrumb li a:after,.opera-mini .nav-secondary ul.breadcrumb.open ul.breadcrumb li a:link:after,.opera-mini .nav-secondary ul.breadcrumb.open ul.breadcrumb li a:visited:after{background-image:url(//assets.ubuntu.com/sites/ubuntu/latest/u/img/patterns/nav-up-arrow.png)}html.no-svg .nav-secondary ul.breadcrumb.open ul.breadcrumb li .after,.opera-mini .nav-secondary ul.breadcrumb.open ul.breadcrumb li .after{background-image:url(//assets.ubuntu.com/sites/ubuntu/latest/u/img/patterns/nav-up-arrow.png)}html.no-svg header.banner .nav-primary #google-appliance-search-form button[type="submit"],.opera-mini header.banner .nav-primary #google-appliance-search-form button[type="submit"]{background-image:url("//assets.ubuntu.com/sites/ubuntu/latest/u/img/search-black.png")}header.banner .nav-toggle{background-image:url("//assets.ubuntu.com/sites/ubuntu/latest/u/img/icons/navigation-menu-plain.svg");background-position:center center;background-repeat:no-repeat;background-size:25px auto;cursor:pointer;display:block;height:48px;position:absolute;right:0;text-indent:-99999px;width:48px}html.no-svg header.banner .nav-toggle,.opera-mini header.banner .nav-toggle{background-image:url("//assets.ubuntu.com/sites/ubuntu/latest/u/img/icons/navigation-menu-plain.png")}}@media only screen and (min-width: 768px){header.banner .nav-primary ul li{border-bottom:0}}@media only screen and (min-width: 769px){header.banner{-moz-box-shadow:0 2px 2px -2px #777777 inset, 2px 1px #FFFFFF;-webkit-box-shadow:0 2px 2px -2px #777777 inset, 2px 1px #FFFFFF;box-shadow:0 2px 2px -2px #777777 inset, 2px 1px #FFFFFF}header.banner nav.nav-primary{-moz-box-shadow:none;-webkit-box-shadow:none;box-shadow:none;border-bottom:0}header.banner .nav-toggle{display:none}header.banner nav ul{background-color:transparent;display:block}header.banner .nav-primary ul li{border-left:1px solid #C64012}header.banner .nav-primary ul li a:active,header.banner .nav-primary ul li a:hover,header.banner .nav-primary ul li a:visited,header.banner nav.nav-primary ul li a:link{border-left:1px solid #EC5B29}header.banner .nav-primary ul li:last-child{border-right:1px solid #C64012;border-left:1px solid #C64012}header.banner .nav-primary ul li a.active{background-color:#B83A10}header.banner .nav-primary ul li a:hover{background-color:#E1662F}.nav-secondary ul:last-child li:last-child{padding-bottom:10px}.nav-secondary ul.breadcrumb li,.nav-secondary ul.second-level-nav li,.nav-secondary ul.third-level-nav li{margin-right:15px}.nav-secondary ul.breadcrumb{float:left}.nav-secondary ul.breadcrumb li{margin-bottom:10px}.nav-secondary ul{float:none;margin-bottom:0}.nav-secondary ul li{margin-bottom:5px}}@media only screen and (min-width: 984px){header.banner{margin-bottom:20px}header.banner nav.nav-primary ul{display:block}header.banner .nav-primary,#nav-global .nav-global-wrapper{width:984px}header.banner .nav-primary.nav-right .logo-ubuntu{margin-left:0}}header.banner .nav-primary ul{position:static}header.banner .nav-primary li ul{-moz-box-shadow:0 2px 2px -1px #777;-webkit-box-shadow:0 2px 2px -1px #777;box-shadow:0 2px 2px -1px #777;-webkit-border-radius:10px;-moz-border-radius:10px;border-radius:10px;background:#f7f7f7;border:1px solid #d5d5d5;display:none;float:none;margin:0;padding:5px 0;position:absolute;top:51px;width:200px}header.banner .nav-primary li:hover ul:after{background:url("//assets.ubuntu.com/sites/ubuntu/latest/u/img/patterns/arrow-up-smaller.png") no-repeat;content:'';display:block;height:8px;left:20px;position:relative;top:-13px;width:200px;z-index:999}.no-generatedcontent header.banner .nav-primary li ul{-webkit-border-radius:0 0 10px 10px;-moz-border-radius:0 0 10px 10px;border-radius:0 0 10px 10px;top:48px}header.banner .nav-primary li ul .arrow-up{display:none}header.banner .nav-primary li ul li{border:0;float:none}header.banner .nav-primary li ul li a:link,header.banner .nav-primary li ul li a:visited{border:0;color:#333333;padding:0 0 11px 14px;text-align:left;width:170px}header.banner .nav-primary li ul li a:hover{background:none repeat scroll 0 0 transparent;-moz-box-shadow:none;-webkit-box-shadow:none;box-shadow:none;color:#DD4814}header.banner .nav-primary li ul li.first a:link,header.banner .nav-primary li ul li.first a:visited,header.banner .nav-primary li ul li:first-of-type a:link{padding:10px 14px}header.banner .nav-primary li ul li.active a:link,header.banner .nav-primary li ul li.active a:visited{background:none repeat scroll 0 0 transparent !important}header.banner .nav-primary li ul .promo{border-top:1px solid #D5D5D5;float:left;margin-top:5px;padding:15px 0 0}header.banner .nav-primary li ul .promo a:link,header.banner .nav-primary li ul .promo a:visited{background:none repeat scroll 0 0 transparent;border-left:0 none;color:#333333;height:auto;padding:0;text-align:left}header.banner .nav-primary li ul .promo p{margin:0 10px}header.banner .nav-primary li ul .promo a:hover{box-shadow:none;color:#DD4814}header.banner .nav-primary li ul .promo img{margin-top:14px;margin-bottom:-6px;-webkit-border-radius:0 0 10px 10px;-moz-border-radius:0 0 10px 10px;border-radius:0 0 10px 10px;position:relative;top:1px}header.banner .nav-primary li ul .promo .category{color:#888;font-size:11px;margin:0 10px;text-transform:uppercase}header.banner .nav-primary li:hover ul{display:block}html.lt-ie8 header.banner .nav-primary li:hover ul{display:none}.header-search,#box-search{padding:7px 0 7px 14px;overflow:hidden}.header-search input[type="search"],.header-search input[type="text"],#box-search input[type="search"],#box-search input[type="text"]{-webkit-appearance:none;-moz-box-shadow:inset 0 1px 4px rgba(0,0,0,0.2);-webkit-box-shadow:inset 0 1px 4px rgba(0,0,0,0.2);box-shadow:inset 0 1px 4px rgba(0,0,0,0.2);-webkit-box-sizing:border-box;-moz-box-sizing:border-box;box-sizing:border-box;-webkit-border-radius:4px;-moz-border-radius:4px;border-radius:4px;-webkit-transition:all 0.5s ease-out;-moz-transition:all 0.5s ease-out;-ms-transition:all 0.5s ease-out;-o-transition:all 0.5s ease-out;transition:all 0.5s ease-out;background-color:#be3d00;border:none;color:#fff;display:block;float:left;font-size:16px;height:2.1em;margin-bottom:0;padding:0.5em 2.5em 0.5em 0.5em;width:100%}.header-search ::-webkit-input-placeholder,#box-search ::-webkit-input-placeholder{color:white;opacity:0.4}.header-search ::-webkit-input-placeholder,#box-search ::-webkit-input-placeholder{color:white;opacity:0.4}.header-search ::-moz-placeholder,#box-search ::-moz-placeholder{color:white;opacity:0.4}.header-search :-ms-input-placeholder,#box-search :-ms-input-placeholder{color:white;opacity:0.4}.header-search input:-moz-placeholder,#box-search input:-moz-placeholder{color:white;opacity:0.4}.header-search ::placeholder,#box-search ::placeholder{color:white;opacity:0.4}.header-search input[type="search"]:focus,#box-search input[type="search"]:focus{background-color:#a63603}.header-search button[type=submit],#box-search button[type=submit]{padding:3px 2px;line-height:0;float:left;margin-left:-40px;display:block;background:none;overflow:visible}.header-search button[type=submit]:hover,#box-search button[type=submit]:hover{background:none}.header-search button[type=submit] img,#box-search button[type=submit] img{height:28px;width:28px}header.banner .search-toggle{-moz-background-size:20px 20px;-webkit-background-size:20px 20px;-o-background-size:20px 20px;background-size:20px 20px;background-image:url("//assets.ubuntu.com/sites/ubuntu/latest/u/img/search_icon_white_64.png");background-image:url("//assets.ubuntu.com/sites/ubuntu/latest/u/img/search.svg");background-position:center center;background-repeat:no-repeat;display:block;height:48px;outline:none;overflow:hidden;position:absolute;right:58px;text-indent:-999em;top:0;width:24px}.search-toggle:link,.search-toggle:active{outline:none}#box-search,.header-search{background:#f0f0f0;border:0;display:none;float:left;margin-bottom:0;position:relative;margin:0 0 -1px 0;padding:0;width:100%;z-index:3}#box-search.active,.header-search.active,.header-search.open{display:block}#box-search div,.header-search div{-moz-box-shadow:inset 0 -4px 4px -4px rgba(0,0,0,0.3),inset 0 5px 5px -5px rgba(0,0,0,0.3);-webkit-box-shadow:inset 0 -4px 4px -4px rgba(0,0,0,0.3),inset 0 5px 5px -5px rgba(0,0,0,0.3);box-shadow:inset 0 -4px 4px -4px rgba(0,0,0,0.3),inset 0 5px 5px -5px rgba(0,0,0,0.3);background:#f0f0f0;margin:10px;position:relative;z-index:1}#box-search form input[type="search"],.header-search form input[type="search"]{font-size:1.14286em;-webkit-border-radius:4px 4px 4px 4px;-moz-border-radius:4px 4px 4px 4px;border-radius:4px 4px 4px 4px;-moz-box-shadow:0 2px 2px rgba(0,0,0,0.3) inset,0 -1px 3px rgba(0,0,0,0.2) inset,0 2px 0 rgba(255,255,255,0.4);-webkit-box-shadow:0 2px 2px rgba(0,0,0,0.3) inset,0 -1px 3px rgba(0,0,0,0.2) inset,0 2px 0 rgba(255,255,255,0.4);box-shadow:0 2px 2px rgba(0,0,0,0.3) inset,0 -1px 3px rgba(0,0,0,0.2) inset,0 2px 0 rgba(255,255,255,0.4);-webkit-box-sizing:border-box;-moz-box-sizing:border-box;box-sizing:border-box;background:#fff;border:0;color:#333;font-size:16px;height:auto;margin:0;float:left;padding:9px 10px;width:100%}.yes-js .header-inner #box-search,.yes-js .header-inner .header-search{display:none}.yes-js .header-inner #box-search form,.yes-js .header-inner .header-search form{-webkit-box-sizing:border-box;-moz-box-sizing:border-box;box-sizing:border-box;margin-left:0;margin-right:0;overflow:hidden;padding:10px;top:0;z-index:999;position:relative;width:100%}@media only screen and (max-width: 768px){header.banner .search-toggle{right:48px}html.no-svg .search-toggle,.opera-mini .search-toggle{background-image:url("//assets.ubuntu.com/sites/ubuntu/latest/u/img/search-white.png")}}@media only screen and (min-width: 768px){header.banner .search-toggle{display:none}}@media only screen and (min-width: 960px){#box-search,.header-search{background:none;overflow:hidden;padding:7px 0 7px 14px;border-right:0 none;float:right;margin-bottom:0;padding-bottom:5px;padding-right:0;padding-top:7px;max-width:220px}#box-search form input[type="text"],#box-search form input[type="search"],.header-search form input[type="text"],.header-search form input[type="search"]{-moz-box-shadow:0 2px 4px rgba(0,0,0,0.4) inset;-webkit-box-shadow:0 2px 4px rgba(0,0,0,0.4) inset;box-shadow:0 2px 4px rgba(0,0,0,0.4) inset;-webkit-box-sizing:content-box;-moz-box-sizing:content-box;box-sizing:content-box;background:url("//assets.ubuntu.com/sites/ubuntu/latest/u/img/icons/icon-search.png") no-repeat scroll 5px center,none repeat scroll 0 0 #BE3D00;border:6px solid #DE6532;border-width:0 0 1px;color:#fff;font-size:0.813em;height:24px;margin-bottom:0;padding:4px 4px 4px 30px;transition:all 0.5s ease 0s;width:86px}}@media only screen and (max-width: 960px){header.banner nav.nav-primary .header-search{padding:0;position:relative;top:0;width:100%}header.banner nav.nav-primary .header-search input[type="search"]{border-radius:0;background:#f7f7f7;color:#333}header.banner nav.nav-primary .header-search button[type="submit"]{width:32px;height:38px;background:url("//assets.ubuntu.com/sites/ubuntu/latest/u/img/search-black.svg") no-repeat scroll center center transparent;background-size:28px 28px}header.banner nav.nav-primary .header-search button[type="submit"] img{max-width:none;display:none}header.banner nav.nav-primary .header-search.open{display:block}header.banner .search-toggle{background-image:url("//assets.ubuntu.com/sites/ubuntu/latest/u/img/search-white.svg");background-position:center center;background-repeat:no-repeat;background-size:25px auto;cursor:pointer;right:0;display:block;height:48px;position:absolute;text-indent:-99999px;width:48px}html.no-svg header.banner .search-toggle,.opera-mini header.banner .search-toggle{background-image:url("//assets.ubuntu.com/sites/ubuntu/latest/u/img/img/search-white.png")}.opera-mini x:-o-prefocus,.opera-mini header.banner .search-toggle{-o-background-size:25px auto;background-size:25px auto}}@media only screen and (min-width: 984px){#box-search,.header-search{display:block;margin-right:0}#box-search form input[type="text"]:focus,.header-search form input[type="text"]:focus{width:160px}}@media only screen and (max-width: 768px){header.banner .search-toggle{right:48px}}body.ubuntu-search .nav-secondary,body.search-results .nav-secondary,body.search-no-results .nav-secondary{display:none}body.ubuntu-search section>h1,body.ubuntu-search section article h1,body.search-results section>h1,body.search-results section article h1,body.search-no-results section>h1,body.search-no-results section article h1{padding-bottom:10px;font-size:1.438em;margin-bottom:0}body.ubuntu-search section>h1,body.search-results section>h1,body.search-no-results section>h1{border-bottom:1px dotted #dfdcd9}body.ubuntu-search .main-search,body.search-results .main-search,body.search-no-results .main-search{padding:20px 0;margin:0 0 20px 0;background-color:transparent}body.ubuntu-search .main-search input[type="search"],body.search-results .main-search input[type="search"],body.search-no-results .main-search input[type="search"]{float:left;width:100%;font-size:2em;border:1px solid #999;-moz-box-sizing:border-box;box-sizing:border-box;padding:0.2em 65px 0.2em 0.2em}body.ubuntu-search .main-search button[type=submit],body.search-results .main-search button[type=submit],body.search-no-results .main-search button[type=submit]{padding:4px;line-height:0;float:left;margin-left:-53px;display:block;background:none;overflow:visible;width:auto;margin-top:-4px}body.ubuntu-search .main-search button[type=submit]:hover,body.search-results .main-search button[type=submit]:hover,body.search-no-results .main-search button[type=submit]:hover{background:none}body.ubuntu-search .main-search button[type=submit] img,body.search-results .main-search button[type=submit] img,body.search-no-results .main-search button[type=submit] img{height:45px;width:45px}body.ubuntu-search .search-result h1 .title-main,body.search-results .search-result h1 .title-main,body.search-no-results .search-result h1 .title-main{margin-right:20px}body.ubuntu-search .search-result h1 .result-url,body.search-results .search-result h1 .result-url,body.search-no-results .search-result h1 .result-url{color:#999;overflow:hidden;text-overflow:ellipsis;display:block;vertical-align:bottom;padding-bottom:2px}body.ubuntu-search .search-result h1 .result-url a,body.search-results .search-result h1 .result-url a,body.search-no-results .search-result h1 .result-url a{color:#999}body.ubuntu-search .search-result p,body.search-results .search-result p,body.search-no-results .search-result p{margin-bottom:0}body.ubuntu-search .num-results,body.search-results .num-results,body.search-no-results .num-results{display:inline-block;margin-left:20px}body.ubuntu-search .bottom-results-total,body.search-results .bottom-results-total,body.search-no-results .bottom-results-total{text-align:center;width:100%;overflow:visible;padding-top:20px;margin:0}body.ubuntu-search .bottom-nav,body.search-results .bottom-nav,body.search-no-results .bottom-nav{overflow:hidden;margin-top:-26px}body.ubuntu-search .bottom-nav ul,body.search-results .bottom-nav ul,body.search-no-results .bottom-nav ul{margin-bottom:0;margin-left:0;padding:0;overflow:hidden}body.ubuntu-search .bottom-nav li,body.search-results .bottom-nav li,body.search-no-results .bottom-nav li{float:left;margin-left:15px}body.ubuntu-search .bottom-nav li:first-child,body.search-results .bottom-nav li:first-child,body.search-no-results .bottom-nav li:first-child{margin-left:0}body.ubuntu-search .nav-back,body.search-results .nav-back,body.search-no-results .nav-back{float:left}body.ubuntu-search .nav-back li:before,body.search-results .nav-back li:before,body.search-no-results .nav-back li:before{content:"\2039";color:#dd4814;margin-right:5px}body.ubuntu-search .nav-back li.item-extreme:before,body.search-results .nav-back li.item-extreme:before,body.search-no-results .nav-back li.item-extreme:before{content:"\2039\2039"}body.ubuntu-search .nav-forward,body.search-results .nav-forward,body.search-no-results .nav-forward{float:right}body.ubuntu-search .nav-forward li:after,body.search-results .nav-forward li:after,body.search-no-results .nav-forward li:after{content:"\203A";color:#dd4814;margin-left:5px}body.ubuntu-search .nav-forward li.item-extreme:after,body.search-results .nav-forward li.item-extreme:after,body.search-no-results .nav-forward li.item-extreme:after{content:"\203A\203A"}body.ubuntu-search .error-notification,body.search-results .error-notification,body.search-no-results .error-notification{background-color:#fdffdc;color:#333;padding:20px;-moz-box-sizing:border-box;box-sizing:border-box;width:100%;margin-top:20px;display:block}body.ubuntu-search .result-line,body.search-results .result-line,body.search-no-results .result-line{color:#ada69e}body.ubuntu-search .results-top,body.search-results .results-top,body.search-no-results .results-top{border-bottom:1px dotted #dfdcd9;padding-bottom:0.5em}body.ubuntu-search .search-container,body.search-results .search-container,body.search-no-results .search-container{padding-bottom:0}@media only screen and (min-width: 768px){.ubuntu-search .main-search button[type=submit]{margin-left:-60px;margin-top:0}}body footer.global #nav-global li:first-of-type a{margin-left:0}footer.global{-webkit-box-sizing:border-box;-moz-box-sizing:border-box;box-sizing:border-box;-moz-box-shadow:inset 0 2px 2px -1px #d3d3d3;-webkit-box-shadow:inset 0 2px 2px -1px #d3d3d3;box-shadow:inset 0 2px 2px -1px #d3d3d3;background:none;border-top:0;clear:both;display:block;padding:30px 10px 20px;position:relative;width:100%}footer.global .legal{margin:0 auto;width:100%}footer.global .legal{background-image:none;position:relative;clear:both;min-height:40px}footer.global .legal p,footer.global .legal ul{padding-left:0}footer.global h2{font-size:0.75em;line-height:1.4;margin-bottom:0;padding-bottom:0.5em}footer.global h2,footer.global h2 a:link,footer.global h2 a:visited{color:#333;font-weight:normal}footer.global nav ul li h2 a:after{content:""}footer.global ul{margin:0}footer.global nav ul li.two-col{display:inline-block;min-height:10em;vertical-align:top}footer.global nav ul li li{font-size:0.85714em;font-size:0.75em;margin-bottom:0}footer.global ul li li a:link,footer.global ul li li a:visited{color:#333;margin-bottom:0}footer.global ul li li a:hover,footer.global ul li li a:active,footer.global h2 a:hover,footer.global h2 a:active{color:#dd4814}footer.global .inline li{display:inline}footer.global p,footer.global ul.inline li a{color:#333;font-size:12px;margin-bottom:0}footer.global ul.inline li a:hover{color:#dd4814}footer.global ul.inline li:after{color:#888;content:"\00b7";vertical-align:middle;margin:0 5px}footer.global ul.inline li:last-child{width:120px}footer.global ul.inline li:last-child:after{content:""}footer.global .inline li{float:none;margin-bottom:0}footer.global .top-link{-moz-box-shadow:0 -4px 4px -4px rgba(0,0,0,0.3) inset;-webkit-box-shadow:0 -4px 4px -4px rgba(0,0,0,0.3) inset;box-shadow:0 -4px 4px -4px rgba(0,0,0,0.3) inset;background:none repeat scroll 0 0 transparent;border:0 none;float:left;font-size:0.75em;letter-spacing:0.05em;margin:0 0 0 -10px;padding-right:20px;text-transform:uppercase;width:100%}footer.global .top-link a{-webkit-box-sizing:border-box;-moz-box-sizing:border-box;box-sizing:border-box;background-image:url("//assets.ubuntu.com/sites/ubuntu/latest/u/img/pictograms/picto-pack/picto-upload-warmgrey.svg");background-position:10px center;background-repeat:no-repeat;background-size:14px 14px;border-bottom:0 none;color:#888888;display:block;float:none;font-weight:400;padding:12px 0 12px 28px}html.no-svg footer.global .top-link a,.opera-mini footer.global .top-link a{background-image:url("//assets.ubuntu.com/sites/ubuntu/latest/u/img/pictograms/picto-pack/picto-upload-warmgrey.png")}@media only screen and (max-width: 768px){footer.no-global .legal{-webkit-box-sizing:content-box;-moz-box-sizing:content-box;box-sizing:content-box;box-shadow:0 2px 2px -1px #D3D3D3 inset;padding-top:10px;margin-left:-10px;padding-left:10px;padding-right:10px}#livechat-eye-catcher{display:block}}@media only screen and (min-width: 768px){footer.global .inline li{display:inline;float:left}}@media only screen and (min-width: 769px){footer.global .top-link{display:none}footer.global .footer-b h2 a i{font-style:normal;display:inline}}@media only screen and (min-width: 984px){footer.global .legal{width:984px}footer.global{padding:30px 0 20px}footer.global .legal{background:url("//assets.ubuntu.com/sites/ubuntu/latest/u/img/logos/logo-ubuntu-grey.png") 100% 0 no-repeat}footer.global .footer-a{display:block}}#context-footer{-webkit-box-sizing:border-box;-moz-box-sizing:border-box;box-sizing:border-box;font-size:1em;border-bottom:0;clear:both;padding-bottom:1px;padding-top:0;position:relative;margin-bottom:0;margin-left:0;margin-right:0;width:100%}#context-footer hr{-moz-box-shadow:inset 0 2px 2px -2px #333;-webkit-box-shadow:inset 0 2px 2px -2px #333;box-shadow:inset 0 2px 2px -2px #333;background:#dd4814;height:14px;margin:0 0 10px;border:0;clear:both}#context-footer div.twelve-col{display:table;float:none;margin-bottom:7px}#context-footer div div{display:block;padding-left:0;margin-bottom:20px}#context-footer div div div{display:block;padding-left:0;margin-bottom:0}#context-footer div div.feature-one{padding-left:0}#context-footer div div.feature-four{margin-bottom:0;margin-right:0}#context-footer>div{padding-left:10px;padding-right:10px}#context-footer ul{margin-bottom:5px}#context-footer li.active{display:none}#context-footer h3{font-size:1.14286em;font-weight:normal}#context-footer .list a:after,#context-footer a.link-arrow:after,#context-footer nav ul li h2 a:after{content:' \203A'}@media only screen and (min-width: 768px){#context-footer{margin-bottom:12px;padding-left:30px;padding-right:30px}#context-footer div+div{width:31%}#context-footer div div.feature-four{padding-bottom:20px}#context-footer hr{margin:0 -30px 40px}#context-footer>div{padding-left:0;padding-right:0}}@media only screen and (min-width: 984px){#context-footer{padding:0 40px 10px}#context-footer div div{display:table-cell;float:none;padding-left:20px;margin-bottom:0}#context-footer hr{margin:0 -40px 40px}}a.link-cta-ubuntu,a.link-cta-canonical,a.link-cta-inverted,button.cta-ubuntu,button.cta-canonical,form button[type="submit"],form input[type="submit"]{-webkit-box-sizing:border-box;-moz-box-sizing:border-box;box-sizing:border-box;font-size:1.14286em;-webkit-border-radius:3px;-moz-border-radius:3px;border-radius:3px;background:#dd4814;color:#fff;text-decoration:none;display:inline-block;margin:0;font-family:Ubuntu, Arial, 'libra sans', sans-serif;font-weight:300;-webkit-font-smoothing:subpixel-antialiased;-moz-font-smoothing:subpixel-antialiased;-o-font-smoothing:subpixel-antialiased;font-smoothing:subpixel-antialiased;padding:8px 14px;width:100%;text-align:center}a.cta-large,button.cta-large{font-size:1.28571em;padding:10px 20px}a.link-cta-canonical,button.cta-canonical,form button.cta-canonical[type="submit"],form input.cta-canonical[type="submit"]{background:#772953;color:#fff}a.link-cta-inverted,button.cta-inverted{background:#fff;color:#333}.row-enterprise a.link-cta-canonical,.row-enterprise button.link-cta-canonical{background:#fff;color:#772953}a.link-cta-ubuntu:hover,a.link-cta-ubuntu:hover,button.cta-ubuntu:hover,form button[type="submit"]:hover,form input[type="submit"]:hover{background:#c03f11;text-decoration:none}a.link-cta-canonical:hover,button.cta-canonical:hover{background:#5f2143;text-decoration:none}a.link-cta-inverted:hover,.row-enterprise a.link-cta-canonical:hover,button.cta-inverted:hover,.row-enterprise button.cta-canonical:hover{background:#fff;text-decoration:underline}a.cta-deactivated,a.cta-deactivated:hover,button.cta-deactivated,button.cta-deactivated:hover{background:#efefef;color:#fff;cursor:not-allowed}@media only screen and (min-width: 768px){a.link-cta-ubuntu,a.link-cta-canonical,a.link-cta-inverted,button.cta-ubuntu,button.cta-canonical,form button[type="submit"],form input[type="submit"]{width:auto}}@media only screen and (min-width: 984px){a.link-cta-ubuntu,a.link-cta-canonical,a.link-cta-inverted,button.cta-ubuntu,button.cta-canonical,form button[type="submit"],form input[type="submit"]{width:auto}}form input,form select,form textarea{-webkit-box-sizing:border-box;-moz-box-sizing:border-box;box-sizing:border-box;width:100%}form .fieldset-submit ul{margin-bottom:0}form fieldset .mktError,form fieldset .errMsg,form fieldset .reqMark{color:#df382c}form fieldset .mktFormMsg{clear:both;display:block}.row{-webkit-box-sizing:border-box;-moz-box-sizing:border-box;box-sizing:border-box;border-bottom:1px dotted #888;clear:both;padding:20px 10px 0;position:relative}.row br{display:none}.row.no-padding-bottom{padding-bottom:0 !important}.row-grey{background:#f7f7f7}.no-border{border:0}#main-content .row-hero{margin-top:20px;padding-top:0}.row-background{color:#fff;background:url("//assets.ubuntu.com/sites/ubuntu/latest/u/img/backgrounds/image-background-wallpaper.jpg") no-repeat scroll 50% 50% #4b1827}.row-background a.alternate{color:#fff;text-decoration:underline}.row-background a.alternate:hover{color:rgba(255,255,255,0.6)}@media only screen and (min-width: 768px){.row-background{background-position:center 50%;background-size:100% auto}}.strip{width:100%;display:block}.strip-dark{background-color:#2c001e;background-image:url("//assets.ubuntu.com/sites/ubuntu/latest/u/img/backgrounds/background-grid.png");background-repeat:repeat;color:#fff}.strip-dark .list-ubuntu li{border:0}.strip-dark .resource{color:#333;-moz-box-shadow:none;-webkit-box-shadow:none;box-shadow:none}.strip-dark .resource:before{border-right-color:#2c001e}#main-content .strip-dark .resource:before{border-bottom-width:29px;right:-2px;top:-1px}#main-content .strip-dark .resource:hover:before{border-bottom-width:34px}#main-content .strip-dark .resource h2{padding-right:20px}.row-aux{background-color:rgba(255,255,255,0.6);text-align:center}.row-aux h2,.row-aux p{text-align:left}.row-aux a p{color:#333;margin-bottom:30px}.row-step h2{position:relative;top:5px}.row-step .step{position:relative;top:-5px;height:32px;width:32px;border-radius:50%;border:3px solid #dd4814;color:#dd4814;line-height:32px;text-align:center;background-color:#fff;font-size:23px;font-weight:400}@media only screen and (min-width: 768px){.row{padding:30px}#main-content .row-hero{margin-top:40px}}@media only screen and (min-width: 769px){.row-step .step{height:42px;width:42px;line-height:42px}.row br{display:block}}@media only screen and (min-width: 984px){.row br{display:block}.row{padding:60px 40px 40px}.no-border{border:0}}.box,.box-grey{-webkit-border-radius:4px;-moz-border-radius:4px;border-radius:4px;padding:1.333em 20px}.box{background:#fff;border:1px solid #dfdcd9}.box-grey{background:#f7f7f7;color:#333}.box-orange{background:#dd4814;color:#fff}.box-highlight{-moz-box-shadow:0 2px 2px 0 #c2c2c2;-webkit-box-shadow:0 2px 2px 0 #c2c2c2;box-shadow:0 2px 2px 0 #c2c2c2;border:1px solid #f7f7f7}.box-textured{-moz-box-shadow:0 2px 2px 0 #c2c2c2;-webkit-box-shadow:0 2px 2px 0 #c2c2c2;box-shadow:0 2px 2px 0 #c2c2c2;background:url("//assets.ubuntu.com/sites/ubuntu/latest/u/img/patterns/grey-textured-background.jpg");border:0}.box-padded{-webkit-border-radius:4px;-moz-border-radius:4px;border-radius:4px;background:#efefef;border:0;margin-bottom:20px;padding:6px 5px}.box-padded h3{font-size:1.39286em;margin-left:5px;margin-top:5px}.box-padded li h3{font-size:1.39286em;margin:0}.box-padded div{-webkit-border-radius:4px;-moz-border-radius:4px;border-radius:4px;background:#fff;overflow:hidden;padding:8px 8px 2px}.box-padded-feature{-webkit-border-radius:4px;-moz-border-radius:4px;border-radius:4px;background:url("//assets.ubuntu.com/sites/ubuntu/latest/u/img/patterns/soft-centre-bkg.gif") repeat scroll 0 0 #a09f9f;border:0;margin-bottom:20px;padding:11px 5px 6px}.box-padded-feature h3{color:#fff;margin-left:5px;font-size:1.39286em}.box-padded-feature h4{font-size:1.14286em;font-weight:normal}.box-padded-feature>div{-webkit-border-radius:4px;-moz-border-radius:4px;border-radius:4px;background:#fff;overflow:hidden;padding:20px 8px}.box-padded-feature div div{margin-bottom:0}.box-padded-feature .inline-icons{display:table;width:100%;margin:0;text-align:center}.box-padded-feature .inline-icons li{display:table-cell;text-align:left;float:none}.box-padded-feature .one-col{width:48px;float:left}.resource{cursor:pointer;padding-bottom:40px;position:relative;-moz-transition:background .2s ease-out;-webkit-transition:background .2s ease-out;transition:background .2s ease-out}.resource h2{padding-right:20px}.resource.five-col h2 a:link,.resource.five-col h2 a:visited,.resource.four-col h2 a:link,.resource.four-col h2 a:visited{font-size:inherit !important}.resource.four-col h2 a:link,.resource.four-col h2 a:visited{font-size:1.125em}.resource.twelve-col h2 a:link,.resource.twelve-col h2 a:visited{font-size:1.40625em}.resource:hover{background-color:#fafafa}.resource:after{-moz-box-shadow:0 -1px 2px 0 #ddd;-webkit-box-shadow:0 -1px 2px 0 #ddd;box-shadow:0 -1px 2px 0 #ddd;content:'';height:1px;position:absolute;right:-6px;top:14px;-ms-transform:rotate(45deg);-webkit-transform:rotate(45deg);transform:rotate(45deg);-moz-transition:all .2s ease-out;-webkit-transition:all .2s ease-out;transition:all .2s ease-out;width:41px;z-index:2}.resource:hover:after{right:-9px;top:18px;width:48px}.resource:before{content:'';position:absolute;-moz-transition:border-width .2s ease-out;-webkit-transition:border-width .2s ease-out;transition:border-width .2s ease-out;top:-2px;right:-3px;width:0;height:0;border-bottom:30px solid #fdfdfd;border-right:30px solid #fff;-webkit-box-shadow:-2px 2px 2px rgba(176,176,176,0.4);-moz-box-shadow:-2px 2px 2px rgba(176,176,176,0.4);box-shadow:-2px 2px 2px rgba(176,176,176,0.4);z-index:2;-webkit-border-radius:0 0 0 0;-moz-border-radius:0 0 0 0;border-radius:0 0 0 0}.resource:hover:before{border-bottom-width:35px;border-right-width:35px}.resource:last-of-type{margin-bottom:30px}.resource .content-cat{background:url("//assets.ubuntu.com/sites/ubuntu/latest/u/img/icons/icon-resource-hub-icon-document.png") left center no-repeat;color:#aea79f;font-size:14px;letter-spacing:1px;margin:0;padding-left:20px;padding:0;position:absolute;text-transform:uppercase}.resource .content-cat-webinar{background:url("//assets.ubuntu.com/sites/ubuntu/latest/u/img/icons/icon-resource-hub-webinar.png") left center no-repeat}.resource.box-image-centered div+span img{margin-top:40px}html.yui3-js-enabled .resource:hover a{text-decoration:underline}.row-grey .resource:before{border-right-color:#f7f7f7}@media only screen and (max-width: 768px){.box-padded-feature .inline-icons li{float:left;display:block}.box-padded-feature .one-col{width:48px;float:left}}.arrow-up,.arrow-down,.arrow-right,.arrow-left{height:11px;position:absolute;width:18px}.arrow-up{background:url("//assets.ubuntu.com/sites/ubuntu/latest/u/img/patterns/arrow-up.png") 0 0 no-repeat;left:20px;top:-11px}.arrow-down{background:url("//assets.ubuntu.com/sites/ubuntu/latest/u/img/patterns/arrow-down.png") 0 0 no-repeat;bottom:-11px;right:20px}.arrow-right{background:url("//assets.ubuntu.com/sites/ubuntu/latest/u/img/patterns/arrow-right.png") 0 0 no-repeat;height:18px;right:-11px;top:20px;width:11px}.arrow-left{background:url("//assets.ubuntu.com/sites/ubuntu/latest/u/img/patterns/arrow-left.png") 0 0 no-repeat;bottom:20px;height:18px;left:-11px;width:11px}div>.arrow-left{left:-10px}@media only screen and (min-width: 769px){html.yui3-js-enabled .arrow{visibility:visible}}.list,.list-ubuntu,.list-canonical{list-style:none;margin-left:0}.list li,.list-ubuntu li,.list-canonical li{border-bottom:1px dotted #888;margin-bottom:0;padding:10px 0}.list li:last-of-type,.list li.last-item,.list-ubuntu li:last-of-type,.list-ubuntu li.last-item,.list-canonical li:last-of-type,.list-canonical li.last-item{border:0;padding-bottom:0}.list article{border-bottom:1px dotted #888;margin-bottom:0;padding:10px 0}.list-spaced article,.list-spaced li{padding:30px 0}nav .list a{display:block}.list-ubuntu li,.list-canonical li{background-repeat:no-repeat;background-position:0 1em;padding-left:25px}.list-ubuntu li{background-image:url("//assets.ubuntu.com/sites/ubuntu/latest/u/img/patterns/tick-orange.svg")}.list-canonical li{background-image:url("//assets.ubuntu.com/sites/ubuntu/latest/u/img/patterns/tick-midaubergine.svg")}.list-warm li{background-image:url("//assets.ubuntu.com/sites/ubuntu/latest/u/img/patterns/tick-warmgrey.svg")}.list-dark li{background-image:url("//assets.ubuntu.com/sites/ubuntu/latest/u/img/patterns/tick-darkaubergine.svg")}.vertical-divider .list-canonical li,.vertical-divider .list-ubuntu li{padding-left:25px}html.no-svg .list-ubuntu li,.opera-mini .list-ubuntu li{background-image:url("//assets.ubuntu.com/sites/ubuntu/latest/u/img/patterns/tick-orange.png")}html.no-svg .list-canonical li,.opera-mini .list-canonical li{background-image:url("//assets.ubuntu.com/sites/ubuntu/latest/u/img/patterns/tick-midaubergine.png")}html.no-svg .list-warm li,.opera-mini .list-warm li{background-image:url("//assets.ubuntu.com/sites/ubuntu/latest/u/img/patterns/tick-warmgrey.png")}html.no-svg .list-dark li,.opera-mini .list-dark li{background-image:url("//assets.ubuntu.com/sites/ubuntu/latest/u/img/patterns/tick-darkaubergine.png")}.no-bullets{list-style:none;margin-left:0}.row .combined-list ul,.row .combined-list div{margin-bottom:0}.row .combined-list li.last-item{border-bottom:1px dotted #888;padding-bottom:10px}.row .combined-list div.last-col,.row .combined-list ul.last-col{margin-bottom:20px}.row .combined-list div.last-col li.last-item,.row .combined-list ul.last-col li.last-item{border-bottom:0;padding-bottom:0}.inline{margin-left:0}.inline li{display:inline;list-style:none;margin-left:0;float:left}@media only screen and (min-width: 768px){.row .combined-list ul,.row .combined-list div{margin-bottom:20px}.row .combined-list li.last-item{border-bottom:0;padding-bottom:0}}ul.inline-logos{float:left;margin-left:0;padding:0;text-align:center;width:100%}ul.inline-logos li{clear:none;display:inline-block;float:none;margin:10px 20px;padding:0}ul.inline-logos li.clear-row{clear:left}ul.inline-logos li.last-item{border:0}ul.inline-logos img{-webkit-transition:all 0.5s ease-out;-moz-transition:all 0.5s ease-out;-ms-transition:all 0.5s ease-out;-o-transition:all 0.5s ease-out;transition:all 0.5s ease-out;vertical-align:middle;max-width:115px;max-height:32px}.inline-icons{margin:0 0 20px}.inline-icons li{margin-right:20px;margin-bottom:20px;text-align:left;display:inline-block}.inline-icons li.last-item{margin-right:0}.inline-icons.no-margin-bottom li{margin-bottom:0}.inline-icons img{vertical-align:middle;max-width:115px;max-height:32px}@media only screen and (max-width: 768px){ul.inline-logos img{max-width:172px;max-height:48px}}@media only screen and (min-width: 769px){ul.inline-logos li{clear:none;display:inline-block;height:auto;margin:20px 0;line-height:60px;padding:0 40px}ul.inline-logos li img{float:none;vertical-align:middle;max-width:200px;max-height:45px}}@media only screen and (min-width: 984px){.inline-icons{text-align:left;margin-bottom:20px}}blockquote.pull-quote{text-indent:0}blockquote.pull-quote p{color:#333;padding-left:10px;padding-right:10px;font-size:1.77379em;text-indent:-.4em;margin-left:.4em;line-height:1.3}blockquote.pull-quote p span{font-weight:bold;color:#dd4814;line-height:0;position:relative;left:-5px}blockquote.pull-quote p span+span{left:5px}blockquote.pull-quote p cite{margin:10px 0 0;font-weight:300;display:block;font-size:.75em;text-indent:0}blockquote.pull-quote.js{padding-left:60px;display:table-cell}blockquote.quote-canonical,blockquote.quote-canonical-white{font-size:1.14286em;background:url("//assets.ubuntu.com/sites/ubuntu/latest/u/img/patterns/quote-white-360x360.png") no-repeat 20px -130px;color:#772953;float:right;font-size:1em;height:215px;margin-top:0;padding:20px 60px 0;position:relative;width:236px}blockquote.quote-canonical-white{background:url("//assets.ubuntu.com/sites/ubuntu/latest/u/img/patterns/quote-aubergine-345x345.png") no-repeat 0 0;color:#fff;padding:80px 60px 0;height:265px}blockquote.quote p:first-child{font-size:1.28571em;line-height:1.3;text-indent:-7px}blockquote.quote-right-bottom{background-image:url("//assets.ubuntu.com/sites/ubuntu/latest/u/img/pictograms/picto-pack/picto-quote-orange.svg");background-repeat:no-repeat;background-size:287px 286px;color:#fff;height:167px;padding:60px 40px;position:static;right:-40px;top:-90px;width:207px}blockquote.quote-right-bottom p{color:#fff}blockquote.quote-grey{font-size:2.57143em;background:url("//assets.ubuntu.com/sites/ubuntu/latest/u/img/patterns/quote-grey-br-211x211.png") no-repeat scroll 0 0 transparent;color:#fff;height:152px;line-height:40px;margin-left:20px;padding:60px 0 0;text-align:center;width:211px}blockquote.quote-bottom-left{background:url("//assets.ubuntu.com/sites/ubuntu/latest/u/img/patterns/quote-orange-bl-287x287.png") no-repeat;color:#fff;height:167px;padding:55px 40px 70px 45px;width:225px}html.no-svg blockquote.quote-right-bottom,.opera-mini blockquote.quote-right-bottom{background-image:url("//assets.ubuntu.com/sites/ubuntu/latest/u/img/pictograms/picto-pack/picto-quote-orange.png")}.row-quote{-webkit-border-radius:0;-moz-border-radius:0;border-radius:0}.row-quote blockquote{-webkit-border-radius:4px;-moz-border-radius:4px;border-radius:4px;margin:0;padding:0}.row-quote blockquote p{margin-bottom:.75em;line-height:1.3;color:#333;padding-left:10px;padding-right:10px;text-indent:0}.row-quote blockquote span{font-weight:bold;color:#dd4814;line-height:0;position:relative;left:-5px}.row-quote blockquote span+span{left:5px}.row-quote blockquote cite{color:#333;font-style:normal;margin-bottom:0;font-size:.75em;text-indent:-14px;text-indent:0}.row-quote .quote-twitter{background:#fcece7 url("//assets.ubuntu.com/sites/ubuntu/latest/u/img/pictograms/pictogram-twitter-115x139.png") 20px bottom no-repeat;padding:20px 20px 20px 23.40425%}.row-quote .quote-twitter-small{background:#fcece7 url("//assets.ubuntu.com/sites/ubuntu/latest/u/img/pictograms/pictogram-twitter-54x63.png") 99% bottom no-repeat;padding:20px 20px 20px 80px}.row-quote .quote-twitter-small p{margin:0;padding:0}blockquote.quote-canonical,blockquote.quote-canonical-white{background:none;color:#333;width:auto;height:auto;padding:0 30px;margin-top:20px}@media only screen and (min-width: 768px){.row-quote blockquote{text-indent:-7px}.pull-quote{text-indent:-.4em}.row-quote blockquote p{font-size:1.77357em}blockquote.pull-quote p,.row-quote blockquote p{padding-left:0;padding-right:0;text-indent:-.7em}blockquote.pull-quote p span,.row-quote blockquote p span{font-size:1.391304348em}blockquote.pull-quote p cite,.row-quote blockquote p cite{margin-left:0;text-indent:0}blockquote.pull-quote p span,.row-quote blockquote p span{top:5px}}@media only screen and (min-width: 769px){.row-quote blockquote p{font-size:1.77357em;text-indent:-.4em}}@media only screen and (min-width: 984px){.row-quote blockquote{padding:0 80px 20px;text-indent:-10px}blockquote.pull-quote p span,.row-quote blockquote p span{top:10px}}html.js .tabbed-content .accordion-button{-webkit-box-sizing:border-box;-moz-box-sizing:border-box;box-sizing:border-box;height:auto;padding-bottom:.6em;padding-right:20px}html.yui3-js-enabled .tabbed-menu{display:none;padding-bottom:20px;padding-top:20px}html.yui3-js-enabled .arrow{display:none;position:absolute;visibility:hidden}html.yui3-js-enabled .tabbed-content{-webkit-border-radius:4px;-moz-border-radius:4px;border-radius:4px;padding:8px 8px 0;background:#f7f7f7;margin-bottom:8px}html.yui3-js-enabled .tabbed-content.hide{display:block;opacity:1 !important}html.yui3-js-enabled .tabbed-content .title{display:none}html.yui3-js-enabled .tabbed-content div{display:none}html.yui3-js-enabled .tabbed-content .accordion-button{-webkit-box-sizing:border-box;-moz-box-sizing:border-box;box-sizing:border-box;background:url("//assets.ubuntu.com/sites/ubuntu/latest/u/img/icons/icon-arrow-down.svg") no-repeat scroll right 3px #f7f7f7;color:#333;display:block;font-size:16px;padding-bottom:.6em;padding-right:20px;width:100%}html.yui3-js-enabled .tabbed-content.open .accordion-button{background-image:url("//assets.ubuntu.com/sites/ubuntu/latest/u/img/icons/icon-arrow-up.svg");margin-bottom:10px}html.yui3-js-enabled .tabbed-content.open div{display:block}html.yui3-js-enabled html.yui3-js-enabled.opera-mini .tabbed-content .accordion-button,html.yui3-js-enabled html.yui3-js-enabled.no-svg .tabbed-content .accordion-button{background-image:url("//assets.ubuntu.com/sites/ubuntu/latest/u/img/icons/icon-arrow-right.png")}html.yui3-js-enabled html.yui3-js-enabled.opera-mini .tabbed-content.open .accordion-button,html.yui3-js-enabled html.yui3-js-enabled.no-svg .tabbed-content.open .accordion-button{background-image:url("//assets.ubuntu.com/sites/ubuntu/latest/u/img/icons/icon-arrow-up.png")}html.yui3-js-enabled html.yui3-js-enabled.opera-mini.tabbed-content .accordion-button{background-image:none;margin-bottom:10px}html.yui3-js-enabled html.yui3-js-enabled.opera-mini.tabbed-content div{display:block}@media only screen and (min-width: 768px){html.yui3-js-enabled .tabbed-menu{display:block}html.yui3-js-enabled .tabbed-content{margin-bottom:20px;padding:40px}html.yui3-js-enabled .tabbed-content.hide{display:none;opacity:0 !important}html.yui3-js-enabled .tabbed-content .title{display:block}html.yui3-js-enabled .tabbed-content div{display:block}html.yui3-js-enabled .tabbed-content .vertical-divider div{display:table-cell}html.yui3-js-enabled .tabbed-content .accordion-button{display:none}}html.yui3-js-enabled .accordion-button.active{background-color:transparent}@media only screen and (min-width: 768px){.tabbed-menu{-moz-box-shadow:0 -1px 10px #cfcfcf inset;-webkit-box-shadow:0 -1px 10px #cfcfcf inset;box-shadow:0 -1px 10px #cfcfcf inset;-webkit-box-sizing:border-box;-moz-box-sizing:border-box;box-sizing:border-box;-webkit-border-radius:4px 4px 0 0;-moz-border-radius:4px 4px 0 0;border-radius:4px 4px 0 0;background:none repeat scroll 0 0 #f7f7f7;padding-bottom:20px;padding-top:20px;position:relative}.tabbed-menu ul{-webkit-box-sizing:border-box;-moz-box-sizing:border-box;box-sizing:border-box;display:table;margin-bottom:0;padding:0;position:relative;table-layout:fixed;width:100%}.tabbed-menu li{text-align:center;display:table-cell}.tabbed-menu a{color:#666;display:block;outline:none}.tabbed-menu a .active{color:#772953;text-decoration:none}.tabbed-menu a:hover{text-decoration:none}.tabbed-menu .arrow{bottom:0;position:absolute}.tabbed-content{*zoom:1;padding:20px 40px 0}.tabbed-content:before,.tabbed-content:after{content:"";display:table}.tabbed-content:after{clear:both}.tabbed-content .row{padding-left:0;padding-right:0}.tabbed-content .main-content{padding-bottom:40px}html.yui3-js-enabled .tabbed-content.hide{display:none;opacity:0}.tabbed-content-bg{-webkit-box-sizing:border-box;-moz-box-sizing:border-box;box-sizing:border-box;background:#fff;margin-left:20px;margin-right:20px}.tabbed-content-bg .row-box{padding-left:0;padding-right:0}html.yui3-js-enabled .arrow{visibility:visible}}.row.vertical-divider{padding-bottom:40px}.vertical-divider div,.vertical-divider li{border-right:0;display:block;padding-left:0;padding-right:0}.vertical-divider-full{padding-bottom:0}.vertical-divider-full>div{padding-bottom:40px}.row.vertical-divider-full{padding-bottom:0}@media only screen and (max-width: 767px){.vertical-divider>div,.vertical-divider>li{border-bottom:1px dotted #888;padding-bottom:20px}.vertical-divider div:last-of-type,.vertical-divider li:last-of-type,.inline-icons li:last-of-type{border-bottom:0;padding-bottom:5px}.row.vertical-divider{padding-bottom:0}.equal-height div,.equal-height li{height:auto !important}}@media only screen and (min-width: 984px){.row.vertical-divider{padding-bottom:60px}.vertical-divider>div,.vertical-divider>li{border-right:1px dotted #888;display:table-cell;float:none;margin-right:0;padding-left:20px;padding-right:20px;vertical-align:top}.vertical-divider>div:last-child,.vertical-divider>li:last-child,.vertical-divider>div.last-col,.vertical-divider>li.last-col,.vertical-divider>div:last-of-type,.vertical-divider>li:last-of-type{border-right:0;padding-right:0}.vertical-divider>div:first-child,.vertical-divider>li:first-child,.vertical-divider>div.first-col,.vertical-divider>li.first-col,.vertical-divider>div:first-of-type,.vertical-divider>li:first-of-type{padding-left:0}}.slider{-webkit-border-radius:4px;-moz-border-radius:4px;border-radius:4px;background:#f7f7f7;padding-top:40px}.slider .slide-window{overflow:hidden;position:relative;height:450px;-moz-transition:left 1s;-webkit-transition:left 1s;-o-transition:left 1s;transition:left 1s}.slider .slide-container{position:absolute;width:2800 px;-moz-transition:left 1s;-webkit-transition:left 1s;-o-transition:left 1s;transition:left 1s;left:0}.slider .slider-dots ul{position:absolute;top:550px;left:220px;z-index:5}.slider .slider-dots li{background-position:0 -8px;background:url("//assets.ubuntu.com/sites/ubuntu/latest/u/img/patterns/sprite-pager.png") no-repeat;float:left;height:7px;list-style-type:none;margin-right:.75em;text-indent:-9999em;width:7px}.slider .slider-dots li.active{background-position:0 0}.slider .slider-dots a{display:block;outline:0}.slider .slide{float:right;width:700px}.slider .slide h3{margin-top:65px;display:inline-block}.slider .slide p{width:350px}.slider .arrow-prev,.slider .arrow-next{font-size:5em;margin-top:150px;display:block;color:#888;outline:0}.slider .arrow-prev:hover,.slider .arrow-next:hover{text-decoration:none;color:#333}.slider .arrow-prev:active,.slider .arrow-next:active{padding-top:1px;text-decoration:none}.slider .arrow-prev:focus,.slider .arrow-next:focus{text-decoration:none}.yui3-tooltip-hidden{display:none}.yui3-tooltip-content{-moz-box-shadow:0 2px 8px rgba(0,0,0,0.2);-webkit-box-shadow:0 2px 8px rgba(0,0,0,0.2);box-shadow:0 2px 8px rgba(0,0,0,0.2);background:url("//assets.ubuntu.com/sites/ubuntu/latest/u/img/patterns/grey-textured-background.jpg") repeat scroll 0 0 transparent;-moz-border-radius:4px;-webkit-border-radius:4px;border-radius:4px;border:1px solid #e3e3e3;color:#333;margin-top:-30px;max-width:520px;position:relative}.yui3-tooltip .yui3-widget-bd{padding:20px;width:320px}.yui3-tooltip .yui3-widget-bd *{max-width:100%}.yui3-tooltip .yui3-widget-bd h5{margin-bottom:10px;font-size:22px;font-weight:300}.yui3-tooltip .yui3-widget-bd img{float:left;margin-right:10px}.yui3-tooltip .yui3-widget-bd q{border-bottom:1px dotted #888;border-top:1px dotted #888;display:block;font-size:16px;font-style:italic;margin-bottom:0;margin-top:20px;padding:10px 0}.yui3-tooltip .yui3-widget-bd p:last-child{margin-bottom:0}.yui3-tooltip .yui3-widget-ft,.yui3-tooltip .yui3-widget-ft div{position:absolute;width:0;height:0;border-style:solid;line-height:0;font-size:0}.yui3-tooltip .yui3-tooltip-align-bottom .yui3-widget-ft,.yui3-tooltip .yui3-tooltip-align-bottom .yui3-widget-ft div{top:-10px;left:50%;margin:0 0 0 -10px;border-width:0 10px 10px;border-color:#efefef transparent}.yui3-tooltip .yui3-tooltip-align-bottom .yui3-widget-ft div{top:0;border-color:#efefef transparent}.tooltip-label{-moz-box-shadow:3px 3px 6px rgba(0,0,0,0.3);-webkit-box-shadow:3px 3px 6px rgba(0,0,0,0.3);box-shadow:3px 3px 6px rgba(0,0,0,0.3);-webkit-border-radius:4px;-moz-border-radius:4px;border-radius:4px;background:#fff;border:1px solid #dfdcd9;display:none;font-size:13px;line-height:1;margin:0;padding:6px 5px;position:absolute;top:-20px;white-space:nowrap;z-index:1000}body,a:link,a:visited{-webkit-font-smoothing:antialiased}code,pre,p{line-height:1.5}body{font-size:16px}@media only screen and (min-width: 768px){code,pre,p{line-height:1.6}body{font-size:16px}}@media (-webkit-min-device-pixel-ratio: 2), (min-resolution: 192dpi){body{font-size:18px}}.not-for-medium{display:none}@media only screen and (min-width: 985px){.not-for-medium{display:block}}header.banner{background:#000;-moz-box-shadow:inset 0 2px 2px -2px #000;-webkit-box-shadow:inset 0 2px 2px -2px #000;box-shadow:inset 0 2px 2px -2px #000;margin-bottom:0}header.banner .nav-primary{-moz-box-shadow:none;-webkit-box-shadow:none;box-shadow:none;*zoom:1}header.banner .nav-primary:before,header.banner .nav-primary:after{content:"";display:table}header.banner .nav-primary:after{clear:both}header.banner nav.nav-primary{border-bottom:1px solid #262626;overflow:visible}header.banner nav.nav-primary .user-nav{float:right;margin-right:20px}header.banner nav.nav-primary .user-dropdown:hover ul:after{display:none}header.banner nav.nav-primary .user-dropdown .menu-link img{margin-right:10px}header.banner nav.nav-primary .user-dropdown .menu-link img.hover{display:none}header.banner nav.nav-primary .user-dropdown .menu-link img.normal{display:inline-block}header.banner nav.nav-primary .user-dropdown .open .menu-link img.hover,header.banner nav.nav-primary .user-dropdown .menu-link:hover img.hover{display:inline-block}header.banner nav.nav-primary .user-dropdown .open .menu-link img.normal,header.banner nav.nav-primary .user-dropdown .menu-link:hover img.normal{display:none}header.banner nav.nav-primary .user-dropdown ul{-webkit-border-radius:0 0 4px 4px;-moz-border-radius:0 0 4px 4px;border-radius:0 0 4px 4px;margin-top:-2px;background-color:#fff;border-width:0}header.banner nav.nav-primary .user-dropdown ul a:hover{background-color:transparent}header.banner nav.nav-primary #user-dropdown .dropdown ul{width:auto}header.banner .nav-primary.nav-right .logo-ubuntu{-moz-background-size:73px 30px;-webkit-background-size:73px 30px;-o-background-size:73px 30px;background-size:73px 30px;background-image:url(../img/logos/logo.svg);background-position:20px;background-repeat:no-repeat;min-width:120px;padding-top:6px;margin-left:0}body.no-svg header.banner .nav-primary.nav-right .logo-ubuntu{background-image:url(../img/logos/logo.png)}input[type=text]::-ms-reveal,input[type=text]::-ms-clear{display:none;width:0;height:0}input[type="search"]::-webkit-search-decoration,input[type="search"]::-webkit-search-cancel-button,input[type="search"]::-webkit-search-results-button,input[type="search"]::-webkit-search-results-decoration{display:none}.contextual-bar{overflow:hidden;background-color:#fff;border-bottom:1px solid #d4d4d4}form.search-form{overflow:hidden;float:right;width:100%;position:relative}form.search-form input{-webkit-border-radius:0;-moz-border-radius:0;border-radius:0;-webkit-box-sizing:border-box;-moz-box-sizing:border-box;box-sizing:border-box;border:0;border-left:0;margin:0;width:100%;height:50px;float:left;font-size:1em;padding-top:0;padding-bottom:0;padding-right:30px;background-color:transparent;-webkit-appearance:none}form.search-form input:focus{border-color:#dd4814}form.search-form button[type=submit],form.search-form button[type=submit]:hover{position:absolute;top:10px;right:10px;display:block;height:30px;width:30px;padding:0;line-height:0;-webkit-appearance:none;background:transparent}form.search-form button img{height:16px}.contextual-nav{border:0;display:block;margin:0;padding-left:10px;background-color:transparent;overflow:hidden;float:left}.contextual-nav li,.contextual-nav li:last-child{font-size:0.875em;float:left;list-style-type:none;margin:0;margin-left:5px}.contextual-nav li a:link,.contextual-nav li a:visited,.contextual-nav .contextual-nav__label{display:block;color:#333;font-weight:300;text-align:center;padding:16px 10px 10px 10px;border-bottom:3px solid transparent}.contextual-nav .contextual-nav__label{color:#cdcdcd}.contextual-nav li a:hover{border-bottom-color:#dd4814;text-decoration:none;color:#dd4814}.contextual-nav li a.active{border-bottom:3px solid #dd4814}.opera-mini header.banner .logo-ubuntu,.no-svg header.banner .logo-ubuntu{background-image:url(../img/logos/logo.png)}@media only screen and (min-width: 769px){header.banner .nav-primary ul li,header.banner .nav-primary ul li:last-child{border-bottom:0;width:auto}header.banner nav.nav-primary li a:link,header.banner nav.nav-primary li a:visited{border-left:1px solid #262626;font-weight:400}header.banner nav.nav-primary ul li a.active{padding-bottom:10px;background-color:#0e0c0b;border-bottom:3px solid #dd4814;border-left:1px solid #262626}header.banner nav.nav-primary ul li{border-left:1px solid #262626}header.banner nav.nav-primary ul li a:hover{background-color:#dd4814}header.banner nav.nav-primary ul{background-color:transparent;border-right:1px solid #262626;display:block}header.banner nav.nav-primary ul li:last-child{border-left:1px solid #262626;border-right:0}header.banner .nav-primary ul li a:active,header.banner .nav-primary ul li a:hover,header.banner .nav-primary ul li a:visited,header.banner nav.nav-primary ul li a:link{border-left:0}header.banner .nav-primary ul li a.external:hover{background-image:url("../img/icons/external-link-grey.png")}form.search-form{width:325px}form.search-form input{border-left:1px solid #d4d4d4;margin:0 20px;width:250px;font-size:0.875em}}@media only screen and (min-width: 1030px){header.banner{height:48px;overflow:hidden}header.banner .nav-primary{width:100%}}body{background-repeat:repeat}.row{border:0;background-color:rgba(255,255,255,0.6)}@media only screen and (min-width: 769px){.append-one{margin-right:10.6%}.row{padding:50px 40px 30px}}.inner-wrapper{*zoom:1;-webkit-border-radius:0;-moz-border-radius:0;border-radius:0;-moz-box-shadow:none;-webkit-box-shadow:none;box-shadow:none;background-color:transparent;background-image:none;margin:0 auto;padding-bottom:0;float:none}.inner-wrapper:before,.inner-wrapper:after{content:"";display:table}.inner-wrapper:after{clear:both}.wrapper{position:static;background:transparent;width:100%;overflow:hidden}img.touch-border{margin-bottom:-50px}.inner-wrapper,footer.global .legal{max-width:1030px;width:auto;padding-left:0;padding-right:0}.touch-top{margin-top:-50px}@media only screen and (max-width: 1030px){.inner-wrapper,footer.global .legal{padding-left:8px;padding-right:8px;width:auto}}.footer-cta{background:#fff;padding-bottom:20px}.footer-wrapper.strip-light{background-color:#fff}.solutions-cta{height:60px;background-color:#dd4814;line-height:60px;text-align:center}.solutions-cta a{color:#fff;font-size:1.25em}footer.global{background-color:white;box-shadow:none;padding-top:0}footer.global .row{padding:10px 0 0}footer.global .two-col{width:46%;float:left;display:inline-block;min-height:200px}footer.global h2{padding-bottom:0;color:#888;font-size:16px}footer.global nav .canonlist ul li,footer.global nav .additional-info ul li{min-height:0;width:48%;float:left}footer.global ul.bullets li:after{line-height:1;color:#888;content:"•";vertical-align:middle;margin:0 5px}footer.global ul.inline li:last-child{width:auto}footer.global a.external{background-image:url("../img/icons/external-link-dark.png")}footer.global .top-link{margin-bottom:10px}footer.global a{color:#333}footer.global a:hover{color:#dd4814}footer.global .legal{background-image:none}footer.global .legal.has-cookie{padding-bottom:70px}footer.global .inner-wrapper{overflow:visible}footer.global a.link-cta-positive,footer.global a.link-cta-negative{width:auto;margin-top:10px;padding-left:20px;padding-right:20px;color:#fff;font-size:14px}footer.global .section__title{background:none;cursor:default}.legal-inner{clear:both;overflow:hidden;float:left;width:100%;padding:20px 10px 0;margin:-3px -10px 0}.social,.social--right{margin-left:0}.social .social__item,.social--right .social__item{display:inline;float:left;padding-right:1em}.social .social__google,.social .social__facebook,.social .social__twitter,.social--right .social__google,.social--right .social__facebook,.social--right .social__twitter{background-image:url("../img/icons/icon-social.png");display:block;width:45px;height:44px}.social .social__google.social__twitter:hover,.social .social__facebook.social__twitter:hover,.social .social__twitter.social__twitter:hover,.social--right .social__google.social__twitter:hover,.social--right .social__facebook.social__twitter:hover,.social--right .social__twitter.social__twitter:hover{background-position:0 -45px}.social .social__google.social__facebook,.social .social__facebook.social__facebook,.social .social__twitter.social__facebook,.social--right .social__google.social__facebook,.social--right .social__facebook.social__facebook,.social--right .social__twitter.social__facebook{background-position:90px 0}.social .social__google.social__facebook:hover,.social .social__facebook.social__facebook:hover,.social .social__twitter.social__facebook:hover,.social--right .social__google.social__facebook:hover,.social--right .social__facebook.social__facebook:hover,.social--right .social__twitter.social__facebook:hover{background-position:90px -45px}.social .social__google.social__google,.social .social__facebook.social__google,.social .social__twitter.social__google,.social--right .social__google.social__google,.social--right .social__facebook.social__google,.social--right .social__twitter.social__google{background-position:135px 0}.social .social__google.social__google:hover,.social .social__facebook.social__google:hover,.social .social__twitter.social__google:hover,.social--right .social__google.social__google:hover,.social--right .social__facebook.social__google:hover,.social--right .social__twitter.social__google:hover{background-position:135px -45px}@media only screen and (min-width: 768px){.social--right{float:right}}#additional-info{border-bottom:0}#additional-info h2:before{background-image:url("../img/icons/external-link-grey.svg"),none;background-repeat:no-repeat;background-size:14px 14px;content:"";display:inline-block;height:15px;margin-right:3px;position:relative;top:3px;width:15px}#additional-info div li{border-left:1px solid #d4d7d4;box-sizing:border-box;display:block;float:left;margin:0;padding:0;width:50%}#additional-info div li a{border-bottom:1px solid #d4d7d4;box-sizing:border-box;color:#333333;display:block;float:left;margin:0;overflow:hidden;padding:8px 10px;text-align:left;white-space:normal;width:100%}#additional-info .section__title{border-bottom:1px solid #d4d7d4;background-position:100% .1em}html.opera-mini footer #nav-global h2:before,html.opera-mini footer #additional-info h2:before,html.no-svg footer #nav-global h2:before,html.no-svg footer #additional-info h2:before{background-image:url("../img/icons/external-link-grey.png")}@media only screen and (min-width: 769px){.footer-wrapper.strip-light{white-space:nowrap}footer.global{padding-top:40px;padding-bottom:40px}footer.global .two-col{width:14.89361%;display:inline-block;min-height:0}footer.global .section{min-height:160px;margin-right:40px;padding-bottom:0;border-right:1px dotted #aaa;border-bottom:0}footer.global .section:last-child{margin-right:0;border-right:0}footer.global li{display:inline;float:left}footer.global ul.no-bullets li{border-right:1px dotted #aaa;padding-right:15px;padding-left:15px}footer.global ul.no-bullets li a{font-size:16px}footer.global ul.no-bullets li a:hover{color:#dd4814}footer.global ul.no-bullets li:last-child{border-right:none}footer.global ul.no-bullets li:first-child{padding-left:0px}#additional-info .section__title{border-bottom:0}#additional-info div li,#additional-info div a:link{width:100%;border:0}}.actions .actions__social-item--twitter,.actions .actions__social-item--google-plus{text-indent:-99999px;background-image:url("../img/icons/icon-social.svg");background-repeat:no-repeat;height:44px;width:44px;overflow:hidden;display:block}.actions .actions__social-item--twitter{background-position:0 0}.actions .actions__social-item--twitter:hover{background-position:0 -45px}.actions .actions__social-item--google-plus{background-position:-45px 0}.actions .actions__social-item--google-plus:hover{background-position:-45px -45px}.anchor{display:inline-block;margin-left:3px;opacity:.0;position:relative;top:1px;width:1em;height:1em;background:url("../img/icons/anchor_16.svg") 0 80% no-repeat;background-size:16px;-moz-transition:opacity .1s;-webkit-transition:opacity .1s;transition:opacity .1s}h1:hover .anchor,h2:hover .anchor,h3:hover .anchor,h4:hover .anchor,dt:hover .anchor,li:hover .anchor{opacity:1}.box{-webkit-border-radius:0;-moz-border-radius:0;border-radius:0;-moz-box-shadow:none;-webkit-box-shadow:none;box-shadow:none;display:block;margin-bottom:0;border-top:0;border-left:0;border-right:0;padding-left:0;padding-right:0}.box-dim{background-color:#fafafa}@media only screen and (min-width: 768px){.box{-webkit-border-radius:4px;-moz-border-radius:4px;border-radius:4px;-moz-box-shadow:0px 1px 1px 0px rgba(0,0,0,0.15);-webkit-box-shadow:0px 1px 1px 0px rgba(0,0,0,0.15);box-shadow:0px 1px 1px 0px rgba(0,0,0,0.15);display:inline-block;margin-bottom:20px;padding-left:20px;padding-right:20px;border:0}}a.indent{-moz-box-shadow:inset 0 1px 2px 0 #333;-webkit-box-shadow:inset 0 1px 2px 0 #333;box-shadow:inset 0 1px 2px 0 #333;background:rgba(0,0,0,0.1);padding:10px 30px;text-weight:normal}a.indent:hover{background:rgba(0,0,0,0.2)}a.link-cta-positive,a.link-cta-negative{-webkit-border-radius:3px;-moz-border-radius:3px;border-radius:3px;-webkit-box-sizing:border-box;-moz-box-sizing:border-box;box-sizing:border-box;background-color:#dd4814;color:#fff;display:inline-block;font-size:1.14286em;font-weight:300;text-decoration:none;margin:0;padding:8px 14px;text-align:center;-moz-transition:background .2s;-webkit-transition:background .2s;transition:background .2s;width:100%}a.link-cta-positive:hover,a.link-cta-negative:hover{background-color:#ae3910}a.link-cta-positive .external,a.link-cta-negative .external{padding-right:1em;background-image:url("../img/icons/external-link-white.svg");background-repeat:no-repeat;background-size:11px;background-position:right top}@media only screen and (min-width: 769px){a.link-cta-positive .external,a.link-cta-negative .external{padding-right:.7em}}a.link-cta-negative{background-color:#b2b2b2}a.link-cta-negative:hover{background-color:#888}.charms__list{list-style:none;margin-bottom:1em;margin-left:0;border-bottom:1px dotted #d4d4d4}.charms__list .charms__list--config{display:none}.charms__list .charms__list--toggle{display:block}.charms__list .charms__list--toggle.is-open+.charms__list--config{display:block}.charms__list .charms__list--config-name{border-top:0}.charms__list--item{font-size:1em;border-top:1px dotted #d4d4d4;padding:10px 0 0 10px;margin-bottom:10px}.charms__list--item:last-of-type{margin-bottom:10px}.charms__list--icon{margin-right:.4em;width:25px;height:25px}.charms__list--toggle{background:url("../img/shared/icon-arrow-down.svg") no-repeat center center;width:16px;height:100%;float:right;text-indent:-99999px;margin-right:20px}.charms__list--toggle.is-open{background-image:url("../img/shared/icon-arrow-up.svg");background-size:14px}.charms__list--config{padding-left:35px;padding-bottom:20px}.charms__list--config-name{font-size:1em;margin-top:15px;padding-top:15px;font-weight:400;border-top:1px dotted #d4d4d4}.charms__list--config-name:first-of-type{border-top:0}.charms__list--config-type{font-weight:400}.charms__list--config-description,.charms__list--config-setting{margin-left:30px;margin-top:8px}.charms__list--config-setting{font-size:0.875em;color:#888;font-family:"Ubuntu Mono","Consolas","Monaco","Lucida Console","Courier New",Courier,monospace}body.no-svg .charms__list .charms__list--toggle{background-image:url("../img/shared/icon-arrow-down.png")}body.no-svg .charms__list .charms__list--toggle.is-open{background-image:url("../img/shared/icon-arrow-up.png")}pre{background:transparent;border:1px solid #888;margin:0 0 1.5em 0}pre:not(:first-child){margin-top:1.5em}code.language-bash .comment{color:#888}.cookie-policy{-moz-box-shadow:0 -1px 2px rgba(0,0,0,0.2);-webkit-box-shadow:0 -1px 2px rgba(0,0,0,0.2);box-shadow:0 -1px 2px rgba(0,0,0,0.2);background-color:#fae4dc;bottom:0;position:fixed;width:100%;z-index:100}.cookie-policy p{-webkit-box-sizing:border-box;-moz-box-sizing:border-box;box-sizing:border-box;font-size:13px;margin-bottom:0;margin-left:0;padding:8px 0;width:100%}.cookie-policy .link-cta{background-image:url(../img/icons/close-orange.svg);background-repeat:no-repeat;color:#fff;float:right;font-size:1em;height:15px;margin:12px 0;margin-top:12px;padding:0;text-decoration:none;text-indent:-9999px;width:16px}html.no-svg .cookie-policy .link-cta,html.opera-mini .cookie-policy .link-cta{background-image:url(../img/icons/close-orange.png)}html.opera-mini .cookie-policy{position:relative;top:0}.deploy-command{margin-bottom:10px;position:relative}.deploy-command .deploy-command__field{-webkit-box-sizing:border-box;-moz-box-sizing:border-box;box-sizing:border-box;-webkit-border-radius:4px 4px 4px 4px;-moz-border-radius:4px 4px 4px 4px;border-radius:4px 4px 4px 4px;-moz-box-shadow:inset 0 1px 2px 0 rgba(0,0,0,0.12);-webkit-box-shadow:inset 0 1px 2px 0 rgba(0,0,0,0.12);box-shadow:inset 0 1px 2px 0 rgba(0,0,0,0.12);background-image:url("../img/icons/code-snippet_16.svg");background-repeat:no-repeat;background-position:5px center;background-color:#fff;background-size:1.142857143em;width:100%;height:37px;border:1px solid #c1c1c1;padding:.6em;color:#888;padding-left:2em;font-size:0.875em;white-space:nowrap;overflow:hidden;cursor:text}.deploy-command .command2clipboard__clip{cursor:pointer;-webkit-border-radius:0 4px 4px 0;-moz-border-radius:0 4px 4px 0;border-radius:0 4px 4px 0;line-height:1;position:absolute;right:1px;top:1px;background-color:#fff;padding:9px 8px 7px;border-left:1px solid #b2b2b2;display:none}.deploy-command .command2clipboard__clip.zeroclipboard-is-hover{background-color:#eee}@media only screen and (min-width: 1030px){.deploy-command .command2clipboard__clip{display:inline-block}}.dropdown-menu{position:relative;display:block}.dropdown-menu.open .menu-link{background-color:#000}.dropdown-menu.open .dropdown{display:block}.dropdown-menu .menu-link .border-box{display:block;color:#f2f2f4}.dropdown-menu .dropdown{-webkit-border-radius:0 0 4px 4px;-moz-border-radius:0 0 4px 4px;border-radius:0 0 4px 4px;display:none;position:absolute;z-index:1000;top:0;left:0;right:0;width:auto;background-color:#fff;box-shadow:0 1px 5px rgba(0,0,0,0.2)}.dropdown-menu .dropdown.right{left:auto;right:0;text-align:right}.dropdown-menu .dropdown.narrow{min-width:140px;width:auto}.dropdown-menu .dropdown a,.dropdown-menu .dropdown p,.dropdown-menu .dropdown li{color:#333}.dropdown-menu .dropdown p{padding:11px 20px}.dropdown-menu .dropdown header,.dropdown-menu .dropdown footer{background-color:#fff}.dropdown-menu .dropdown header{padding:11px 20px;color:#f2f2f4;font-size:16px;font-weight:300}.dropdown-menu .dropdown footer{padding:20px}.dropdown-menu .dropdown ul{width:auto;left:0;right:0}.dropdown-menu .dropdown ul li a{width:auto;display:block;padding:15px 20px}.files .files__list{list-style:none;margin-bottom:1em;border-left:1px solid #cbcbcb;margin-left:1em}@media only screen and (min-width: 768px){.files .files__list{margin-left:0}}.files .files__list li{position:relative}.files .files__list li a:link,.files .files__list li a:visited{color:#333;text-decoration:none}.files .files__list li:before{content:'';width:12px;height:1px;background:#d4d4d4;display:inline-block;position:relative;top:-4px;margin-right:5px}.files .files__list li:last-child:after{content:'';width:4px;height:1em;position:absolute;display:block;left:-2px;top:.85em;background:#fff}.files .files__list ul:last-child>li a:after{content:'';width:4px;height:3em;position:absolute;display:block;left:-1.8em;top:-1.45em;background:white;cursor:default}.files .files__list .files__list{margin-left:1.3em}.files .files__list .files__list--item,.files .files__list .files__list--item-folder{font-size:0.875em;margin-bottom:0.75em}.files .files__list .files__list--item-folder{background-position:center right;background-size:12px;cursor:pointer}.files .files__list .files__list--item-folder:after{font-size:14px;display:block;content:"-";position:absolute;left:-7px;top:4px;padding:0 4px;line-height:0.9em;background:#fff;border:1px solid #888}.files .files__list .files__list--item-folder.is-closed+ul{display:none}.files .files__list .files__list--item-folder.is-closed:after{content:"+";padding:0 2px}.files .files__actions--launchpad{background:url("../img/icons/icon-launchpad.svg") no-repeat;padding-left:1.4em}#main-content .row-hero{padding-top:20px;margin-top:0}#main-content .row-hero .intro{font-size:16px}@media only screen and (min-width: 769px){#main-content .row-hero{padding-top:60px}#main-content .row-hero .intro{font-size:1.4375em;margin-bottom:40px}}.how-to div div img{float:left;margin:0 20px 20px 0}header.banner a.external,header.banner a.external:hover{background-image:url("../img/icons/external-link-grey.png")}a.external,a.external:hover,header.banner nav.nav-primary ul li a.external:link,header.banner nav.nav-primary ul li a.external:visited,header.banner nav.nav-primary ul li a.external:hover{background-repeat:no-repeat}a.external,a.external:hover header.banner nav.nav-primary ul li a.external:link,header.banner nav.nav-primary ul li a.external:visited,header.banner nav.nav-primary ul li a.external:hover{background-position:right 14px top 14px;padding-right:35px;background-size:auto}@media only screen and (max-width: 769px){header.banner nav.nav-primary ul li a.external:link,header.banner nav.nav-primary ul li a.external:visited,header.banner nav.nav-primary ul li a.external:hover{background:none}header.banner nav.nav-primary ul li a.external:after{display:inline-block;width:11px;height:11px;margin-left:0.25em;background-image:url("../img/icons/external-link-dark.png");vertical-align:text-top}}.list__icons{margin-left:0;margin-bottom:5px}.list__icons li{list-style:none;float:left;padding:8px 8px 0 0;margin-bottom:0}.list__icons li img{width:24px;height:24px;vertical-align:top}.list__tick{list-style-image:url("../img/icons/tick.png")}.list__middot{margin-left:0;list-style:none}.list__middot li{display:inline}.list__middot li:after{content:"•";color:#888;margin:0 5px 0 8px;vertical-align:middle}.list__middot li.files__actions--last:after{content:""}.combined-list .list li{border-bottom:1px dotted #888;padding:10px 0}@media only screen and (max-width: 767px){.combined-list .last-col .list li:last-of-type{border-bottom:0;padding-bottom:0}}@media only screen and (min-width: 768px){.combined-list .list li:last-of-type{border-bottom:0;padding-bottom:0}}.events-list li{position:relative;padding-bottom:20px}.events-list dd{margin-left:0;background-position:0 center;background-repeat:no-repeat;background-size:20px 20px;padding:6px 20px 6px 24px}.events-list .event-map{display:none}.events-list .event-date{background-image:url("../img/icons/calendar.svg")}.events-list .location{background-image:url("../img/icons/location.svg")}@media only screen and (min-width: 769px){.events-list .event-details-wrapper{padding-left:120px}.events-list .event-map{-webkit-border-radius:10px;-moz-border-radius:10px;border-radius:10px;position:absolute;left:0;top:0;height:100px;width:100px;float:left;margin-right:10px;margin-top:5px;overflow:hidden;display:block}}body.no-svg .events-list .event-date{background-image:url("../img/icons/calendar.png")}body.no-svg .events-list .location{background-image:url("../img/icons/location.png")}.maintainers .maintainer__email{display:block}.ratings ul{margin-left:2px;margin-bottom:0}.ratings ul li{margin-bottom:0}.ratings ul li img{vertical-align:text-top}.ratings ul li:first-of-type{margin-left:0}.revisions__list{list-style:none;margin-left:0}.revisions__list .revisions__list-item{margin-bottom:1em}.revisions__list .revisions__list_meta{color:#888;margin-bottom:.2em}.revisions__list .revisions__list_meta_date{float:right}.section{-webkit-box-sizing:border-box;-moz-box-sizing:border-box;box-sizing:border-box;overflow:hidden;padding-bottom:20px;padding-top:20px}.section .section__title{background:url(../img/shared/icon-arrow-up.svg) no-repeat center right;cursor:pointer;margin-bottom:1em}.section.is-closed{height:60px}.section.is-closed .section__title{background-image:url(../img/shared/icon-arrow-down.svg)}.no-svg .row.section .section__title{background:url(../img/shared/icon-arrow-up.png)}.no-svg .row.section.is-closed .section__title{background:url(../img/shared/icon-arrow-down.png)}footer .section{margin-bottom:1em;padding-bottom:1em}footer .section.is-closed{height:auto;padding-bottom:0}footer .section.is-closed ul{display:none}@media only screen and (min-width: 769px){.row .section .section__title{background-image:none;cursor:auto}.row .section.is-closed{height:auto}}.list--concealed .list-item{display:none}.list--concealed .list-item:first-of-type{display:list-item}.list--concealed.list--visible-6 .list-item:nth-child(-n+6){display:list-item}.list--concealed.list--visible-4 .list-item:nth-child(-n+4){display:list-item}.list--concealed.list--visible-2 .list-item:nth-child(-n+2){display:list-item}.list--concealed a.btn__see--less{display:none}.list--concealed a.btn__see--more{display:inline}.list--revealed .list__controls,.list--concealed .list__controls{display:block;margin-top:2em}.list--revealed .list--item{display:list-item}.list--revealed a.btn__see--less{display:inline}.list--revealed a.btn__see--more{display:none}a.btn__see--more,a.btn__see--less{-moz-transition:background .2s;-webkit-transition:background .2s;transition:background .2s;-webkit-border-radius:3px;-moz-border-radius:3px;border-radius:3px;color:#333;border-radius:2px;border:1px solid #b2b2b2;background:#fff;padding:0.384615385em 1.153846154em}a.btn__see--more:hover,a.btn__see--less:hover{background:#eee;text-decoration:none}.strip-dark,.strip-light{clear:both}.strip-dark{background-color:#2c001e;background-image:none;background-repeat:repeat;color:#fff}.strip-dark.solid{background-image:none;background-color:#2c001e}.strip-dark ul,.strip-dark ol{margin:0;padding:0}.strip-dark .icon,.strip-dark ol span{-moz-background-size:40px 40px;-webkit-background-size:40px 40px;-o-background-size:40px 40px;background-size:40px 40px;background-image:url(../img/icons/list-icon-background.png);background-repeat:no-repeat;display:block;margin:0 20px 20px 0;padding:24px;float:left;width:16px;height:16px;padding:12px}.strip-light .icon,.strip-dark .icon{position:absolute}.strip-light{background-color:rgba(255,255,255,0.6)}.strip-dark ol,.strip-dark ul{padding:20px 0}.strip-light .icon{display:block;background-image:url(../img/icons/list-icon-background.png);padding:24px;margin:0 auto 48px}.strip-dark .connected-list li,.strip-light .connected-list li{margin-bottom:10px;min-height:52px}.strip-dark .connected-list li p,.strip-light .connected-list li p,.strip-dark .connected-list li h3,.strip-light .connected-list li h3{padding-left:50px}.strip-dark ol.connected-list li p .strip-dark ol.connected-list li h3{padding-left:50px}.strip-dark ol.connected-list li span{float:left;font-size:22px;font-weight:normal;height:26px;margin-left:0;margin-right:20px;padding-top:2px;position:absolute;text-align:center;width:16px}.strip-white{background:#fff}.strip-trans{background:transparent}.strip-green{background-image:linear-gradient(to right, #6fad23 0%, #7cc227 100%);overflow:hidden}.strip-green,.strip-green a{color:#fff}.strip-blue{background-image:linear-gradient(to right, #1076a2 0%, #359fcd 100%);overflow:hidden}.strip-blue,.strip-blue a{color:#fff}.tag-list{list-style:none;margin:0}.tag-list--item{display:inline-block;text-transform:lowercase}.tag-list--item a:link,.tag-list--item a:visited{color:#333}.tag-list--item a:after{content:','}.tag-list--item:last-child a:after{content:''}#twitter-feed,#blog-feed{margin:35px 0}#twitter-feed>ul,#blog-feed>ul{list-style:none;margin-left:0}#twitter-feed>ul li,#blog-feed>ul li{position:relative;margin-bottom:30px}#twitter-feed .user,#blog-feed .user{font-size:0.875em;margin-bottom:0.5em}#twitter-feed .user img,#blog-feed .user img{-webkit-border-radius:4px;-moz-border-radius:4px;border-radius:4px;width:24px;height:24px;vertical-align:middle;margin-right:5px}#twitter-feed .user a:link,#twitter-feed .user a:visited,#twitter-feed .user a:hover,#blog-feed .user a:link,#blog-feed .user a:visited,#blog-feed .user a:hover{text-decoration:none}#twitter-feed .user span[data-scribe="element:name"],#blog-feed .user span[data-scribe="element:name"]{color:#333}#twitter-feed .tweet,#blog-feed .tweet{padding-left:33px;margin-bottom:.3em}#twitter-feed .timePosted,#blog-feed .timePosted{padding-left:33px;font-size:0.875em}#twitter-feed .interact,#blog-feed .interact{padding-left:33px}#twitter-feed .interact a:link,#twitter-feed .interact a:visited,#blog-feed .interact a:link,#blog-feed .interact a:visited{margin-right:20px}@media only screen and (min-width: 769px){#twitter-feed .timePosted{position:absolute;top:0;right:0;padding-left:0}}.spaced-segment{margin-bottom:50px}.spaced-segment h3{margin-bottom:1.3em}.strip-dark,.strip-light{clear:both}.strip-dark{background-color:#2c001e;background-image:none;background-repeat:repeat;color:#fff}.strip-dark.solid{background-image:none;background-color:#2c001e}.strip-dark ul,.strip-dark ol{margin:0;padding:0}.strip-dark .icon,.strip-dark ol span{-moz-background-size:40px 40px;-webkit-background-size:40px 40px;-o-background-size:40px 40px;background-size:40px 40px;background-image:url(../img/icons/list-icon-background.png);background-repeat:no-repeat;display:block;margin:0 20px 20px 0;padding:24px;float:left;width:16px;height:16px;padding:12px}.strip-light .icon,.strip-dark .icon{position:absolute}.strip-light{background-color:rgba(255,255,255,0.6)}.strip-dark ol,.strip-dark ul{padding:20px 0}.strip-light .icon{display:block;background-image:url(../img/icons/list-icon-background.png);padding:24px;margin:0 auto 48px}.strip-dark .connected-list li,.strip-light .connected-list li{margin-bottom:10px;min-height:52px}.strip-dark .connected-list li p,.strip-light .connected-list li p,.strip-dark .connected-list li h3,.strip-light .connected-list li h3{padding-left:50px}.strip-dark ol.connected-list li p .strip-dark ol.connected-list li h3{padding-left:50px}.strip-dark ol.connected-list li span{float:left;font-size:22px;font-weight:normal;height:26px;margin-left:0;margin-right:20px;padding-top:2px;position:absolute;text-align:center;width:16px}.strip-white{background:#fff}.strip-trans{background:transparent}.strip-green{background-image:linear-gradient(to right, #6fad23 0%, #7cc227 100%);overflow:hidden}.strip-green,.strip-green a{color:#fff}.strip-blue{background-image:linear-gradient(to right, #1076a2 0%, #359fcd 100%);overflow:hidden}.strip-blue,.strip-blue a{color:#fff}@media only screen and (min-width: 769px){.tip,.command2clipboard__clip{position:relative;display:inline-block}.tip .tip-content,.command2clipboard__clip .tip-content{position:absolute;z-index:98;left:-1000px;right:-1000px;top:-30px;font-weight:300;margin:auto;display:block;text-align:center;white-space:nowrap}.tip:hover .tip-content:after,.command2clipboard__clip.zeroclipboard-is-hover .tip-content:after{display:table;z-index:98;margin:auto;color:#fff;border-radius:3px;background:#000;box-shadow:none;font-size:12px;content:attr(data-tooltip);padding:4px 6px;white-space:nowrap;text-align:center}.tip:hover .tip-content:before,.command2clipboard__clip.zeroclipboard-is-hover .tip-content:before{position:absolute;top:100%;left:50%;margin-left:-5px;content:'';border:solid transparent;border-width:5px;border-top-color:#000}}.fake{display:block}*{margin:0}html{height:100%}body{height:100%;font-size:1.0em;font-family:'Ubuntu', Arial, 'libra sans', sans-serif;font-weight:300}hr{border:none;background:#B2B2B2;width:100%;height:1px;display:block;width:100%;float:left;margin-bottom:20px}ul,ol{margin-left:0}a:link,a:visited{color:#333;text-decoration:none;border-bottom:1px solid #CDCDCD}a:link:hover,a:visited:hover{color:#dd4814;text-decoration:none}a:active,a:focus{outline:none}::selection{color:#FFF;background:#dd4814}::-moz-selection{color:#FFF;background:#dd4814}@font-face{font-family:"Ubuntu";font-style:normal;font-weight:300;src:url("../fonts/ubuntu-l-webfont.eot?") format("eot"),url("../fonts/ubuntu-l-webfont.woff") format("woff"),url("../fonts/ubuntu-l-webfont.ttf") format("truetype"),url("../fonts/ubuntu-l-webfont.svg#Ubuntu") format("svg")}@font-face{font-family:"Ubuntu";font-style:italic;font-weight:300;src:url("../fonts/ubuntu-li-webfont.eot?") format("eot"),url("../fonts/ubuntu-li-webfont.woff2") format("woff2"),url("../fonts/ubuntu-li-webfont.woff") format("woff"),url("../fonts/ubuntu-li-webfont.ttf") format("truetype"),url("../fonts/ubuntu-li-webfont.svg#Ubuntu") format("svg")}@font-face{font-family:"Ubuntu";font-style:normal;font-weight:400;src:url("../fonts/ubuntu-r-webfont.eot?") format("eot"),url("../fonts/ubuntu-r-webfont.woff") format("woff"),url("../fonts/ubuntu-r-webfont.ttf") format("truetype"),url("../fonts/ubuntu-r-webfont.svg#Ubuntu") format("svg")}@font-face{font-family:"Ubuntu";font-style:normal;font-weight:500;src:url("../fonts/ubuntu-m-webfont.eot?") format("eot"),url("../fonts/ubuntu-m-webfont.woff") format("woff"),url("../fonts/ubuntu-m-webfont.ttf") format("truetype"),url("../fonts/ubuntu-m-webfont.svg#Ubuntu") format("svg")}@font-face{font-family:"Ubuntu";font-style:italic;font-weight:500;src:url("../fonts/ubuntu-mi-webfont.eot?") format("eot"),url("../fonts/ubuntu-mi-webfont.woff2") format("woff2"),url("../fonts/ubuntu-mi-webfont.woff") format("woff"),url("../fonts/ubuntu-mi-webfont.ttf") format("truetype"),url("../fonts/ubuntu-mi-webfont.svg#Ubuntu") format("svg")}@font-face{font-family:"Ubuntu";font-style:normal;font-weight:700;src:url("../fonts/ubuntu-b-webfont.eot?") format("eot"),url("../fonts/ubuntu-b-webfont.woff2") format("woff2"),url("../fonts/ubuntu-b-webfont.woff") format("woff"),url("../fonts/ubuntu-b-webfont.ttf") format("truetype"),url("../fonts/ubuntu-b-webfont.svg#Ubuntu") format("svg")}@font-face{font-family:'Ubuntu';font-style:italic;font-weight:400;src:url("https://themes.googleusercontent.com/static/fonts/ubuntu/v5/GZMdC02DTXXx8AdUvU2etw.woff") format("woff")}@font-face{font-family:'Ubuntu';font-style:italic;font-weight:700;src:url("https://themes.googleusercontent.com/static/fonts/ubuntu/v5/pqisLQoeO9YTDCNnlQ9bfz8E0i7KZn-EPnyo3HZu7kw.woff") format("woff")}.accordion{-webkit-box-sizing:border-box;-moz-box-sizing:border-box;box-sizing:border-box;-webkit-border-radius:2px;-moz-border-radius:2px;border-radius:2px;list-style:none;background:#FFF;box-shadow:0 1px 1px rgba(0,0,0,0.1);margin-bottom:40px}.disabled .accordion{opacity:.5;pointer-events:none}.accordion .accordion__title{border-bottom:1px dotted #B2B2B2;padding:13px 20px 12px;margin:0;font-size:1.3em}.accordion .accordion__tab{border-bottom:1px dotted #B2B2B2}.accordion .accordion__tab:last-of-type{border:none}.accordion .accordion__tab .accordion__tab-title{padding:12px 20px;margin:0;color:#888;cursor:pointer;background:transparent url("../img/icons/accordion-open.svg") top 20px right 20px no-repeat}.accordion .accordion__tab .accordion__tab-title.active{background-image:url("../img/icons/accordion-close.svg")}.accordion .accordion__tab .accordion__tab-title.active+.accordion__tab-content{max-height:400px;transition:max-height .5s ease-in;overflow-y:auto}.accordion .accordion__tab .accordion__tab-content{max-height:0;transition:max-height .5s ease-out;overflow:hidden}.accordion .accordion__tab .accordion__tab-content .accordion__tab-list{list-style-type:none;padding:0 20px 14px;margin:0}.accordion .accordion__tab .accordion__tab-content .accordion__tab-list .accordion__tab-item{margin-bottom:0.15em}.accordion .accordion__tab .accordion__tab-content .accordion__tab-list .accordion__tab-item .accordion__tab-link{-webkit-box-sizing:border-box;-moz-box-sizing:border-box;box-sizing:border-box;color:#333;width:100%;display:inline-block;padding-right:20px;border-bottom:0}.accordion .accordion__tab .accordion__tab-content .accordion__tab-list .accordion__tab-item .accordion__tab-link:hover{color:#dd4814;text-decoration:none}.disabled .accordion .accordion__tab .accordion__tab-content .accordion__tab-list .accordion__tab-item .accordion__tab-link{color:#333}.accordion .accordion__tab .accordion__tab-content .accordion__tab-list .accordion__tab-item.active{font-weight:400}.accordion .accordion__tab .accordion__tab-content .accordion__tab-list .accordion__tab-item.active .accordion__tab-link{background:transparent url("../img/icons/cross.svg") top 7px right 0px no-repeat}.accordion .accordion__tab .accordion__tab-content .accordion__tab-list .accordion__tab-item.active:hover{color:#dd4814}.accordion .accordion__tab .accordion__tab-content .accordion__tab-list .accordion__tab-item.active:hover .accordion__tab-link{color:#dd4814;background-image:url("../img/icons/cross-orange.svg")}.cta-group .cta-group__link{-webkit-box-sizing:border-box;-moz-box-sizing:border-box;box-sizing:border-box;-webkit-border-radius:3px 0px 0px 3px;-moz-border-radius:3px 0px 0px 3px;border-radius:3px 0px 0px 3px;display:inline-block;padding:10px 14px;text-align:center;color:#fff;background-color:#dd4814}.cta-group .cta-group__link:hover{cursor:pointer;text-decoration:none;background-color:#c03f11}.cta-group.secondary .cta-group__link{color:#dd4814;border:1px solid #b2b2b2;background-color:#FFF;line-height:1}.cta-group.secondary .cta-group__link:hover{cursor:pointer;background-color:#F2F2F2}a.link-cta-ubuntu,button.cta-ubuntu,input[type='submit'],form button[type='submit'],form input[type='submit']{color:#fff;font-size:1em;border:none;max-height:37px;border-bottom:0}a.link-cta-ubuntu:hover,button.cta-ubuntu:hover,input[type='submit']:hover,form button[type='submit']:hover,form input[type='submit']:hover{color:#fff}a.link-cta-ubuntu[disabled],a.link-cta-ubuntu.disabled,button.cta-ubuntu[disabled],button.cta-ubuntu.disabled,input[type='submit'][disabled],input[type='submit'].disabled,form button[type='submit'][disabled],form button[type='submit'].disabled,form input[type='submit'][disabled],form input[type='submit'].disabled{cursor:default;opacity:.5}a.link-cta-ubuntu.clear,button.cta-ubuntu.clear,input[type='submit'].clear,form button[type='submit'].clear,form input[type='submit'].clear{background:none;color:#333}a.link-cta-ubuntu.secondary,button.cta-ubuntu.secondary,input[type='submit'].secondary,form button[type='submit'].secondary,form input[type='submit'].secondary{color:#dd4814;border:1px solid #b2b2b2;background:#FFF}a.link-cta-ubuntu.secondary.external,button.cta-ubuntu.secondary.external,input[type='submit'].secondary.external,form button[type='submit'].secondary.external,form input[type='submit'].secondary.external{background-image:url("../img/external-link-black.svg");background-size:16px 16px;background-repeat:no-repeat;background-position:top 8px right 8px}a.link-cta-ubuntu.secondary:hover,button.cta-ubuntu.secondary:hover,input[type='submit'].secondary:hover,form button[type='submit'].secondary:hover,form input[type='submit'].secondary:hover{background-color:#F2F2F2;cursor:pointer}a.link-cta-ubuntu.secondary[disabled],a.link-cta-ubuntu.secondary.disabled,button.cta-ubuntu.secondary[disabled],button.cta-ubuntu.secondary.disabled,input[type='submit'].secondary[disabled],input[type='submit'].secondary.disabled,form button[type='submit'].secondary[disabled],form button[type='submit'].secondary.disabled,form input[type='submit'].secondary[disabled],form input[type='submit'].secondary.disabled{cursor:default;color:#f5ae95;border:1px solid #ddd;background:#FFF;opacity:1}a.link-cta-ubuntu.secondary[disabled]:hover,a.link-cta-ubuntu.secondary.disabled:hover,button.cta-ubuntu.secondary[disabled]:hover,button.cta-ubuntu.secondary.disabled:hover,input[type='submit'].secondary[disabled]:hover,input[type='submit'].secondary.disabled:hover,form button[type='submit'].secondary[disabled]:hover,form button[type='submit'].secondary.disabled:hover,form input[type='submit'].secondary[disabled]:hover,form input[type='submit'].secondary.disabled:hover{background:#FFF}a.link-cta-ubuntu.text-button,button.cta-ubuntu.text-button,input[type='submit'].text-button,form button[type='submit'].text-button,form input[type='submit'].text-button{background-color:transparent;color:#333}a.link-cta-ubuntu.text-button:hover,button.cta-ubuntu.text-button:hover,input[type='submit'].text-button:hover,form button[type='submit'].text-button:hover,form input[type='submit'].text-button:hover{text-decoration:underline}a.link-cta-ubuntu.full,button.cta-ubuntu.full,input[type='submit'].full,form button[type='submit'].full,form input[type='submit'].full{display:block;width:100%}@media screen and (max-width: 768px){a.link-cta-ubuntu,button.cta-ubuntu,input[type='submit'],form button[type='submit'],form input[type='submit']{margin-bottom:20px}}a.link-cta-ubuntu{line-height:20px}.cta-group{float:left;width:auto;clear:both;position:relative;overflow:hidden;min-width:150px}.cta-group .cta-group__link{-webkit-border-radius:3px;-moz-border-radius:3px;border-radius:3px;max-height:36px;padding-right:49px;width:100%;line-height:1.2;position:relative;margin:0;text-align:left}.cta-group .cta-group__link:after{-webkit-border-radius:0px 3px 3px 0px;-moz-border-radius:0px 3px 3px 0px;border-radius:0px 3px 3px 0px;content:'';display:block;height:36px;width:34px;background:red;position:absolute;top:0;right:0;background-image:url("../img/chevron-white.svg");background-color:#dd4814;background-repeat:no-repeat;background-position:center}.cta-group .cta-group__link:hover{background-color:#c03f11}.cta-group .cta-group__link:hover:after{background-color:#c03f11}.cta-group .cta-group__dropdown{-webkit-box-sizing:border-box;-moz-box-sizing:border-box;box-sizing:border-box;-webkit-border-radius:3px;-moz-border-radius:3px;border-radius:3px;right:0;list-style:none;background:#fff;box-shadow:0 1px 1px rgba(0,0,0,0.1);z-index:20;max-height:1000px;transition:max-height 0.3s ease-in;overflow:hidden;position:relative;clear:both}.cta-group .cta-group__dropdown.ng-hide{display:block !important;max-height:0;overflow:hidden;transition:max-height 0.3s ease-out}.cta-group .cta-group__dropdown .cta-group__item{-webkit-box-sizing:border-box;-moz-box-sizing:border-box;box-sizing:border-box;float:left;clear:both;padding:5px 10px;margin:0}.cta-group .cta-group__dropdown .cta-group__item a{color:#333;cursor:pointer;width:100%;float:left;margin:0}.cta-group .cta-group__dropdown .cta-group__item a:hover{color:#dd4814;text-decoration:none}.cta-group.secondary .cta-group__link{float:left;max-height:36px;width:100%}.cta-group.secondary .cta-group__link:after{-webkit-box-sizing:border-box;-moz-box-sizing:border-box;box-sizing:border-box;background-image:url("../img/icons/accordion-open.svg");background-repeat:no-repeat;background-color:#fff;border:1px solid #b2b2b2;border-left:none;top:-1px;right:-1px}.cta-group.secondary .cta-group__link:hover{background-color:#F2F2F2}.cta-group.secondary .cta-group__link:hover:after{background-color:#F2F2F2}.flash-messages{margin:0px auto;padding:0;max-width:1440px}@media screen and (max-width: 1030px){.flash-messages{margin:0px 10px 20px}}.flash-messages .flash-messages__item{-webkit-box-sizing:border-box;-moz-box-sizing:border-box;box-sizing:border-box;-webkit-border-radius:2px;-moz-border-radius:2px;border-radius:2px;list-style:none;padding:15px 20px 15px 45px;margin:0;font-weight:400;font-size:0.875em;background:#FFF;background-position:top 50% left 15px;background-repeat:no-repeat;margin:0 0 20px;box-shadow:0 1px 1px rgba(0,0,0,0.1)}.flash-messages .flash-messages__item.info{background-image:url("../img/icons/info.png");background-image:url("../img/icons/info.svg"),none}.flash-messages .flash-messages__item.success{background-image:url("../img/icons/success.png");background-image:url("../img/icons/success.svg"),none}.flash-messages .flash-messages__item.warning{background-image:url("../img/icons/warning.png");background-image:url("../img/icons/warning.svg"),none}.flash-messages .flash-messages__item.error{background-image:url("../img/icons/error.png");background-image:url("../img/icons/error.svg"),none}input[type='text'],input[type='number'],input[type='search'],input[type='password'],input[type='email'],input[type='url'],textarea,select,tags-input .tags .input,.accounts .api li input[type='text']{-webkit-border-radius:2px;-moz-border-radius:2px;border-radius:2px;-webkit-box-sizing:border-box;-moz-box-sizing:border-box;box-sizing:border-box;-webkit-appearance:textfield;border-radius:2px;border:1px solid #d2d2d2;box-shadow:inset 0 1px 2px rgba(0,0,0,0.12);font-size:16px;margin:0;outline:none;padding:.6956522em .869565em;vertical-align:baseline;font-weight:300}input[type='text']:hover,input[type='number']:hover,input[type='search']:hover,input[type='password']:hover,input[type='email']:hover,input[type='url']:hover,textarea:hover,select:hover,tags-input .tags .input:hover,.accounts .api li input[type='text']:hover{border-color:#B2B2B2;outline:none}input[type='text']:active,input[type='number']:active,input[type='search']:active,input[type='password']:active,input[type='email']:active,input[type='url']:active,textarea:active,select:active,tags-input .tags .input:active,.accounts .api li input[type='text']:active{border-color:#888;outline:none}input[type='text']:focus,input[type='number']:focus,input[type='search']:focus,input[type='password']:focus,input[type='email']:focus,input[type='url']:focus,textarea:focus,select:focus,tags-input .tags .input:focus,.accounts .api li input[type='text']:focus{border-color:#888;outline:none}input.ng-dirty.invalid[type='text'],input.ng-dirty.invalid[type='number'],input.ng-dirty.invalid[type='search'],input.ng-dirty.invalid[type='password'],input.ng-dirty.invalid[type='email'],input.ng-dirty.invalid[type='url'],textarea.ng-dirty.invalid,select.ng-dirty.invalid,tags-input .tags .ng-dirty.invalid.input,.accounts .api li input.ng-dirty.invalid[type='text'],input.ng-dirty.ng-invalid[type='text'],input.ng-dirty.ng-invalid[type='number'],input.ng-dirty.ng-invalid[type='search'],input.ng-dirty.ng-invalid[type='password'],input.ng-dirty.ng-invalid[type='email'],input.ng-dirty.ng-invalid[type='url'],textarea.ng-dirty.ng-invalid,select.ng-dirty.ng-invalid,tags-input .tags .ng-dirty.ng-invalid.input,.accounts .api li input.ng-dirty.ng-invalid[type='text']{border-color:#d90000 !important}input[type='text'] .disabled,input[type='number'] .disabled,input[type='search'] .disabled,input[type='password'] .disabled,input[type='email'] .disabled,input[type='url'] .disabled,textarea .disabled,select .disabled,tags-input .tags .input .disabled,.accounts .api li input[type='text'] .disabled,input[disabled="disabled"][type='text'],input[disabled="disabled"][type='number'],input[disabled="disabled"][type='search'],input[disabled="disabled"][type='password'],input[disabled="disabled"][type='email'],input[disabled="disabled"][type='url'],textarea[disabled="disabled"],select[disabled="disabled"],tags-input .tags [disabled="disabled"].input,.accounts .api li input[disabled="disabled"][type='text']{-webkit-text-fill-color:#333;color:#333;border-color:#e3e3e3;background-color:transparent;cursor:not-allowed}label{position:relative}.disabled label{cursor:default}form li.help-msg{margin-bottom:1em}form li.help-msg .help{color:#888;font-size:0.875em}form label span{color:#888}form fieldset{background:none;margin-left:0;padding:0}input[type='text'],input[type='number'],input[type='search'],input[type='password'],input[type='email'],input[type='url']{padding:7px 10px !important;box-shadow:none;width:100%;border:1px solid #d2d2d2}input[type='text']::-webkit-input-placeholder,input[type='number']::-webkit-input-placeholder,input[type='search']::-webkit-input-placeholder,input[type='password']::-webkit-input-placeholder,input[type='email']::-webkit-input-placeholder,input[type='url']::-webkit-input-placeholder{color:#888}input[type='text']:-moz-placeholder,input[type='number']:-moz-placeholder,input[type='search']:-moz-placeholder,input[type='password']:-moz-placeholder,input[type='email']:-moz-placeholder,input[type='url']:-moz-placeholder{color:#888}input[type='text']::-moz-placeholder,input[type='number']::-moz-placeholder,input[type='search']::-moz-placeholder,input[type='password']::-moz-placeholder,input[type='email']::-moz-placeholder,input[type='url']::-moz-placeholder{color:#888}input[type='text']:-ms-input-placeholder,input[type='number']:-ms-input-placeholder,input[type='search']:-ms-input-placeholder,input[type='password']:-ms-input-placeholder,input[type='email']:-ms-input-placeholder,input[type='url']:-ms-input-placeholder{color:#888}input[type='number']{padding-right:15px}input[type='search']{-webkit-appearance:textfield}input[type='search']::-webkit-search-decoration,input[type='search']::-webkit-search-cancel-button{-webkit-appearance:none}input[type='radio'],input[type='image']{display:inline-block;margin-right:10px}textarea{overflow:auto;height:auto;min-height:175px;padding:7px 10px;max-height:none;vertical-align:top;resize:both;width:100%;box-shadow:none}select{display:block;clear:both;cursor:pointer;margin:0;background-image:url("../img/icons/accordion-open.svg");background-repeat:no-repeat;background-position:top 14px right 10px;background-color:#fff;padding:7px 30px 7px 10px !important;box-shadow:none;width:100%;-moz-appearance:none;text-indent:.01px;text-overflow:''}select[multiple],select[size]{height:auto;background-image:none;padding-top:10px}select:-moz-focusring{color:transparent;text-shadow:0 0 0 #000}select[disabled]{color:#888;background-image:none}select::-ms-expand{display:none}.checkbox:not(:checked),.checkbox:checked{position:absolute;left:-9999px}.checkbox+label{position:relative;padding-left:25px;cursor:pointer;max-width:100%;display:inline;vertical-align:middle;width:auto}.checkbox+label:before{content:'';position:absolute;left:0;top:3px;width:11px;height:11px;border:1px solid #cdcdcd;background:#fff;border-radius:2px}.checkbox:checked+label:before{background-color:#dd4814;border-color:#dd4814}.checkbox:checked+label:after{content:'✔';position:absolute;top:3px;left:2px;font-size:10px;color:#fff;transition:all .2s}.checkbox[disabled]{cursor:not-allowed}.checkbox[disabled]+label{cursor:not-allowed}.checkbox[disabled]+label:before{opacity:.5}.radio:not(:checked),.radio:checked{position:absolute;left:-9999px}.radio+label{position:relative;padding-left:25px;cursor:pointer}.radio+label:before{content:'';position:absolute;left:0;top:1px;width:13px;height:13px;border:1px solid #cdcdcd;background:#fff;border-radius:50%}.radio:checked+label:after{content:'';position:absolute;left:3px;top:4px;width:9px;height:9px;background:#dd4814;border-radius:50%}.field-error,.errors{color:#DF382C}.field-error li,.errors li{margin:7px 0}.field-error .errorlist,.errors .errorlist{margin:0}.field-error .errorlist li,.errors .errorlist li{margin:0 0 14px 0}.inline{display:inline-block;width:100%;font-size:0;margin-bottom:10px}.inline.error{background-color:#fdf5f5;box-shadow:0px 0px 0px 5px #fdf5f5}.inline.error .ng-invalid{border-color:#D2D2D2}.inline:last-of-type{margin-bottom:0}.inline label{display:inline-block;line-height:37px;color:#888;font-size:16px;margin:0}.inline input[type='submit'],.inline input[type='text'],.inline input[type='number'],.inline input[type='search'],.inline input[type='password'],.inline input[type='email'],.inline input[type='checkbox'],.inline select{display:inline-block;clear:none;margin:0;float:none;font-size:16px}.inline input[type='submit']:invalid,.inline input[type='text']:invalid,.inline input[type='number']:invalid,.inline input[type='search']:invalid,.inline input[type='password']:invalid,.inline input[type='email']:invalid,.inline input[type='checkbox']:invalid,.inline select:invalid{-moz-box-shadow:none}.inline input[type='submit']:-moz-submit-invalid,.inline input[type='text']:-moz-submit-invalid,.inline input[type='number']:-moz-submit-invalid,.inline input[type='search']:-moz-submit-invalid,.inline input[type='password']:-moz-submit-invalid,.inline input[type='email']:-moz-submit-invalid,.inline input[type='checkbox']:-moz-submit-invalid,.inline select:-moz-submit-invalid{box-shadow:none}.inline input[type='submit']:-moz-ui-invalid,.inline input[type='text']:-moz-ui-invalid,.inline input[type='number']:-moz-ui-invalid,.inline input[type='search']:-moz-ui-invalid,.inline input[type='password']:-moz-ui-invalid,.inline input[type='email']:-moz-ui-invalid,.inline input[type='checkbox']:-moz-ui-invalid,.inline select:-moz-ui-invalid{box-shadow:none}.inline div{float:none;margin:0}.inline input.cta-ubuntu,.inline a.link-cta-ubuntu,.inline button.cta-ubuntu{font-size:16px}.inline .icon{position:absolute;top:11px;right:10px;cursor:pointer}.inline .error-message{font-size:12px;color:#e85232;margin-top:10px;margin-bottom:10px;font-weight:normal}.form-inline{clear:both}.form-inline label,.form-inline button,.form-inline input[type='submit'],.form-inline input[type='text'],.form-inline input[type='number'],.form-inline input[type='search'],.form-inline input[type='password'],.form-inline input[type='email'],.form-inline input[type='checkbox'],.form-inline select{display:inline-block;width:auto;vertical-align:middle;margin-bottom:0}.form-inline input,.form-inline input[type='submit'] input[type='text'],.form-inline input[type='number'],.form-inline input[type='search'],.form-inline input[type='password'],.form-inline input[type='email'],.form-inline input[type='checkbox'],.form-inline select{margin-left:20px}.form-inline fieldset{width:auto;display:inline-block;margin:0 40px 0 0}.controls{position:absolute;top:0;right:20px}.controls a,.controls button{margin-left:20px}.form .form__siblings{float:left;width:100%}.form .form__siblings:hover .form__group--subtle input,.form .form__siblings:hover .form__group--subtle select,.form .form__siblings:hover .form__group--subtle textarea{border-color:#B2B2B2;background-color:#fff;outline:none}.form .form__siblings.form__siblings--active .form__group--subtle input,.form .form__siblings.form__siblings--active .form__group--subtle select,.form .form__siblings.form__siblings--active .form__group--subtle textarea{border-color:#B2B2B2;background-color:#fff;outline:none}.form .form__group{margin-bottom:10px}.form .form__group .form__group-errors{margin-top:5px}.form .form__group.form__group--inline{width:100%;float:left}.form .form__group.form__group--inline [class*='-col']{margin-bottom:0}.form .form__group.form__group--inline label{display:inline-block;float:none;font-size:16px;margin-top:0;margin-bottom:0;line-height:36px;vertical-align:top}.form .form__group.form__group--inline input,.form .form__group.form__group--inline select,.form .form__group.form__group--inline textarea,.form .form__group.form__group--inline .form__group-input{display:inline-block;clear:none;margin:0;float:none;font-size:16px}.form .form__group.form__group--subtle label{color:#888}.form .form__group.form__group--subtle input,.form .form__group.form__group--subtle select,.form .form__group.form__group--subtle textarea{border-color:#e3e3e3;background-color:transparent}.form .form__group.form__group--subtle input:hover,.form .form__group.form__group--subtle select:hover,.form .form__group.form__group--subtle textarea:hover{border-color:#B2B2B2;background-color:#fff;outline:none}.form .form__group.form__group--subtle input:active,.form .form__group.form__group--subtle input:focus,.form .form__group.form__group--subtle select:active,.form .form__group.form__group--subtle select:focus,.form .form__group.form__group--subtle textarea:active,.form .form__group.form__group--subtle textarea:focus{border-color:#888;outline:none;background-color:#fff}.form.form--inline .form__group{display:inline-block;margin-bottom:0;vertical-align:middle}.form.form--inline .form__group label{display:inline-block;max-width:100%;margin-bottom:0px}.form.form--inline .form__group input,.form.form--inline .form__group select,.form.form--inline .form__group textarea,.form.form--inline .form__group .form__group-input{display:inline-block;width:auto;vertical-align:middle;margin:0}.form .form__help-text{font-size:0.875pxem;color:#888}.onoffswitch{position:relative;display:inline-block;vertical-align:middle;width:38px;-webkit-user-select:none;-moz-user-select:none;-ms-user-select:none}.onoffswitch-checkbox{display:none}.onoffswitch-label{display:block;overflow:hidden;cursor:pointer;border-radius:2px}.onoffswitch-inner{display:block;width:200%;margin-left:-100%;transition:margin 0.3s ease-in 0s}.onoffswitch-inner:before,.onoffswitch-inner:after{display:block;float:left;width:50%;height:18px;padding:0;line-height:18px;font-size:14px;color:white;font-family:Trebuchet, Arial, sans-serif;font-weight:bold;box-sizing:border-box}.onoffswitch-inner:before{content:"";padding-left:10px;background-color:#3FB24F;color:#FFFFFF}.onoffswitch-inner:after{content:"";padding-right:10px;background-color:#EEEEEE;color:#999999;text-align:right}.onoffswitch-switch{display:block;width:19px;margin:0px;background:#FFFFFF;position:absolute;top:0;bottom:0;right:16px;border:1px solid #D2D2D2;border-radius:2px;transition:all 0.3s ease-in 0s}.onoffswitch-checkbox:checked+.onoffswitch-label .onoffswitch-inner{margin-left:0}.onoffswitch-checkbox:checked+.onoffswitch-label .onoffswitch-switch{right:0px}.icon.icon__loading{background:url("../img/in_progress.png") no-repeat;-webkit-animation:spin 1s infinite linear;-moz-animation:spin 1s infinite linear;animation:spin 1s infinite linear}a.icon{cursor:pointer}.icon-controls .icon{display:none;float:right;text-align:left}table tr:hover .icon-controls .icon{display:block}.listing-filter .listing-filter__label{height:53px;line-height:53px}.listing-filter .listing-filter__select{min-width:150px}dl dt{clear:left;color:#888}dl dd{color:#333;margin-left:0}dl dt,dl dd{display:inline-block;float:left;line-height:37px;margin-bottom:10px !important;word-wrap:break-word}.list__tree{list-style:none;border-left:1px solid #d4d4d4;position:relative}.list__tree.list__tree--sub-level{margin-top:10px;margin-left:20px;clear:both}.list__tree.list__tree--sub-level .list__item .list__item-feedback{left:180px}.list__tree .list__item{list-style:none}.list__tree .list__item:before{content:'';width:12px;height:1px;background:#d4d4d4;display:inline-block;position:relative;top:-4px;margin-right:5px}.list__tree .list__item:last-child::after{content:'';width:4px;height:1em;position:absolute;display:block;left:-2px;bottom:-6px;background:#f8f8f8}.list__tree .list__item .list__item-feedback{position:relative;left:200px;margin-top:-24px}tags-input{outline:none}tags-input .host:focus{outline:none}tags-input .tags:focus,tags-input .tags.focused{outline:none}tags-input .tags .tag-list{margin:4px 0 0;padding:0;list-style-type:none;width:100%;float:left}tags-input .tags .tag-item{display:inline-block;float:left;font-family:Ubuntu,Arial,"libra sans",sans-serif;font-size:1em;font-weight:300;height:30px;line-height:30px;cursor:default;color:#000;padding-right:15px;position:relative;margin:0 11px 0 0}tags-input .tags .tag-item .remove-button{display:inline-block;width:12px;height:12px;text-indent:-999em;background:url("../img/icons/cross.svg") no-repeat;background-size:12px 12px;position:absolute;right:0;top:9px;cursor:pointer}tags-input .tags .tag-item .remove-button:hover{text-decoration:none}tags-input .tags .input{padding:7px 10px;width:100% !important;float:left;position:relative !important;left:0}tags-input .tags .input::-ms-clear{display:none}tags-input .autocomplete{float:left;width:100%}tags-input .autocomplete .suggestion-list{background:#FFF;padding:10px 8px;border:1px solid #D2D2D2;border-top:0;border-radius:0 0 2px 2px}tags-input .autocomplete .suggestion-list li:hover{background:#EEE;cursor:pointer}tags-input[disabled] .host:focus{outline:none}tags-input[disabled] .tags{cursor:default}tags-input[disabled] .tags .tag-item .remove-button{cursor:default}tags-input[disabled] .tags .input{cursor:default}.tag-link{margin-right:10px}.tag-link:last-of-type:after{content:''}.table__data tags-input .tags{margin-top:-10px}.table__data tags-input .tags input{margin-left:0;width:50% !important;float:left}.pagination{margin:10px 0;text-align:center}.pagination .inactive{color:#AEA79F}.pagination a,.pagination span{margin:0 5px}.search{position:relative;padding-bottom:20px}.search input[type='search']{-webkit-appearance:textfield}.search .search__input{-webkit-box-sizing:border-box;-moz-box-sizing:border-box;box-sizing:border-box;-webkit-border-radius:4px;-moz-border-radius:4px;border-radius:4px;list-style:none;background:#FFF;box-shadow:0 1px 1px rgba(0,0,0,0.1);width:100%;border:none;padding:10px !important;font-size:1em;max-height:none}.search .search__input::-webkit-input-placeholder{color:#888 !important}.search .search__input:-moz-placeholder{color:#888 !important}.search .search__input::-moz-placeholder{color:#888 !important}.search .search__input:-ms-input-placeholder{color:#888 !important}.search .search__input[disabled="disabled"]{background-color:#fff;opacity:.5;pointer-events:none}.search .search__input[disabled="disabled"]+.search__submit.close{pointer-events:none;opacity:.5}.search .search__submit{position:absolute;top:10px;right:15px;background-color:transparent;background-image:url("../img/search-icon.svg");background-repeat:no-repeat;text-indent:-999em;display:block;width:21px;height:20px;overflow:hidden;outline:none;padding:0;border:none}.search .search__submit:hover{background-color:transparent;background-image:url("../img/search-icon.svg")}.search .search__submit.close{background-image:url("../img/icons/cross.svg");background-size:21px;margin-top:2px}.search .search__submit.close:hover{background-image:url("../img/icons/cross.svg")}.u-margin{margin:20px}.u-margin--none{margin:none !important}.u-margin--tiny{margin:5px !important}.u-margin--small{margin:10px !important}.u-margin--large{margin:40px !important}.u-margin--huge{margin:80px !important}.u-margin--top{margin-top:20px !important}.u-margin--top-none{margin-top:none !important}.u-margin--top-tiny{margin-top:5px !important}.u-margin--top-small{margin-top:10px !important}.u-margin--top-large{margin-top:40px !important}.u-margin--top-huge{margin-top:80px !important}.u-margin--right{margin-right:20px !important}.u-margin--right-none{margin-right:none !important}.u-margin--right-tiny{margin-right:5px !important}.u-margin--right-small{margin-right:10px !important}.u-margin--right-large{margin-right:40px !important}.u-margin--right-huge{margin-right:80px !important}.u-margin--bottom{margin-bottom:20px !important}.u-margin--bottom-none{margin-bottom:none !important}.u-margin--bottom-tiny{margin-bottom:5px !important}.u-margin--bottom-small{margin-bottom:10px !important}.u-margin--bottom-large{margin-bottom:40px !important}.u-margin--bottom-huge{margin-bottom:80px !important}.u-margin--left{margin-left:20px !important}.u-margin--left-none{margin-left:none !important}.u-margin--left-tiny{margin-left:5px !important}.u-margin--left-small{margin-left:10px !important}.u-margin--left-large{margin-left:40px !important}.u-margin--left-huge{margin-left:80px !important}.u-padding{padding:20px}.u-padding--none{padding:none !important}.u-padding--tiny{padding:5px !important}.u-padding--small{padding:10px !important}.u-padding--large{padding:40px !important}.u-padding--huge{padding:80px !important}.u-padding--top{padding-top:20px !important}.u-padding--top-none{padding-top:none !important}.u-padding--top-tiny{padding-top:5px !important}.u-padding--top-small{padding-top:10px !important}.u-padding--top-large{padding-top:40px !important}.u-padding--top-huge{padding-top:80px !important}.u-padding--right{padding-right:20px !important}.u-padding--right-none{padding-right:none !important}.u-padding--right-tiny{padding-right:5px !important}.u-padding--right-small{padding-right:10px !important}.u-padding--right-large{padding-right:40px !important}.u-padding--right-huge{padding-right:80px !important}.u-padding--bottom{padding-bottom:20px !important}.u-padding--bottom-none{padding-bottom:none !important}.u-padding--bottom-tiny{padding-bottom:5px !important}.u-padding--bottom-small{padding-bottom:10px !important}.u-padding--bottom-large{padding-bottom:40px !important}.u-padding--bottom-huge{padding-bottom:80px !important}.u-padding--left{padding-left:20px !important}.u-padding--left-none{padding-left:none !important}.u-padding--left-tiny{padding-left:5px !important}.u-padding--left-small{padding-left:10px !important}.u-padding--left-large{padding-left:40px !important}.u-padding--left-huge{padding-left:80px !important}.spinner-col{width:10px}.spinner{float:left;margin:0 auto;text-indent:-9999em}.spinner.spin{background:url("../img/in_progress.png") no-repeat;background-size:16px 16px;width:16px;height:16px;-webkit-animation:spin 1s infinite linear;-moz-animation:spin 1s infinite linear;animation:spin 1s infinite linear;padding:0}@-webkit-keyframes spin{0%{-webkit-transform:rotate(0deg);transform:rotate(0deg)}100%{-webkit-transform:rotate(360deg);transform:rotate(360deg)}}@keyframes spin{0%{-webkit-transform:rotate(0deg);transform:rotate(0deg)}100%{-webkit-transform:rotate(360deg);transform:rotate(360deg)}}table,.table{border-color:#d2d2d2;border-spacing:0;overflow-x:scroll;margin-bottom:20px;margin:0 0 2.5em;width:100%;text-align:left;border-collapse:separate}table tr,.table table tr,table .table tr,.table .table__row{width:100%;border-color:#b2b2b2;border-bottom-style:dotted;border-bottom-width:1px}table th,.table table th,table .table th,table td,.table table td,table .table td,.table .table__header,.table .table__data{font-size:1em;padding:10px;box-sizing:border-box;min-height:21px;background:none;border:0;text-align:left;border-collapse:separate;vertical-align:top;backface-visibility:hidden;position:relative}table thead tr,.table table thead tr,table thead .table tr,.table .table__head .table__row{color:#888;border-bottom:1px solid}table thead tr:hover,.table .table__head .table__row:hover{background-color:transparent}table thead th,.table table thead th,table thead .table th,.table .table__head .table__header{font-size:0.8125em;background:none;color:#888;font-size:13px}table thead th input[type="radio"]+label,.table .table__head .table__header input[type="radio"]+label,table thead th input[type="checkbox"]+label,.table .table__head .table__header input[type="checkbox"]+label{margin:0;top:-3px}table thead th a:link,table thead th a:visited,.table .table__head .table__header .table__header-link{color:#888}table thead th a:hover:link,table thead th a:hover:visited,.table .table__head .table__header .table__header-link:hover{color:#333;text-decoration:none;border-bottom:1px solid #333}table thead th a.active:link,table thead th a.active:visited,.table .table__head .table__header .active.table__header-link{color:#333;text-decoration:none}table thead th a.sort:link,table thead th a.sort:visited,.table .table__head .table__header .sort.table__header-link{border-bottom:1px solid #333}table thead .divide,.table .table__head .table__header-divide,.table .table__head .divide{width:1px;display:inline-block;background:#888;height:10px;margin:0 5px}table .numerical,.table .numerical{text-align:right}table input,.table input,table select,.table select{margin:0 0 0 -14px}table input[type="radio"]+label,.table input[type="radio"]+label,table input[type="checkbox"]+label,.table input[type="checkbox"]+label{margin:0;top:-1px}table th{color:#888;border-bottom:1px solid}table td{border-color:#b2b2b2;border-bottom-style:dotted;border-bottom-width:1px}table td a:link,table td a:visited{color:#333;border-bottom:1px solid #d2d2d2}table td a:link:hover,table td a:visited:hover{text-decoration:none;color:#dd4814}.table{display:table}.table .table__row{float:left;display:table-row}.table .table__row:hover{background-color:#fff}.table .table__row:hover .table__input{background-color:#fff;border-color:#d2d2d2;background-position:right 10px top 16px}.table .table__row:hover .table__input[disabled]{border-color:transparent}.table .table__row:hover .table__controls{z-index:1;opacity:1}.table .table__row:hover .table__controls--secondary{z-index:1;opacity:1}.table .table__row.active{background-color:#fff}.table .table__row.active .table__input{background-color:#fff;border-color:#d2d2d2;background-position:right 10px top 16px}.table .table__row.active .table__controls{z-index:1;opacity:1}.table .table__row.active .table__controls--secondary{z-index:1;opacity:1}.table .table__row.active .table__dropdown .table__row{display:none}.table .table__row.active .table__dropdown .table__row.active{display:block}.table .table__header,.table .table__data{display:table-cell;float:left}.table .table__data a:link,.table .table__data a:visited{color:#333;border-bottom:1px solid #d2d2d2}.table .table__data a:link:hover,.table .table__data a:visited:hover{text-decoration:none;color:#dd4814}.table .table__head{display:table-head;width:100%;box-sizing:border-box}.table .table__body{display:table-row-group}.table .table__footer{display:table-footer-group}.table .table__label{clear:both;display:block;margin-top:11px;color:#bcbcbc}.table .table__label a{color:#bcbcbc}.table .table__label a:hover{color:#dd4814}.table .table__label.active a{color:#dd4814}.table .table__controls{width:100%;text-align:right;opacity:0;z-index:-1000}.table .table__controls--secondary{opacity:0;z-index:-1000;width:auto;text-align:left}.table .table__controls a,.table .table__controls a:link,.table .table__controls a:visited{color:#333;border-bottom:1px solid #d2d2d2}.table .table__controls a:hover,.table .table__controls a:link:hover,.table .table__controls a:visited:hover{text-decoration:none;color:#dd4814}.table .table__input{display:inline-block;margin:-7px 0 -7px -14px;background-color:transparent;border-color:transparent;background-position:-9999px -9999px}.table .table__input[disabled]{background-color:transparent;border-color:transparent;pointer-events:none;background-position:-9999px -9999px;color:#333}.table .table__dropdown{width:100%}.table .table__dropdown .table__row{border-bottom:0;display:none;position:relative}.table .table__dropdown .table__row:before{display:block;margin:0 auto;width:calc(100% - 20px);border-top:1px dotted #d2d2d2;position:absolute;height:1px;content:'';top:0;left:10px}.table .table__dropdown .table__row.table__dropdown-row--head{border-bottom:0}.table .table__dropdown .table__row.table__dropdown-row--head .table__header{color:#bcbcbc;font-size:13px}.table .table__dropdown .table__row.no-border:before{display:none}.table .table__dropdown .table__row.border:before{display:block;margin:0 auto;width:calc(100% - 20px);border-top:1px dotted #d2d2d2;position:absolute;height:1px;content:'';top:0;left:10px}.table .table__dropdown .table__row.active .table__input{background-color:#fff;border-color:#d2d2d2;background-position:right 10px top 16px;pointer-events:all}.table .table__dropdown .table__row.active .table__input[disabled]{border-color:transparent;cursor:pointer}.table .table__dropdown--info .table__row{border-bottom:0}.table .table__dropdown--info .table__data{color:#bcbcbc}.form .form__group input,.form .form__group select{margin:0}.table--error{border-color:#d83832}.table--error .table__header,.table--error .table__data,.table--error th,.table--error td{border-color:#d83832;background-color:#f9dedd}.table--warning{border-color:#eca918}.table--warning .table__header,.table--warning .table__data,.table--warning th,.table--warning td{border-color:#eca918;background-color:#fcefd4}.table--success{border-color:#38b44a}.table--success .table__header,.table--success .table__data,.table--success th,.table--success td{border-color:#38b44a;background-color:#caeecf}.table--information{border-color:#2ab7ec}.table--information .table__header,.table--information .table__data,.table--information th,.table--information td{border-color:#2ab7ec;background-color:#e5f6fd}.table-col--1{width:1%}.table-col--2{width:2%}.table-col--3{width:3%}.table-col--4{width:4%}.table-col--5{width:5%}.table-col--6{width:6%}.table-col--7{width:7%}.table-col--8{width:8%}.table-col--9{width:9%}.table-col--10{width:10%}.table-col--11{width:11%}.table-col--12{width:12%}.table-col--13{width:13%}.table-col--14{width:14%}.table-col--15{width:15%}.table-col--16{width:16%}.table-col--17{width:17%}.table-col--18{width:18%}.table-col--19{width:19%}.table-col--20{width:20%}.table-col--21{width:21%}.table-col--22{width:22%}.table-col--23{width:23%}.table-col--24{width:24%}.table-col--25{width:25%}.table-col--26{width:26%}.table-col--27{width:27%}.table-col--28{width:28%}.table-col--29{width:29%}.table-col--30{width:30%}.table-col--31{width:31%}.table-col--32{width:32%}.table-col--33{width:33%}.table-col--34{width:34%}.table-col--35{width:35%}.table-col--36{width:36%}.table-col--37{width:37%}.table-col--38{width:38%}.table-col--39{width:39%}.table-col--40{width:40%}.table-col--41{width:41%}.table-col--42{width:42%}.table-col--43{width:43%}.table-col--44{width:44%}.table-col--45{width:45%}.table-col--46{width:46%}.table-col--47{width:47%}.table-col--48{width:48%}.table-col--49{width:49%}.table-col--50{width:50%}.table-col--51{width:51%}.table-col--52{width:52%}.table-col--53{width:53%}.table-col--54{width:54%}.table-col--55{width:55%}.table-col--56{width:56%}.table-col--57{width:57%}.table-col--58{width:58%}.table-col--59{width:59%}.table-col--60{width:60%}.table-col--61{width:61%}.table-col--62{width:62%}.table-col--63{width:63%}.table-col--64{width:64%}.table-col--65{width:65%}.table-col--66{width:66%}.table-col--67{width:67%}.table-col--68{width:68%}.table-col--69{width:69%}.table-col--70{width:70%}.table-col--71{width:71%}.table-col--72{width:72%}.table-col--73{width:73%}.table-col--74{width:74%}.table-col--75{width:75%}.table-col--76{width:76%}.table-col--77{width:77%}.table-col--78{width:78%}.table-col--79{width:79%}.table-col--80{width:80%}.table-col--81{width:81%}.table-col--82{width:82%}.table-col--83{width:83%}.table-col--84{width:84%}.table-col--85{width:85%}.table-col--86{width:86%}.table-col--87{width:87%}.table-col--88{width:88%}.table-col--89{width:89%}.table-col--90{width:90%}.table-col--91{width:91%}.table-col--92{width:92%}.table-col--93{width:93%}.table-col--94{width:94%}.table-col--95{width:95%}.table-col--96{width:96%}.table-col--97{width:97%}.table-col--98{width:98%}.table-col--99{width:99%}.table-col--100{width:100%}h1 span,h2 span,h3 span,h4 span,h5 span{color:#888;font-size:75%;padding-left:20px}h1{font-size:2em}h2{font-size:1.5em}h3{font-size:1.25em}h4{font-size:1em;font-weight:300}h5{font-size:0.875em}pre{border:0;background-color:#FFF;border-radius:2px}pre code{counter-reset:line-numbering}pre code .line{float:left}pre code .line::before{content:counter(line-numbering);counter-increment:line-numbering;padding-right:1em;width:1.5em;text-align:right;opacity:0.5;pointer-events:none;user-select:none}.yui3-node-add-widget{width:65.9292%;margin-right:2.21238%}@media screen and (max-width: 768px){.yui3-node-add-widget{width:100%;margin:0}}.yui3-node-add-widget .buttons{margin-top:30px}.yui3-node-add-widget .add-link img.icon{margin-right:6px}.yui3-overlay{-webkit-border-radius:0 0 6px 6px;-moz-border-radius:0 0 6px 6px;border-radius:0 0 6px 6px;background-color:#fff;-webkit-box-shadow:0 0 10px 0 rgba(0,0,0,0.5);box-shadow:0 0 10px 0 rgba(0,0,0,0.5)}.yui3-overlay ul{padding:5px 0}.yui3-overlay li{float:none}.yui3-overlay li:last-child a{border-bottom:none}.yui3-overlay a{display:block;padding:6px 20px;color:#dd4814 !important;border-bottom:1px solid #e5e2e0}.yui3-overlay a:focus,.yui3-overlay a:hover{background-color:#f2f2f2}.yui3-overlay-hidden{display:none}.yui3-widget-mask{background-color:#000;opacity:0.3}.yui3-panel{-webkit-border-radius:0 0 6px 6px;-moz-border-radius:0 0 6px 6px;border-radius:0 0 6px 6px;background-color:#FFF;padding:50px 80px 50px 80px;-webkit-box-shadow:0 0 15px 0 #000;box-shadow:0 0 15px 0 #000}.yui3-panel .yui3-button{float:right}.yui3-panel .yui3-button.link-button{float:left;padding-left:0;padding-right:0;color:#dd4814;border:none;background:none;-webkit-box-shadow:none;box-shadow:none;font-size:13px}.yui3-widget-hd{margin-bottom:30px;font-size:24px}.yui3-widget-ft{margin-top:50px}.yui3-widget-button-wrapper{width:100%}.color--success{color:#38B44A}.color--error{color:#D83832}.icon{margin-left:5px;width:16px;height:16px;display:inline-block;text-indent:-999em;background-repeat:no-repeat;background-size:16px 16px;vertical-align:middle;margin-top:-3px;position:relative;text-align:left;border-bottom:0 !important;padding:0}.icon:hover{border-bottom:0}.icon.info{background-image:url("../img/icons/info.png");background-image:url("../img/icons/info.svg"),none}.icon.help{background-image:url("../img/icons/help.png");background-image:url("../img/icons/help.svg"),none}.icon.edit{background-image:url("../img/icons/edit.png");background-image:url("../img/icons/edit.svg"),none}.icon.delete{background-image:url("../img/icons/delete.png");background-image:url("../img/icons/delete.svg"),none}.icon.remove{background-image:url("../img/icons/filter-remove.svg"),none}.icon.warning{background-image:url("../img/icons/warning.png");background-image:url("../img/icons/warning.svg"),none}.icon.debug{background-image:url("../img/icons/debug.png");background-image:url("../img/icons/debug.svg"),none}.icon.success,.icon.tick{background-image:url("../img/icons/success.png");background-image:url("../img/icons/success.svg"),none}.icon.simple-tick{background-image:url("../img/icons/green-tick.svg")}.icon.error{background-image:url("../img/icons/error.png");background-image:url("../img/icons/error.svg"),none}.icon.partition{background-image:url("../img/icons/partition.svg")}.icon.add{background-image:url("../img/icons/add.svg")}.icon.tags{background-image:url("../img/icons/tags.svg")}.icon.mount{background-image:url("../img/icons/mount.svg")}.icon.unmount{background-image:url("../img/icons/unmount.svg")}.icon.notification-error{background-image:url("../img/icons/notification-error.png");background-size:12px 11px;background-position:top 1px center}.icon.open{background-image:url("../img/icons/accordion-open.svg")}.icon.close{background-image:url("../img/icons/accordion-close.svg")}.clear{clear:both}.hidden{display:none}.align-right{text-align:right}.align-center{text-align:center}.align-left{text-align:left}.right{float:right !important}.left{float:left !important}.border{border-top:1px dotted #B2B2B2}.border.bottom{border-bottom:1px dotted #B2B2B2}.border.solid{border-style:solid}.vertical-align{position:relative;top:50%;-webkit-transform:translateY(-50%);-ms-transform:translateY(-50%);transform:translateY(-50%)}.margin-top{margin-top:20px}.margin-top--five{margin-top:5px}.margin-top--ten{margin-top:10px}.margin-right{margin-right:20px !important}.margin-right--ten{margin-right:10px !important}.margin-bottom{margin-bottom:20px}.margin-bottom--ten{margin-bottom:10px !important}.margin-left{margin-left:20px !important}.margin-left--ten{margin-left:10px !important}.margin-left--thirty{margin-left:30px !important}.padding{padding:20px}.padding--ten{padding:10px}.padding-top{padding-top:20px}.padding-top--ten{padding-top:10px !important}.padding-right{padding-right:20px}.padding-right--ten{padding-right:10px}.padding-bottom{padding-bottom:20px !important}.padding-bottom--ten{padding-bottom:10px !important}.padding-left{padding-left:20px !important}.padding-left--ten{padding-left:10px}.padding-left--30,.padding-left--thirty{padding-left:30px !important}.padding-left--35{padding-left:35px !important}.padding-left--45{padding-left:45px !important}.padding-left--50{padding-left:50px !important}.border-top{border-top:1px dotted #888}.add-machine__list .add-machine__details,.border-bottom{border-bottom:1px dotted #888}.border--light{border-color:#d2d2d2}.no-margin{margin:0 !important}.no-margin-top{margin-top:0 !important}.no-padding{padding:0}.no-padding-top{padding-top:0 !important}.no-padding-left{padding-left:0}.no-padding-bottom{padding-bottom:0 !important}.no-margin-bottom{margin-bottom:0}.no-border--top{border-top:0 !important}.width--auto{width:auto}.width--half{width:50%}.width--full{width:100%}.box-sizing{box-sizing:border-box}.u-display--inline{display:inline}.u-display--inline-block{display:inline-block}.tooltip{position:relative}.tooltip::before{position:absolute;left:50%;-webkit-transform:translateX(-50%) translateY(-14px);-moz-transform:translateX(-50%) translateY(-14px);transform:translateX(-50%) translateY(-14px);content:'';width:0;height:0;border-left:5px solid transparent;border-right:5px solid transparent;z-index:-1;visibility:hidden;transition:opacity 0.2s ease-out;border-top:5px solid #333;opacity:0;bottom:100%;margin-bottom:-11px}.tooltip::after{content:attr(data-tooltip);font-size:13px;font-weight:400;line-height:16px;position:absolute;z-index:-1;visibility:hidden;left:50%;bottom:100%;-webkit-transform:translateX(-50%) translateY(-8px);-moz-transform:translateX(-50%) translateY(-8px);transform:translateX(-50%) translateY(-8px);background:#333;color:#FFF;padding:10px;height:auto;text-indent:0;opacity:0;transition:opacity 0.2s ease-out;border-radius:5px;box-shadow:0px 1px 3px 0 rgba(51,51,51,0.2);white-space:pre;box-sizing:border-box}.tooltip:hover::after,.tooltip:hover::before{opacity:1 !important;z-index:1000;visibility:visible}.tooltip.tooltip--right::before{border-top:5px solid transparent;border-right:5px solid #333;border-bottom:5px solid transparent;left:100%;bottom:inherit;top:50%;-webkit-transform:translateX(4px) translateY(-50%);-moz-transform:translateX(4px) translateY(-50%);transform:translateX(4px) translateY(-50%)}.tooltip.tooltip--right::after{left:100%;bottom:inherit;top:50%;margin-bottom:-14px;-webkit-transform:translateX(14px) translateY(-50%);-moz-transform:translateX(14px) translateY(-50%);transform:translateX(14px) translateY(-50%)}.footer-wrapper{border-top:1px dotted #b2b2b2}.footer-wrapper footer.global{background-color:transparent;padding-top:0px;padding-right:20px;padding-left:20px;background-image:url("../img/logos/logo-ubuntu-orange.png");background-image:url("../img/logos/logo-ubuntu-orange.svg"),none;background-size:107px 25px;background-position:top 20px right 20px;background-repeat:no-repeat;max-width:1480px}.footer-wrapper footer.global p{font-size:0.875em}.footer-wrapper footer.global a{margin:0 5px}.footer-wrapper footer.global .version{font-weight:500;margin-right:5px}.footer-wrapper footer.global .copy{margin-top:10px}.footer-wrapper footer.global .legal{max-width:1440px}.wrapper{min-height:100%;height:auto !important;height:100%;margin:0 auto -112px;position:relative;background:rgba(255,255,255,0.6);padding-top:172px}.wrapper:after{content:'';position:absolute;display:block;top:0;right:0;bottom:0;left:0;background:url("../img/backgrounds/image-background-paper.png");height:100%;width:100%;z-index:-1}.inner-wrapper{max-width:1480px;padding:0px 20px;margin:0 auto;position:relative}.push,.footer-wrapper{height:112px}.row{background-color:transparent;border-bottom:1px dotted #CCC}.row:last-child{border-bottom:none}@media only screen and (min-width: 768px){.equal-height{display:flex;flex-wrap:wrap;flex-direction:row;width:100%}.equal-height>.equal-height__item{box-sizing:border-box;display:flex;flex:auto;flex-direction:column}}header.banner{overflow:visible;z-index:20;position:fixed;top:0}header.banner .logo{padding-left:15px}header.banner .logo a{border-bottom:0}header.banner nav.nav-primary{border-bottom:none}header.banner .nav-primary.nav-right .logo-ubuntu{-moz-background-size:100px 30px;-webkit-background-size:100px 30px;-o-background-size:100px 30px;background-size:100px 30px;background-position:5px 9px;background-image:url("../img/logos/logo.png");background-image:url("../img/logos/logo.svg"),none}header.banner .nav-primary li:hover ul:after{display:none}@media screen and (max-width: 768px){header.banner .nav-primary ul{border-right:none}}header.banner #right-nav{float:right;margin-right:20px}@media screen and (max-width: 768px){header.banner #right-nav{margin-right:0}}header.banner #user-link{position:relative}@media screen and (max-width: 768px){header.banner #user-link{border-top:1px solid #d4d7d4;width:100%}}header.banner #user-link>a{padding-bottom:12px}@media screen and (max-width: 768px){header.banner #user-link>a{display:none}}header.banner #user-link .normal,header.banner #user-link .hover{margin-right:7px}@media screen and (max-width: 768px){header.banner #user-link .normal,header.banner #user-link .hover{display:none}}header.banner #user-link .hover{display:none}header.banner #user-link .nav{-webkit-border-radius:0px 0px 4px 4px;-moz-border-radius:0px 0px 4px 4px;border-radius:0px 0px 4px 4px;background-color:#FFF;border:none;display:none;position:absolute;right:0;top:48px;box-shadow:0px 2px 4px rgba(0,0,0,0.15)}@media screen and (min-width: 769px){header.banner #user-link .nav a.active{background:none;border:none}}header.banner #user-link .nav a:hover{background-color:transparent}@media screen and (max-width: 768px){header.banner #user-link .nav a:hover{background-color:#F8F8F8;color:#333}}@media screen and (max-width: 768px){header.banner #user-link .nav{background:none;position:relative;top:inherit;width:100%;box-shadow:none;float:left;padding:0}header.banner #user-link .nav li{border-bottom:1px solid #d4d7d4;width:100%;float:left}header.banner #user-link .nav li a{padding:10px 14px;width:100%}}@media screen and (max-width: 768px){header.banner #user-link:hover>a{background-color:transparent}}header.banner #user-link:hover .normal{display:none}header.banner #user-link:hover .hover{display:inline-block}@media screen and (max-width: 768px){header.banner #user-link:hover .hover{display:none}}header.banner #user-link:hover .nav{display:block}header.banner .nav-toggle{background-image:url("../img/icons/navigation-menu-plain.png");background-image:url("../img/icons/navigation-menu-plain.svg"),none;top:0}@media screen and (max-width: 768px){header.banner .nav-toggles .open{display:block}header.banner .nav-toggles .close{display:none}header.banner #canonlist:target ul{display:block}header.banner #canonlist:target+.nav-toggles .open{display:none}header.banner #canonlist:target+.nav-toggles .close{display:block}}.page-header{-webkit-box-sizing:border-box;-moz-box-sizing:border-box;box-sizing:border-box;background:#FFF;box-shadow:0 1px 1px rgba(0,0,0,0.1);width:100%;float:left;position:fixed;z-index:10;top:48px;min-height:104px}.page-header .page-header__nav{position:absolute;top:10px;z-index:1000}.page-header .page-header__nav a{font-weight:300}.page-header .page-header__title{font-size:2em;width:auto;padding:31px 0;margin:0;float:left;font-size:32px}.page-header .page-header__title [contenteditable="true"]{padding:8px 10px;width:auto;box-sizing:border-box;border:1px solid transparent;margin:-10px 0 -10px -10px;border-radius:2px;color:#333;cursor:default;font-size:32px;display:inline-block}.page-header .page-header__title [contenteditable="true"].editmode,.page-header .page-header__title [contenteditable="true"].editable:hover{border:1px solid #D2D2D2;cursor:text}.page-header .page-header__title [contenteditable="true"].editmode:hover,.page-header .page-header__title [contenteditable="true"]:active,.page-header .page-header__title [contenteditable="true"]:focus{outline:none;background-color:#FFF;border:1px solid #B2B2B2}.page-header .page-header__title [contenteditable="true"].invalid,.page-header .page-header__title [contenteditable="true"].invalid:hover,.page-header .page-header__title [contenteditable="true"].invalid:active,.page-header .page-header__title [contenteditable="true"].invalid:focus{border-color:#d90000}.page-header .page-header__title [contenteditable="true"] br{display:none}.page-header .page-header__title .page-header__title-dot{display:inline-block;width:auto;padding:0}.page-header .page-header__title .page-header__title-domain{display:inline-block;width:auto;max-height:59px;line-height:25px;min-height:59px;background-position:top 27px right 10px;margin:-9px 0;font-size:32px}.page-header .page-header__title .icon{vertical-align:3px;margin-right:10px}.page-header .page-header__title .page-header__title--identicator{font-size:0.6em;width:auto;display:inline-block;position:relative;top:0px;padding-left:20px;margin-left:10px}.page-header .page-header__title .page-header__title--identicator a{color:#888;border-bottom:0}.page-header .page-header__title .page-header__title--identicator a:hover{text-decoration:none;border-bottom:3px solid #888}.page-header .page-header__title .page-header__title--identicator a:focus,.page-header .page-header__title .page-header__title--identicator a:active{text-decoration:none}.page-header .page-header__title .page-header__title--identicator a.active{color:#333;border-bottom:3px solid #dd4814}.page-header .page-header__title .page-header__title--identicator a.active:hover{text-decoration:none;cursor:default}.page-header .page-header__title .page-header__title--identicator .divide{width:1px;display:inline-block;background:#D2D2D2;height:11px;padding:0;margin:0 5px}.page-header .page-header__title .page-header__title--identicator .page-header__title-loadmore{font-size:14px;margin-left:10px}.page-header .page-header__title .page-header__title--identicator .page-header__title-loadmore:hover{border:0;text-decoration:underline}.page-header .page-header__title .link-cta-ubuntu,.page-header .page-header__title .alt{font-size:16px;margin-left:20px;position:relative;vertical-align:middle;margin-top:-5px}.page-header .page-header__actions{float:right;padding:34px 0;margin-bottom:0}.page-header .page-header__actions .page-header__cta{float:right;position:relative;height:auto;max-height:36px}.page-header .page-header__actions .page-header__cta .cta-group{float:right}.page-header .page-header__actions .page-header__cta .page-header__cta-feedback{display:inline-block;position:relative;line-height:36px;text-align:right;color:#dd4814;margin-right:20px;cursor:pointer}.page-header .page-header__actions .page-header__cta .page-header__cta-feedback:hover{text-decoration:underline}.page-header .page-header__dropdown{float:left;width:100%;max-height:1000px;transition:max-height 0.3s ease-in;overflow:hidden;border-color:#888 !important}.page-header .page-header__dropdown.ng-hide{display:block !important;max-height:0;overflow:hidden;transition:max-height 0.3s ease-out;border-top:none}.page-header .page-header__dropdown .page-header__feedback{border-top:1px dotted #888;display:block;float:left;width:100%;padding:20px 0}.page-header .page-header__dropdown .page-header__feedback .page-header__feedback-message{-webkit-box-sizing:border-box;-moz-box-sizing:border-box;box-sizing:border-box;margin:0;background-position:top 3px left 0px;background-repeat:no-repeat;padding:6px 0 5px 25px;width:auto;display:inline-block;position:relative}.page-header .page-header__dropdown .page-header__feedback .page-header__feedback-message.info,.page-header .page-header__dropdown .page-header__feedback .page-header__feedback-message.error{background-image:url("../img/icons/error.png");background-image:url("../img/icons/error.svg"),none;background-position:0px 9px}.page-header .page-header__dropdown .page-header__feedback .page-header__feedback-message.info.progress,.page-header .page-header__dropdown .page-header__feedback .page-header__feedback-message.error.progress{width:100%}.page-header .page-header__dropdown .page-header__feedback .page-header__feedback-message.warning{background-image:url("../img/icons/warning.png");background-image:url("../img/icons/warning.svg"),none;background-position:0px 9px}.page-header .page-header__dropdown .page-header__feedback .page-header__feedback-message.progress{padding-left:0}.page-header .page-header__dropdown .page-header__feedback .page-header__feedback-message.progress .loader{position:relative;top:1px}.title .title__indicator .title__link{color:#888;font-size:20px}.title .title__indicator .title__link:hover{color:#333;text-decoration:none;border-bottom:1px solid #333}.title .title__indicator .title__link:focus,.title .title__indicator .title__link:active{text-decoration:none}.title .title__indicator .title__link.active{color:#333;border-bottom:1px #dd4814 solid}.title .title__indicator .divide{width:1px;display:inline-block;background:#D2D2D2;height:11px;padding:0;margin:0 5px}.accounts .logout .divide{padding:0 20px 0 30px;display:inline-block}.accounts .api li{position:relative}.accounts .api li input[type='text']{line-height:30px;padding-right:30px;width:100%}.accounts .api li input[type='text']::-webkit-input-placeholder{color:#333}.accounts .api li input[type='text']:-moz-placeholder{color:#333}.accounts .api li input[type='text']::-moz-placeholder{color:#333}.accounts .api li input[type='text']:-ms-input-placeholder{color:#333}.accounts .api li .delete-link{position:absolute;top:7px;right:7px}form.page-title-form{margin-bottom:30px}form.page-title-form input{-webkit-border-radius:5px;-moz-border-radius:5px;border-radius:5px;border:1px solid transparent;background-color:transparent;font-size:36px;line-height:26px;color:#333;margin:6px 10px;padding:4px;height:auto;box-shadow:none}form.page-title-form input:hover{outline:none;background:#FFF;border-color:#D2D2D2;box-shadow:inset 0 1px 1px rgba(0,0,0,0.1)}form.page-title-form input:focus{border:1px solid #dd4814;background-color:#fff;outline:none}.small-icon{width:12px}.images-info{text-align:center;padding:10px}.images-warning{-webkit-box-sizing:border-box;-moz-box-sizing:border-box;box-sizing:border-box;-webkit-border-radius:2px;-moz-border-radius:2px;border-radius:2px;list-style:none;padding:15px 20px 15px 45px;margin:0;font-weight:400;font-size:0.875em;background:#FFF;background-position:top 50% left 15px;background-repeat:no-repeat;margin:0 0 10px;box-shadow:0 1px 1px rgba(0,0,0,0.1);border:1px solid #EEE;background-image:url("../img/icons/warning.png");background-image:url("../img/icons/warning.svg"),none}#loader{width:10px;margin:16px auto 0 auto}#importing{-webkit-box-sizing:border-box;-moz-box-sizing:border-box;box-sizing:border-box;-webkit-border-radius:2px;-moz-border-radius:2px;border-radius:2px;list-style:none;padding:15px 20px 15px 45px;margin:0;font-weight:400;font-size:0.875em;background:#FFF;background-position:top 50% left 15px;background-repeat:no-repeat;margin:0 0 10px;box-shadow:0 1px 1px rgba(0,0,0,0.1);border:1px solid #EEE;position:relative}#importing .spinner{position:absolute;left:15px}.importing-dot{opacity:0;-webkit-animation:dot 1.3s infinite;animation:dot 1.3s infinite}.selector{-webkit-box-sizing:border-box;-moz-box-sizing:border-box;box-sizing:border-box;-webkit-border-radius:2px;-moz-border-radius:2px;border-radius:2px;padding:20px;background:#FFF;margin:0 0 20px;box-shadow:0 1px 1px rgba(0,0,0,0.1);border:1px solid #EEE;width:100%;float:left}.selector h2{font-size:1em;font-weight:300}.selector h2 img{-webkit-box-sizing:border-box;-moz-box-sizing:border-box;box-sizing:border-box;display:inline-block;background:url("../img/icons/help.svg") no-repeat;width:16px;height:16px;padding-left:16px;margin-left:5px}.selector .selector-available,.selector .selector-chosen{width:46%;float:left;margin:0;text-align:left}@media screen and (max-width: 768px){.selector .selector-available,.selector .selector-chosen{width:100%}}.selector .selector-available h2,.selector .selector-chosen h2{background:none;border:none}.selector .selector-available select,.selector .selector-chosen select{margin-bottom:10px}.selector ul.selector-chooser{width:8%;float:left;margin:20% 0 0}@media screen and (max-width: 768px){.selector ul.selector-chooser{width:100%;margin:0 0 10px;text-align:center}}.selector ul.selector-chooser li{width:100%;text-align:center}@media screen and (max-width: 768px){.selector ul.selector-chooser li{width:auto;display:inline-block;height:16px;width:16px;margin:0 20px}}.selector ul.selector-chooser li a{display:block;text-indent:999em;width:16px;height:16px;overflow:hidden;margin:0 auto}.selector ul.selector-chooser li a.selector-add{background-image:url("../img/icons/chevron_right.svg")}@media screen and (max-width: 768px){.selector ul.selector-chooser li a.selector-add{background-image:url("../img/icons/chevron_down.svg")}}.selector ul.selector-chooser li a.selector-remove{background-image:url("../img/icons/chevron_left.svg")}@media screen and (max-width: 768px){.selector ul.selector-chooser li a.selector-remove{background-image:url("../img/icons/chevron_up.svg")}}.selector .selector-filter img{display:none}.selector .selector-filter input{background-image:url("../img/search-orange.png");background-repeat:no-repeat;background-position:top 7px right 8px}.selector select#id_mac_addresses_to.filtered{height:269px !important}.nodes{position:relative}.nodes .search{position:absolute;right:0}.nodes .search input[type='submit']{position:absolute;top:8px;right:12px;background-color:transparent;background-image:url("../img/search-icon.svg");background-repeat:no-repeat;text-indent:-999em;display:block;width:21px;height:20px;overflow:hidden;outline:none;padding:0}.nodes .search input[type='submit']:hover{color:transparent;background-color:transparent;background-image:url("../img/search-icon.svg");background-repeat:no-repeat}@media screen and (max-width: 768px){.nodes .search{position:relative}}@media screen and (max-width: 768px){.nodes .actions select{width:100%;margin-bottom:20px}.nodes .actions input{position:absolute;right:0;top:0}}.powerstates{width:14px;height:15px;display:inline-block}.powerstates.power-on{background:transparent url("../img/icons/power-on.svg") left top no-repeat}.powerstates.power-off{background:transparent url("../img/icons/power-off.svg") left top no-repeat}.powerstates.power-unknown{background:none}.powerstates.power-error{background:transparent url("../img/icons/power-error.svg") left top no-repeat}.powerstates.power-check-ok{-webkit-border-radius:50%;-moz-border-radius:50%;border-radius:50%;width:10px;height:10px;color:#33CC00}.powerstates.power-check-error{-webkit-border-radius:50%;-moz-border-radius:50%;border-radius:50%;width:10px;height:10px;color:#FF0000}.node-actions .link-cta-ubuntu,.node-actions .cta-ubuntu{margin-bottom:10px;float:left;font-size:16px}.buttons{margin-top:30px}#network-interfaces li{list-style-type:none}#content-discovery-data{padding-top:20px;margin-top:20px;border-top:1px dotted #B2B2B2}#content-discovery-data .slider{height:0;overflow:hidden}.slider{padding-top:0 !important}.slider .content{-webkit-border-radius:0 0 4px 4px;-moz-border-radius:0 0 4px 4px;border-radius:0 0 4px 4px;box-shadow:0 1px 1px rgba(0,0,0,0.1);background:#FFF;border:1px solid #EEE;border-top:none;padding:20px}.slider .content pre{margin:0}.slider .tabs{-webkit-box-sizing:border-box;-moz-box-sizing:border-box;box-sizing:border-box;-webkit-border-radius:4px 4px 0 0;-moz-border-radius:4px 4px 0 0;border-radius:4px 4px 0 0;padding:8px 20px;margin:0;font-weight:400;font-size:0.875em;background:#FFF;background-position:top 50% left 15px;background-repeat:no-repeat;border:1px solid #EEE}.add-machine__list{padding:13px 0 20px;border-top:1px dotted #888;margin-bottom:0}.add-machine__list .add-machine__details{background:transparent url("../img/icons/accordion-open.svg") top 12px right 10px no-repeat}.add-machine__list .add-machine__details>div{margin-bottom:0}.add-machine__list .add-machine__details .add-machine__details-form{display:none}.add-machine__list .add-machine__details.active{background-image:url("../img/icons/accordion-close.svg")}.add-machine__list .add-machine__details.active .add-machine__details-form{display:block}.power-status{display:inline-block;font-size:100%;padding-left:0}.power-status--power{display:inline-block;margin-left:20px;position:relative;font-size:100%}.power-status--power.checking{color:#2AB7EC;background:url("../img/status_in_progress.svg");padding-left:20px}.power-status--power.on{padding-left:20px;color:#38B44A;background:transparent url("../img/icons/power-on.svg") left top 4px no-repeat}.power-status--power.off{padding-left:20px;color:#D2D2D2;background:transparent url("../img/icons/power-off.svg") left top 4px no-repeat}.power-status--power.error{padding-left:20px;color:#DB3832;background:transparent url("../img/icons/power-error.svg") left top 4px no-repeat}.power-status--power .power-check{font-size:0.75em;color:#D2D2D2;display:inline-block;padding:0}.power-status--power .power-check .power-check__link{color:#888;text-decoration:none;margin-left:5px}.power-status--power .power-check .power-check__link:hover{border-bottom:0 !important;text-decoration:underline !important}.loading,.loader{background:url("../img/in_progress.png") no-repeat;background-size:16px 16px;width:16px;height:16px;-webkit-animation:spin 1s infinite linear;-moz-animation:spin 1s infinite linear;animation:spin 1s infinite linear;padding:0;display:inline-block}.details__used{color:#BCBCBC}.details .details__label{clear:both;display:block;margin-top:11px;color:#BCBCBC}.details .details__label a{color:#BCBCBC}.details .details__label a:hover{color:#dd4814}.details .details__label.active a{color:#dd4814}.details .details__controls{width:100%;text-align:right;opacity:0;z-index:-1000}.details .details__controls--secondary{opacity:0;z-index:-1000;width:auto;text-align:left}.details .table-row .details__input{display:inline-block;margin:-7px 0 -8px -14px;background-color:transparent;border-color:transparent;background-position:-9999px -9999px}.details .table-row .details__text{line-height:37px}.details .table-row:hover .details__input{background-color:#FFF;border-color:#D2D2D2;background-position:right 10px top 16px}.details .table-row:hover .details__controls{z-index:1;opacity:1}.details .table-row:hover .details__controls--secondary{z-index:1;opacity:1}.details .table-row.active:hover .details__input{background-color:transparent;border-color:transparent;pointer-events:none;background-position:-9999px -9999px}.details .table-row.active:hover .details__controls{opacity:0;z-index:-1000;pointer-events:none}.details .table-row.active:hover .details__controls--secondary{z-index:-1000;opacity:0;pointer-events:none}.details .table-row label{font-size:13px;color:#BCBCBC}.details .table-row input,.details .table-row select{margin:0 0 0 -14px}.details .table-row input[type="radio"]{margin-left:0}.details .details__dropdown .details__row{border-bottom:0;position:relative}.details .details__dropdown .details__row:before{display:block;margin:0 auto;width:calc(100% - 20px);border-top:1px dotted #B2B2B2;position:relative;height:1px;content:''}.details .details__dropdown .details__row.details__row--head{border-bottom:0}.details .details__dropdown .details__row.details__row--head .table-cell{color:#BCBCBC;font-size:13px}.details .details__dropdown .details__row.no-border{border:0}.details .details__dropdown .details__row.no-border:before{border:0}.details .details__dropdown--info .table-row{border-bottom:0}.details .details__dropdown--info .table-cell{color:#BCBCBC}
1889
1890=== modified file 'src/maasserver/static/js/angular/controllers/node_details_networking.js'
1891=== modified file 'src/maasserver/static/js/angular/controllers/node_details_storage.js'
1892=== modified file 'src/maasserver/static/js/angular/controllers/node_events.js'
1893=== modified file 'src/maasserver/static/js/angular/controllers/tests/test_node_details_networking.js'
1894=== modified file 'src/maasserver/static/js/angular/controllers/tests/test_node_details_storage.js'
1895--- src/maasserver/static/js/angular/controllers/tests/test_node_details_storage.js 2016-10-12 16:34:23 +0000
1896+++ src/maasserver/static/js/angular/controllers/tests/test_node_details_storage.js 2016-10-26 23:39:26 +0000
1897@@ -2810,6 +2810,7 @@
1898 });
1899 });
1900
1901+<<<<<<< TREE
1902 describe("getCannotCreateBcacheMsg", function() {
1903
1904 it("returns msg if no cachesets",
1905@@ -3075,6 +3076,94 @@
1906
1907 });
1908
1909+=======
1910+ describe("getCannotCreateBcacheMsg", function() {
1911+
1912+ it("returns msg if no cachesets",
1913+ function() {
1914+ var controller = makeController();
1915+ $scope.available = [
1916+ {
1917+ fstype: null,
1918+ $selected: true,
1919+ has_partitions: false
1920+ }
1921+ ];
1922+ $scope.cachesets = [];
1923+ expect($scope.getCannotCreateBcacheMsg()).toBe(
1924+ "Create at least one cache set to create bcache");
1925+ });
1926+
1927+ it("returns msg if two selected", function() {
1928+ var controller = makeController();
1929+ $scope.cachesets = [{}];
1930+ $scope.available = [ { $selected: true }, { $selected: true }];
1931+ expect($scope.getCannotCreateBcacheMsg()).toBe(
1932+ "Select only one available device to create bcache");
1933+ });
1934+
1935+ it("returns msg if selected has fstype", function() {
1936+ var controller = makeController();
1937+ $scope.available = [
1938+ {
1939+ fstype: "ext4",
1940+ $selected: true,
1941+ has_partitions: false
1942+ }
1943+ ];
1944+ $scope.cachesets = [{}];
1945+
1946+ expect($scope.getCannotCreateBcacheMsg()).toBe(
1947+ "Device is formatted; unformat the device to create bcache");
1948+ });
1949+
1950+ it("returns msg if selected is volume group", function() {
1951+ var controller = makeController();
1952+ $scope.available = [
1953+ {
1954+ type: "lvm-vg",
1955+ fstype: null,
1956+ $selected: true,
1957+ has_partitions: false
1958+ }
1959+ ];
1960+ $scope.cachesets = [{}];
1961+ expect($scope.getCannotCreateBcacheMsg()).toBe(
1962+ "Cannot use a logical volume as a backing device for bcache.");
1963+ });
1964+
1965+ it("returns msg if selected has partitions", function() {
1966+ var controller = makeController();
1967+ $scope.available = [
1968+ {
1969+ fstype: null,
1970+ $selected: true,
1971+ has_partitions: true
1972+ }
1973+ ];
1974+ $scope.cachesets = [{}];
1975+ expect($scope.getCannotCreateBcacheMsg()).toBe(
1976+ "Device has already been partitioned; create a " +
1977+ "new partition to use as the bcache backing " +
1978+ "device");
1979+ });
1980+
1981+ it("returns null if selected is valid",
1982+ function() {
1983+ var controller = makeController();
1984+ $scope.available = [
1985+ {
1986+ fstype: null,
1987+ $selected: true,
1988+ has_partitions: false
1989+ }
1990+ ];
1991+ $scope.cachesets = [{}];
1992+ expect($scope.getCannotCreateBcacheMsg()).toBeNull();
1993+ });
1994+ });
1995+
1996+>>>>>>> MERGE-SOURCE
1997 describe("canCreateBcache", function() {
1998
1999 it("returns false when isAvailableDisabled is true", function() {
2000
2001=== modified file 'src/maasserver/static/js/angular/controllers/tests/test_node_events.js'
2002=== modified file 'src/maasserver/static/js/angular/directives/controller_image_status.js'
2003=== modified file 'src/maasserver/static/partials/node-details.html'
2004--- src/maasserver/static/partials/node-details.html 2016-10-26 13:53:50 +0000
2005+++ src/maasserver/static/partials/node-details.html 2016-10-26 23:39:26 +0000
2006@@ -1941,9 +1941,15 @@
2007 data-ng-disabled="!canCreateCacheSet()"
2008 data-ng-hide="isAllStorageDisabled() || !isSuperUser()"
2009 data-ng-click="createCacheSet()">Create cache Set</a>
2010+<<<<<<< TREE
2011 <a class="button--secondary button--inline tooltip"
2012 aria-label="{$ getCannotCreateBcacheMsg() $}"
2013 data-ng-class="{ tooltip: !canCreateBcache() }"
2014+=======
2015+ <a class="link-cta-ubuntu secondary margin-right"
2016+ data-tooltip="{$ getCannotCreateBcacheMsg() $}"
2017+ data-ng-class="{ tooltip: !canCreateBcache() }"
2018+>>>>>>> MERGE-SOURCE
2019 data-ng-disabled="!canCreateBcache()"
2020 data-ng-hide="isAllStorageDisabled() || !isSuperUser()"
2021 data-ng-click="createBcache()">Create bcache</a>
2022
2023=== modified file 'src/maasserver/static/partials/node-events.html'
2024--- src/maasserver/static/partials/node-events.html 2016-08-25 20:45:36 +0000
2025+++ src/maasserver/static/partials/node-events.html 2016-10-26 23:39:26 +0000
2026@@ -6,6 +6,7 @@
2027 </header>
2028 </div>
2029 <div class="ng-hide" data-ng-show="loaded">
2030+<<<<<<< TREE
2031 <header class="page-header u-margin--bottom">
2032 <div class="wrapper--inner">
2033 <h1 class="page-header__title">{$ node.fqdn $}</h1>
2034@@ -15,6 +16,19 @@
2035 </p>
2036 <div class="page-header__controls u-float--right">
2037 <a class="button--base button--inline" href="#/node/{$ node.system_id $}">&lsaquo;&nbsp;Back to machine details</a>
2038+=======
2039+ <header class="page-header margin-bottom">
2040+ <div class="inner-wrapper">
2041+ <h1 class="page-header__title eight-col">
2042+ {$ node.fqdn $}
2043+ <span class="page-header__title--identicator">
2044+ {$ events.length $} machine events in the past {$ days $} day(s)
2045+ <a href="" class="page-header__title-loadmore" data-ng-click="loadMore()">load 1 more day</a>
2046+ </span>
2047+ </h1>
2048+ <div class="page-header__actions four-col last-col">
2049+ <a class="right link-cta-ubuntu text-button" href="#/node/{$ node.system_id $}">&lsaquo;&nbsp;Back to machine details</a>
2050+>>>>>>> MERGE-SOURCE
2051 </div>
2052 </div>
2053 </header>
2054
2055=== modified file 'src/maasserver/static/partials/nodes-list.html'
2056--- src/maasserver/static/partials/nodes-list.html 2016-10-26 13:53:50 +0000
2057+++ src/maasserver/static/partials/nodes-list.html 2016-10-26 23:39:26 +0000
2058@@ -1,3 +1,4 @@
2059+<<<<<<< TREE
2060 <header class="page-header">
2061 <div class="wrapper--inner">
2062 <!-- XXX ricgard 2016-06-16 - Need to add e2e test. -->
2063@@ -237,6 +238,178 @@
2064 </div>
2065 <div class="form__group">
2066 <label for="zone" class="form__group-label two-col">Zone</label>
2067+=======
2068+<header class="page-header margin-bottom" data-maas-sticky-header>
2069+ <div class="inner-wrapper">
2070+ <h1 class="page-header__title eight-col">{$ $parent.site $} MAAS
2071+ <span class="page-header__title--identicator" id="bulk-actions">
2072+ <!-- XXX blake_r 2015-02-19 - Need to add e2e test. -->
2073+ <a href=""
2074+ class="tooltip"
2075+ data-tooltip="A deployable node managed by MAAS."
2076+ data-ng-class="{ active: currentpage === 'nodes' }"
2077+ data-ng-click="toggleTab('nodes')">{$ nodes.length $} <ng-pluralize count="nodes.length" when="{'one': 'Machine', 'other': 'Machines'}"></ng-pluralize></a>
2078+ <span class="divide"></span>
2079+ <a href=""
2080+ class="tooltip"
2081+ data-tooltip="A node known to MAAS, but is not deployable."
2082+ data-ng-class="{ active: currentpage === 'devices' }"
2083+ data-ng-click="toggleTab('devices')">{$ devices.length $} <ng-pluralize count="devices.length" when="{'one': 'Device', 'other': 'Devices'}"></ng-pluralize>
2084+ </a>
2085+ <span data-ng-show="isSuperUser()" class="divide"></span>
2086+ <a href="" data-ng-show="isSuperUser()"
2087+ class="tooltip"
2088+ data-tooltip="A node that provides MAAS services."
2089+ data-ng-class="{ active: currentpage === 'controllers' }"
2090+ data-ng-click="toggleTab('controllers')">{$ controllers.length $} <ng-pluralize count="controllers.length" when="{'one': 'Controller', 'other': 'Controllers'}"></ng-pluralize>
2091+ </a>
2092+ <span class="power-status--power" data-ng-show="loading">
2093+ <span class="loader"></span>
2094+ Loading...
2095+ </span>
2096+ </span>
2097+ </h1>
2098+ <!-- XXX blake_r 2015-02-19 - Need to add e2e test. -->
2099+ <div data-ng-show="currentpage === 'nodes'">
2100+ <div class="page-header__actions">
2101+ <div class="page-header__cta" data-ng-hide="tabs.nodes.selectedItems.length">
2102+ <div class="right" data-maas-cta="addHardwareOptions"
2103+ data-ng-model="addHardwareOption"
2104+ data-ng-change="addHardwareOptionChanged()" data-default-title="Add hardware">
2105+ </div>
2106+ </div>
2107+ <div class="page-header__cta ng-hide" data-ng-show="tabs.nodes.selectedItems.length">
2108+ <span class="page-header__cta-feedback" data-ng-click="showSelected('nodes')">
2109+ {$ tabs.nodes.selectedItems.length $} Selected
2110+ </span>
2111+ <div data-maas-cta="tabs.nodes.takeActionOptions"
2112+ data-ng-model="tabs.nodes.actionOption"
2113+ data-ng-change="actionOptionSelected('nodes')">
2114+ </div>
2115+ </div>
2116+ </div>
2117+ <!-- XXX blake_r 2015-02-19 - Need to add e2e test. -->
2118+ <div class="page-header__dropdown ng-hide" data-ng-show="tabs.nodes.actionOption">
2119+ <!-- XXX blake_r 2015-02-19 - Need to add e2e test. -->
2120+ <div class="page-header__feedback ng-hide" data-ng-hide="isActionError('nodes') || hasActionsInProgress('nodes')">
2121+ <form class="form form--inline">
2122+ <div class="nine-col no-margin-bottom ng-hide" data-ng-show="tabs.nodes.actionOption.name === 'commission'">
2123+ <div class="form__group u-margin--right u-margin--top-tiny">
2124+ <input class="checkbox margin-right" id="enableSSH" type="checkbox"
2125+ data-ng-model="tabs.nodes.commissionOptions.enableSSH">
2126+ <label class="checkbox-label" for="enableSSH">Allow SSH access and prevent machine from powering off</label>
2127+ </div>
2128+ <div class="form__group u-margin--right u-margin--top-tiny">
2129+ <input class="checkbox margin-right" id="skipNetworking" type="checkbox"
2130+ data-ng-model="tabs.nodes.commissionOptions.skipNetworking">
2131+ <label class="checkbox-label" for="skipNetworking">Retain network configuration</label>
2132+ </div>
2133+ <div class="form__group u-margin--top-tiny">
2134+ <input class="checkbox" id="skipStorage" type="checkbox"
2135+ data-ng-model="tabs.nodes.commissionOptions.skipStorage">
2136+ <label class="checkbox-label" for="skipStorage">Retain storage configuration</label>
2137+ </div>
2138+ </div>
2139+ <span class="form__group ng-hide" data-ng-show="tabs.nodes.actionOption.name === 'deploy'">
2140+ <label for="image" class="u-margin--right">Choose your image</label>
2141+ <span data-maas-os-select="osinfo" data-ng-model="tabs.nodes.osSelection"></span>
2142+ </span>
2143+ <!-- XXX rbanffy 2015-03-23 - Need to add e2e test. -->
2144+ <span class="form__group ng-hide" data-ng-show="tabs.nodes.actionOption.name === 'set-zone'">
2145+ <label for="zone" class="u-margin--right">Select Zone</label>
2146+ <select name="zone" id="zone" placeholder="Choose a zone"
2147+ data-ng-model="tabs.nodes.zoneSelection"
2148+ data-ng-options="zone as zone.name for zone in zones">
2149+ <option value="" disabled="disabled">Choose a zone</option>
2150+ </select>
2151+ </span>
2152+ <div class="right">
2153+ <a href="" class="link-cta-ubuntu text-button" data-ng-click="actionCancel('nodes')">Cancel</a>
2154+ <button class="cta-ubuntu" data-ng-click="actionGo('nodes')" data-ng-hide="hasActionsFailed('nodes')">Go</button>
2155+ <button class="cta-ubuntu" data-ng-click="actionGo('nodes')" data-ng-show="hasActionsFailed('nodes')">Retry</button>
2156+ </div>
2157+ </form>
2158+ </div>
2159+
2160+ <!-- XXX blake_r 2015-02-19 - Need to add e2e test. -->
2161+ <div class="page-header__feedback ng-hide" data-ng-show="isActionError('nodes')">
2162+ <!-- XXX blake_r 2015-02-19 - Need to add e2e test. -->
2163+ <p class="page-header__feedback-message info" data-ng-hide="isDeployError('nodes') || isSSHKeyError('nodes')">
2164+ {$ tabs.nodes.actionErrorCount $}
2165+ <span data-ng-pluralize count="tabs.nodes.selectedItems.length" when="{'one': 'node', 'other': 'nodes'}"></span>
2166+ cannot be {$ tabs.nodes.actionOption.sentence $}. To proceed, update your selection.
2167+ </p>
2168+ <!-- XXX blake_r 2015-02-19 - Need to add e2e test. -->
2169+ <p class="page-header__feedback-message info ng-hide" data-ng-show="isDeployError('nodes')">
2170+ {$ tabs.nodes.selectedItems.length $}
2171+ <span data-ng-pluralize count="tabs.nodes.selectedItems.length" when="{'one': 'node', 'other': 'nodes'}"></span>
2172+ cannot be {$ tabs.nodes.actionOption.sentence $}, because the required boot images have not been imported. To import boot images, visit the <a href="images/">images page</a>.
2173+ </p>
2174+ <p class="page-header__feedback-message info ng-hide" data-ng-show="isSSHKeyError('nodes')">
2175+ {$ tabs.nodes.selectedItems.length $}
2176+ <span data-ng-pluralize count="tabs.nodes.selectedItems.length" when="{'one': 'node', 'other': 'nodes'}"></span>
2177+ cannot be {$ tabs.nodes.actionOption.sentence $}, because an SSH key has not been added to your account. To add an SSH key, visit <a href="account/prefs/">your account page</a>.
2178+ </p>
2179+ </div>
2180+
2181+ <!-- XXX blake_r 2015-05-07 - Need to add e2e test. -->
2182+ <div class="page-header__feedback ng-hide" data-ng-show="hasActionsInProgress('nodes') || hasActionsFailed('nodes')">
2183+ <p class="page-header__feedback-message progress" data-ng-show="hasActionsInProgress('nodes')">
2184+ <span class="loader"></span>
2185+ {$ tabs.nodes.actionProgress.completed $} of {$ tabs.nodes.actionProgress.total $}
2186+ nodes have been {$ tabs.nodes.actionOption.sentence $}.
2187+ </p>
2188+ <p class="page-header__feedback-message error"
2189+ data-ng-repeat="(error, nodes) in tabs.nodes.actionProgress.errors">
2190+ The {$ tabs.nodes.actionOption.title.toLowerCase() $} action for {$ nodes.length $}
2191+ <span data-ng-pluralize count="nodes.length" when="{'one': 'node', 'other': 'nodes'}"></span>
2192+ failed with error: {$ error $}
2193+ </p>
2194+ </div>
2195+ </div>
2196+ <div class="page-header__dropdown border ng-hide" data-ng-show="addHardwareScope.viewable" data-ng-controller="AddHardwareController">
2197+ <h3 class="u-padding--top" data-ng-if="showMachine()">Add machine</h3>
2198+ <h3 class="u-padding--top" data-ng-if="showChassis()">Add chassis</h3>
2199+ <div class="page-header__feedback" data-ng-hide="architectures.length">
2200+ <!-- XXX blake_r 2015-02-24 - Need to add e2e test. -->
2201+ <p class="page-header__feedback-message info">
2202+ Cannot add {$ mode $} until boot images have been imported. To fix, visit the <a href="images">images page</a>.
2203+ </p>
2204+ </div>
2205+ <form class="twelve-col ng-hide" data-ng-show="showMachine()">
2206+ <fieldset class="six-col no-padding no-margin-bottom">
2207+ <div class="inline">
2208+ <label for="machine-name" class="two-col">Machine name</label>
2209+ <input type="text" id="machine-name" class="three-col" placeholder="Choose a machine name (optional)"
2210+ data-ng-model="machine.name">
2211+ </div>
2212+ <div class="inline">
2213+ <label for="domain" class="two-col">Domain</label>
2214+ <select name="domain" id="domain" class="three-col"
2215+ data-ng-model="machine.domain"
2216+ data-ng-options="domain as domain.name for domain in domains">
2217+ <option value="" disabled>Choose a domain</option>
2218+ </select>
2219+ </div>
2220+ <div class="inline">
2221+ <label for="architecture" class="two-col">Architecture</label>
2222+ <select name="architecture" id="architecture" class="three-col"
2223+ data-ng-model="machine.architecture"
2224+ data-ng-options="arch for arch in architectures">
2225+ <option value="" disabled>Choose an architecture</option>
2226+ </select>
2227+ </div>
2228+ <div class="inline">
2229+ <label for="min_hwe_kernel" class="two-col">Minimum Kernel</label>
2230+ <select name="min_hwe_kernel" id="min_hwe_kernel" class="three-col" placeholder="No minimum kernel"
2231+ data-ng-model="machine.min_hwe_kernel"
2232+ data-ng-options="hwe_kernel[0] as hwe_kernel[1] for hwe_kernel in hwe_kernels">
2233+ <option value="">No minimum kernel</option>
2234+ </select>
2235+ </div>
2236+ <div class="inline">
2237+ <label for="zone" class="two-col">Zone</label>
2238+>>>>>>> MERGE-SOURCE
2239 <select name="zone" id="zone" class="three-col" placeholder="Choose a zone"
2240 data-ng-model="machine.zone"
2241 data-ng-options="zone as zone.name for zone in zones">
2242
2243=== added directory 'src/maasserver/static/scss/maas'
2244=== added directory 'src/maasserver/static/scss/maas/components'
2245=== added file 'src/maasserver/static/scss/maas/components/_accordion.scss.OTHER'
2246--- src/maasserver/static/scss/maas/components/_accordion.scss.OTHER 1970-01-01 00:00:00 +0000
2247+++ src/maasserver/static/scss/maas/components/_accordion.scss.OTHER 2016-10-26 23:39:26 +0000
2248@@ -0,0 +1,112 @@
2249+@charset "UTF-8";
2250+
2251+////
2252+/// MAAS accordion styles
2253+///
2254+/// @project MAAS
2255+/// @author Web Team at Canonical Ltd
2256+/// @copyright 2015 Canonical Ltd
2257+///
2258+////
2259+
2260+.accordion {
2261+ @include box-sizing();
2262+ @include rounded-corners(2px);
2263+ list-style: none;
2264+ background:#FFF;
2265+ box-shadow: 0 1px 1px rgba(0, 0, 0, .1);
2266+ margin-bottom: 40px;
2267+
2268+ .disabled & {
2269+ opacity: .5;
2270+ pointer-events: none;
2271+ }
2272+
2273+ // accordion main sidebar title
2274+ .accordion__title {
2275+ border-bottom: 1px dotted #B2B2B2;
2276+ padding: 13px 20px 12px;
2277+ margin: 0;
2278+ font-size: 1.3em;
2279+ }
2280+
2281+ // accordion data block, contains all filter links and controls.
2282+ .accordion__tab {
2283+ border-bottom: 1px dotted #B2B2B2;
2284+
2285+ &:last-of-type {
2286+ border: none;
2287+ }
2288+
2289+ // Block level title
2290+ .accordion__tab-title {
2291+ padding: 12px 20px;
2292+ margin: 0;
2293+ color: #888;
2294+ cursor: pointer;
2295+ background: transparent url('../img/icons/accordion-open.svg') top 20px right 20px no-repeat;
2296+
2297+ &.active {
2298+ background-image: url('../img/icons/accordion-close.svg');
2299+
2300+ + .accordion__tab-content {
2301+ max-height: 400px;
2302+ transition: max-height .5s ease-in;
2303+ overflow-y: auto;
2304+ }
2305+ }
2306+ }
2307+
2308+ // Filter list
2309+ .accordion__tab-content {
2310+ max-height: 0;
2311+ transition: max-height .5s ease-out;
2312+ overflow: hidden;
2313+
2314+ .accordion__tab-list {
2315+ list-style-type: none;
2316+ padding: 0 20px 14px;
2317+ margin: 0;
2318+
2319+ .accordion__tab-item {
2320+ margin-bottom: 0.15em;
2321+
2322+ .accordion__tab-link {
2323+ @include box-sizing();
2324+ color: #333;
2325+ width: 100%;
2326+ display: inline-block;
2327+ padding-right: 20px;
2328+ border-bottom: 0;
2329+
2330+ &:hover {
2331+ color: $ubuntu-orange;
2332+ text-decoration: none;
2333+ }
2334+
2335+ .disabled & {
2336+ color: #333;
2337+ }
2338+ }
2339+
2340+ &.active {
2341+ font-weight: 400;
2342+
2343+ .accordion__tab-link {
2344+ background: transparent url('../img/icons/cross.svg') top 7px right 0px no-repeat;
2345+ }
2346+
2347+ &:hover {
2348+ color: $ubuntu-orange;
2349+
2350+ .accordion__tab-link {
2351+ color: $ubuntu-orange;
2352+ background-image: url('../img/icons/cross-orange.svg');
2353+ }
2354+ }
2355+ }
2356+ }
2357+ }
2358+ }
2359+ }
2360+}
2361
2362=== modified file 'src/maasserver/templates/maasserver/base.html'
2363--- src/maasserver/templates/maasserver/base.html 2016-10-05 15:01:48 +0000
2364+++ src/maasserver/templates/maasserver/base.html 2016-10-26 23:39:26 +0000
2365@@ -196,6 +196,7 @@
2366 <div class="twelve-col">
2367 <div class="eight-col">
2368 {% block footer-copyright %}
2369+<<<<<<< TREE
2370 <p>
2371 <small>
2372 <strong class="u-margin--right">MAAS Version {{version}}</strong>&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>
2373@@ -206,6 +207,12 @@
2374 &copy; 2016 Canonical Ltd. Ubuntu and Canonical are registered trademarks of Canonical Ltd.
2375 </small>
2376 </p>
2377+=======
2378+ <p class="twelve-col">
2379+ <span class="version">MAAS Version {{version}}</span> <a href="http://maas.ubuntu.com/{{doc_version}}/changelog.html#id1">View release notes</a>|<a href="http://maas.io/docs">View documentation</a>
2380+ </p>
2381+ <p class="twelve-col copy">&copy; 2016 Canonical Ltd. Ubuntu and Canonical are registered trademarks of Canonical Ltd.</p>
2382+>>>>>>> MERGE-SOURCE
2383 {% endblock %}
2384 </div>
2385 <div class="four-col last-col">
2386
2387=== modified file 'src/maasserver/templates/maasserver/index.html'
2388--- src/maasserver/templates/maasserver/index.html 2016-10-05 15:01:48 +0000
2389+++ src/maasserver/templates/maasserver/index.html 2016-10-26 23:39:26 +0000
2390@@ -146,6 +146,7 @@
2391 <li{% if message.tags %} class="flash-messages__item flash-messages__item--{{ message.tags }}" {% endif %}>{{ message }}</li>
2392 {% endfor %}
2393 {% endif %}
2394+<<<<<<< TREE
2395 </ul>
2396 </div>
2397 {% endif %}
2398@@ -231,5 +232,25 @@
2399 </div>
2400 </div>
2401 </footer>
2402+=======
2403+ <div id="content" data-ng-view>
2404+ </div>
2405+ </div>
2406+ <div class="push"></div>
2407+ </main>
2408+ </div>
2409+ <div class="footer-wrapper" data-maas-error-toggle>
2410+ <footer class="global inner-wrapper clearfix">
2411+ <div class="legal clearfix">
2412+ <div class="legal-inner">
2413+ <p class="twelve-col">
2414+ <span class="version">MAAS Version {{version}}</span> <a href="http://maas.ubuntu.com/{{doc_version}}/changelog.html#id1">View release notes</a>|<a href="http://maas.io/docs">View documentation</a>
2415+ </p>
2416+ <p class="twelve-col copy">&copy; 2016 Canonical Ltd. Ubuntu and Canonical are registered trademarks of Canonical Ltd.</p>
2417+ </div>
2418+ </div>
2419+ </footer>
2420+ </div>
2421+>>>>>>> MERGE-SOURCE
2422 </body>
2423 </html>
2424
2425=== modified file 'src/maasserver/templates/maasserver/prefs.html'
2426--- src/maasserver/templates/maasserver/prefs.html 2016-10-13 13:13:12 +0000
2427+++ src/maasserver/templates/maasserver/prefs.html 2016-10-26 23:39:26 +0000
2428@@ -29,8 +29,13 @@
2429 <ul class="no-bullets">
2430 {% for token in user.userprofile.get_authorisation_tokens %}
2431 <li class="bundle">
2432+<<<<<<< TREE
2433 <a href="#" class="delete-link icon right">
2434 <img title="Delete MAAS key" class="left" src="{{ STATIC_URL }}assets/images/icons/delete.png" />
2435+=======
2436+ <a href="#" class="u-margin--top-tiny delete-link icon right">
2437+ <img title="Delete MAAS key" class="left" src="{{ STATIC_URL }}img/delete.png" />
2438+>>>>>>> MERGE-SOURCE
2439 </a>
2440 <input type="text" value="{{ token.consumer.key }}:{{ token.key }}:{{ token.secret }}" id="{{ token.key }}" class="disabled" />
2441 </li>
2442
2443=== modified file 'src/maasserver/testing/factory.py'
2444--- src/maasserver/testing/factory.py 2016-10-24 19:12:33 +0000
2445+++ src/maasserver/testing/factory.py 2016-10-26 23:39:26 +0000
2446@@ -441,9 +441,14 @@
2447 ip_address = existing_static_ips[0]
2448 bmc_ip_address = self.pick_ip_in_Subnet(ip_address.subnet)
2449 node.power_parameters = {
2450+<<<<<<< TREE
2451 "power_address": "qemu+ssh://user@%s/system" % (
2452 factory.ip_to_url_format(bmc_ip_address)),
2453 "power_id": factory.make_name("power_id"),
2454+=======
2455+ "power_address": "qemu+ssh://user@%s/system" % bmc_ip_address,
2456+ "power_id": factory.make_name("power_id"),
2457+>>>>>>> MERGE-SOURCE
2458 }
2459 node.save()
2460
2461
2462=== modified file 'src/maasserver/tests/test_bootresources.py'
2463--- src/maasserver/tests/test_bootresources.py 2016-10-12 15:26:17 +0000
2464+++ src/maasserver/tests/test_bootresources.py 2016-10-26 23:39:26 +0000
2465@@ -932,6 +932,7 @@
2466 with rfile.largefile.content.open('rb') as stream:
2467 written_data = stream.read()
2468 self.assertEqual(content, written_data)
2469+<<<<<<< TREE
2470 rfile.largefile = reload_object(rfile.largefile)
2471 self.assertEqual(rfile.largefile.size, len(written_data))
2472 self.assertEqual(rfile.largefile.size, rfile.largefile.total_size)
2473@@ -948,6 +949,11 @@
2474 self.assertEqual(b'', written_data)
2475 rfile.largefile = reload_object(rfile.largefile)
2476 self.assertEqual(rfile.largefile.size, 0)
2477+=======
2478+ rfile.largefile = reload_object(rfile.largefile)
2479+ self.assertEqual(rfile.largefile.size, len(written_data))
2480+ self.assertEqual(rfile.largefile.size, rfile.largefile.total_size)
2481+>>>>>>> MERGE-SOURCE
2482
2483 @skip(
2484 "XXX blake_r: Skipped because it causes the test that runs after this "
2485@@ -2098,6 +2104,7 @@
2486 response = {"images": [make_rpc_boot_image()]}
2487 cluster_rpc.ListBootImages.return_value = succeed(response)
2488 self.assertTrue(service.are_boot_images_available_in_any_rack())
2489+<<<<<<< TREE
2490
2491
2492 class TestBootResourceRepoWriter(MAASServerTestCase):
2493@@ -2335,3 +2342,104 @@
2494 mock_insert = self.patch(boot_resource_repo_writer.store, 'insert')
2495 boot_resource_repo_writer.insert_item(data, src, None, pedigree, None)
2496 self.assertThat(mock_insert, MockNotCalled())
2497+=======
2498+
2499+
2500+class TestBootResourceRepoWriter(MAASServerTestCase):
2501+ """Tests for `BootResourceRepoWriter`."""
2502+
2503+ def create_simplestream(self, ftypes):
2504+ version = '16.04'
2505+ arch = 'amd64'
2506+ subarch = 'hwe-x'
2507+ product = "com.ubuntu.maas.daily:v2:boot:%s:%s:%s" % (
2508+ version, arch, subarch)
2509+ version = datetime.now().date().strftime('%Y%m%d.0')
2510+ versions = {
2511+ version: {
2512+ 'items': {
2513+ ftype: {
2514+ 'sha256': factory.make_name('sha256'),
2515+ 'path': factory.make_name('path'),
2516+ 'ftype': ftype,
2517+ 'size': random.randint(0, 2**64),
2518+ } for ftype in ftypes
2519+ }
2520+ }
2521+ }
2522+ products = {
2523+ product: {
2524+ 'krel': 'xenial',
2525+ 'subarch': subarch,
2526+ 'label': 'daily',
2527+ 'os': 'ubuntu',
2528+ 'arch': arch,
2529+ 'subarches': 'generic,%s' % subarch,
2530+ 'kflavor': 'generic',
2531+ 'version': version,
2532+ 'versions': versions,
2533+ }
2534+ }
2535+ src = {
2536+ 'datatype': 'image-downloads',
2537+ 'format': 'products:1.0',
2538+ 'updated': format_datetime(datetime.now()),
2539+ 'products': products,
2540+ 'content_id': 'com.ubuntu.maas:daily:v2:download'
2541+ }
2542+ return src, product, version
2543+
2544+ def test_insert_prefers_squashfs_over_root_image(self):
2545+ boot_resource_repo_writer = BootResourceRepoWriter(
2546+ BootResourceStore(), None)
2547+ src, product, version = self.create_simplestream([
2548+ BOOT_RESOURCE_FILE_TYPE.ROOT_IMAGE,
2549+ BOOT_RESOURCE_FILE_TYPE.SQUASHFS_IMAGE,
2550+ ])
2551+ data = src['products'][product]['versions'][version]['items'][
2552+ BOOT_RESOURCE_FILE_TYPE.ROOT_IMAGE]
2553+ pedigree = (product, version, BOOT_RESOURCE_FILE_TYPE.ROOT_IMAGE)
2554+ mock_insert = self.patch(boot_resource_repo_writer.store, 'insert')
2555+ boot_resource_repo_writer.insert_item(data, src, None, pedigree, None)
2556+ self.assertThat(mock_insert, MockNotCalled())
2557+
2558+ def test_insert_allows_squashfs(self):
2559+ boot_resource_repo_writer = BootResourceRepoWriter(
2560+ BootResourceStore(), None)
2561+ src, product, version = self.create_simplestream([
2562+ BOOT_RESOURCE_FILE_TYPE.SQUASHFS_IMAGE,
2563+ ])
2564+ data = src['products'][product]['versions'][version]['items'][
2565+ BOOT_RESOURCE_FILE_TYPE.SQUASHFS_IMAGE]
2566+ pedigree = (product, version, BOOT_RESOURCE_FILE_TYPE.SQUASHFS_IMAGE)
2567+ mock_insert = self.patch(boot_resource_repo_writer.store, 'insert')
2568+ boot_resource_repo_writer.insert_item(data, src, None, pedigree, None)
2569+ self.assertThat(mock_insert, MockCalledOnce())
2570+
2571+ def test_insert_allows_root_image(self):
2572+ boot_resource_repo_writer = BootResourceRepoWriter(
2573+ BootResourceStore(), None)
2574+ src, product, version = self.create_simplestream([
2575+ BOOT_RESOURCE_FILE_TYPE.ROOT_IMAGE,
2576+ ])
2577+ data = src['products'][product]['versions'][version]['items'][
2578+ BOOT_RESOURCE_FILE_TYPE.ROOT_IMAGE]
2579+ pedigree = (product, version, BOOT_RESOURCE_FILE_TYPE.ROOT_IMAGE)
2580+ mock_insert = self.patch(boot_resource_repo_writer.store, 'insert')
2581+ boot_resource_repo_writer.insert_item(data, src, None, pedigree, None)
2582+ self.assertThat(mock_insert, MockCalledOnce())
2583+
2584+ def test_insert_ignores_unknown_ftypes(self):
2585+ boot_resource_repo_writer = BootResourceRepoWriter(
2586+ BootResourceStore(), None)
2587+ unknown_ftype = factory.make_name('ftype')
2588+ src, product, version = self.create_simplestream([
2589+ unknown_ftype,
2590+ ])
2591+ data = src['products'][product]['versions'][version]['items'][
2592+ unknown_ftype]
2593+ pedigree = (product, version, unknown_ftype)
2594+ mock_insert = self.patch(boot_resource_repo_writer.store, 'insert')
2595+ boot_resource_repo_writer.insert_item(data, src, None, pedigree, None)
2596+ self.assertThat(mock_insert, MockNotCalled())
2597+>>>>>>> MERGE-SOURCE
2598
2599=== modified file 'src/maasserver/tests/test_config_forms.py'
2600=== modified file 'src/maasserver/tests/test_dhcp.py'
2601--- src/maasserver/tests/test_dhcp.py 2016-09-30 18:18:11 +0000
2602+++ src/maasserver/tests/test_dhcp.py 2016-10-26 23:39:26 +0000
2603@@ -736,6 +736,7 @@
2604 'dhcp_snippets',
2605 ]))
2606
2607+<<<<<<< TREE
2608 def test__sets_ipv4_dns_from_arguments(self):
2609 rack_controller = factory.make_RackController(interface=False)
2610 vlan = factory.make_VLAN()
2611@@ -837,6 +838,79 @@
2612 self.assertThat(
2613 config['dns_servers'],
2614 Equals([IPAddress(addr) for addr in subnet_dns_servers]))
2615+=======
2616+ def test__sets_ipv4_dns_from_arguments(self):
2617+ rack_controller = factory.make_RackController(interface=False)
2618+ vlan = factory.make_VLAN()
2619+ subnet = factory.make_Subnet(vlan=vlan, dns_servers=[], version=4)
2620+ factory.make_Interface(
2621+ INTERFACE_TYPE.PHYSICAL, vlan=vlan, node=rack_controller)
2622+ maas_dns = factory.make_ipv4_address()
2623+ ntp = factory.make_name('ntp')
2624+ default_domain = Domain.objects.get_default_domain()
2625+ config = dhcp.make_subnet_config(
2626+ rack_controller, subnet, maas_dns, ntp, default_domain)
2627+ self.expectThat(config['dns_servers'], Equals(maas_dns))
2628+
2629+ def test__sets_ipv6_dns_from_arguments(self):
2630+ rack_controller = factory.make_RackController(interface=False)
2631+ vlan = factory.make_VLAN()
2632+ subnet = factory.make_Subnet(vlan=vlan, dns_servers=[], version=6)
2633+ factory.make_Interface(
2634+ INTERFACE_TYPE.PHYSICAL, vlan=vlan, node=rack_controller)
2635+ maas_dns = factory.make_ipv6_address()
2636+ ntp = factory.make_name('ntp')
2637+ default_domain = Domain.objects.get_default_domain()
2638+ config = dhcp.make_subnet_config(
2639+ rack_controller, subnet, maas_dns, ntp, default_domain)
2640+ self.expectThat(config['dns_servers'], Equals(maas_dns))
2641+
2642+ def test__sets_ntp_from_arguments(self):
2643+ rack_controller = factory.make_RackController(interface=False)
2644+ vlan = factory.make_VLAN()
2645+ subnet = factory.make_Subnet(vlan=vlan, dns_servers=[])
2646+ factory.make_Interface(
2647+ INTERFACE_TYPE.PHYSICAL, vlan=vlan, node=rack_controller)
2648+ ntp = factory.make_name('ntp')
2649+ default_domain = Domain.objects.get_default_domain()
2650+ config = dhcp.make_subnet_config(
2651+ rack_controller, subnet, "", ntp, default_domain)
2652+ self.expectThat(config['ntp_server'], Equals(ntp))
2653+>>>>>>> MERGE-SOURCE
2654+
2655+ def test__overrides_ipv4_dns_from_subnet(self):
2656+ rack_controller = factory.make_RackController(interface=False)
2657+ vlan = factory.make_VLAN()
2658+ subnet = factory.make_Subnet(vlan=vlan, version=4)
2659+ maas_dns = factory.make_ipv4_address()
2660+ subnet_dns_servers = ["8.8.8.8", "8.8.4.4"]
2661+ subnet.dns_servers = subnet_dns_servers
2662+ subnet.save()
2663+ factory.make_Interface(
2664+ INTERFACE_TYPE.PHYSICAL, vlan=vlan, node=rack_controller)
2665+ ntp = factory.make_name('ntp')
2666+ default_domain = Domain.objects.get_default_domain()
2667+ config = dhcp.make_subnet_config(
2668+ rack_controller, subnet, maas_dns, ntp, default_domain)
2669+ self.expectThat(
2670+ config['dns_servers'], Equals(", ".join(subnet_dns_servers)))
2671+
2672+ def test__overrides_ipv6_dns_from_subnet(self):
2673+ rack_controller = factory.make_RackController(interface=False)
2674+ vlan = factory.make_VLAN()
2675+ subnet = factory.make_Subnet(vlan=vlan, version=6)
2676+ maas_dns = factory.make_ipv6_address()
2677+ subnet_dns_servers = ["2001:db8::1", "2001:db8::2"]
2678+ subnet.dns_servers = subnet_dns_servers
2679+ subnet.save()
2680+ factory.make_Interface(
2681+ INTERFACE_TYPE.PHYSICAL, vlan=vlan, node=rack_controller)
2682+ ntp = factory.make_name('ntp')
2683+ default_domain = Domain.objects.get_default_domain()
2684+ config = dhcp.make_subnet_config(
2685+ rack_controller, subnet, maas_dns, ntp, default_domain)
2686+ self.expectThat(
2687+ config['dns_servers'], Equals(", ".join(subnet_dns_servers)))
2688
2689 def test__sets_domain_name_from_passed_domain(self):
2690 rack_controller = factory.make_RackController(interface=False)
2691
2692=== modified file 'src/maasserver/tests/test_forms_interface_link.py'
2693=== modified file 'src/maasserver/tests/test_preseed.py'
2694--- src/maasserver/tests/test_preseed.py 2016-10-13 16:32:38 +0000
2695+++ src/maasserver/tests/test_preseed.py 2016-10-26 23:39:26 +0000
2696@@ -457,6 +457,33 @@
2697 'server_url', 'syslog_host_port'],
2698 context)
2699
2700+<<<<<<< TREE
2701+=======
2702+ def test_get_preseed_context_archive_refs(self):
2703+ # urlparse lowercases the hostnames. That should not have any
2704+ # impact but for testing, create lower-case hostnames.
2705+ main_archive = factory.make_url(netloc="main-archive.example.com")
2706+ ports_archive = factory.make_url(netloc="ports-archive.example.com")
2707+ Config.objects.set_config('main_archive', main_archive)
2708+ Config.objects.set_config('ports_archive', ports_archive)
2709+ context = get_preseed_context()
2710+ parsed_main_archive = urlparse(main_archive)
2711+ parsed_ports_archive = urlparse(ports_archive)
2712+ self.assertEqual(
2713+ (
2714+ parsed_main_archive.hostname,
2715+ parsed_main_archive.path.lstrip('/'),
2716+ parsed_ports_archive.hostname,
2717+ parsed_ports_archive.path.lstrip('/'),
2718+ ),
2719+ (
2720+ context['main_archive_hostname'],
2721+ context['main_archive_directory'],
2722+ context['ports_archive_hostname'],
2723+ context['ports_archive_directory'],
2724+ ))
2725+
2726+>>>>>>> MERGE-SOURCE
2727
2728 class TestNodePreseedContext(
2729 PreseedRPCMixin, BootImageHelperMixin, MAASTransactionServerTestCase):
2730
2731=== modified file 'src/maasserver/tests/test_preseed_network.py'
2732--- src/maasserver/tests/test_preseed_network.py 2016-10-24 23:23:10 +0000
2733+++ src/maasserver/tests/test_preseed_network.py 2016-10-26 23:39:26 +0000
2734@@ -243,12 +243,21 @@
2735
2736 def collectDNSConfig(self, node, ipv4=True, ipv6=True):
2737 config = "- type: nameserver\n address: %s\n search:\n" % (
2738+<<<<<<< TREE
2739 repr(node.get_default_dns_servers(ipv4=ipv4, ipv6=ipv6)))
2740 domain_name = node.domain.name
2741 dns_searches = [domain_name] + [
2742 name
2743 for name in sorted(get_dns_search_paths())
2744 if name != domain_name]
2745+=======
2746+ repr(node.get_default_dns_servers()))
2747+ domain_name = node.domain.name
2748+ dns_searches = [domain_name] + [
2749+ name
2750+ for name in sorted(get_dns_search_paths())
2751+ if name != domain_name]
2752+>>>>>>> MERGE-SOURCE
2753 for dns_name in dns_searches:
2754 config += " - %s\n" % dns_name
2755 return config
2756
2757=== modified file 'src/maasserver/tests/test_preseed_storage.py'
2758=== modified file 'src/maasserver/tests/test_start_up.py'
2759--- src/maasserver/tests/test_start_up.py 2016-06-29 10:00:16 +0000
2760+++ src/maasserver/tests/test_start_up.py 2016-10-26 23:39:26 +0000
2761@@ -145,6 +145,7 @@
2762 self.assertThat(RegionController.objects.all(), HasLength(1))
2763
2764 def test__creates_maas_id_file(self):
2765+<<<<<<< TREE
2766 start_up.is_master_process.return_value = False
2767 self.assertThat(get_maas_id(), Is(None))
2768 with post_commit_hooks:
2769@@ -173,3 +174,95 @@
2770 Traceback (most recent call last):...
2771 Failure: maastesting.factory.TestException#...: boom
2772 """))
2773+=======
2774+ self.patch(start_up, "is_master_process").return_value = True
2775+ mock_set_maas_id = self.patch_autospec(start_up, "set_maas_id")
2776+ self.patch(start_up.RegionController, 'refresh')
2777+ with post_commit_hooks:
2778+ start_up.inner_start_up()
2779+ self.assertThat(mock_set_maas_id, MockCalledOnce())
2780+
2781+ def test__doesnt_create_maas_id_file_if_not_master(self):
2782+ self.patch(start_up, "is_master_process").return_value = False
2783+ mock_set_maas_id = self.patch_autospec(start_up, "set_maas_id")
2784+ with post_commit_hooks:
2785+ start_up.inner_start_up()
2786+ self.assertThat(mock_set_maas_id, MockNotCalled())
2787+
2788+
2789+class TestCreateRegionObj(MAASServerTestCase):
2790+
2791+ """Tests for the actual work done in `create_region_obj`."""
2792+
2793+ def test__creates_obj(self):
2794+ region = start_up.create_region_obj()
2795+ self.assertIsNotNone(region)
2796+ self.assertIsNotNone(
2797+ RegionController.objects.get(system_id=region.system_id))
2798+
2799+ def test__doesnt_read_maas_id_from_cache(self):
2800+ set_maas_id(factory.make_string())
2801+ os.unlink(get_path('/var/lib/maas/maas_id'))
2802+ region = start_up.create_region_obj()
2803+ self.assertIsNotNone(region)
2804+ self.assertIsNotNone(
2805+ RegionController.objects.get(system_id=region.system_id))
2806+
2807+ def test__finds_region_by_maas_id(self):
2808+ region = factory.make_RegionController()
2809+ self.useFixture(MAASIDFixture(region.system_id))
2810+ self.assertEquals(region, start_up.create_region_obj())
2811+
2812+ def test__finds_region_by_hostname(self):
2813+ region = factory.make_RegionController()
2814+ mock_gethostname = self.patch_autospec(start_up, "gethostname")
2815+ mock_gethostname.return_value = region.hostname
2816+ self.assertEquals(region, start_up.create_region_obj())
2817+
2818+ def test__finds_region_by_mac(self):
2819+ region = factory.make_RegionController()
2820+ factory.make_Interface(node=region)
2821+ mock_get_mac_addresses = self.patch_autospec(
2822+ start_up, "get_mac_addresses")
2823+ mock_get_mac_addresses.return_value = [
2824+ nic.mac_address.raw
2825+ for nic in region.interface_set.all()
2826+ ]
2827+ self.assertEquals(region, start_up.create_region_obj())
2828+
2829+ def test__converts_rack_to_region_rack(self):
2830+ rack = factory.make_RackController()
2831+ self.useFixture(MAASIDFixture(rack.system_id))
2832+ region_rack = start_up.create_region_obj()
2833+ self.assertEquals(rack, region_rack)
2834+ self.assertEquals(
2835+ region_rack.node_type, NODE_TYPE.REGION_AND_RACK_CONTROLLER)
2836+
2837+ def test__converts_node_to_region_rack(self):
2838+ node = factory.make_Node(
2839+ node_type=factory.pick_choice(
2840+ NODE_TYPE_CHOICES,
2841+ but_not=[
2842+ NODE_TYPE.REGION_CONTROLLER,
2843+ NODE_TYPE.RACK_CONTROLLER,
2844+ NODE_TYPE.REGION_AND_RACK_CONTROLLER,
2845+ ]))
2846+ self.useFixture(MAASIDFixture(node.system_id))
2847+ region = start_up.create_region_obj()
2848+ self.assertEquals(node, region)
2849+ self.assertEquals(region.node_type, NODE_TYPE.REGION_CONTROLLER)
2850+
2851+ def test__sets_owner_if_none(self):
2852+ region = factory.make_RegionController()
2853+ self.useFixture(MAASIDFixture(region.system_id))
2854+ self.assertEquals(
2855+ get_worker_user(), start_up.create_region_obj().owner)
2856+
2857+ def test__leaves_owner_if_set(self):
2858+ region = factory.make_RegionController()
2859+ self.useFixture(MAASIDFixture(region.system_id))
2860+ user = factory.make_User()
2861+ region.owner = user
2862+ region.save()
2863+ self.assertEquals(user, start_up.create_region_obj().owner)
2864+>>>>>>> MERGE-SOURCE
2865
2866=== modified file 'src/maasserver/triggers/tests/test_websocket.py'
2867=== modified file 'src/maasserver/triggers/tests/test_websocket_listener.py'
2868--- src/maasserver/triggers/tests/test_websocket_listener.py 2016-10-12 15:26:17 +0000
2869+++ src/maasserver/triggers/tests/test_websocket_listener.py 2016-10-26 23:39:26 +0000
2870@@ -2923,6 +2923,7 @@
2871 yield listener.stopService()
2872
2873
2874+<<<<<<< TREE
2875 class TestPackageRepositoryListener(
2876 MAASTransactionServerTestCase, TransactionalHelpersMixin):
2877 """End-to-end test of both the listeners code and the cluster
2878@@ -3102,6 +3103,129 @@
2879 yield listener.stopService()
2880
2881
2882+=======
2883+class TestIPRangeSubnetListener(
2884+ MAASTransactionServerTestCase, TransactionalHelpersMixin):
2885+ """End-to-end test of both the listeners code and the triggers on
2886+ maasserver_iprange tables that notifies affected subnets."""
2887+
2888+ @wait_for_reactor
2889+ @inlineCallbacks
2890+ def test__calls_handler_on_create_notification(self):
2891+ yield deferToDatabase(register_websocket_triggers)
2892+ subnet = yield deferToDatabase(
2893+ self.create_subnet, {
2894+ "cidr": '192.168.0.0/24',
2895+ "gateway_ip": '192.168.0.1',
2896+ "dns_servers": [],
2897+ })
2898+
2899+ listener = PostgresListenerService()
2900+ dv = DeferredValue()
2901+ listener.register("subnet", lambda *args: dv.set(args))
2902+ yield listener.startService()
2903+ try:
2904+ iprange = yield deferToDatabase(
2905+ self.create_iprange, {
2906+ "type": IPRANGE_TYPE.DYNAMIC,
2907+ "subnet": subnet,
2908+ "start_ip": '192.168.0.100',
2909+ "end_ip": '192.168.0.110',
2910+ })
2911+ yield dv.get(timeout=2)
2912+ self.assertEqual(('update', '%s' % iprange.subnet.id), dv.value)
2913+ finally:
2914+ yield listener.stopService()
2915+
2916+ @wait_for_reactor
2917+ @inlineCallbacks
2918+ def test__calls_handler_on_update_notification(self):
2919+ yield deferToDatabase(register_websocket_triggers)
2920+ iprange = yield deferToDatabase(self.create_iprange)
2921+ new_end_ip = factory.pick_ip_in_IPRange(iprange)
2922+
2923+ listener = PostgresListenerService()
2924+ dv = DeferredValue()
2925+ listener.register("subnet", lambda *args: dv.set(args))
2926+ yield listener.startService()
2927+ try:
2928+ yield deferToDatabase(
2929+ self.update_iprange,
2930+ iprange.id, {"end_ip": new_end_ip})
2931+ yield dv.get(timeout=2)
2932+ self.assertEqual(('update', '%s' % iprange.subnet.id), dv.value)
2933+ finally:
2934+ yield listener.stopService()
2935+
2936+ @wait_for_reactor
2937+ @inlineCallbacks
2938+ def test__calls_handler_on_update_on_old_and_new_subnet_notification(self):
2939+ yield deferToDatabase(register_websocket_triggers)
2940+ old_subnet = yield deferToDatabase(
2941+ self.create_subnet, {
2942+ "cidr": '192.168.0.0/24',
2943+ "gateway_ip": '192.168.0.1',
2944+ "dns_servers": [],
2945+ })
2946+ new_subnet = yield deferToDatabase(
2947+ self.create_subnet, {
2948+ "cidr": '192.168.1.0/24',
2949+ "gateway_ip": '192.168.1.1',
2950+ "dns_servers": [],
2951+ })
2952+ iprange = yield deferToDatabase(
2953+ self.create_iprange, {
2954+ "type": IPRANGE_TYPE.DYNAMIC,
2955+ "subnet": old_subnet,
2956+ "start_ip": '192.168.0.100',
2957+ "end_ip": '192.168.0.110',
2958+ })
2959+ dvs = [DeferredValue(), DeferredValue()]
2960+
2961+ def set_defer_value(*args):
2962+ for dv in dvs:
2963+ if not dv.isSet:
2964+ dv.set(args)
2965+ break
2966+
2967+ listener = PostgresListenerService()
2968+ listener.register("subnet", set_defer_value)
2969+ yield listener.startService()
2970+ try:
2971+ yield deferToDatabase(self.update_iprange, iprange.id, {
2972+ "type": IPRANGE_TYPE.DYNAMIC,
2973+ "subnet": new_subnet,
2974+ "start_ip": '192.168.1.10',
2975+ "end_ip": '192.168.1.150',
2976+ })
2977+ yield dvs[0].get(timeout=2)
2978+ yield dvs[1].get(timeout=2)
2979+ self.assertItemsEqual([
2980+ ('update', '%s' % old_subnet.id),
2981+ ('update', '%s' % new_subnet.id),
2982+ ], [dvs[0].value, dvs[1].value])
2983+ finally:
2984+ yield listener.stopService()
2985+
2986+ @wait_for_reactor
2987+ @inlineCallbacks
2988+ def test__calls_handler_on_delete_notification(self):
2989+ yield deferToDatabase(register_websocket_triggers)
2990+ iprange = yield deferToDatabase(self.create_iprange)
2991+
2992+ listener = PostgresListenerService()
2993+ dv = DeferredValue()
2994+ listener.register("subnet", lambda *args: dv.set(args))
2995+ yield listener.startService()
2996+ try:
2997+ yield deferToDatabase(self.delete_iprange, iprange.id)
2998+ yield dv.get(timeout=2)
2999+ self.assertEqual(('update', '%s' % iprange.subnet.id), dv.value)
3000+ finally:
3001+ yield listener.stopService()
3002+
3003+
3004+>>>>>>> MERGE-SOURCE
3005 class TestNodeTypeChange(
3006 MAASTransactionServerTestCase, TransactionalHelpersMixin):
3007 """End-to-end test of node type change triggers code."""
3008
3009=== modified file 'src/maasserver/triggers/websocket.py'
3010=== added file 'src/maasserver/views/tests/test_images.py.OTHER'
3011--- src/maasserver/views/tests/test_images.py.OTHER 1970-01-01 00:00:00 +0000
3012+++ src/maasserver/views/tests/test_images.py.OTHER 2016-10-26 23:39:26 +0000
3013@@ -0,0 +1,991 @@
3014+# Copyright 2014-2016 Canonical Ltd. This software is licensed under the
3015+# GNU Affero General Public License version 3 (see the file LICENSE).
3016+
3017+"""Test maasserver images views."""
3018+
3019+__all__ = []
3020+
3021+import datetime
3022+import http.client
3023+import json
3024+import random
3025+
3026+from django.conf import settings
3027+from django.core.urlresolvers import reverse
3028+from lxml.html import fromstring
3029+from maasserver.enum import (
3030+ BOOT_RESOURCE_TYPE,
3031+ NODE_STATUS,
3032+)
3033+from maasserver.models import (
3034+ BootResource,
3035+ BootSourceCache,
3036+ BootSourceSelection,
3037+ Config,
3038+)
3039+from maasserver.models.signals import bootsources
3040+from maasserver.testing import extract_redirect
3041+from maasserver.testing.factory import factory
3042+from maasserver.testing.testcase import MAASServerTestCase
3043+from maasserver.utils.converters import human_readable_bytes
3044+from maasserver.utils.orm import (
3045+ get_one,
3046+ reload_object,
3047+)
3048+from maasserver.views import images as images_view
3049+from maastesting.matchers import (
3050+ MockCalledOnceWith,
3051+ MockCalledWith,
3052+)
3053+from requests import ConnectionError
3054+from testtools.matchers import (
3055+ ContainsAll,
3056+ HasLength,
3057+)
3058+
3059+
3060+class UbuntuImagesTest(MAASServerTestCase):
3061+
3062+ def setUp(self):
3063+ super(UbuntuImagesTest, self).setUp()
3064+ # Disable boot source cache signals.
3065+ self.addCleanup(bootsources.signals.enable)
3066+ bootsources.signals.disable()
3067+
3068+ def patch_get_os_info_from_boot_sources(
3069+ self, sources, releases=None, arches=None):
3070+ if releases is None:
3071+ releases = [factory.make_name('release') for _ in range(3)]
3072+ if arches is None:
3073+ arches = [factory.make_name('arch') for _ in range(3)]
3074+ mock_get_os_info = self.patch(
3075+ images_view, 'get_os_info_from_boot_sources')
3076+ mock_get_os_info.return_value = (sources, releases, arches)
3077+ return mock_get_os_info
3078+
3079+ def test_shows_connection_error(self):
3080+ self.client_log_in(as_admin=True)
3081+ mock_get_os_info = self.patch(
3082+ images_view, 'get_os_info_from_boot_sources')
3083+ mock_get_os_info.side_effect = ConnectionError()
3084+ response = self.client.get(reverse('images'))
3085+ doc = fromstring(response.content)
3086+ warnings = doc.cssselect('div#connection-error')
3087+ self.assertEqual(1, len(warnings))
3088+
3089+ def test_shows_no_ubuntu_sources(self):
3090+ self.client_log_in(as_admin=True)
3091+ response = self.client.get(reverse('images'))
3092+ doc = fromstring(response.content)
3093+ warnings = doc.cssselect('div#no-ubuntu-sources')
3094+ self.assertEqual(1, len(warnings))
3095+
3096+ def test_shows_too_many_ubuntu_sources(self):
3097+ self.client_log_in(as_admin=True)
3098+ sources = [factory.make_BootSource() for _ in range(2)]
3099+ self.patch_get_os_info_from_boot_sources(sources)
3100+ response = self.client.get(reverse('images'))
3101+ doc = fromstring(response.content)
3102+ warnings = doc.cssselect('div#too-many-ubuntu-sources')
3103+ self.assertEqual(1, len(warnings))
3104+
3105+ def test_shows_release_options(self):
3106+ self.client_log_in(as_admin=True)
3107+ sources = [factory.make_BootSource()]
3108+ releases = [factory.make_name('release') for _ in range(3)]
3109+ self.patch_get_os_info_from_boot_sources(sources, releases=releases)
3110+ response = self.client.get(reverse('images'))
3111+ doc = fromstring(response.content)
3112+ releases_content = doc.cssselect(
3113+ 'ul#ubuntu-releases')[0].text_content()
3114+ self.assertThat(releases_content, ContainsAll(releases))
3115+
3116+ def test_shows_architecture_options(self):
3117+ self.client_log_in(as_admin=True)
3118+ sources = [factory.make_BootSource()]
3119+ arches = [factory.make_name('arch') for _ in range(3)]
3120+ self.patch_get_os_info_from_boot_sources(sources, arches=arches)
3121+ response = self.client.get(reverse('images'))
3122+ doc = fromstring(response.content)
3123+ arches_content = doc.cssselect(
3124+ 'ul#ubuntu-arches')[0].text_content()
3125+ self.assertThat(arches_content, ContainsAll(arches))
3126+
3127+ def test_shows_missing_images_warning_if_not_ubuntu_boot_resources(self):
3128+ self.client_log_in()
3129+ response = self.client.get(reverse('images'))
3130+ doc = fromstring(response.content)
3131+ warnings = doc.cssselect('div#missing-ubuntu-images')
3132+ self.assertEqual(1, len(warnings))
3133+
3134+ def test_hides_import_button_if_not_admin(self):
3135+ self.client_log_in()
3136+ sources = [factory.make_BootSource()]
3137+ self.patch_get_os_info_from_boot_sources(sources)
3138+ response = self.client.get(reverse('images'))
3139+ doc = fromstring(response.content)
3140+ import_button = doc.cssselect(
3141+ '#ubuntu-images')[0].cssselect('input[type="submit"]')
3142+ self.assertEqual(0, len(import_button))
3143+
3144+ def test_shows_import_button_if_admin(self):
3145+ self.client_log_in(as_admin=True)
3146+ sources = [factory.make_BootSource()]
3147+ self.patch_get_os_info_from_boot_sources(sources)
3148+ response = self.client.get(reverse('images'))
3149+ doc = fromstring(response.content)
3150+ import_button = doc.cssselect(
3151+ '#ubuntu-images')[0].cssselect('input[type="submit"]')
3152+ self.assertEqual(1, len(import_button))
3153+
3154+ def test_post_returns_forbidden_if_not_admin(self):
3155+ self.client_log_in()
3156+ response = self.client.post(
3157+ reverse('images'), {'ubuntu_images': 1})
3158+ self.assertEqual(http.client.FORBIDDEN, response.status_code)
3159+
3160+ def test_import_calls_import_resources(self):
3161+ self.client_log_in(as_admin=True)
3162+ sources = [factory.make_BootSource()]
3163+ self.patch_get_os_info_from_boot_sources(sources)
3164+ mock_import = self.patch(images_view, 'import_resources')
3165+ response = self.client.post(
3166+ reverse('images'), {'ubuntu_images': 1})
3167+ self.assertEqual(http.client.FOUND, response.status_code)
3168+ self.assertThat(mock_import, MockCalledOnceWith())
3169+
3170+ def test_import_sets_empty_selections(self):
3171+ self.client_log_in(as_admin=True)
3172+ source = factory.make_BootSource()
3173+ self.patch_get_os_info_from_boot_sources([source])
3174+ self.patch(images_view, 'import_resources')
3175+ response = self.client.post(
3176+ reverse('images'), {'ubuntu_images': 1})
3177+ self.assertEqual(http.client.FOUND, response.status_code)
3178+
3179+ selections = BootSourceSelection.objects.filter(boot_source=source)
3180+ self.assertThat(selections, HasLength(1))
3181+ self.assertEqual(
3182+ (selections[0].os, selections[0].release,
3183+ selections[0].arches, selections[0].subarches,
3184+ selections[0].labels),
3185+ ("ubuntu", "", [], ["*"], ["*"]))
3186+
3187+ def test_import_sets_release_selections(self):
3188+ self.client_log_in(as_admin=True)
3189+ source = factory.make_BootSource()
3190+ releases = [factory.make_name('release') for _ in range(3)]
3191+ self.patch_get_os_info_from_boot_sources([source])
3192+ self.patch(images_view, 'import_resources')
3193+ response = self.client.post(
3194+ reverse('images'), {'ubuntu_images': 1, 'release': releases})
3195+ self.assertEqual(http.client.FOUND, response.status_code)
3196+
3197+ selections = BootSourceSelection.objects.filter(boot_source=source)
3198+ self.assertThat(selections, HasLength(len(releases)))
3199+ self.assertItemsEqual(
3200+ releases,
3201+ [selection.release for selection in selections])
3202+
3203+ def test_import_sets_arches_on_selections(self):
3204+ self.client_log_in(as_admin=True)
3205+ source = factory.make_BootSource()
3206+ releases = [factory.make_name('release') for _ in range(3)]
3207+ arches = [factory.make_name('arches') for _ in range(3)]
3208+ self.patch_get_os_info_from_boot_sources([source])
3209+ self.patch(images_view, 'import_resources')
3210+ response = self.client.post(
3211+ reverse('images'),
3212+ {'ubuntu_images': 1, 'release': releases, 'arch': arches})
3213+ self.assertEqual(http.client.FOUND, response.status_code)
3214+
3215+ selections = BootSourceSelection.objects.filter(boot_source=source)
3216+ self.assertThat(selections, HasLength(len(releases)))
3217+ self.assertItemsEqual(
3218+ [arches, arches, arches],
3219+ [selection.arches for selection in selections])
3220+
3221+ def test_import_removes_old_selections(self):
3222+ self.client_log_in(as_admin=True)
3223+ source = factory.make_BootSource()
3224+ release = factory.make_name('release')
3225+ delete_selection = BootSourceSelection.objects.create(
3226+ boot_source=source, os='ubuntu',
3227+ release=factory.make_name('release'))
3228+ keep_selection = BootSourceSelection.objects.create(
3229+ boot_source=source, os='ubuntu', release=release)
3230+ self.patch_get_os_info_from_boot_sources([source])
3231+ self.patch(images_view, 'import_resources')
3232+ response = self.client.post(
3233+ reverse('images'), {'ubuntu_images': 1, 'release': [release]})
3234+ self.assertEqual(http.client.FOUND, response.status_code)
3235+ self.assertIsNone(reload_object(delete_selection))
3236+ self.assertIsNotNone(reload_object(keep_selection))
3237+
3238+
3239+class OtherImagesTest(MAASServerTestCase):
3240+
3241+ def setUp(self):
3242+ super(OtherImagesTest, self).setUp()
3243+ # Disable boot source cache signals.
3244+ self.addCleanup(bootsources.signals.enable)
3245+ bootsources.signals.disable()
3246+
3247+ def make_other_resource(self, os=None, arch=None, subarch=None,
3248+ release=None):
3249+ if os is None:
3250+ os = factory.make_name('os')
3251+ if arch is None:
3252+ arch = factory.make_name('arch')
3253+ if subarch is None:
3254+ subarch = factory.make_name('subarch')
3255+ if release is None:
3256+ release = factory.make_name('release')
3257+ name = '%s/%s' % (os, release)
3258+ architecture = '%s/%s' % (arch, subarch)
3259+ resource = factory.make_BootResource(
3260+ rtype=BOOT_RESOURCE_TYPE.SYNCED,
3261+ name=name, architecture=architecture)
3262+ resource_set = factory.make_BootResourceSet(resource)
3263+ factory.make_boot_resource_file_with_content(resource_set)
3264+ return resource
3265+
3266+ def test_hides_other_synced_images_section(self):
3267+ self.client_log_in()
3268+ BootSourceCache.objects.all().delete()
3269+ response = self.client.get(reverse('images'))
3270+ doc = fromstring(response.content)
3271+ section = doc.cssselect('div#other-sync-images')
3272+ self.assertEqual(
3273+ 0, len(section), "Didn't hide the other images section.")
3274+
3275+ def test_shows_other_synced_images_section(self):
3276+ self.client_log_in(as_admin=True)
3277+ factory.make_BootSourceCache()
3278+ response = self.client.get(reverse('images'))
3279+ doc = fromstring(response.content)
3280+ section = doc.cssselect('div#other-sync-images')
3281+ self.assertEqual(
3282+ 1, len(section), "Didn't show the other images section.")
3283+
3284+ def test_hides_image_from_boot_source_cache_without_admin(self):
3285+ self.client_log_in()
3286+ factory.make_BootSourceCache()
3287+ response = self.client.get(reverse('images'))
3288+ doc = fromstring(response.content)
3289+ rows = doc.cssselect('table#other-resources > tbody > tr')
3290+ self.assertEqual(
3291+ 0, len(rows), "Didn't hide unselected boot image from non-admin.")
3292+
3293+ def test_shows_image_from_boot_source_cache_with_admin(self):
3294+ self.client_log_in(as_admin=True)
3295+ cache = factory.make_BootSourceCache()
3296+ response = self.client.get(reverse('images'))
3297+ doc = fromstring(response.content)
3298+ title = doc.cssselect(
3299+ 'table#other-resources > tbody > '
3300+ 'tr > td')[1].text_content().strip()
3301+ self.assertEqual('%s/%s' % (cache.os, cache.release), title)
3302+
3303+ def test_shows_checkbox_for_boot_source_cache(self):
3304+ self.client_log_in(as_admin=True)
3305+ factory.make_BootSourceCache()
3306+ response = self.client.get(reverse('images'))
3307+ doc = fromstring(response.content)
3308+ checkbox = doc.cssselect(
3309+ 'table#other-resources > tbody > tr > td > input')
3310+ self.assertEqual(
3311+ 1, len(checkbox), "Didn't show checkbox for boot image.")
3312+
3313+ def test_shows_last_update_time_for_synced_resource(self):
3314+ self.client_log_in(as_admin=True)
3315+ cache = factory.make_BootSourceCache()
3316+ self.make_other_resource(
3317+ os=cache.os, arch=cache.arch,
3318+ subarch=cache.subarch, release=cache.release)
3319+ response = self.client.get(reverse('images'))
3320+ doc = fromstring(response.content)
3321+ last_update = doc.cssselect(
3322+ 'table#other-resources > tbody > '
3323+ 'tr > td')[5].text_content().strip()
3324+ dt = datetime.datetime.strptime(last_update, '%a, %d %b. %Y %H:%M:%S')
3325+ # TimestampedModel includes microseconds which we don't print. To
3326+ # confirm the right time is returned its easiest to just compare the
3327+ # ctimes
3328+ self.assertEquals(cache.updated.ctime(), dt.ctime())
3329+
3330+ def test_shows_number_of_nodes_for_synced_resource(self):
3331+ self.client_log_in(as_admin=True)
3332+ cache = factory.make_BootSourceCache()
3333+ resource = self.make_other_resource(
3334+ os=cache.os, arch=cache.arch,
3335+ subarch=cache.subarch, release=cache.release)
3336+ factory.make_Node(
3337+ status=NODE_STATUS.DEPLOYED,
3338+ osystem=cache.os, distro_series=cache.release,
3339+ architecture=resource.architecture)
3340+ response = self.client.get(reverse('images'))
3341+ doc = fromstring(response.content)
3342+ number_of_nodes = doc.cssselect(
3343+ 'table#other-resources > tbody > '
3344+ 'tr > td')[4].text_content().strip()
3345+ self.assertEqual(
3346+ 1, int(number_of_nodes),
3347+ "Incorrect number of deployed nodes for resource.")
3348+
3349+ def test_shows_apply_button_if_admin(self):
3350+ self.client_log_in(as_admin=True)
3351+ factory.make_BootSourceCache()
3352+ response = self.client.get(reverse('images'))
3353+ doc = fromstring(response.content)
3354+ apply_button = doc.cssselect(
3355+ '#other-sync-images')[0].cssselect('input[type="submit"]')
3356+ self.assertEqual(
3357+ 1, len(apply_button), "Didn't show apply button for admin.")
3358+
3359+ def test_hides_apply_button_if_import_running(self):
3360+ self.client_log_in(as_admin=True)
3361+ factory.make_BootSourceCache()
3362+ self.patch(
3363+ images_view, 'is_import_resources_running').return_value = True
3364+ response = self.client.get(reverse('images'))
3365+ doc = fromstring(response.content)
3366+ apply_button = doc.cssselect(
3367+ '#other-sync-images')[0].cssselect('input[type="submit"]')
3368+ self.assertEqual(
3369+ 0, len(apply_button),
3370+ "Didn't hide apply button when import running.")
3371+
3372+ def test_calls_get_os_release_title_for_other_resource(self):
3373+ self.client_log_in()
3374+ title = factory.make_name('title')
3375+ cache = factory.make_BootSourceCache()
3376+ resource = self.make_other_resource(
3377+ os=cache.os, arch=cache.arch,
3378+ subarch=cache.subarch, release=cache.release)
3379+ mock_get_title = self.patch(images_view, 'get_os_release_title')
3380+ mock_get_title.return_value = title
3381+ response = self.client.get(reverse('images'))
3382+ doc = fromstring(response.content)
3383+ row_title = doc.cssselect(
3384+ 'table#other-resources > tbody > '
3385+ 'tr > td')[1].text_content().strip()
3386+ self.assertEqual(title, row_title)
3387+ os, release = resource.name.split('/')
3388+ self.assertThat(mock_get_title, MockCalledWith(os, release))
3389+
3390+ def test_post_returns_forbidden_if_not_admin(self):
3391+ self.client_log_in()
3392+ response = self.client.post(
3393+ reverse('images'), {'other_images': 1})
3394+ self.assertEqual(http.client.FORBIDDEN, response.status_code)
3395+
3396+ def test_post_clears_all_other_os_selections(self):
3397+ self.client_log_in(as_admin=True)
3398+ source = factory.make_BootSource()
3399+ ubuntu_selection = BootSourceSelection.objects.create(
3400+ boot_source=source, os='ubuntu')
3401+ other_selection = BootSourceSelection.objects.create(
3402+ boot_source=source, os=factory.make_name('os'))
3403+ self.patch(images_view, 'import_resources')
3404+ response = self.client.post(
3405+ reverse('images'), {'other_images': 1, 'image': []})
3406+ self.assertEqual(http.client.FOUND, response.status_code)
3407+ self.assertIsNotNone(reload_object(ubuntu_selection))
3408+ self.assertIsNone(reload_object(other_selection))
3409+
3410+ def test_post_creates_selection_with_multiple_arches(self):
3411+ self.client_log_in(as_admin=True)
3412+ source = factory.make_BootSource()
3413+ os = factory.make_name('os')
3414+ release = factory.make_name('release')
3415+ arches = [factory.make_name('arch') for _ in range(3)]
3416+ images = []
3417+ for arch in arches:
3418+ factory.make_BootSourceCache(
3419+ boot_source=source, os=os, release=release, arch=arch)
3420+ images.append('%s/%s/subarch/%s' % (os, arch, release))
3421+ self.patch(images_view, 'import_resources')
3422+ response = self.client.post(
3423+ reverse('images'), {'other_images': 1, 'image': images})
3424+ self.assertEqual(http.client.FOUND, response.status_code)
3425+
3426+ selection = get_one(BootSourceSelection.objects.filter(
3427+ boot_source=source, os=os, release=release))
3428+ self.assertIsNotNone(selection)
3429+ self.assertItemsEqual(arches, selection.arches)
3430+
3431+ def test_post_calls_import_resources(self):
3432+ self.client_log_in(as_admin=True)
3433+ mock_import = self.patch(images_view, 'import_resources')
3434+ response = self.client.post(
3435+ reverse('images'), {'other_images': 1, 'image': []})
3436+ self.assertEqual(http.client.FOUND, response.status_code)
3437+ self.assertThat(mock_import, MockCalledOnceWith())
3438+
3439+
3440+class GeneratedImagesTest(MAASServerTestCase):
3441+
3442+ def make_generated_resource(self, os=None, arch=None, subarch=None,
3443+ release=None):
3444+ if os is None:
3445+ os = factory.make_name('os')
3446+ if arch is None:
3447+ arch = factory.make_name('arch')
3448+ if subarch is None:
3449+ subarch = factory.make_name('subarch')
3450+ if release is None:
3451+ release = factory.make_name('release')
3452+ name = '%s/%s' % (os, release)
3453+ architecture = '%s/%s' % (arch, subarch)
3454+ resource = factory.make_BootResource(
3455+ rtype=BOOT_RESOURCE_TYPE.GENERATED,
3456+ name=name, architecture=architecture)
3457+ resource_set = factory.make_BootResourceSet(resource)
3458+ factory.make_boot_resource_file_with_content(resource_set)
3459+ return resource
3460+
3461+ def test_hides_generated_images_section(self):
3462+ self.client_log_in()
3463+ response = self.client.get(reverse('images'))
3464+ doc = fromstring(response.content)
3465+ section = doc.cssselect('div#generated-images')
3466+ self.assertEqual(
3467+ 0, len(section), "Didn't hide the generated images section.")
3468+
3469+ def test_shows_generated_images_section(self):
3470+ self.client_log_in()
3471+ self.make_generated_resource()
3472+ response = self.client.get(reverse('images'))
3473+ doc = fromstring(response.content)
3474+ section = doc.cssselect('div#generated-images')
3475+ self.assertEqual(
3476+ 1, len(section), "Didn't show the generated images section.")
3477+
3478+ def test_shows_generated_resources(self):
3479+ self.client_log_in()
3480+ resources = [self.make_generated_resource() for _ in range(3)]
3481+ names = [resource.name for resource in resources]
3482+ response = self.client.get(reverse('images'))
3483+ doc = fromstring(response.content)
3484+ table_content = doc.cssselect(
3485+ 'table#generated-resources')[0].text_content()
3486+ self.assertThat(table_content, ContainsAll(names))
3487+
3488+ def test_shows_delete_button_for_generated_resource(self):
3489+ self.client_log_in(as_admin=True)
3490+ self.make_generated_resource()
3491+ response = self.client.get(reverse('images'))
3492+ doc = fromstring(response.content)
3493+ delete_btn = doc.cssselect(
3494+ 'table#generated-resources > tbody > tr > td > '
3495+ 'a[title="Delete image"]')
3496+ self.assertEqual(
3497+ 1, len(delete_btn),
3498+ "Didn't show delete button for generated image.")
3499+
3500+ def test_shows_last_update_time_for_synced_resource(self):
3501+ self.client_log_in(as_admin=True)
3502+ resource = self.make_generated_resource()
3503+ response = self.client.get(reverse('images'))
3504+ doc = fromstring(response.content)
3505+ last_update = doc.cssselect(
3506+ 'table#generated-resources > tbody > '
3507+ 'tr > td')[5].text_content().strip()
3508+ dt = datetime.datetime.strptime(last_update, '%a, %d %b. %Y %H:%M:%S')
3509+ # TimestampedModel includes microseconds which we don't print. To
3510+ # confirm the right time is returned its easiest to just compare the
3511+ # ctimes
3512+ self.assertEquals(resource.updated.ctime(), dt.ctime())
3513+
3514+ def test_hides_delete_button_for_generated_resource_when_not_admin(self):
3515+ self.client_log_in()
3516+ self.make_generated_resource()
3517+ response = self.client.get(reverse('images'))
3518+ doc = fromstring(response.content)
3519+ delete_btn = doc.cssselect(
3520+ 'table#generated-resources > tbody > tr > td > '
3521+ 'a[title="Delete image"]')
3522+ self.assertEqual(
3523+ 0, len(delete_btn),
3524+ "Didn't hide delete button for generated image when not admin.")
3525+
3526+ def test_calls_get_os_release_title_for_generated_resource(self):
3527+ self.client_log_in()
3528+ title = factory.make_name('title')
3529+ resource = self.make_generated_resource()
3530+ mock_get_title = self.patch(images_view, 'get_os_release_title')
3531+ mock_get_title.return_value = title
3532+ response = self.client.get(reverse('images'))
3533+ doc = fromstring(response.content)
3534+ row_title = doc.cssselect(
3535+ 'table#generated-resources > tbody > '
3536+ 'tr > td')[1].text_content().strip()
3537+ self.assertEqual(title, row_title)
3538+ os, release = resource.name.split('/')
3539+ self.assertThat(mock_get_title, MockCalledOnceWith(os, release))
3540+
3541+
3542+class UploadedImagesTest(MAASServerTestCase):
3543+
3544+ def make_uploaded_resource(self, name=None):
3545+ if name is None:
3546+ name = factory.make_name('name')
3547+ arch = factory.make_name('arch')
3548+ subarch = factory.make_name('subarch')
3549+ architecture = '%s/%s' % (arch, subarch)
3550+ resource = factory.make_BootResource(
3551+ rtype=BOOT_RESOURCE_TYPE.UPLOADED,
3552+ name=name, architecture=architecture)
3553+ resource_set = factory.make_BootResourceSet(resource)
3554+ factory.make_boot_resource_file_with_content(resource_set)
3555+ return resource
3556+
3557+ def test_shows_no_custom_images_message(self):
3558+ self.client_log_in()
3559+ response = self.client.get(reverse('images'))
3560+ doc = fromstring(response.content)
3561+ warnings = doc.cssselect('div#no-custom-images')
3562+ self.assertEqual(1, len(warnings))
3563+
3564+ def test_shows_uploaded_resources(self):
3565+ self.client_log_in()
3566+ names = [factory.make_name('name') for _ in range(3)]
3567+ [self.make_uploaded_resource(name) for name in names]
3568+ response = self.client.get(reverse('images'))
3569+ doc = fromstring(response.content)
3570+ table_content = doc.cssselect(
3571+ 'table#uploaded-resources')[0].text_content()
3572+ self.assertThat(table_content, ContainsAll(names))
3573+
3574+ def test_shows_uploaded_resources_name_if_title_blank(self):
3575+ self.client_log_in()
3576+ name = factory.make_name('name')
3577+ resource = self.make_uploaded_resource(name)
3578+ resource.extra['title'] = ''
3579+ resource.save()
3580+ response = self.client.get(reverse('images'))
3581+ doc = fromstring(response.content)
3582+ name_col = doc.cssselect(
3583+ 'table#uploaded-resources > tbody > tr > td')[1].text_content()
3584+ self.assertEqual(name, name_col.strip())
3585+
3586+ def test_shows_last_update_time_for_synced_resource(self):
3587+ self.client_log_in(as_admin=True)
3588+ resource = self.make_uploaded_resource()
3589+ response = self.client.get(reverse('images'))
3590+ doc = fromstring(response.content)
3591+ last_update = doc.cssselect(
3592+ 'table#uploaded-resources > tbody > '
3593+ 'tr > td')[5].text_content().strip()
3594+ dt = datetime.datetime.strptime(last_update, '%a, %d %b. %Y %H:%M:%S')
3595+ # TimestampedModel includes microseconds which we don't print. To
3596+ # confirm the right time is returned its easiest to just compare the
3597+ # ctimes
3598+ self.assertEquals(resource.updated.ctime(), dt.ctime())
3599+
3600+ def test_shows_delete_button_for_uploaded_resource(self):
3601+ self.client_log_in(as_admin=True)
3602+ self.make_uploaded_resource()
3603+ response = self.client.get(reverse('images'))
3604+ doc = fromstring(response.content)
3605+ delete_btn = doc.cssselect(
3606+ 'table#uploaded-resources > tbody > tr > td > '
3607+ 'a[title="Delete image"]')
3608+ self.assertEqual(1, len(delete_btn))
3609+
3610+ def test_hides_delete_button_for_uploaded_resource_when_not_admin(self):
3611+ self.client_log_in()
3612+ self.make_uploaded_resource()
3613+ response = self.client.get(reverse('images'))
3614+ doc = fromstring(response.content)
3615+ delete_btn = doc.cssselect(
3616+ 'table#uploaded-resources > tbody > tr > td > '
3617+ 'a[title="Delete image"]')
3618+ self.assertEqual(0, len(delete_btn))
3619+
3620+
3621+class TestImageAjax(MAASServerTestCase):
3622+
3623+ def get_images_ajax(self):
3624+ return self.client.get(
3625+ reverse('images'), HTTP_X_REQUESTED_WITH='XMLHttpRequest')
3626+
3627+ def test__returns_json(self):
3628+ self.client_log_in()
3629+ response = self.get_images_ajax()
3630+ self.assertEqual('application/json', response['Content-Type'])
3631+
3632+ def test__returns_region_import_running_True(self):
3633+ self.client_log_in()
3634+ self.patch(
3635+ images_view, 'is_import_resources_running').return_value = True
3636+ response = self.get_images_ajax()
3637+ json_obj = json.loads(
3638+ response.content.decode(settings.DEFAULT_CHARSET))
3639+ self.assertTrue(json_obj['region_import_running'])
3640+
3641+ def test__returns_region_import_running_False(self):
3642+ self.client_log_in()
3643+ self.patch(
3644+ images_view, 'is_import_resources_running').return_value = False
3645+ response = self.get_images_ajax()
3646+ json_obj = json.loads(
3647+ response.content.decode(settings.DEFAULT_CHARSET))
3648+ self.assertFalse(json_obj['region_import_running'])
3649+
3650+ def test__returns_cluster_import_running_True(self):
3651+ self.client_log_in()
3652+ self.patch(
3653+ images_view, 'is_import_boot_images_running').return_value = True
3654+ response = self.get_images_ajax()
3655+ json_obj = json.loads(
3656+ response.content.decode(settings.DEFAULT_CHARSET))
3657+ self.assertTrue(json_obj['cluster_import_running'])
3658+
3659+ def test__returns_cluster_import_running_False(self):
3660+ self.client_log_in()
3661+ self.patch(
3662+ images_view, 'is_import_boot_images_running').return_value = False
3663+ response = self.get_images_ajax()
3664+ json_obj = json.loads(
3665+ response.content.decode(settings.DEFAULT_CHARSET))
3666+ self.assertFalse(json_obj['cluster_import_running'])
3667+
3668+ def test_returns_resources(self):
3669+ self.client_log_in()
3670+ resources = [factory.make_usable_boot_resource() for _ in range(3)]
3671+ resource_ids = [resource.id for resource in resources]
3672+ response = self.get_images_ajax()
3673+ json_obj = json.loads(
3674+ response.content.decode(settings.DEFAULT_CHARSET))
3675+ json_ids = [
3676+ json_resource['id']
3677+ for json_resource in json_obj['resources']
3678+ ]
3679+ self.assertItemsEqual(resource_ids, json_ids)
3680+
3681+ def test_returns_resources_datetime_format(self):
3682+ """Ensure the date/time format is correct"""
3683+ self.client_log_in()
3684+ resource = factory.make_usable_boot_resource()
3685+ response = self.get_images_ajax()
3686+ json_obj = json.loads(
3687+ response.content.decode(settings.DEFAULT_CHARSET))
3688+ json_updated = datetime.datetime.strptime(
3689+ json_obj['resources'][0]['lastUpdate'], "%a, %d %b. %Y %H:%M:%S")
3690+ self.assertEqual(resource.updated.timetuple(),
3691+ json_updated.timetuple())
3692+
3693+ def test_returns_resource_attributes(self):
3694+ self.client_log_in()
3695+ factory.make_usable_boot_resource()
3696+ response = self.get_images_ajax()
3697+ json_obj = json.loads(
3698+ response.content.decode(settings.DEFAULT_CHARSET))
3699+ json_resource = json_obj['resources'][0]
3700+ self.assertThat(
3701+ json_resource,
3702+ ContainsAll([
3703+ 'id', 'rtype', 'name', 'title', 'arch', 'size',
3704+ 'complete', 'status', 'downloading',
3705+ 'numberOfNodes', 'lastUpdate']))
3706+
3707+ def test_returns_ubuntu_release_version_name(self):
3708+ self.client_log_in()
3709+ # Use trusty as known to map to "14.04 LTS"
3710+ version = '14.04 LTS'
3711+ name = 'ubuntu/trusty'
3712+ factory.make_usable_boot_resource(
3713+ rtype=BOOT_RESOURCE_TYPE.SYNCED, name=name)
3714+ response = self.get_images_ajax()
3715+ json_obj = json.loads(
3716+ response.content.decode(settings.DEFAULT_CHARSET))
3717+ json_resource = json_obj['resources'][0]
3718+ self.assertEqual(version, json_resource['title'])
3719+
3720+ def test_shows_number_of_nodes_deployed_for_resource(self):
3721+ self.client_log_in()
3722+ resource = factory.make_usable_boot_resource(
3723+ rtype=BOOT_RESOURCE_TYPE.SYNCED)
3724+ os_name, series = resource.name.split('/')
3725+ number_of_nodes = random.randint(1, 4)
3726+ for _ in range(number_of_nodes):
3727+ factory.make_Node(
3728+ status=NODE_STATUS.DEPLOYED,
3729+ osystem=os_name, distro_series=series,
3730+ architecture=resource.architecture)
3731+ response = self.get_images_ajax()
3732+ json_obj = json.loads(
3733+ response.content.decode(settings.DEFAULT_CHARSET))
3734+ json_resource = json_obj['resources'][0]
3735+ self.assertEqual(number_of_nodes, json_resource['numberOfNodes'])
3736+
3737+ def test_shows_number_of_nodes_deployed_for_resource_with_defaults(self):
3738+ self.client_log_in()
3739+ resource = factory.make_usable_boot_resource(
3740+ rtype=BOOT_RESOURCE_TYPE.SYNCED)
3741+ os_name, series = resource.name.split('/')
3742+ Config.objects.set_config('default_osystem', os_name)
3743+ Config.objects.set_config('default_distro_series', series)
3744+ number_of_nodes = random.randint(1, 4)
3745+ for _ in range(number_of_nodes):
3746+ factory.make_Node(
3747+ status=NODE_STATUS.DEPLOYED,
3748+ architecture=resource.architecture)
3749+ response = self.get_images_ajax()
3750+ json_obj = json.loads(
3751+ response.content.decode(settings.DEFAULT_CHARSET))
3752+ json_resource = json_obj['resources'][0]
3753+ self.assertEqual(number_of_nodes, json_resource['numberOfNodes'])
3754+
3755+ def test_shows_number_of_nodes_deployed_for_ubuntu_subarch_resource(self):
3756+ self.client_log_in()
3757+ resource = factory.make_usable_boot_resource(
3758+ rtype=BOOT_RESOURCE_TYPE.SYNCED)
3759+ arch, subarch = resource.split_arch()
3760+ extra_subarch = factory.make_name('subarch')
3761+ resource.extra['subarches'] = ','.join([subarch, extra_subarch])
3762+ resource.save()
3763+
3764+ os_name, series = resource.name.split('/')
3765+ node_architecture = '%s/%s' % (arch, extra_subarch)
3766+ number_of_nodes = random.randint(1, 4)
3767+ for _ in range(number_of_nodes):
3768+ factory.make_Node(
3769+ status=NODE_STATUS.DEPLOYED,
3770+ osystem=os_name, distro_series=series,
3771+ architecture=node_architecture)
3772+ response = self.get_images_ajax()
3773+ json_obj = json.loads(
3774+ response.content.decode(settings.DEFAULT_CHARSET))
3775+ json_resource = json_obj['resources'][0]
3776+ self.assertEqual(number_of_nodes, json_resource['numberOfNodes'])
3777+
3778+ def test_combines_subarch_resources_into_one_resource(self):
3779+ self.client_log_in()
3780+ name = 'ubuntu/%s' % factory.make_name('series')
3781+ arch = factory.make_name('arch')
3782+ subarches = [factory.make_name('subarch') for _ in range(3)]
3783+ for subarch in subarches:
3784+ factory.make_usable_boot_resource(
3785+ rtype=BOOT_RESOURCE_TYPE.SYNCED,
3786+ name=name, architecture='%s/%s' % (arch, subarch))
3787+ response = self.get_images_ajax()
3788+ json_obj = json.loads(
3789+ response.content.decode(settings.DEFAULT_CHARSET))
3790+ self.assertEqual(
3791+ 1, len(json_obj['resources']),
3792+ 'More than one resource was returned.')
3793+
3794+ def test_combined_subarch_resource_calculates_unique_size(self):
3795+ self.client_log_in()
3796+ name = 'ubuntu/%s' % factory.make_name('series')
3797+ arch = factory.make_name('arch')
3798+ subarches = [factory.make_name('subarch') for _ in range(3)]
3799+ largefile = factory.make_LargeFile()
3800+ for subarch in subarches:
3801+ resource = factory.make_BootResource(
3802+ rtype=BOOT_RESOURCE_TYPE.SYNCED,
3803+ name=name, architecture='%s/%s' % (arch, subarch))
3804+ resource_set = factory.make_BootResourceSet(resource)
3805+ factory.make_BootResourceFile(resource_set, largefile)
3806+ response = self.get_images_ajax()
3807+ json_obj = json.loads(
3808+ response.content.decode(settings.DEFAULT_CHARSET))
3809+ json_resource = json_obj['resources'][0]
3810+ self.assertEqual(
3811+ human_readable_bytes(largefile.total_size), json_resource['size'])
3812+
3813+ def test_combined_subarch_resource_calculates_num_of_nodes_deployed(self):
3814+ self.client_log_in()
3815+ osystem = 'ubuntu'
3816+ series = factory.make_name('series')
3817+ name = '%s/%s' % (osystem, series)
3818+ arch = factory.make_name('arch')
3819+ subarches = [factory.make_name('subarch') for _ in range(3)]
3820+ for subarch in subarches:
3821+ factory.make_usable_boot_resource(
3822+ rtype=BOOT_RESOURCE_TYPE.SYNCED,
3823+ name=name, architecture='%s/%s' % (arch, subarch))
3824+
3825+ number_of_nodes = random.randint(1, 4)
3826+ for _ in range(number_of_nodes):
3827+ subarch = random.choice(subarches)
3828+ node_architecture = '%s/%s' % (arch, subarch)
3829+ factory.make_Node(
3830+ status=NODE_STATUS.DEPLOYED,
3831+ osystem=osystem, distro_series=series,
3832+ architecture=node_architecture)
3833+
3834+ response = self.get_images_ajax()
3835+ json_obj = json.loads(
3836+ response.content.decode(settings.DEFAULT_CHARSET))
3837+ json_resource = json_obj['resources'][0]
3838+ self.assertEqual(number_of_nodes, json_resource['numberOfNodes'])
3839+
3840+ def test_combined_subarch_resource_calculates_complete_True(self):
3841+ self.client_log_in()
3842+ name = 'ubuntu/%s' % factory.make_name('series')
3843+ arch = factory.make_name('arch')
3844+ subarches = [factory.make_name('subarch') for _ in range(3)]
3845+ resources = [
3846+ factory.make_usable_boot_resource(
3847+ rtype=BOOT_RESOURCE_TYPE.SYNCED,
3848+ name=name, architecture='%s/%s' % (arch, subarch))
3849+ for subarch in subarches
3850+ ]
3851+ self.patch(
3852+ BootResource.objects,
3853+ 'get_resources_matching_boot_images').return_value = resources
3854+ response = self.get_images_ajax()
3855+ json_obj = json.loads(
3856+ response.content.decode(settings.DEFAULT_CHARSET))
3857+ json_resource = json_obj['resources'][0]
3858+ self.assertTrue(json_resource['complete'])
3859+
3860+ def test_combined_subarch_resource_calculates_complete_False(self):
3861+ self.client_log_in()
3862+ name = 'ubuntu/%s' % factory.make_name('series')
3863+ arch = factory.make_name('arch')
3864+ subarches = [factory.make_name('subarch') for _ in range(3)]
3865+ incomplete_subarch = subarches.pop()
3866+ factory.make_BootResource(
3867+ rtype=BOOT_RESOURCE_TYPE.SYNCED,
3868+ name=name, architecture='%s/%s' % (arch, incomplete_subarch))
3869+ for subarch in subarches:
3870+ factory.make_usable_boot_resource(
3871+ rtype=BOOT_RESOURCE_TYPE.SYNCED,
3872+ name=name, architecture='%s/%s' % (arch, subarch))
3873+ response = self.get_images_ajax()
3874+ json_obj = json.loads(
3875+ response.content.decode(settings.DEFAULT_CHARSET))
3876+ json_resource = json_obj['resources'][0]
3877+ self.assertFalse(json_resource['complete'])
3878+
3879+ def test_combined_subarch_resource_calculates_progress(self):
3880+ self.client_log_in()
3881+ name = 'ubuntu/%s' % factory.make_name('series')
3882+ arch = factory.make_name('arch')
3883+ subarches = [factory.make_name('subarch') for _ in range(3)]
3884+ largefile = factory.make_LargeFile()
3885+ largefile.total_size = largefile.total_size * 2
3886+ largefile.save()
3887+ for subarch in subarches:
3888+ resource = factory.make_BootResource(
3889+ rtype=BOOT_RESOURCE_TYPE.SYNCED,
3890+ name=name, architecture='%s/%s' % (arch, subarch))
3891+ resource_set = factory.make_BootResourceSet(resource)
3892+ factory.make_BootResourceFile(resource_set, largefile)
3893+ response = self.get_images_ajax()
3894+ json_obj = json.loads(
3895+ response.content.decode(settings.DEFAULT_CHARSET))
3896+ json_resource = json_obj['resources'][0]
3897+ self.assertEqual("Downloading 50%", json_resource['status'])
3898+
3899+ def test_combined_subarch_resource_shows_queued_if_no_progress(self):
3900+ self.client_log_in()
3901+ name = 'ubuntu/%s' % factory.make_name('series')
3902+ arch = factory.make_name('arch')
3903+ subarches = [factory.make_name('subarch') for _ in range(3)]
3904+ largefile = factory.make_LargeFile(
3905+ content="".encode(settings.DEFAULT_CHARSET))
3906+ for subarch in subarches:
3907+ resource = factory.make_BootResource(
3908+ rtype=BOOT_RESOURCE_TYPE.SYNCED,
3909+ name=name, architecture='%s/%s' % (arch, subarch))
3910+ resource_set = factory.make_BootResourceSet(resource)
3911+ factory.make_BootResourceFile(resource_set, largefile)
3912+ response = self.get_images_ajax()
3913+ json_obj = json.loads(
3914+ response.content.decode(settings.DEFAULT_CHARSET))
3915+ json_resource = json_obj['resources'][0]
3916+ self.assertEqual("Queued for download", json_resource['status'])
3917+
3918+ def test_combined_subarch_resource_shows_complete_status(self):
3919+ self.client_log_in()
3920+ name = 'ubuntu/%s' % factory.make_name('series')
3921+ arch = factory.make_name('arch')
3922+ subarches = [factory.make_name('subarch') for _ in range(3)]
3923+ resources = [
3924+ factory.make_usable_boot_resource(
3925+ rtype=BOOT_RESOURCE_TYPE.SYNCED,
3926+ name=name, architecture='%s/%s' % (arch, subarch))
3927+ for subarch in subarches
3928+ ]
3929+ self.patch(
3930+ BootResource.objects,
3931+ 'get_resources_matching_boot_images').return_value = resources
3932+ response = self.get_images_ajax()
3933+ json_obj = json.loads(
3934+ response.content.decode(settings.DEFAULT_CHARSET))
3935+ json_resource = json_obj['resources'][0]
3936+ self.assertEqual("Complete", json_resource['status'])
3937+
3938+ def test_combined_subarch_resource_shows_waiting_for_cluster_to_sync(self):
3939+ self.client_log_in()
3940+ name = 'ubuntu/%s' % factory.make_name('series')
3941+ arch = factory.make_name('arch')
3942+ subarches = [factory.make_name('subarch') for _ in range(3)]
3943+ for subarch in subarches:
3944+ factory.make_usable_boot_resource(
3945+ rtype=BOOT_RESOURCE_TYPE.SYNCED,
3946+ name=name, architecture='%s/%s' % (arch, subarch))
3947+ self.patch(
3948+ BootResource.objects,
3949+ 'get_resources_matching_boot_images').return_value = []
3950+ response = self.get_images_ajax()
3951+ json_obj = json.loads(
3952+ response.content.decode(settings.DEFAULT_CHARSET))
3953+ json_resource = json_obj['resources'][0]
3954+ self.assertEqual(
3955+ "Waiting for rack controller(s) to sync", json_resource['status'])
3956+
3957+ def test_combined_subarch_resource_shows_clusters_syncing(self):
3958+ self.client_log_in()
3959+ name = 'ubuntu/%s' % factory.make_name('series')
3960+ arch = factory.make_name('arch')
3961+ subarches = [factory.make_name('subarch') for _ in range(3)]
3962+ for subarch in subarches:
3963+ factory.make_usable_boot_resource(
3964+ rtype=BOOT_RESOURCE_TYPE.SYNCED,
3965+ name=name, architecture='%s/%s' % (arch, subarch))
3966+ self.patch(
3967+ BootResource.objects,
3968+ 'get_resources_matching_boot_images').return_value = []
3969+ self.patch(
3970+ images_view, 'is_import_boot_images_running').return_value = True
3971+ response = self.get_images_ajax()
3972+ json_obj = json.loads(
3973+ response.content.decode(settings.DEFAULT_CHARSET))
3974+ json_resource = json_obj['resources'][0]
3975+ self.assertEqual(
3976+ "Syncing to rack controller(s)", json_resource['status'])
3977+
3978+
3979+class TestImageDelete(MAASServerTestCase):
3980+
3981+ def test_non_admin_cannot_delete(self):
3982+ self.client_log_in()
3983+ resource = factory.make_BootResource(rtype=BOOT_RESOURCE_TYPE.UPLOADED)
3984+ response = self.client.post(
3985+ reverse('image-delete', args=[resource.id]))
3986+ self.assertEqual(http.client.FORBIDDEN, response.status_code)
3987+ self.assertIsNotNone(reload_object(resource))
3988+
3989+ def test_deletes_resource(self):
3990+ self.client_log_in(as_admin=True)
3991+ resource = factory.make_BootResource(rtype=BOOT_RESOURCE_TYPE.UPLOADED)
3992+ response = self.client.post(
3993+ reverse('image-delete', args=[resource.id]),
3994+ {'post': 'yes'})
3995+ self.assertEqual(http.client.FOUND, response.status_code)
3996+ self.assertIsNone(reload_object(resource))
3997+
3998+ def test_redirects_to_images(self):
3999+ self.client_log_in(as_admin=True)
4000+ resource = factory.make_BootResource(rtype=BOOT_RESOURCE_TYPE.UPLOADED)
4001+ response = self.client.post(
4002+ reverse('image-delete', args=[resource.id]),
4003+ {'post': 'yes'})
4004+ self.assertEqual(reverse('images'), extract_redirect(response))
4005
4006=== modified file 'src/maasserver/websockets/handlers/machine.py'
4007=== modified file 'src/maasserver/websockets/handlers/subnet.py'
4008=== modified file 'src/maasserver/websockets/handlers/tests/test_machine.py'
4009=== modified file 'src/maasserver/websockets/handlers/tests/test_subnet.py'
4010=== modified file 'src/maasserver/websockets/tests/test_base.py'
4011=== modified file 'src/metadataserver/api.py'
4012--- src/metadataserver/api.py 2016-09-13 20:58:15 +0000
4013+++ src/metadataserver/api.py 2016-10-26 23:39:26 +0000
4014@@ -168,6 +168,7 @@
4015 else:
4016 type_name = EVENT_TYPES.NODE_COMMISSIONING_EVENT_FAILED
4017 elif node.status == NODE_STATUS.DEPLOYING:
4018+<<<<<<< TREE
4019 if result in ['SUCCESS', None]:
4020 type_name = EVENT_TYPES.NODE_INSTALL_EVENT
4021 else:
4022@@ -177,6 +178,12 @@
4023 type_name = EVENT_TYPES.NODE_ENTERING_RESCUE_MODE_EVENT
4024 else:
4025 type_name = EVENT_TYPES.NODE_ENTERING_RESCUE_MODE_EVENT_FAILED
4026+=======
4027+ if result in ['SUCCESS', None]:
4028+ type_name = EVENT_TYPES.NODE_INSTALL_EVENT
4029+ else:
4030+ type_name = EVENT_TYPES.NODE_INSTALL_EVENT_FAILED
4031+>>>>>>> MERGE-SOURCE
4032 elif node.node_type in [
4033 NODE_TYPE.RACK_CONTROLLER,
4034 NODE_TYPE.REGION_AND_RACK_CONTROLLER]:
4035
4036=== modified file 'src/metadataserver/fields.py'
4037=== modified file 'src/metadataserver/tests/test_fields.py'
4038=== modified file 'src/provisioningserver/boot/powernv.py'
4039=== modified file 'src/provisioningserver/boot/tests/test_powernv.py'
4040=== modified file 'src/provisioningserver/dns/tests/test_zoneconfig.py'
4041--- src/provisioningserver/dns/tests/test_zoneconfig.py 2016-09-12 19:33:29 +0000
4042+++ src/provisioningserver/dns/tests/test_zoneconfig.py 2016-10-26 23:39:26 +0000
4043@@ -210,6 +210,53 @@
4044 )
4045 )
4046
4047+ def test_handles_slash_32_dynamic_range(self):
4048+ target_dir = patch_dns_config_path(self)
4049+ domain = factory.make_string()
4050+ network = factory.make_ipv4_network()
4051+ dns_ip = factory.pick_ip_in_network(network)
4052+ ipv4_hostname = factory.make_name('host')
4053+ ipv4_ip = factory.pick_ip_in_network(network)
4054+ range_ip = factory.pick_ip_in_network(network, but_not={ipv4_ip})
4055+ ipv6_hostname = factory.make_name('host')
4056+ ipv6_ip = factory.make_ipv6_address()
4057+ ttl = random.randint(10, 300)
4058+ mapping = {
4059+ ipv4_hostname: HostnameIPMapping(None, ttl, {ipv4_ip}),
4060+ ipv6_hostname: HostnameIPMapping(None, ttl, {ipv6_ip}),
4061+ }
4062+ dynamic_range = IPRange(IPAddress(range_ip), IPAddress(range_ip))
4063+ expected_generate_directives = (
4064+ DNSForwardZoneConfig.get_GENERATE_directives(
4065+ dynamic_range))
4066+ other_mapping = {ipv4_hostname: HostnameRRsetMapping(
4067+ None, {(ttl, 'MX', '10 bar')})}
4068+ dns_zone_config = DNSForwardZoneConfig(
4069+ domain, serial=random.randint(1, 100),
4070+ other_mapping=other_mapping, default_ttl=ttl,
4071+ mapping=mapping, dns_ip=dns_ip,
4072+ dynamic_ranges=[dynamic_range])
4073+ dns_zone_config.write_config()
4074+ self.assertThat(
4075+ os.path.join(target_dir, 'zone.%s' % domain),
4076+ FileContains(
4077+ matcher=ContainsAll(
4078+ [
4079+ '$TTL %d' % ttl,
4080+ '%s %d IN A %s' % (ipv4_hostname, ttl, ipv4_ip),
4081+ '%s %d IN AAAA %s' % (ipv6_hostname, ttl, ipv6_ip),
4082+ '%s %d IN MX 10 bar' % (ipv4_hostname, ttl),
4083+ ] +
4084+ [
4085+ '$GENERATE %s %s IN A %s' % (
4086+ iterator_values, reverse_dns, hostname)
4087+ for iterator_values, reverse_dns, hostname in
4088+ expected_generate_directives
4089+ ]
4090+ )
4091+ )
4092+ )
4093+
4094 def test_writes_dns_zone_config(self):
4095 target_dir = patch_dns_config_path(self)
4096 domain = factory.make_string()
4097
4098=== modified file 'src/provisioningserver/dns/zoneconfig.py'
4099=== modified file 'src/provisioningserver/events.py'
4100--- src/provisioningserver/events.py 2016-10-25 13:57:02 +0000
4101+++ src/provisioningserver/events.py 2016-10-26 23:39:26 +0000
4102@@ -70,6 +70,7 @@
4103 NODE_COMMISSIONING_EVENT = "NODE_COMMISSIONING_EVENT"
4104 NODE_COMMISSIONING_EVENT_FAILED = "NODE_COMMISSIONING_EVENT_FAILED"
4105 NODE_INSTALL_EVENT = "NODE_INSTALL_EVENT"
4106+<<<<<<< TREE
4107 NODE_INSTALL_EVENT_FAILED = "NODE_INSTALL_EVENT_FAILED"
4108 NODE_ENTERING_RESCUE_MODE_EVENT = "NODE_ENTERING_RESCUE_MODE_EVENT"
4109 NODE_ENTERING_RESCUE_MODE_EVENT_FAILED = (
4110@@ -77,6 +78,9 @@
4111 NODE_EXITING_RESCUE_MODE_EVENT = "NODE_EXITING_RESCUE_MODE_EVENT"
4112 NODE_EXITING_RESCUE_MODE_EVENT_FAILED = (
4113 "NODE_EXITING_RESCUE_MODE_EVENT_FAILED")
4114+=======
4115+ NODE_INSTALL_EVENT_FAILED = "NODE_INSTALL_EVENT_FAILED"
4116+>>>>>>> MERGE-SOURCE
4117 # Node user request events
4118 REQUEST_NODE_START_COMMISSIONING = "REQUEST_NODE_START_COMMISSIONING"
4119 REQUEST_NODE_ABORT_COMMISSIONING = "REQUEST_NODE_ABORT_COMMISSIONING"
4120@@ -180,6 +184,7 @@
4121 description="Node installation",
4122 level=DEBUG,
4123 ),
4124+<<<<<<< TREE
4125 EVENT_TYPES.NODE_INSTALL_EVENT_FAILED: EventDetail(
4126 description="Node installation failure",
4127 level=ERROR,
4128@@ -200,6 +205,12 @@
4129 description="Node exiting rescue mode failure",
4130 level=ERROR,
4131 ),
4132+=======
4133+ EVENT_TYPES.NODE_INSTALL_EVENT_FAILED: EventDetail(
4134+ description="Node installation failure",
4135+ level=ERROR,
4136+ ),
4137+>>>>>>> MERGE-SOURCE
4138 EVENT_TYPES.REQUEST_NODE_START_COMMISSIONING: EventDetail(
4139 description="User starting node commissioning",
4140 level=INFO,
4141
4142=== modified file 'src/provisioningserver/import_images/boot_resources.py'
4143=== modified file 'src/provisioningserver/import_images/tests/test_boot_resources.py'
4144=== modified file 'src/provisioningserver/plugin.py'
4145--- src/provisioningserver/plugin.py 2016-10-19 17:06:30 +0000
4146+++ src/provisioningserver/plugin.py 2016-10-26 23:39:26 +0000
4147@@ -179,10 +179,18 @@
4148 import crochet
4149 crochet.no_setup()
4150
4151+<<<<<<< TREE
4152 def _configureLogging(self, verbosity: int):
4153 # Get something going with the logs.
4154 logger.configure(verbosity, logger.LoggingMode.TWISTD)
4155
4156+=======
4157+ def _configureLogging(self):
4158+ # Get something going with the logs.
4159+ from provisioningserver import logger
4160+ logger.basicConfig()
4161+
4162+>>>>>>> MERGE-SOURCE
4163 def makeService(self, options):
4164 """Construct the MAAS Cluster service."""
4165 register_sigusr2_thread_dump_handler()
4166@@ -190,7 +198,11 @@
4167 add_patches_to_twisted()
4168
4169 self._configureCrochet()
4170+<<<<<<< TREE
4171 self._configureLogging(options["verbosity"])
4172+=======
4173+ self._configureLogging()
4174+>>>>>>> MERGE-SOURCE
4175
4176 with ClusterConfiguration.open() as config:
4177 tftp_root = config.tftp_root
4178
4179=== modified file 'src/provisioningserver/power/query.py'
4180=== modified file 'src/provisioningserver/power/schema.py'
4181--- src/provisioningserver/power/schema.py 2016-10-20 20:55:30 +0000
4182+++ src/provisioningserver/power/schema.py 2016-10-26 23:39:26 +0000
4183@@ -349,6 +349,7 @@
4184 'description': 'Digital Loggers, Inc. PDU',
4185 'fields': [
4186 make_json_field(
4187+<<<<<<< TREE
4188 'outlet_id', "Outlet ID", scope=POWER_PARAMETER_SCOPE.NODE,
4189 required=True),
4190 make_json_field('power_address', "Power address", required=True),
4191@@ -363,6 +364,11 @@
4192 'description': "Facebook's Wedge",
4193 'fields': [
4194 make_json_field('power_address', "IP address", required=True),
4195+=======
4196+ 'outlet_id', "Outlet ID", scope=POWER_PARAMETER_SCOPE.NODE,
4197+ required=True),
4198+ make_json_field('power_address', "Power address", required=True),
4199+>>>>>>> MERGE-SOURCE
4200 make_json_field('power_user', "Power user"),
4201 make_json_field(
4202 'power_pass', "Power password", field_type='password'),
4203
4204=== modified file 'src/provisioningserver/power/tests/test_query.py'
4205=== modified file 'src/provisioningserver/rackdservices/tests/test_tftp.py'
4206=== modified file 'src/provisioningserver/rackdservices/tftp.py'
4207=== modified file 'src/provisioningserver/refresh/tests/test_node_info_scripts.py'
4208=== modified file 'src/provisioningserver/templates/commissioning-user-data/snippets/maas_enlist.sh'
4209=== modified file 'src/provisioningserver/tests/test_plugin.py'
4210--- src/provisioningserver/tests/test_plugin.py 2016-10-19 21:08:54 +0000
4211+++ src/provisioningserver/tests/test_plugin.py 2016-10-26 23:39:26 +0000
4212@@ -81,7 +81,11 @@
4213 self.useFixture(ClusterConfigurationFixture())
4214 self.patch(provisioningserver, "services", MultiService())
4215 self.patch_autospec(crochet, "no_setup")
4216+<<<<<<< TREE
4217 self.patch_autospec(logger, "configure")
4218+=======
4219+ self.patch_autospec(logger, "basicConfig")
4220+>>>>>>> MERGE-SOURCE
4221 self.tempdir = self.make_dir()
4222
4223 def test_init(self):
4224@@ -108,9 +112,13 @@
4225 "Not all services are named.")
4226 self.assertEqual(service, provisioningserver.services)
4227 self.assertThat(crochet.no_setup, MockCalledOnceWith())
4228+<<<<<<< TREE
4229 self.assertThat(
4230 logger.configure, MockCalledOnceWith(
4231 options["verbosity"], logger.LoggingMode.TWISTD))
4232+=======
4233+ self.assertThat(logger.basicConfig, MockCalledOnceWith())
4234+>>>>>>> MERGE-SOURCE
4235
4236 def test_makeService_patches_tftp_service(self):
4237 mock_tftp_patch = (
4238
4239=== modified file 'src/provisioningserver/utils/tests/test_env.py'