diff --git a/app/code/Magento/AdminNotification/Ui/Component/DataProvider/DataProvider.php b/app/code/Magento/AdminNotification/Ui/Component/DataProvider/DataProvider.php
new file mode 100644
index 0000000000000000000000000000000000000000..296fcf5c461ad4521fed496e376ed9289f50cf0c
--- /dev/null
+++ b/app/code/Magento/AdminNotification/Ui/Component/DataProvider/DataProvider.php
@@ -0,0 +1,36 @@
+<?php
+/**
+ * Copyright © 2016 Magento. All rights reserved.
+ * See COPYING.txt for license details.
+ */
+
+namespace Magento\AdminNotification\Ui\Component\DataProvider;
+
+use Magento\AdminNotification\Model\ResourceModel\System\Message\Collection\SynchronizedFactory;
+
+/**
+ * Class DataProvider
+ */
+class DataProvider extends \Magento\Ui\DataProvider\AbstractDataProvider
+{
+    /**
+     * DataProvider constructor.
+     * @param string $name
+     * @param string $primaryFieldName
+     * @param string $requestFieldName
+     * @param SynchronizedFactory $messageCollectionFactory
+     * @param array $meta
+     * @param array $data
+     */
+    public function __construct(
+        $name,
+        $primaryFieldName,
+        $requestFieldName,
+        SynchronizedFactory $messageCollectionFactory,
+        array $meta = [],
+        array $data = []
+    ) {
+        $this->collection = $messageCollectionFactory->create();
+        parent::__construct($name, $primaryFieldName, $requestFieldName, $meta, $data);
+    }
+}
diff --git a/app/code/Magento/AdminNotification/composer.json b/app/code/Magento/AdminNotification/composer.json
index 0a29908b77f6f4f6511fe5671702217ef95ceb90..527268df36b41efe5078528138342a03fa33745d 100644
--- a/app/code/Magento/AdminNotification/composer.json
+++ b/app/code/Magento/AdminNotification/composer.json
@@ -7,6 +7,7 @@
         "magento/module-backend": "100.2.*",
         "magento/module-media-storage": "100.2.*",
         "magento/framework": "100.2.*",
+        "magento/module-ui": "100.2.*",
         "lib-libxml": "*"
     },
     "type": "magento2-module",
diff --git a/app/code/Magento/AdminNotification/view/adminhtml/layout/default.xml b/app/code/Magento/AdminNotification/view/adminhtml/layout/default.xml
index e3d32f17b6356bc974627b0daaba5dca5c467eea..780e48378a3a4069c29b50153eb6ca013f36147e 100644
--- a/app/code/Magento/AdminNotification/view/adminhtml/layout/default.xml
+++ b/app/code/Magento/AdminNotification/view/adminhtml/layout/default.xml
@@ -8,11 +8,7 @@
 <page xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="urn:magento:framework:View/Layout/etc/page_configuration.xsd">
     <body>
         <referenceContainer name="notifications">
-            <block class="Magento\AdminNotification\Block\System\Messages"
-                   name="system_messages"
-                   as="system_messages"
-                   before="-"
-                   template="Magento_AdminNotification::system/messages.phtml"/>
+            <uiComponent name="notification_area"/>
             <block class="Magento\AdminNotification\Block\System\Messages\UnreadMessagePopup"
                    name="unread_system_messages"
                    as="unread_system_messages"
diff --git a/app/code/Magento/AdminNotification/view/adminhtml/ui_component/notification_area.xml b/app/code/Magento/AdminNotification/view/adminhtml/ui_component/notification_area.xml
new file mode 100644
index 0000000000000000000000000000000000000000..e0149fff714deaa5fc95910169ec92a26e4c673c
--- /dev/null
+++ b/app/code/Magento/AdminNotification/view/adminhtml/ui_component/notification_area.xml
@@ -0,0 +1,52 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+/**
+ * Copyright © 2016 Magento. All rights reserved.
+ * See COPYING.txt for license details.
+ */
+-->
+<listing xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="urn:magento:module:Magento_Ui:etc/ui_configuration.xsd">
+    <argument name="data" xsi:type="array">
+        <item name="js_config" xsi:type="array">
+            <item name="provider" xsi:type="string">notification_area.notification_area_data_source</item>
+            <item name="deps" xsi:type="string">notification_area.notification_area_data_source</item>
+        </item>
+        <item name="spinner" xsi:type="string">columns</item>
+    </argument>
+    <dataSource name="notification_area_data_source">
+        <argument name="dataProvider" xsi:type="configurableObject">
+            <argument name="class" xsi:type="string">Magento\AdminNotification\Ui\Component\DataProvider\DataProvider</argument>
+            <argument name="name" xsi:type="string">notification_area_data_source</argument>
+            <argument name="primaryFieldName" xsi:type="string">identity</argument>
+            <argument name="requestFieldName" xsi:type="string">identity</argument>
+            <argument name="data" xsi:type="array">
+                <item name="config" xsi:type="array">
+                    <item name="component" xsi:type="string">Magento_Ui/js/grid/provider</item>
+                    <item name="update_url" xsi:type="url" path="mui/index/render"/>
+                    <item name="storageConfig" xsi:type="array">
+                        <item name="indexField" xsi:type="string">identity</item>
+                    </item>
+                </item>
+            </argument>
+        </argument>
+    </dataSource>
+    <columns name="columns">
+        <argument name="data" xsi:type="array">
+            <item name="config" xsi:type="array">
+                <item name="component" xsi:type="string">Magento_AdminNotification/js/grid/listing</item>
+                <item name="template" xsi:type="string">Magento_AdminNotification/grid/listing</item>
+            </item>
+        </argument>
+        <column name="created_at">
+            <argument name="data" xsi:type="array">
+                <item name="config" xsi:type="array">
+                    <item name="component" xsi:type="string">Magento_AdminNotification/js/grid/columns/message</item>
+                    <item name="label" xsi:type="string" translate="true"/>
+                    <item name="dataType" xsi:type="string">text</item>
+                    <item name="sorting" xsi:type="string">asc</item>
+                    <item name="sortOrder" xsi:type="number">30</item>
+                </item>
+            </argument>
+        </column>
+    </columns>
+</listing>
diff --git a/app/code/Magento/AdminNotification/view/adminhtml/web/js/grid/columns/message.js b/app/code/Magento/AdminNotification/view/adminhtml/web/js/grid/columns/message.js
new file mode 100644
index 0000000000000000000000000000000000000000..aa5477ebafcf0df511981f2f874c7a1e4c7081bb
--- /dev/null
+++ b/app/code/Magento/AdminNotification/view/adminhtml/web/js/grid/columns/message.js
@@ -0,0 +1,46 @@
+/**
+ * Copyright © 2016 Magento. All rights reserved.
+ * See COPYING.txt for license details.
+ */
+define([
+    'Magento_Ui/js/grid/columns/column',
+    'underscore'
+], function (Column, _) {
+    'use strict';
+
+    return Column.extend({
+        defaults: {
+            bodyTmpl: 'Magento_AdminNotification/grid/cells/message',
+            messageIndex: 'text',
+            fieldClass: {
+                message: true,
+                'message-warning': false,
+                'message-progress': false,
+                'message-success': false,
+                'message-error': false
+            },
+            statusMap: {
+                0: 'info',
+                1: 'progress',
+                2: 'success',
+                3: 'error'
+            }
+        },
+
+        /** @inheritdoc */
+        getLabel: function (record) {
+            return record[this.messageIndex];
+        },
+
+        /** @inheritdoc */
+        getFieldClass: function ($row) {
+            var status = this.statusMap[$row.status] || 'warning',
+                result = {};
+
+            result['message-' + status] = true;
+            result = _.extend({}, this.fieldClass, result);
+
+            return result;
+        }
+    });
+});
diff --git a/app/code/Magento/AdminNotification/view/adminhtml/web/js/grid/listing.js b/app/code/Magento/AdminNotification/view/adminhtml/web/js/grid/listing.js
new file mode 100644
index 0000000000000000000000000000000000000000..1fbda8d0196ca1fa9e1b58f40d0804892bf70891
--- /dev/null
+++ b/app/code/Magento/AdminNotification/view/adminhtml/web/js/grid/listing.js
@@ -0,0 +1,63 @@
+/**
+ * Copyright © 2016 Magento. All rights reserved.
+ * See COPYING.txt for license details.
+ */
+
+define([
+    'Magento_Ui/js/grid/listing',
+    'Magento_Ui/js/lib/spinner',
+    'jquery'
+], function (Listing, loader, $) {
+    'use strict';
+
+    return Listing.extend({
+        defaults: {
+            imports: {
+                totalRecords: '${ $.provider }:data.totalRecords'
+            },
+            selectors: {
+                collapsible: '.message-system-collapsible',
+                messages: '.message-system'
+            }
+        },
+
+        /** @inheritdoc */
+        initObservable: function () {
+            this._super()
+                .track({
+                    totalRecords: 0
+                });
+
+            return this;
+        },
+
+        /** @inheritdoc */
+        showLoader: function () {
+            if (!this.source.firstLoad) {
+                this.fixLoaderHeight();
+                this._super();
+            }
+        },
+
+        /**
+         * Calculates loader height
+         *
+         * @param {Boolean} [closed]
+         */
+        fixLoaderHeight: function (closed) {
+            var $messagesBlock = $(this.selectors.messages),
+                $collapsibleBlock = $(this.selectors.collapsible),
+                resultHeight = 0;
+
+            if ($messagesBlock.length) {
+                resultHeight += $messagesBlock.outerHeight();
+            }
+
+            if ($collapsibleBlock.length && $collapsibleBlock.is(':visible') && !closed) {
+                resultHeight += $collapsibleBlock.outerHeight();
+            }
+
+            loader.get(this.name).height(resultHeight);
+        }
+    });
+});
diff --git a/app/code/Magento/AdminNotification/view/adminhtml/web/template/grid/cells/message.html b/app/code/Magento/AdminNotification/view/adminhtml/web/template/grid/cells/message.html
new file mode 100644
index 0000000000000000000000000000000000000000..869842e8ee87bcc9465d3763dc610c95361cad72
--- /dev/null
+++ b/app/code/Magento/AdminNotification/view/adminhtml/web/template/grid/cells/message.html
@@ -0,0 +1,8 @@
+<!--
+/**
+ * Copyright © 2016 Magento. All rights reserved.
+ * See COPYING.txt for license details.
+ */
+-->
+<div css="$col.getFieldClass($row())"
+     html="$col.getLabel($row())"/>
\ No newline at end of file
diff --git a/app/code/Magento/AdminNotification/view/adminhtml/web/template/grid/listing.html b/app/code/Magento/AdminNotification/view/adminhtml/web/template/grid/listing.html
new file mode 100644
index 0000000000000000000000000000000000000000..80f2f4f2423fbcad5ba9b8e030e56c806db5ef9a
--- /dev/null
+++ b/app/code/Magento/AdminNotification/view/adminhtml/web/template/grid/listing.html
@@ -0,0 +1,32 @@
+<!--
+/**
+ * Copyright © 2016 Magento. All rights reserved.
+ * See COPYING.txt for license details.
+ */
+-->
+<div id="system_messages" class="message-system" collapsible visible="totalRecords">
+    <div class="message-system-inner" outerClick="fixLoaderHeight.bind($data, true)">
+        <div class="message-system-short">
+            <button class="message-system-action-dropdown" toggleCollapsible>
+                <span>
+                    <translate args="'System Messages'"/>:
+                    <text args="totalRecords"/>
+                </span>
+            </button>
+            <div class="message-system-short-wrapper" if="rows[0]" repeat="foreach: [rows[0]], item: '$row'" visible="!$collapsible.opened()">
+                <fastForEach args="data: getVisible(), as: '$col'" >
+                    <render args="$col.getBody()"/>
+                </fastForEach>
+            </div>
+        </div>
+        <div class="message-system-collapsible">
+            <ul class="message-system-list">
+                <li repeat="foreach: rows, item: '$row'">
+                    <fastForEach args="data: getVisible(), as: '$col'" >
+                        <render args="$col.getBody()"/>
+                    </fastForEach>
+                </li>
+            </ul>
+        </div>
+    </div>
+</div>
diff --git a/app/code/Magento/Catalog/Api/Data/ProductAttributeInterface.php b/app/code/Magento/Catalog/Api/Data/ProductAttributeInterface.php
index 4025b2accbb178e7685947ccfdff9605e41fabf3..804f06ff667a3ce26144e60b8c3297d04faaf8ed 100644
--- a/app/code/Magento/Catalog/Api/Data/ProductAttributeInterface.php
+++ b/app/code/Magento/Catalog/Api/Data/ProductAttributeInterface.php
@@ -12,7 +12,6 @@ namespace Magento\Catalog\Api\Data;
 interface ProductAttributeInterface extends \Magento\Catalog\Api\Data\EavAttributeInterface
 {
     const ENTITY_TYPE_CODE = 'catalog_product';
-    const CODE_TIER_PRICE_FIELD_PRICE = 'price';
     const CODE_HAS_WEIGHT = 'product_has_weight';
     const CODE_SPECIAL_PRICE = 'special_price';
     const CODE_PRICE = 'price';
@@ -27,6 +26,9 @@ interface ProductAttributeInterface extends \Magento\Catalog\Api\Data\EavAttribu
     const CODE_COST = 'cost';
     const CODE_SEO_FIELD_URL_KEY = 'url_key';
     const CODE_TIER_PRICE = 'tier_price';
+    const CODE_TIER_PRICE_FIELD_PRICE = 'price';
+    const CODE_TIER_PRICE_FIELD_PERCENTAGE_VALUE = 'percentage_value';
+    const CODE_TIER_PRICE_FIELD_VALUE_TYPE = 'value_type';
     const CODE_SEO_FIELD_META_DESCRIPTION = 'meta_description';
     const CODE_WEIGHT = 'weight';
 }
diff --git a/app/code/Magento/Catalog/Api/ProductTierPriceManagementInterface.php b/app/code/Magento/Catalog/Api/ProductTierPriceManagementInterface.php
index d587aec01ccb6f062e6e5bdb4abe62a5031d519b..f02c3afa5aed9de2235e96648ae8de47b337d54f 100644
--- a/app/code/Magento/Catalog/Api/ProductTierPriceManagementInterface.php
+++ b/app/code/Magento/Catalog/Api/ProductTierPriceManagementInterface.php
@@ -8,6 +8,7 @@ namespace Magento\Catalog\Api;
 
 /**
  * @api
+ * @deprecated use ScopedProductTierPriceManagementInterface instead
  */
 interface ProductTierPriceManagementInterface
 {
diff --git a/app/code/Magento/Catalog/Api/ScopedProductTierPriceManagementInterface.php b/app/code/Magento/Catalog/Api/ScopedProductTierPriceManagementInterface.php
new file mode 100644
index 0000000000000000000000000000000000000000..8a27480b776b643f86079af240fe179f33ac1573
--- /dev/null
+++ b/app/code/Magento/Catalog/Api/ScopedProductTierPriceManagementInterface.php
@@ -0,0 +1,45 @@
+<?php
+/**
+ *
+ * Copyright © 2016 Magento. All rights reserved.
+ * See COPYING.txt for license details.
+ */
+namespace Magento\Catalog\Api;
+
+/**
+ * @api
+ */
+interface ScopedProductTierPriceManagementInterface
+{
+    /**
+     * Create tier price for product
+     *
+     * @param string $sku
+     * @param \Magento\Catalog\Api\Data\ProductTierPriceInterface $tierPrice
+     * @return boolean
+     * @throws \Magento\Framework\Exception\NoSuchEntityException
+     * @throws \Magento\Framework\Exception\CouldNotSaveException
+     */
+    public function add($sku, \Magento\Catalog\Api\Data\ProductTierPriceInterface $tierPrice);
+
+    /**
+     * Remove tier price from product
+     *
+     * @param string $sku
+     * @param \Magento\Catalog\Api\Data\ProductTierPriceInterface $tierPrice
+     * @return boolean
+     * @throws \Magento\Framework\Exception\NoSuchEntityException
+     * @throws \Magento\Framework\Exception\CouldNotSaveException
+     */
+    public function remove($sku, \Magento\Catalog\Api\Data\ProductTierPriceInterface $tierPrice);
+
+    /**
+     * Get tier price of product
+     *
+     * @param string $sku
+     * @param string $customerGroupId 'all' can be used to specify 'ALL GROUPS'
+     * @return \Magento\Catalog\Api\Data\ProductTierPriceInterface[]
+     * @throws \Magento\Framework\Exception\NoSuchEntityException
+     */
+    public function getList($sku, $customerGroupId);
+}
diff --git a/app/code/Magento/Catalog/Model/Config/Source/Product/Options/Price.php b/app/code/Magento/Catalog/Model/Config/Source/Product/Options/Price.php
index 9eb5e2e1fc770a8cb776e39b77d3a886d766b192..b994c787bee7aa76f8a4baa3648bcbb2c0e4ee27 100644
--- a/app/code/Magento/Catalog/Model/Config/Source/Product/Options/Price.php
+++ b/app/code/Magento/Catalog/Model/Config/Source/Product/Options/Price.php
@@ -5,12 +5,14 @@
  */
 namespace Magento\Catalog\Model\Config\Source\Product\Options;
 
+use Magento\Catalog\Model\Config\Source\ProductPriceOptionsInterface;
+
 /**
  * Price types mode source
  *
  * @author     Magento Core Team <core@magentocommerce.com>
  */
-class Price implements \Magento\Framework\Option\ArrayInterface
+class Price implements ProductPriceOptionsInterface
 {
     /**
      * {@inheritdoc}
@@ -20,8 +22,8 @@ class Price implements \Magento\Framework\Option\ArrayInterface
     public function toOptionArray()
     {
         return [
-            ['value' => 'fixed', 'label' => __('Fixed')],
-            ['value' => 'percent', 'label' => __('Percent')]
+            ['value' => self::VALUE_FIXED, 'label' => __('Fixed')],
+            ['value' => self::VALUE_PERCENT, 'label' => __('Percent')],
         ];
     }
 }
diff --git a/app/code/Magento/Catalog/Model/Config/Source/ProductPriceOptionsInterface.php b/app/code/Magento/Catalog/Model/Config/Source/ProductPriceOptionsInterface.php
new file mode 100644
index 0000000000000000000000000000000000000000..d5d5062bad75832a5d712becbe67442fd7a10ac1
--- /dev/null
+++ b/app/code/Magento/Catalog/Model/Config/Source/ProductPriceOptionsInterface.php
@@ -0,0 +1,21 @@
+<?php
+/**
+ * Copyright © 2016 Magento. All rights reserved.
+ * See COPYING.txt for license details.
+ */
+namespace Magento\Catalog\Model\Config\Source;
+
+use Magento\Framework\Data\OptionSourceInterface;
+
+/**
+ * Interface ProductPriceOptionsInterface
+ */
+interface ProductPriceOptionsInterface extends OptionSourceInterface
+{
+    /**#@+
+     * Values
+     */
+    const VALUE_FIXED = 'fixed';
+    const VALUE_PERCENT = 'percent';
+    /**#@-*/
+}
diff --git a/app/code/Magento/Catalog/Model/Indexer/Product/Price/AbstractAction.php b/app/code/Magento/Catalog/Model/Indexer/Product/Price/AbstractAction.php
index 720e491cef05e9de600c0db36ce4828ede4e6344..464adb27686d02882a91b1f318e8e998fdcd4a5c 100644
--- a/app/code/Magento/Catalog/Model/Indexer/Product/Price/AbstractAction.php
+++ b/app/code/Magento/Catalog/Model/Indexer/Product/Price/AbstractAction.php
@@ -70,6 +70,11 @@ abstract class AbstractAction
      */
     protected $_indexers;
 
+    /**
+     * @var \Magento\Catalog\Model\ResourceModel\Product
+     */
+    private $productResource;
+
     /**
      * @param \Magento\Framework\App\Config\ScopeConfigInterface $config
      * @param \Magento\Store\Model\StoreManagerInterface $storeManager
@@ -213,12 +218,19 @@ abstract class AbstractAction
         $table = $this->_defaultIndexerResource->getTable('catalog_product_index_tier_price');
         $this->_emptyTable($table);
 
+        $tierPriceExpression = $this->_connection->getCheckSql(
+            'tp.value = 0',
+            'product_price.value * (1 - tp.percentage_value / 100)',
+            'tp.value'
+        );
         $websiteExpression = $this->_connection->getCheckSql(
             'tp.website_id = 0',
-            'ROUND(tp.value * cwd.rate, 4)',
-            'tp.value'
+            'ROUND(' . $tierPriceExpression . ' * cwd.rate, 4)',
+            $tierPriceExpression
         );
         $linkField = $this->getProductIdFieldName();
+        $priceAttribute = $this->getProductResource()->getAttribute('price');
+
         $select = $this->_connection->select()->from(
             ['cpe' => $this->_defaultIndexerResource->getTable('catalog_product_entity')],
             ['cpe.entity_id']
@@ -238,8 +250,15 @@ abstract class AbstractAction
             ['cwd' => $this->_defaultIndexerResource->getTable('catalog_product_index_website')],
             'cw.website_id = cwd.website_id',
             []
+        )->join(
+            ['product_price' => $priceAttribute->getBackend()->getTable()],
+            'tp.' . $linkField . ' = product_price.' . $linkField,
+            []
         )->where(
             'cw.website_id != 0'
+        )->where(
+            'product_price.attribute_id = ?',
+            $priceAttribute->getAttributeId()
         )->columns(
             new \Zend_Db_Expr("MIN({$websiteExpression})")
         )->group(
@@ -462,4 +481,17 @@ abstract class AbstractAction
         $indexList = $this->_connection->getIndexList($table);
         return $indexList[$this->_connection->getPrimaryKeyName($table)]['COLUMNS_LIST'][0];
     }
+
+    /**
+     * @return \Magento\Catalog\Model\ResourceModel\Product
+     * @deprecated
+     */
+    private function getProductResource()
+    {
+        if (null === $this->productResource) {
+            $this->productResource = \Magento\Framework\App\ObjectManager::getInstance()
+                ->get(\Magento\Catalog\Model\ResourceModel\Product::class);
+        }
+        return $this->productResource;
+    }
 }
diff --git a/app/code/Magento/Catalog/Model/Product/Attribute/Backend/GroupPrice/AbstractGroupPrice.php b/app/code/Magento/Catalog/Model/Product/Attribute/Backend/GroupPrice/AbstractGroupPrice.php
index d104dc2ab687f076fb6bbd70ab2abcd65028d0df..3c5cef0e6ce78c10d7feea308ec622f201ff1b8c 100644
--- a/app/code/Magento/Catalog/Model/Product/Attribute/Backend/GroupPrice/AbstractGroupPrice.php
+++ b/app/code/Magento/Catalog/Model/Product/Attribute/Backend/GroupPrice/AbstractGroupPrice.php
@@ -129,6 +129,18 @@ abstract class AbstractGroupPrice extends Price
         return [];
     }
 
+    /**
+     * Get additional fields
+     *
+     * @param array $objectArray
+     * @return array
+     * @SuppressWarnings(PHPMD.UnusedFormalParameter)
+     */
+    protected function getAdditionalFields($objectArray)
+    {
+        return [];
+    }
+
     /**
      * Whether group price value fixed or percent of original price
      *
@@ -176,9 +188,7 @@ abstract class AbstractGroupPrice extends Price
                 throw new \Magento\Framework\Exception\LocalizedException(__($this->_getDuplicateErrorMessage()));
             }
 
-            if (!$this->isPositiveOrZero($priceRow['price'])) {
-                return __('Group price must be a number greater than 0.');
-            }
+            $this->validatePrice($priceRow);
 
             $duplicates[$compare] = true;
         }
@@ -228,6 +238,20 @@ abstract class AbstractGroupPrice extends Price
         return true;
     }
 
+    /**
+     * @param array $priceRow
+     * @return void
+     * @throws \Magento\Framework\Exception\LocalizedException
+     */
+    protected function validatePrice(array $priceRow)
+    {
+        if (!isset($priceRow['price']) || !$this->isPositiveOrZero($priceRow['price'])) {
+            throw new \Magento\Framework\Exception\LocalizedException(
+                __('Group price must be a number greater than 0.')
+            );
+        }
+    }
+
     /**
      * Prepare group prices data for website
      *
@@ -270,37 +294,70 @@ abstract class AbstractGroupPrice extends Price
      */
     public function afterLoad($object)
     {
-        $storeId = $object->getStoreId();
+        $data = $this->_getResource()->loadPriceData(
+            $object->getData($this->getMetadataPool()->getMetadata(ProductInterface::class)->getLinkField()),
+            $this->getWebsiteId($object->getStoreId())
+        );
+        $this->setPriceData($object, $data);
+
+        return $this;
+    }
+
+    /**
+     * @param int $storeId
+     * @return int|null
+     */
+    private function getWebsiteId($storeId)
+    {
         $websiteId = null;
         if ($this->getAttribute()->isScopeGlobal()) {
             $websiteId = 0;
         } elseif ($storeId) {
             $websiteId = $this->_storeManager->getStore($storeId)->getWebsiteId();
         }
+        return $websiteId;
+    }
 
-        $data = $this->_getResource()->loadPriceData(
-            $object->getData($this->getMetadataPool()->getMetadata(ProductInterface::class)->getLinkField()),
-            $websiteId
-        );
-        foreach ($data as $k => $v) {
-            $data[$k]['website_price'] = $v['price'];
-            if ($v['all_groups']) {
-                $data[$k]['cust_group'] = $this->_groupManagement->getAllCustomersGroup()->getId();
-            }
-        }
-
+    /**
+     * @param \Magento\Catalog\Model\Product $object
+     * @param array $priceData
+     */
+    public function setPriceData($object, $priceData)
+    {
+        $priceData = $this->modifyPriceData($object, $priceData);
+        $websiteId = $this->getWebsiteId($object->getStoreId());
         if (!$object->getData('_edit_mode') && $websiteId) {
-            $data = $this->preparePriceData($data, $object->getTypeId(), $websiteId);
+            $priceData = $this->preparePriceData($priceData, $object->getTypeId(), $websiteId);
         }
 
-        $object->setData($this->getAttribute()->getName(), $data);
-        $object->setOrigData($this->getAttribute()->getName(), $data);
+        $object->setData($this->getAttribute()->getName(), $priceData);
+        $object->setOrigData($this->getAttribute()->getName(), $priceData);
 
         $valueChangedKey = $this->getAttribute()->getName() . '_changed';
         $object->setOrigData($valueChangedKey, 0);
         $object->setData($valueChangedKey, 0);
+    }
 
-        return $this;
+    /**
+     * Perform price modification
+     *
+     * @param \Magento\Catalog\Model\Product $object
+     * @param array $data
+     * @return array
+     * @SuppressWarnings(PHPMD.UnusedFormalParameter)
+     */
+    protected function modifyPriceData($object, $data)
+    {
+        /** @var array $priceItem */
+        foreach ($data as $key => $priceItem) {
+            if (isset($priceItem['price']) && $priceItem['price'] > 0) {
+                $data[$key]['website_price'] = $priceItem['price'];
+            }
+            if ($priceItem['all_groups']) {
+                $data[$key]['cust_group'] = $this->_groupManagement->getAllCustomersGroup()->getId();
+            }
+        }
+        return $data;
     }
 
     /**
@@ -372,13 +429,13 @@ abstract class AbstractGroupPrice extends Price
 
             $useForAllGroups = $data['cust_group'] == $this->_groupManagement->getAllCustomersGroup()->getId();
             $customerGroupId = !$useForAllGroups ? $data['cust_group'] : 0;
-
             $new[$key] = array_merge(
+                $this->getAdditionalFields($data),
                 [
                     'website_id' => $data['website_id'],
                     'all_groups' => $useForAllGroups ? 1 : 0,
                     'customer_group_id' => $customerGroupId,
-                    'value' => $data['price'],
+                    'value' => isset($data['price']) ? $data['price'] : null,
                 ],
                 $this->_getAdditionalUniqueFields($data)
             );
@@ -412,14 +469,7 @@ abstract class AbstractGroupPrice extends Price
         }
 
         if (!empty($update)) {
-            foreach ($update as $k => $v) {
-                if ($old[$k]['price'] != $v['value']) {
-                    $price = new \Magento\Framework\DataObject(['value_id' => $old[$k]['price_id'], 'value' => $v['value']]);
-                    $this->_getResource()->savePriceData($price);
-
-                    $isChanged = true;
-                }
-            }
+            $isChanged = $this->updateValues($update, $old);
         }
 
         if ($isChanged) {
@@ -430,6 +480,29 @@ abstract class AbstractGroupPrice extends Price
         return $this;
     }
 
+    /**
+     * @param array $valuesToUpdate
+     * @param array $oldValues
+     * @return boolean
+     */
+    protected function updateValues(array $valuesToUpdate, array $oldValues)
+    {
+        $isChanged = false;
+        foreach ($valuesToUpdate as $key => $value) {
+            if ($oldValues[$key]['price'] != $value['value']) {
+                $price = new \Magento\Framework\DataObject(
+                    [
+                        'value_id' => $oldValues[$key]['price_id'],
+                        'value' => $value['value']
+                    ]
+                );
+                $this->_getResource()->savePriceData($price);
+                $isChanged = true;
+            }
+        }
+        return $isChanged;
+    }
+
     /**
      * Retrieve data for update attribute
      *
diff --git a/app/code/Magento/Catalog/Model/Product/Attribute/Backend/Tierprice.php b/app/code/Magento/Catalog/Model/Product/Attribute/Backend/Tierprice.php
index fc0fc07d4d524db04afd4b18b304e1e074980634..480f8e8942e87f200ce3829641ea026d351b1967 100644
--- a/app/code/Magento/Catalog/Model/Product/Attribute/Backend/Tierprice.php
+++ b/app/code/Magento/Catalog/Model/Product/Attribute/Backend/Tierprice.php
@@ -75,6 +75,18 @@ class Tierprice extends \Magento\Catalog\Model\Product\Attribute\Backend\GroupPr
         return $uniqueFields;
     }
 
+    /**
+     * @inheritdoc
+     */
+    protected function getAdditionalFields($objectArray)
+    {
+        $percentageValue = $this->getPercentage($objectArray);
+        return [
+            'value' => $percentageValue ? null : $objectArray['price'],
+            'percentage_value' => $percentageValue ?: null,
+        ];
+    }
+
     /**
      * Error message when duplicates
      *
@@ -105,4 +117,89 @@ class Tierprice extends \Magento\Catalog\Model\Product\Attribute\Backend\GroupPr
     {
         return false;
     }
+
+    /**
+     * @inheritdoc
+     */
+    public function validate($object)
+    {
+        $attribute = $this->getAttribute();
+        $priceRows = $object->getData($attribute->getName());
+        $priceRows = array_filter((array)$priceRows);
+
+        foreach ($priceRows as $priceRow) {
+            $percentage = $this->getPercentage($priceRow);
+            if ($percentage !== null && (!$this->isPositiveOrZero($percentage) || $percentage > 100)) {
+                throw new \Magento\Framework\Exception\LocalizedException(
+                    __('Percentage value must be a number between 0 and 100.')
+                );
+            }
+        }
+
+        return parent::validate($object);
+    }
+
+    /**
+     * @inheritdoc
+     */
+    protected function validatePrice(array $priceRow)
+    {
+        if (!$this->getPercentage($priceRow)) {
+            parent::validatePrice($priceRow);
+        }
+    }
+
+    /**
+     * @inheritdoc
+     */
+    protected function modifyPriceData($object, $data)
+    {
+        $data = parent::modifyPriceData($object, $data);
+        foreach ($data as $key => $tierPrice) {
+            if ($this->getPercentage($tierPrice)) {
+                $data[$key]['website_price'] = $object->getPrice() * (1 - $this->getPercentage($tierPrice) / 100);
+            }
+        }
+        return $data;
+    }
+
+    /**
+     * @param array $valuesToUpdate
+     * @param array $oldValues
+     * @return boolean
+     */
+    protected function updateValues(array $valuesToUpdate, array $oldValues)
+    {
+        $isChanged = false;
+        foreach ($valuesToUpdate as $key => $value) {
+            if ($oldValues[$key]['price'] != $value['value']
+                || $this->getPercentage($oldValues[$key]) != $this->getPercentage($value)
+            ) {
+                $price = new \Magento\Framework\DataObject(
+                    [
+                        'value_id' => $oldValues[$key]['price_id'],
+                        'value' => $value['value'],
+                        'percentage_value' => $this->getPercentage($value)
+                    ]
+                );
+                $this->_getResource()->savePriceData($price);
+
+                $isChanged = true;
+            }
+        }
+        return $isChanged;
+    }
+
+    /**
+     * Check whether price has percentage value.
+     *
+     * @param array $priceRow
+     * @return null
+     */
+    private function getPercentage($priceRow)
+    {
+        return isset($priceRow['percentage_value']) && is_numeric($priceRow['percentage_value'])
+            ? $priceRow['percentage_value']
+            : null;
+    }
 }
diff --git a/app/code/Magento/Catalog/Model/Product/ScopedTierPriceManagement.php b/app/code/Magento/Catalog/Model/Product/ScopedTierPriceManagement.php
new file mode 100644
index 0000000000000000000000000000000000000000..f08702bdd73c426533e8656894bab2e161c2d5a3
--- /dev/null
+++ b/app/code/Magento/Catalog/Model/Product/ScopedTierPriceManagement.php
@@ -0,0 +1,156 @@
+<?php
+/**
+ * Copyright © 2016 Magento. All rights reserved.
+ * See COPYING.txt for license details.
+ */
+namespace Magento\Catalog\Model\Product;
+
+use Magento\Catalog\Api\Data\ProductTierPriceInterface;
+use Magento\Catalog\Api\ScopedProductTierPriceManagementInterface;
+use Magento\Store\Model\ScopeInterface;
+
+class ScopedTierPriceManagement implements ScopedProductTierPriceManagementInterface
+{
+    /**
+     * @var \Magento\Catalog\Api\ProductRepositoryInterface
+     */
+    private $productRepository;
+
+    /**
+     * @var \Magento\Store\Model\StoreManagerInterface
+     */
+    private $storeManager;
+
+    /**
+     * @var \Magento\Framework\App\Config\ScopeConfigInterface
+     */
+    private $config;
+
+    /**
+     * @var PriceModifier
+     */
+    private $priceModifier;
+
+    /**
+     * @var TierPriceManagement
+     */
+    private $tierPriceManagement;
+
+    /**
+     * @param \Magento\Catalog\Api\ProductRepositoryInterface $productRepository
+     * @param \Magento\Store\Model\StoreManagerInterface $storeManager
+     * @param \Magento\Framework\App\Config\ScopeConfigInterface $config
+     * @param PriceModifier $priceModifier
+     * @param TierPriceManagement $tierPriceManagement
+     */
+    public function __construct(
+        \Magento\Catalog\Api\ProductRepositoryInterface $productRepository,
+        \Magento\Store\Model\StoreManagerInterface $storeManager,
+        \Magento\Framework\App\Config\ScopeConfigInterface $config,
+        PriceModifier $priceModifier,
+        TierPriceManagement $tierPriceManagement
+    ) {
+        $this->productRepository = $productRepository;
+        $this->storeManager = $storeManager;
+        $this->priceModifier = $priceModifier;
+        $this->config = $config;
+        $this->tierPriceManagement = $tierPriceManagement;
+    }
+
+    /**
+     * {@inheritdoc}
+     */
+    public function add($sku, ProductTierPriceInterface $tierPrice)
+    {
+        $product = $this->productRepository->get($sku, ['edit_mode' => true]);
+        $product->setTierPrices(
+            $this->prepareTierPrices($product->getTierPrices(), $tierPrice)
+        );
+        try {
+            $this->productRepository->save($product);
+        } catch (\Exception $e) {
+            throw new \Magento\Framework\Exception\CouldNotSaveException(__('Could not save group price'));
+        }
+        return true;
+    }
+
+    /**
+     * @param array $tierPrices
+     * @param ProductTierPriceInterface $tierPrice
+     * @return ProductTierPriceInterface[]|null
+     */
+    private function prepareTierPrices(array $tierPrices, ProductTierPriceInterface $tierPrice)
+    {
+        $this->validate($tierPrice);
+        $websiteId = $this->getWebsiteId();
+
+        foreach ($tierPrices as $index => $item) {
+            $tierPriceWebsite = $tierPrice->getExtensionAttributes()
+                ? $tierPrice->getExtensionAttributes()->getWebsiteId()
+                : 0;
+
+            if ($item->getCustomerGroupId() == $tierPrice->getCustomerGroupId()
+                && $websiteId == $tierPriceWebsite
+                && $item->getQty() == $tierPrice->getQty()
+            ) {
+                unset($tierPrices[$index]);
+                break;
+            }
+        }
+
+        $tierPrices[] = $tierPrice;
+        return $tierPrices;
+    }
+
+    /**
+     * @return int
+     */
+    private function getWebsiteId()
+    {
+        $websiteIdentifier = 0;
+        $value = $this->config->getValue('catalog/price/scope', ScopeInterface::SCOPE_WEBSITE);
+        if ($value != 0) {
+            $websiteIdentifier = $this->storeManager->getWebsite()->getId();
+        }
+
+        return $websiteIdentifier;
+    }
+
+    /**
+     * @param ProductTierPriceInterface $tierPrice
+     * @throws \Magento\Framework\Exception\InputException
+     * @return void
+     */
+    private function validate(ProductTierPriceInterface $tierPrice)
+    {
+        $data = ['qty' => $tierPrice->getQty(), 'price' => $tierPrice->getValue()];
+        foreach ($data as $value) {
+            if (!is_float($value) || $value <= 0) {
+                throw new \Magento\Framework\Exception\InputException(__('Please provide valid data'));
+            }
+        }
+    }
+
+    /**
+     * {@inheritdoc}
+     */
+    public function remove($sku, ProductTierPriceInterface $tierPrice)
+    {
+        $product = $this->productRepository->get($sku, ['edit_mode' => true]);
+        $this->priceModifier->removeTierPrice(
+            $product,
+            $tierPrice->getCustomerGroupId(),
+            $tierPrice->getQty(),
+            $this->getWebsiteId()
+        );
+        return true;
+    }
+
+    /**
+     * {@inheritdoc}
+     */
+    public function getList($sku, $customerGroupId)
+    {
+        return $this->tierPriceManagement->getList($sku, $customerGroupId);
+    }
+}
diff --git a/app/code/Magento/Catalog/Model/Product/TierPrice.php b/app/code/Magento/Catalog/Model/Product/TierPrice.php
index 131280af1b1a428981d1c1f30c523a6dc940757d..e40b24631359c9874fe900bb171c13cd74a2e05d 100644
--- a/app/code/Magento/Catalog/Model/Product/TierPrice.php
+++ b/app/code/Magento/Catalog/Model/Product/TierPrice.php
@@ -1,6 +1,5 @@
 <?php
 /**
- *
  * Copyright © 2016 Magento. All rights reserved.
  * See COPYING.txt for license details.
  */
@@ -78,8 +77,6 @@ class TierPrice extends \Magento\Framework\Model\AbstractExtensibleModel impleme
 
     /**
      * {@inheritdoc}
-     *
-     * @return \Magento\Catalog\Api\Data\ProductTierPriceExtensionInterface|null
      */
     public function getExtensionAttributes()
     {
@@ -88,9 +85,6 @@ class TierPrice extends \Magento\Framework\Model\AbstractExtensibleModel impleme
 
     /**
      * {@inheritdoc}
-     *
-     * @param \Magento\Catalog\Api\Data\ProductTierPriceExtensionInterface $extensionAttributes
-     * @return $this
      */
     public function setExtensionAttributes(
         \Magento\Catalog\Api\Data\ProductTierPriceExtensionInterface $extensionAttributes
diff --git a/app/code/Magento/Catalog/Model/Product/Type/Price.php b/app/code/Magento/Catalog/Model/Product/Type/Price.php
index 20d25b9e5afcbc414f4abbe3af74328dce95770d..af15e049203f3f6aae27ef2c6ff071d49cdab8cd 100644
--- a/app/code/Magento/Catalog/Model/Product/Type/Price.php
+++ b/app/code/Magento/Catalog/Model/Product/Type/Price.php
@@ -10,6 +10,7 @@ use Magento\Catalog\Model\Product;
 use Magento\Customer\Api\GroupManagementInterface;
 use Magento\Framework\Pricing\PriceCurrencyInterface;
 use Magento\Store\Model\Store;
+use Magento\Catalog\Api\Data\ProductTierPriceExtensionFactory;
 
 /**
  * Product type price model
@@ -80,6 +81,11 @@ class Price
      */
     protected $config;
 
+    /**
+     * @var ProductTierPriceExtensionFactory
+     */
+    private $tierPriceExtensionFactory;
+
     /**
      * Price constructor.
      * @param \Magento\CatalogRule\Model\ResourceModel\RuleFactory $ruleFactory
@@ -354,7 +360,8 @@ class Price
         $tierPrices = $this->getExistingPrices($product, 'tier_price');
         foreach ($tierPrices as $price) {
             /** @var \Magento\Catalog\Api\Data\ProductTierPriceInterface $tierPrice */
-            $tierPrice = $this->tierPriceFactory->create();
+            $tierPrice = $this->tierPriceFactory->create()
+                ->setExtensionAttributes($this->getTierPriceExtensionAttributes());
             $tierPrice->setCustomerGroupId($price['cust_group']);
             if (array_key_exists('website_price', $price)) {
                 $value = $price['website_price'];
@@ -363,11 +370,29 @@ class Price
             }
             $tierPrice->setValue($value);
             $tierPrice->setQty($price['price_qty']);
+            if (isset($price['percentage_value'])) {
+                $tierPrice->getExtensionAttributes()->setPercentageValue($price['percentage_value']);
+            }
+            $websiteId = isset($price['website_id']) ? $price['website_id'] : $this->getWebsiteForPriceScope();
+            $tierPrice->getExtensionAttributes()->setWebsiteId($websiteId);
             $prices[] = $tierPrice;
         }
         return $prices;
     }
 
+    /**
+     * @deprecated
+     * @return \Magento\Catalog\Api\Data\ProductTierPriceExtensionInterface
+     */
+    private function getTierPriceExtensionAttributes()
+    {
+        if (!$this->tierPriceExtensionFactory) {
+            $this->tierPriceExtensionFactory = \Magento\Framework\App\ObjectManager::getInstance()
+                ->get(ProductTierPriceExtensionFactory::class);
+        }
+        return $this->tierPriceExtensionFactory->create();
+    }
+
     /**
      * Sets list of product tier prices
      *
@@ -382,19 +407,24 @@ class Price
             return $this;
         }
 
-        $websiteId = $this->getWebsiteForPriceScope();
         $allGroupsId = $this->getAllCustomerGroupsId();
+        $websiteId = $this->getWebsiteForPriceScope();
 
         // build the new array of tier prices
         $prices = [];
         foreach ($tierPrices as $price) {
+            $extensionAttributes = $price->getExtensionAttributes();
+            $websiteId = $extensionAttributes && $extensionAttributes->getWebsiteId()
+                ? $extensionAttributes->getWebsiteId()
+                : $websiteId;
             $prices[] = [
                 'website_id' => $websiteId,
                 'cust_group' => $price->getCustomerGroupId(),
                 'website_price' => $price->getValue(),
                 'price' => $price->getValue(),
                 'all_groups' => ($price->getCustomerGroupId() == $allGroupsId),
-                'price_qty' => $price->getQty()
+                'price_qty' => $price->getQty(),
+                'percentage_value' => $extensionAttributes ? $extensionAttributes->getPercentageValue() : null
             ];
         }
         $product->setData('tier_price', $prices);
diff --git a/app/code/Magento/Catalog/Model/ResourceModel/Product/Attribute/Backend/GroupPrice/AbstractGroupPrice.php b/app/code/Magento/Catalog/Model/ResourceModel/Product/Attribute/Backend/GroupPrice/AbstractGroupPrice.php
index 677d048de6a7f596014887971db414aad391614b..717056ddde02f1cff0bff978eb9bfb7652fa19e3 100644
--- a/app/code/Magento/Catalog/Model/ResourceModel/Product/Attribute/Backend/GroupPrice/AbstractGroupPrice.php
+++ b/app/code/Magento/Catalog/Model/ResourceModel/Product/Attribute/Backend/GroupPrice/AbstractGroupPrice.php
@@ -22,8 +22,21 @@ abstract class AbstractGroupPrice extends \Magento\Framework\Model\ResourceModel
      */
     public function loadPriceData($productId, $websiteId = null)
     {
-        $connection = $this->getConnection();
+        $select = $this->getSelect($websiteId);
+        $productIdFieldName = $this->getProductIdFieldName();
+        $select->where("{$productIdFieldName} = ?", $productId);
+
+        $this->_loadPriceDataSelect($select);
 
+        return $this->getConnection()->fetchAll($select);
+    }
+
+    /**
+     * @param int|null $websiteId
+     * @return \Magento\Framework\DB\Select
+     */
+    public function getSelect($websiteId = null)
+    {
         $columns = [
             'price_id' => $this->getIdFieldName(),
             'website_id' => 'website_id',
@@ -34,12 +47,8 @@ abstract class AbstractGroupPrice extends \Magento\Framework\Model\ResourceModel
 
         $columns = $this->_loadPriceDataColumns($columns);
 
-        $productIdFieldName = $this->getProductIdFieldName();
-        $select = $connection->select()
-            ->from($this->getMainTable(), $columns)
-            ->where("{$productIdFieldName} = ?", $productId);
-
-        $this->_loadPriceDataSelect($select);
+        $select = $this->getConnection()->select()
+            ->from($this->getMainTable(), $columns);
 
         if ($websiteId !== null) {
             if ($websiteId == '0') {
@@ -48,8 +57,7 @@ abstract class AbstractGroupPrice extends \Magento\Framework\Model\ResourceModel
                 $select->where('website_id IN(?)', [0, $websiteId]);
             }
         }
-
-        return $connection->fetchAll($select);
+        return $select;
     }
 
     /**
diff --git a/app/code/Magento/Catalog/Model/ResourceModel/Product/Attribute/Backend/Tierprice.php b/app/code/Magento/Catalog/Model/ResourceModel/Product/Attribute/Backend/Tierprice.php
index e75442637b9bb09cbcb8a235cd9ffb4abc415402..0f17ecbf98e4cec5b5fd8eebc84dea07da5dd9e5 100644
--- a/app/code/Magento/Catalog/Model/ResourceModel/Product/Attribute/Backend/Tierprice.php
+++ b/app/code/Magento/Catalog/Model/ResourceModel/Product/Attribute/Backend/Tierprice.php
@@ -34,6 +34,7 @@ class Tierprice extends AbstractGroupPrice
     {
         $columns = parent::_loadPriceDataColumns($columns);
         $columns['price_qty'] = 'qty';
+        $columns['percentage_value'] = 'percentage_value';
         return $columns;
     }
 
diff --git a/app/code/Magento/Catalog/Model/ResourceModel/Product/Collection.php b/app/code/Magento/Catalog/Model/ResourceModel/Product/Collection.php
index f53c1b29f8bd0d83680520cde33aea1bec1e310d..b2b20b9d223e223a093d7f6ae222e19631084528 100644
--- a/app/code/Magento/Catalog/Model/ResourceModel/Product/Collection.php
+++ b/app/code/Magento/Catalog/Model/ResourceModel/Product/Collection.php
@@ -2090,12 +2090,13 @@ class Collection extends \Magento\Catalog\Model\ResourceModel\Collection\Abstrac
         if ($this->getFlag('tier_price_added')) {
             return $this;
         }
+        $linkField = $this->getConnection()->getAutoIncrementField($this->getTable('catalog_product_entity'));
 
         $tierPrices = [];
         $productIds = [];
         foreach ($this->getItems() as $item) {
-            $productIds[] = $item->getId();
-            $tierPrices[$item->getId()] = [];
+            $productIds[] = $item->getData($linkField);
+            $tierPrices[$item->getData($linkField)] = [];
         }
         if (!$productIds) {
             return $this;
@@ -2103,57 +2104,27 @@ class Collection extends \Magento\Catalog\Model\ResourceModel\Collection\Abstrac
 
         /** @var $attribute \Magento\Catalog\Model\ResourceModel\Eav\Attribute */
         $attribute = $this->getAttribute('tier_price');
+        /* @var $backend \Magento\Catalog\Model\Product\Attribute\Backend\Tierprice */
+        $backend = $attribute->getBackend();
         $websiteId = 0;
         if (!$attribute->isScopeGlobal() && null !== $this->getStoreId()) {
             $websiteId = $this->_storeManager->getStore($this->getStoreId())->getWebsiteId();
         }
 
-        $linkField = $this->getConnection()->getAutoIncrementField($this->getTable('catalog_product_entity'));
-        $connection = $this->getConnection();
-        $columns = [
-            'price_id' => 'value_id',
-            'website_id' => 'website_id',
-            'all_groups' => 'all_groups',
-            'cust_group' => 'customer_group_id',
-            'price_qty' => 'qty',
-            'price' => 'value',
-            'product_id' => $linkField,
-        ];
-        $select = $connection->select()->from(
-            $this->getTable('catalog_product_entity_tier_price'),
-            $columns
-        )->where(
+        $select = $backend->getResource()->getSelect($websiteId);
+        $select->columns(['product_id' => $linkField])->where(
             $linkField .' IN(?)',
             $productIds
         )->order(
-            [$linkField, 'qty']
+            $linkField
         );
 
-        if ($websiteId == 0) {
-            $select->where('website_id = ?', $websiteId);
-        } else {
-            $select->where('website_id IN(?)', [0, $websiteId]);
-        }
-
-        foreach ($connection->fetchAll($select) as $row) {
-            $tierPrices[$row['product_id']][] = [
-                'website_id' => $row['website_id'],
-                'cust_group' => $row['all_groups'] ? $this->_groupManagement->getAllCustomersGroup()->getId() : $row['cust_group'],
-                'price_qty' => $row['price_qty'],
-                'price' => $row['price'],
-                'website_price' => $row['price'],
-            ];
+        foreach ($this->getConnection()->fetchAll($select) as $row) {
+            $tierPrices[$row['product_id']][] = $row;
         }
 
-        /* @var $backend \Magento\Catalog\Model\Product\Attribute\Backend\Tierprice */
-        $backend = $attribute->getBackend();
-
         foreach ($this->getItems() as $item) {
-            $data = $tierPrices[$item->getId()];
-            if (!empty($data) && $websiteId) {
-                $data = $backend->preparePriceData($data, $item->getTypeId(), $websiteId);
-            }
-            $item->setData('tier_price', $data);
+            $backend->setPriceData($item, $tierPrices[$item->getData($linkField)]);
         }
 
         $this->setFlag('tier_price_added', true);
diff --git a/app/code/Magento/Catalog/Setup/UpgradeSchema.php b/app/code/Magento/Catalog/Setup/UpgradeSchema.php
index 03f3ac9581729b96eeab485da5be125072f23773..9683632f121b6af18a3397e53bac2943521683c9 100644
--- a/app/code/Magento/Catalog/Setup/UpgradeSchema.php
+++ b/app/code/Magento/Catalog/Setup/UpgradeSchema.php
@@ -32,6 +32,10 @@ class UpgradeSchema implements UpgradeSchemaInterface
         if (version_compare($context->getVersion(), '2.0.6', '<')) {
             $this->addUniqueKeyToCategoryProductTable($setup);
         }
+
+        if (version_compare($context->getVersion(), '2.1.0', '<')) {
+            $this->addPercentageValueColumn($setup);
+        }
         $setup->endSetup();
     }
 
@@ -278,4 +282,25 @@ class UpgradeSchema implements UpgradeSchemaInterface
             $connection->dropColumn($setup->getTable($filedInfo['table']), $filedInfo['column']);
         }
     }
+
+    /**
+     * Add percentage value column
+     * @param SchemaSetupInterface $setup
+     * @return void
+     */
+    private function addPercentageValueColumn(SchemaSetupInterface $setup)
+    {
+        $connection = $setup->getConnection();
+        $connection->addColumn(
+            $setup->getTable('catalog_product_entity_tier_price'),
+            'percentage_value',
+            [
+                'type' => \Magento\Framework\DB\Ddl\Table::TYPE_DECIMAL,
+                'nullable' => true,
+                'length' => '5,2',
+                'comment' => 'Percentage value',
+                'after' => 'value'
+            ]
+        );
+    }
 }
diff --git a/app/code/Magento/Catalog/Test/Unit/Model/Product/Type/PriceTest.php b/app/code/Magento/Catalog/Test/Unit/Model/Product/Type/PriceTest.php
index c51f9486c2ecb26c8fefb82ad3875c24b904af2d..5868e749446e391a987928314d3c133c1fc19537 100644
--- a/app/code/Magento/Catalog/Test/Unit/Model/Product/Type/PriceTest.php
+++ b/app/code/Magento/Catalog/Test/Unit/Model/Product/Type/PriceTest.php
@@ -8,11 +8,15 @@
 
 namespace Magento\Catalog\Test\Unit\Model\Product\Type;
 
+use Magento\Catalog\Api\Data\ProductTierPriceExtensionFactory;
+use Magento\Catalog\Api\Data\ProductTierPriceExtensionInterface;
 use Magento\Framework\TestFramework\Unit\Helper\ObjectManager as ObjectManagerHelper;
 use Magento\Customer\Model\GroupManagement;
 
 /**
  * Price Test
+ *
+ * @SuppressWarnings(PHPMD.CouplingBetweenObjects)
  */
 class PriceTest extends \PHPUnit_Framework_TestCase
 {
@@ -60,18 +64,18 @@ class PriceTest extends \PHPUnit_Framework_TestCase
         $this->objectManagerHelper = new ObjectManagerHelper($this);
         $this->product = $this->objectManagerHelper->getObject(\Magento\Catalog\Model\Product::class);
 
-        $this->tpFactory = $this->getMockForAbstractClass(
+        $this->tpFactory = $this->getMock(
             \Magento\Catalog\Api\Data\ProductTierPriceInterfaceFactory::class,
+            ['create'],
             [],
             '',
             false,
-            true,
-            true,
-            ['create']
+            false,
+            false
         );
 
         $this->websiteMock = $this->getMock(\Magento\Store\Model\Website::class, ['getId'], [], '', false);
-        $storeMangerMock = $this->getMockForAbstractClass(
+        $storeMangerMock = $this->getMockForAbstractClass(
             \Magento\Store\Model\StoreManagerInterface::class,
             [],
             '',
@@ -84,7 +88,7 @@ class PriceTest extends \PHPUnit_Framework_TestCase
             ->method('getWebsite')
             ->will($this->returnValue($this->websiteMock));
 
-        $this->scopeConfigMock = $this->getMockForAbstractClass(
+        $this->scopeConfigMock = $this->getMockForAbstractClass(
             \Magento\Framework\App\Config\ScopeConfigInterface::class,
             [],
             '',
@@ -94,7 +98,7 @@ class PriceTest extends \PHPUnit_Framework_TestCase
             ['getValue']
         );
 
-        $group = $this->getMock(
+        $group = $this->getMock(
             \Magento\Customer\Model\Data\Group::class,
             [],
             [],
@@ -107,7 +111,7 @@ class PriceTest extends \PHPUnit_Framework_TestCase
         $this->groupManagementMock->expects($this->any())->method('getAllCustomersGroup')
             ->will($this->returnValue($group));
 
-        $this->model = $this->objectManagerHelper->getObject(
+        $this->model = $this->objectManagerHelper->getObject(
             \Magento\Catalog\Model\Product\Type\Price::class,
             [
                 'tierPriceFactory' => $this->tpFactory,
@@ -164,12 +168,8 @@ class PriceTest extends \PHPUnit_Framework_TestCase
     public function testTierPrices($priceScope, $expectedWebsiteId)
     {
         // establish the behavior of the mocks
-        $this->scopeConfigMock->expects($this->any())
-            ->method('getValue')
-            ->will($this->returnValue($priceScope));
-        $this->websiteMock->expects($this->any())
-            ->method('getId')
-            ->will($this->returnValue($expectedWebsiteId));
+        $this->scopeConfigMock->expects($this->any())->method('getValue')->will($this->returnValue($priceScope));
+        $this->websiteMock->expects($this->any())->method('getId')->will($this->returnValue($expectedWebsiteId));
         $this->tpFactory->expects($this->any())
             ->method('create')
             ->will($this->returnCallback(function () {
@@ -177,14 +177,21 @@ class PriceTest extends \PHPUnit_Framework_TestCase
             }));
 
         // create sample TierPrice objects that would be coming from a REST call
+        $tierPriceExtensionMock = $this->getMockBuilder(ProductTierPriceExtensionInterface::class)
+            ->setMethods(['getWebsiteId', 'setWebsiteId', 'getPercentageValue', 'setPercentageValue'])
+            ->getMock();
+        $tierPriceExtensionMock->expects($this->any())->method('getWebsiteId')->willReturn($expectedWebsiteId);
+        $tierPriceExtensionMock->expects($this->any())->method('getPercentageValue')->willReturn(null);
         $tp1 = $this->objectManagerHelper->getObject(\Magento\Catalog\Model\Product\TierPrice::class);
         $tp1->setValue(10);
         $tp1->setCustomerGroupId(1);
         $tp1->setQty(11);
+        $tp1->setExtensionAttributes($tierPriceExtensionMock);
         $tp2 = $this->objectManagerHelper->getObject(\Magento\Catalog\Model\Product\TierPrice::class);
         $tp2->setValue(20);
         $tp2->setCustomerGroupId(2);
         $tp2->setQty(22);
+        $tp2->setExtensionAttributes($tierPriceExtensionMock);
         $tps = [$tp1, $tp2];
 
         // force the product to have null tier prices
@@ -213,11 +220,28 @@ class PriceTest extends \PHPUnit_Framework_TestCase
             $this->assertEquals($tps[$i]->getQty(), $tpData['price_qty'], 'Qty does not match');
         }
 
+        $tierPriceExtention = $this->getMockBuilder(ProductTierPriceExtensionInterface::class)
+            ->setMethods(['getWebsiteId', 'setWebsiteId', 'getPercentageValue', 'setPercentageValue'])
+            ->getMock();
+        $tierPriceExtention->expects($this->any())->method('getPercentageValue')->willReturn(50);
+        $tierPriceExtention->expects($this->any())->method('setWebsiteId');
+        $factoryMock = $this->getMockBuilder(ProductTierPriceExtensionFactory::class)->setMethods(['create'])
+            ->disableOriginalConstructor()->getMock();
+        $factoryMock->expects($this->any())->method('create')->willReturn($tierPriceExtention);
+
+        $reflection = new \ReflectionClass(get_class($this->model));
+        $reflectionProperty = $reflection->getProperty('tierPriceExtensionFactory');
+        $reflectionProperty->setAccessible(true);
+        $reflectionProperty->setValue($this->model, $factoryMock);
+
         // test with the data retrieved as a REST object
         $tpRests = $this->model->getTierPrices($this->product);
         $this->assertNotNull($tpRests);
         $this->assertTrue(is_array($tpRests));
         $this->assertEquals(sizeof($tps), sizeof($tpRests));
+        foreach ($tpRests as $tpRest) {
+            $this->assertEquals(50, $tpRest->getExtensionAttributes()->getPercentageValue());
+        }
 
         for ($i = 0; $i < sizeof($tps); $i++) {
             $this->assertEquals(
diff --git a/app/code/Magento/Catalog/Ui/DataProvider/Product/Form/Modifier/AdvancedPricing.php b/app/code/Magento/Catalog/Ui/DataProvider/Product/Form/Modifier/AdvancedPricing.php
index 3b92e54355fb66be4eb76cec2651ffbbab88434b..c42efd5a1e61fe1249c0cf28d53b8737d22f826d 100644
--- a/app/code/Magento/Catalog/Ui/DataProvider/Product/Form/Modifier/AdvancedPricing.php
+++ b/app/code/Magento/Catalog/Ui/DataProvider/Product/Form/Modifier/AdvancedPricing.php
@@ -7,9 +7,10 @@ namespace Magento\Catalog\Ui\DataProvider\Product\Form\Modifier;
 
 use Magento\Catalog\Api\Data\ProductAttributeInterface;
 use Magento\Catalog\Model\Locator\LocatorInterface;
+use Magento\Customer\Model\Customer\Source\GroupSourceInterface;
 use Magento\Directory\Helper\Data;
+use Magento\Framework\App\ObjectManager;
 use Magento\Store\Model\StoreManagerInterface;
-use Magento\Customer\Api\Data\GroupInterface;
 use Magento\Customer\Api\GroupManagementInterface;
 use Magento\Customer\Api\GroupRepositoryInterface;
 use Magento\Framework\Api\SearchCriteriaBuilder;
@@ -81,6 +82,11 @@ class AdvancedPricing extends AbstractModifier
      */
     protected $meta = [];
 
+    /**
+     * @var GroupSourceInterface
+     */
+    private $customerGroupSource;
+
     /**
      * @param LocatorInterface $locator
      * @param StoreManagerInterface $storeManager
@@ -91,6 +97,7 @@ class AdvancedPricing extends AbstractModifier
      * @param Data $directoryHelper
      * @param ArrayManager $arrayManager
      * @param string $scopeName
+     * @param GroupSourceInterface $customerGroupSource
      * @SuppressWarnings(PHPMD.ExcessiveParameterList)
      */
     public function __construct(
@@ -102,7 +109,8 @@ class AdvancedPricing extends AbstractModifier
         ModuleManager $moduleManager,
         Data $directoryHelper,
         ArrayManager $arrayManager,
-        $scopeName = ''
+        $scopeName = '',
+        GroupSourceInterface $customerGroupSource = null
     ) {
         $this->locator = $locator;
         $this->storeManager = $storeManager;
@@ -113,6 +121,8 @@ class AdvancedPricing extends AbstractModifier
         $this->directoryHelper = $directoryHelper;
         $this->arrayManager = $arrayManager;
         $this->scopeName = $scopeName;
+        $this->customerGroupSource = $customerGroupSource
+            ?: ObjectManager::getInstance()->get(GroupSourceInterface::class);
     }
 
     /**
@@ -174,7 +184,7 @@ class AdvancedPricing extends AbstractModifier
      *
      * @return $this
      */
-    protected function customizeTierPrice()
+    private function customizeTierPrice()
     {
         $tierPricePath = $this->arrayManager->findPath(
             ProductAttributeInterface::CODE_TIER_PRICE,
@@ -209,28 +219,13 @@ class AdvancedPricing extends AbstractModifier
      *
      * @return array
      */
-    protected function getCustomerGroups()
+    private function getCustomerGroups()
     {
         if (!$this->moduleManager->isEnabled('Magento_Customer')) {
             return [];
         }
-        $customerGroups = [
-            [
-                'label' => __('ALL GROUPS'),
-                'value' => GroupInterface::CUST_GROUP_ALL,
-            ]
-        ];
-
-        /** @var GroupInterface[] $groups */
-        $groups = $this->groupRepository->getList($this->searchCriteriaBuilder->create());
-        foreach ($groups->getItems() as $group) {
-            $customerGroups[] = [
-                'label' => $group->getCode(),
-                'value' => $group->getId(),
-            ];
-        }
 
-        return $customerGroups;
+        return $this->customerGroupSource->toOptionArray();
     }
 
     /**
@@ -238,7 +233,7 @@ class AdvancedPricing extends AbstractModifier
      *
      * @return bool
      */
-    protected function isScopeGlobal()
+    private function isScopeGlobal()
     {
         return $this->locator->getProduct()
             ->getResource()
@@ -251,7 +246,7 @@ class AdvancedPricing extends AbstractModifier
      *
      * @return array
      */
-    protected function getWebsites()
+    private function getWebsites()
     {
         $websites = [
             [
@@ -292,7 +287,7 @@ class AdvancedPricing extends AbstractModifier
      *
      * @return int
      */
-    protected function getDefaultCustomerGroup()
+    private function getDefaultCustomerGroup()
     {
         return $this->groupManagement->getAllCustomersGroup()->getId();
     }
@@ -316,7 +311,7 @@ class AdvancedPricing extends AbstractModifier
      *
      * @return bool
      */
-    protected function isShowWebsiteColumn()
+    private function isShowWebsiteColumn()
     {
         if ($this->isScopeGlobal() || $this->storeManager->isSingleStoreMode()) {
             return false;
@@ -329,7 +324,7 @@ class AdvancedPricing extends AbstractModifier
      *
      * @return bool
      */
-    protected function isMultiWebsites()
+    private function isMultiWebsites()
     {
         return !$this->storeManager->isSingleStoreMode();
     }
@@ -339,7 +334,7 @@ class AdvancedPricing extends AbstractModifier
      *
      * @return bool
      */
-    protected function isAllowChangeWebsite()
+    private function isAllowChangeWebsite()
     {
         if (!$this->isShowWebsiteColumn() || $this->locator->getProduct()->getStoreId()) {
             return false;
@@ -352,7 +347,7 @@ class AdvancedPricing extends AbstractModifier
      *
      * @return $this
      */
-    protected function addAdvancedPriceLink()
+    private function addAdvancedPriceLink()
     {
         $pricePath = $this->arrayManager->findPath(
             ProductAttributeInterface::CODE_PRICE,
@@ -405,7 +400,7 @@ class AdvancedPricing extends AbstractModifier
      * @return array
      * @SuppressWarnings(PHPMD.ExcessiveMethodLength)
      */
-    protected function getTierPriceStructure($tierPricePath)
+    private function getTierPriceStructure($tierPricePath)
     {
         return [
             'arguments' => [
@@ -452,6 +447,7 @@ class AdvancedPricing extends AbstractModifier
                                         'value' => $this->getDefaultWebsite(),
                                         'visible' => $this->isMultiWebsites(),
                                         'disabled' => ($this->isShowWebsiteColumn() && !$this->isAllowChangeWebsite()),
+                                        'sortOrder' => 10,
                                     ],
                                 ],
                             ],
@@ -467,6 +463,7 @@ class AdvancedPricing extends AbstractModifier
                                         'label' => __('Customer Group'),
                                         'options' => $this->getCustomerGroups(),
                                         'value' => $this->getDefaultCustomerGroup(),
+                                        'sortOrder' => 20,
                                     ],
                                 ],
                             ],
@@ -480,6 +477,7 @@ class AdvancedPricing extends AbstractModifier
                                         'dataType' => Number::NAME,
                                         'label' => __('Quantity'),
                                         'dataScope' => 'price_qty',
+                                        'sortOrder' => 30,
                                         'validation' => [
                                             'required-entry' => true,
                                             'validate-greater-than-zero' => true,
@@ -494,6 +492,7 @@ class AdvancedPricing extends AbstractModifier
                                 'data' => [
                                     'config' => [
                                         'componentType' => Field::NAME,
+                                        'component' => 'Magento_Catalog/js/form/element/price-input',
                                         'formElement' => Input::NAME,
                                         'dataType' => Price::NAME,
                                         'label' => __('Price'),
@@ -502,11 +501,15 @@ class AdvancedPricing extends AbstractModifier
                                         'addbefore' => $this->locator->getStore()
                                                                      ->getBaseCurrency()
                                                                      ->getCurrencySymbol(),
+                                        'sortOrder' => 40,
                                         'validation' => [
                                             'required-entry' => true,
                                             'validate-greater-than-zero' => true,
                                             'validate-number' => true,
                                         ],
+                                        'imports' => [
+                                            'priceValue' => '${ $.provider }:data.product.price',
+                                        ],
                                     ],
                                 ],
                             ],
@@ -518,6 +521,7 @@ class AdvancedPricing extends AbstractModifier
                                         'componentType' => 'actionDelete',
                                         'dataType' => Text::NAME,
                                         'label' => '',
+                                        'sortOrder' => 50,
                                     ],
                                 ],
                             ],
@@ -533,7 +537,7 @@ class AdvancedPricing extends AbstractModifier
      *
      * @return $this
      */
-    protected function specialPriceDataToInline()
+    private function specialPriceDataToInline()
     {
         $pathFrom = $this->arrayManager->findPath('special_from_date', $this->meta, null, 'children');
         $pathTo = $this->arrayManager->findPath('special_to_date', $this->meta, null, 'children');
@@ -589,7 +593,7 @@ class AdvancedPricing extends AbstractModifier
      *
      * @return $this
      */
-    protected function customizeAdvancedPricing()
+    private function customizeAdvancedPricing()
     {
         $this->meta['advanced-pricing']['arguments']['data']['config']['opened'] = true;
         $this->meta['advanced-pricing']['arguments']['data']['config']['collapsible'] = false;
@@ -646,9 +650,9 @@ class AdvancedPricing extends AbstractModifier
     /**
      * Retrieve store
      *
-     * @return \Magento\Store\Model\Store
+     * @return \Magento\Store\Api\Data\StoreInterface
      */
-    protected function getStore()
+    private function getStore()
     {
         return $this->locator->getStore();
     }
diff --git a/app/code/Magento/Catalog/etc/di.xml b/app/code/Magento/Catalog/etc/di.xml
index 36f072d3b0a15b358463a2715e5e214c30b51b24..49ad7d67d705899077d4beb13f4a26383982dcf5 100644
--- a/app/code/Magento/Catalog/etc/di.xml
+++ b/app/code/Magento/Catalog/etc/di.xml
@@ -46,6 +46,7 @@
     <preference for="Magento\Catalog\Api\AttributeSetFinderInterface" type="Magento\Catalog\Model\Product\Attribute\AttributeSetFinder" />
     <preference for="Magento\Catalog\Api\CategoryListInterface" type="Magento\Catalog\Model\CategoryList" />
     <preference for="Magento\Catalog\Api\Data\CategorySearchResultsInterface" type="Magento\Framework\Api\SearchResults" />
+    <preference for="Magento\Catalog\Model\Config\Source\ProductPriceOptionsInterface" type="Magento\Catalog\Model\Config\Source\Product\Options\Price"/>
     <type name="Magento\Customer\Model\ResourceModel\Visitor">
         <plugin name="catalogLog" type="Magento\Catalog\Model\Plugin\Log" />
     </type>
@@ -470,6 +471,7 @@
     <preference for="Magento\Catalog\Api\ProductCustomOptionRepositoryInterface" type="\Magento\Catalog\Model\Product\Option\Repository" />
     <preference for="Magento\Catalog\Api\Data\ProductCustomOptionTypeInterface" type="Magento\Catalog\Model\Product\Option\Type" />
     <preference for="Magento\Catalog\Api\ProductTierPriceManagementInterface" type="\Magento\Catalog\Model\Product\TierPriceManagement" />
+    <preference for="Magento\Catalog\Api\ScopedProductTierPriceManagementInterface" type="\Magento\Catalog\Model\Product\ScopedTierPriceManagement" />
     <preference for="Magento\Catalog\Api\Data\ProductTierPriceInterface" type="Magento\Catalog\Model\Product\TierPrice" />
     <preference for="Magento\Catalog\Api\Data\CategoryProductLinkInterface" type="Magento\Catalog\Model\CategoryProductLink" />
     <preference for="Magento\Catalog\Api\ProductCustomOptionTypeListInterface" type="Magento\Catalog\Model\ProductOptions\TypeList" />
diff --git a/app/code/Magento/Catalog/etc/extension_attributes.xml b/app/code/Magento/Catalog/etc/extension_attributes.xml
index 976031cb937fe414bb97eff0560e1496ab3ac691..509c3240bb6c8046ae74fcc36a55160376e195f1 100644
--- a/app/code/Magento/Catalog/etc/extension_attributes.xml
+++ b/app/code/Magento/Catalog/etc/extension_attributes.xml
@@ -25,4 +25,10 @@
     <extension_attributes for="Magento\Catalog\Api\Data\ProductInterface">
         <attribute code="category_links" type="Magento\Catalog\Api\Data\CategoryLinkInterface[]" />
     </extension_attributes>
+    <extension_attributes for="Magento\Catalog\Api\Data\ProductTierPriceInterface">
+        <attribute code="percentage_value" type="float" />
+    </extension_attributes>
+    <extension_attributes for="Magento\Catalog\Api\Data\ProductTierPriceInterface">
+        <attribute code="website_id" type="int" />
+    </extension_attributes>
 </config>
diff --git a/app/code/Magento/Catalog/etc/module.xml b/app/code/Magento/Catalog/etc/module.xml
index 87e82543fc65b2991d5f10da91941f389dc18fcd..1250b55b968480bf9c1d9f9952da6d864048d98f 100644
--- a/app/code/Magento/Catalog/etc/module.xml
+++ b/app/code/Magento/Catalog/etc/module.xml
@@ -6,7 +6,7 @@
  */
 -->
 <config xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="urn:magento:framework:Module/etc/module.xsd">
-    <module name="Magento_Catalog" setup_version="2.0.7">
+    <module name="Magento_Catalog" setup_version="2.1.0">
         <sequence>
             <module name="Magento_Eav"/>
             <module name="Magento_Cms"/>
diff --git a/app/code/Magento/Catalog/view/adminhtml/web/js/form/element/price-input.js b/app/code/Magento/Catalog/view/adminhtml/web/js/form/element/price-input.js
new file mode 100644
index 0000000000000000000000000000000000000000..0939619809a460b49cc7f254702797357c3b06f5
--- /dev/null
+++ b/app/code/Magento/Catalog/view/adminhtml/web/js/form/element/price-input.js
@@ -0,0 +1,15 @@
+/**
+ * Copyright © 2016 Magento. All rights reserved.
+ * See COPYING.txt for license details.
+ */
+define([
+    'Magento_Ui/js/form/element/abstract'
+], function (Abstract) {
+    'use strict';
+
+    return Abstract.extend({
+        defaults: {
+            elementTmpl: 'Magento_Catalog/form/element/price-input'
+        }
+    });
+});
diff --git a/app/code/Magento/Catalog/view/adminhtml/web/template/form/element/price-input.html b/app/code/Magento/Catalog/view/adminhtml/web/template/form/element/price-input.html
new file mode 100644
index 0000000000000000000000000000000000000000..ce8ae751e6702ff11099db5a06c8dbb8d4b1cbb9
--- /dev/null
+++ b/app/code/Magento/Catalog/view/adminhtml/web/template/form/element/price-input.html
@@ -0,0 +1,22 @@
+<!--
+/**
+ * Copyright © 2016 Magento. All rights reserved.
+ * See COPYING.txt for license details.
+ */
+-->
+<input class="admin__control-text" type="text"
+       data-bind="
+        event: {
+            change: userChanges,
+            input: onInput
+        },
+        value: value,
+        hasFocus: focused,
+        valueUpdate: valueUpdate,
+        attr: {
+            name: inputName,
+            placeholder: placeholder,
+            'aria-describedby': noticeId,
+            id: uid,
+            disabled: disabled
+    }"/>
diff --git a/app/code/Magento/CatalogInventory/view/adminhtml/ui_component/product_form.xml b/app/code/Magento/CatalogInventory/view/adminhtml/ui_component/product_form.xml
index 362aac7c8cf309523fedae6ea833e08f1bab789b..64e0d2ec59d105f2e312a1e24a5b5ab864b8eec7 100644
--- a/app/code/Magento/CatalogInventory/view/adminhtml/ui_component/product_form.xml
+++ b/app/code/Magento/CatalogInventory/view/adminhtml/ui_component/product_form.xml
@@ -242,7 +242,7 @@
                         </argument>
                         <field name="customer_group_id">
                             <argument name="data" xsi:type="array">
-                                <item name="options" xsi:type="object">Magento\Customer\Model\Customer\Source\Group</item>
+                                <item name="options" xsi:type="object">Magento\Customer\Model\Customer\Source\GroupSourceInterface</item>
                                 <item name="config" xsi:type="array">
                                     <item name="dataType" xsi:type="string">text</item>
                                     <item name="formElement" xsi:type="string">select</item>
diff --git a/app/code/Magento/CatalogRule/view/adminhtml/ui_component/catalog_rule_form.xml b/app/code/Magento/CatalogRule/view/adminhtml/ui_component/catalog_rule_form.xml
index 3aff684574652c2c9de84a2a6713fa8d7002d82a..af60e0247169bb402d5b6d71be4576ad2ed24566 100644
--- a/app/code/Magento/CatalogRule/view/adminhtml/ui_component/catalog_rule_form.xml
+++ b/app/code/Magento/CatalogRule/view/adminhtml/ui_component/catalog_rule_form.xml
@@ -149,7 +149,7 @@
                     <item name="source" xsi:type="string">catalog_rule</item>
                     <item name="dataScope" xsi:type="string">customer_group_ids</item>
                 </item>
-                <item name="options" xsi:type="object">Magento\CatalogRule\Model\Rule\CustomerGroupsOptionsProvider</item>
+                <item name="options" xsi:type="object">\Magento\Customer\Model\Customer\Source\GroupSourceInterface</item>
             </argument>
         </field>
         <field name="from_date">
diff --git a/app/code/Magento/ConfigurableProduct/Setup/UpgradeData.php b/app/code/Magento/ConfigurableProduct/Setup/UpgradeData.php
new file mode 100644
index 0000000000000000000000000000000000000000..4c321f521bb79491ab282545b13d29040cc382bd
--- /dev/null
+++ b/app/code/Magento/ConfigurableProduct/Setup/UpgradeData.php
@@ -0,0 +1,65 @@
+<?php
+/**
+ * Copyright © 2016 Magento. All rights reserved.
+ * See COPYING.txt for license details.
+ */
+namespace Magento\ConfigurableProduct\Setup;
+
+use Magento\ConfigurableProduct\Model\Product\Type\Configurable;
+use Magento\Framework\Setup\UpgradeDataInterface;
+use Magento\Framework\Setup\ModuleContextInterface;
+use Magento\Framework\Setup\ModuleDataSetupInterface;
+use Magento\Eav\Setup\EavSetup;
+use Magento\Eav\Setup\EavSetupFactory;
+
+/**
+ * Upgrade Data script
+ * @codeCoverageIgnore
+ */
+class UpgradeData implements UpgradeDataInterface
+{
+    /**
+     * EAV setup factory
+     *
+     * @var EavSetupFactory
+     */
+    private $eavSetupFactory;
+
+    /**
+     * Init
+     *
+     * @param EavSetupFactory $eavSetupFactory
+     */
+    public function __construct(EavSetupFactory $eavSetupFactory)
+    {
+        $this->eavSetupFactory = $eavSetupFactory;
+    }
+
+    /**
+     * {@inheritdoc}
+     */
+    public function upgrade(ModuleDataSetupInterface $setup, ModuleContextInterface $context)
+    {
+        $setup->startSetup();
+        if (version_compare($context->getVersion(), '2.2.0') < 0) {
+            /** @var EavSetup $eavSetup */
+            $eavSetup = $this->eavSetupFactory->create(['setup' => $setup]);
+            $relatedProductTypes = explode(
+                ',',
+                $eavSetup->getAttribute(\Magento\Catalog\Model\Product::ENTITY, 'tier_price', 'apply_to')
+            );
+            $key = array_search(Configurable::TYPE_CODE, $relatedProductTypes);
+            if ($key !== false) {
+                unset($relatedProductTypes[$key]);
+                $eavSetup->updateAttribute(
+                    \Magento\Catalog\Model\Product::ENTITY,
+                    'tier_price',
+                    'apply_to',
+                    implode(',', $relatedProductTypes)
+                );
+            }
+        }
+
+        $setup->endSetup();
+    }
+}
diff --git a/app/code/Magento/ConfigurableProduct/Test/Unit/Model/Product/Type/ConfigurableTest.php b/app/code/Magento/ConfigurableProduct/Test/Unit/Model/Product/Type/ConfigurableTest.php
index 21730cf65f020f5766fafe123b4448a1f387f6f9..efb409dd156af2af33f6a233a583fdf8a3fcedd0 100644
--- a/app/code/Magento/ConfigurableProduct/Test/Unit/Model/Product/Type/ConfigurableTest.php
+++ b/app/code/Magento/ConfigurableProduct/Test/Unit/Model/Product/Type/ConfigurableTest.php
@@ -384,7 +384,7 @@ class ConfigurableTest extends \PHPUnit_Framework_TestCase
                 'addAttributeToSelect',
                 'addFilterByRequiredOptions',
                 'setStoreId',
-                'addPriceData',
+                'addTierPriceData',
                 'getIterator',
                 'load',
             ]
@@ -393,7 +393,7 @@ class ConfigurableTest extends \PHPUnit_Framework_TestCase
         $productCollection->expects($this->any())->method('addAttributeToSelect')->will($this->returnSelf());
         $productCollection->expects($this->any())->method('setProductFilter')->will($this->returnSelf());
         $productCollection->expects($this->any())->method('setFlag')->will($this->returnSelf());
-        $productCollection->expects($this->any())->method('addPriceData')->will($this->returnSelf());
+        $productCollection->expects($this->any())->method('addTierPriceData')->will($this->returnSelf());
         $productCollection->expects($this->any())->method('addFilterByRequiredOptions')->will($this->returnSelf());
         $productCollection->expects($this->any())->method('setStoreId')->with(5)->will($this->returnValue([]));
         $productCollection->expects($this->any())->method('getIterator')->willReturn(
diff --git a/app/code/Magento/ConfigurableProduct/etc/module.xml b/app/code/Magento/ConfigurableProduct/etc/module.xml
index 8a64e9f026d9d86fd22b5c4d8fb8bf40cb781c29..706338092c4b6ae56d25dd3c125a728578b6be19 100644
--- a/app/code/Magento/ConfigurableProduct/etc/module.xml
+++ b/app/code/Magento/ConfigurableProduct/etc/module.xml
@@ -6,7 +6,7 @@
  */
 -->
 <config xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="urn:magento:framework:Module/etc/module.xsd">
-    <module name="Magento_ConfigurableProduct" setup_version="2.0.0">
+    <module name="Magento_ConfigurableProduct" setup_version="2.2.0">
         <sequence>
             <module name="Magento_Catalog"/>
             <module name="Magento_Msrp"/>
diff --git a/app/code/Magento/Customer/Model/Config/Source/Group.php b/app/code/Magento/Customer/Model/Config/Source/Group.php
index 40c7712f5aab3724071ef9def4ecd27eb5e1b8a5..e693bf7a3c45e29791dede512a6b00a293633cf7 100644
--- a/app/code/Magento/Customer/Model/Config/Source/Group.php
+++ b/app/code/Magento/Customer/Model/Config/Source/Group.php
@@ -6,6 +6,8 @@
 namespace Magento\Customer\Model\Config\Source;
 
 use Magento\Customer\Api\GroupManagementInterface;
+use Magento\Customer\Model\Customer\Attribute\Source\GroupSourceLoggedInOnlyInterface;
+use Magento\Framework\App\ObjectManager;
 
 class Group implements \Magento\Framework\Option\ArrayInterface
 {
@@ -15,37 +17,48 @@ class Group implements \Magento\Framework\Option\ArrayInterface
     protected $_options;
 
     /**
+     * @deprecated
      * @var GroupManagementInterface
      */
     protected $_groupManagement;
 
     /**
+     * @deprecated
      * @var \Magento\Framework\Convert\DataObject
      */
     protected $_converter;
 
+    /**
+     * @var GroupSourceLoggedInOnlyInterface
+     */
+    private $groupSourceLoggedInOnly;
+
     /**
      * @param GroupManagementInterface $groupManagement
      * @param \Magento\Framework\Convert\DataObject $converter
+     * @param GroupSourceLoggedInOnlyInterface $groupSourceForLoggedInCustomers
      */
     public function __construct(
         GroupManagementInterface $groupManagement,
-        \Magento\Framework\Convert\DataObject $converter
+        \Magento\Framework\Convert\DataObject $converter,
+        GroupSourceLoggedInOnlyInterface $groupSourceForLoggedInCustomers = null
     ) {
         $this->_groupManagement = $groupManagement;
         $this->_converter = $converter;
+        $this->groupSourceLoggedInOnly = $groupSourceForLoggedInCustomers
+            ?: ObjectManager::getInstance()->get(GroupSourceLoggedInOnlyInterface::class);
     }
 
     /**
-     * @return array
+     * @inheritdoc
      */
     public function toOptionArray()
     {
         if (!$this->_options) {
-            $groups = $this->_groupManagement->getLoggedInGroups();
-            $this->_options = $this->_converter->toOptionArray($groups, 'id', 'code');
+            $this->_options = $this->groupSourceLoggedInOnly->toOptionArray();
             array_unshift($this->_options, ['value' => '', 'label' => __('-- Please Select --')]);
         }
+
         return $this->_options;
     }
 }
diff --git a/app/code/Magento/Customer/Model/Config/Source/Group/Multiselect.php b/app/code/Magento/Customer/Model/Config/Source/Group/Multiselect.php
index 708e3243e26992589f553ddf4e7eda6bc19ed02b..adfc78448cdfc1b29412a9e5e5caca3907c6e487 100644
--- a/app/code/Magento/Customer/Model/Config/Source/Group/Multiselect.php
+++ b/app/code/Magento/Customer/Model/Config/Source/Group/Multiselect.php
@@ -5,7 +5,9 @@
  */
 namespace Magento\Customer\Model\Config\Source\Group;
 
+use Magento\Customer\Model\Customer\Attribute\Source\GroupSourceLoggedInOnlyInterface;
 use Magento\Customer\Api\GroupManagementInterface;
+use Magento\Framework\App\ObjectManager;
 
 class Multiselect implements \Magento\Framework\Option\ArrayInterface
 {
@@ -17,25 +19,36 @@ class Multiselect implements \Magento\Framework\Option\ArrayInterface
     protected $_options;
 
     /**
+     * @deprecated
      * @var GroupManagementInterface
      */
     protected $_groupManagement;
 
     /**
+     * @deprecated
      * @var \Magento\Framework\Convert\DataObject
      */
     protected $_converter;
 
+    /**
+     * @var GroupSourceLoggedInOnlyInterface
+     */
+    private $groupSourceLoggedInOnly;
+
     /**
      * @param GroupManagementInterface $groupManagement
      * @param \Magento\Framework\Convert\DataObject $converter
+     * @param GroupSourceLoggedInOnlyInterface|null $groupSourceLoggedInOnly
      */
     public function __construct(
         GroupManagementInterface $groupManagement,
-        \Magento\Framework\Convert\DataObject $converter
+        \Magento\Framework\Convert\DataObject $converter,
+        GroupSourceLoggedInOnlyInterface $groupSourceLoggedInOnly = null
     ) {
         $this->_groupManagement = $groupManagement;
         $this->_converter = $converter;
+        $this->groupSourceLoggedInOnly = $groupSourceLoggedInOnly
+            ?: ObjectManager::getInstance()->get(GroupSourceLoggedInOnlyInterface::class);
     }
 
     /**
@@ -46,8 +59,7 @@ class Multiselect implements \Magento\Framework\Option\ArrayInterface
     public function toOptionArray()
     {
         if (!$this->_options) {
-            $groups = $this->_groupManagement->getLoggedInGroups();
-            $this->_options = $this->_converter->toOptionArray($groups, 'id', 'code');
+            $this->_options = $this->groupSourceLoggedInOnly->toOptionArray();
         }
         return $this->_options;
     }
diff --git a/app/code/Magento/Customer/Model/Customer/Attribute/Source/Group.php b/app/code/Magento/Customer/Model/Customer/Attribute/Source/Group.php
index 4663d26b628b09d13a9e2a1556f03a31b951a181..9af4b7fc67340b390aec50dc4fb0c2891aef03e3 100644
--- a/app/code/Magento/Customer/Model/Customer/Attribute/Source/Group.php
+++ b/app/code/Magento/Customer/Model/Customer/Attribute/Source/Group.php
@@ -12,7 +12,7 @@ use Magento\Customer\Api\GroupManagementInterface;
  *
  * @author      Magento Core Team <core@magentocommerce.com>
  */
-class Group extends \Magento\Eav\Model\Entity\Attribute\Source\Table
+class Group extends \Magento\Eav\Model\Entity\Attribute\Source\Table implements GroupSourceLoggedInOnlyInterface
 {
     /**
      * @var GroupManagementInterface
@@ -50,6 +50,7 @@ class Group extends \Magento\Eav\Model\Entity\Attribute\Source\Table
             $groups = $this->_groupManagement->getLoggedInGroups();
             $this->_options = $this->_converter->toOptionArray($groups, 'id', 'code');
         }
+
         return $this->_options;
     }
 }
diff --git a/app/code/Magento/Customer/Model/Customer/Attribute/Source/GroupSourceLoggedInOnlyInterface.php b/app/code/Magento/Customer/Model/Customer/Attribute/Source/GroupSourceLoggedInOnlyInterface.php
new file mode 100644
index 0000000000000000000000000000000000000000..5570b4073dfdba163fb403c528790e26d904713c
--- /dev/null
+++ b/app/code/Magento/Customer/Model/Customer/Attribute/Source/GroupSourceLoggedInOnlyInterface.php
@@ -0,0 +1,14 @@
+<?php
+/**
+ * Copyright © 2016 Magento. All rights reserved.
+ * See COPYING.txt for license details.
+ */
+
+namespace Magento\Customer\Model\Customer\Attribute\Source;
+
+use \Magento\Framework\Data\OptionSourceInterface;
+
+interface GroupSourceLoggedInOnlyInterface extends OptionSourceInterface
+{
+
+}
diff --git a/app/code/Magento/Customer/Model/Customer/Source/Group.php b/app/code/Magento/Customer/Model/Customer/Source/Group.php
index 59dc3be52f1e57f87d7cd6b7888f27050f83e1dc..5973ec6ffaab3e91a706e4ec542500ec61b41b29 100644
--- a/app/code/Magento/Customer/Model/Customer/Source/Group.php
+++ b/app/code/Magento/Customer/Model/Customer/Source/Group.php
@@ -5,12 +5,13 @@
  */
 namespace Magento\Customer\Model\Customer\Source;
 
+use Magento\Customer\Api\Data\GroupSearchResultsInterface;
 use Magento\Framework\Module\Manager as ModuleManager;
 use Magento\Customer\Api\Data\GroupInterface;
 use Magento\Customer\Api\GroupRepositoryInterface;
 use Magento\Framework\Api\SearchCriteriaBuilder;
 
-class Group implements \Magento\Framework\Option\ArrayInterface
+class Group implements GroupSourceInterface
 {
     /**
      * @var ModuleManager
@@ -52,14 +53,13 @@ class Group implements \Magento\Framework\Option\ArrayInterface
         if (!$this->moduleManager->isEnabled('Magento_Customer')) {
             return [];
         }
-        $customerGroups = [
-            [
-                'label' => __('ALL GROUPS'),
-                'value' => GroupInterface::CUST_GROUP_ALL,
-            ]
+        $customerGroups = [];
+        $customerGroups[] = [
+            'label' => __('ALL GROUPS'),
+            'value' => GroupInterface::CUST_GROUP_ALL,
         ];
 
-        /** @var GroupInterface[] $groups */
+        /** @var GroupSearchResultsInterface $groups */
         $groups = $this->groupRepository->getList($this->searchCriteriaBuilder->create());
         foreach ($groups->getItems() as $group) {
             $customerGroups[] = [
diff --git a/app/code/Magento/Customer/Model/Customer/Source/GroupSourceInterface.php b/app/code/Magento/Customer/Model/Customer/Source/GroupSourceInterface.php
new file mode 100644
index 0000000000000000000000000000000000000000..c044740dc35e75ec46f5b20d66b79ad5832a31f9
--- /dev/null
+++ b/app/code/Magento/Customer/Model/Customer/Source/GroupSourceInterface.php
@@ -0,0 +1,13 @@
+<?php
+/**
+ * Copyright © 2016 Magento. All rights reserved.
+ * See COPYING.txt for license details.
+ */
+namespace Magento\Customer\Model\Customer\Source;
+
+use Magento\Framework\Data\OptionSourceInterface;
+
+interface GroupSourceInterface extends OptionSourceInterface
+{
+
+}
diff --git a/app/code/Magento/Customer/Test/Unit/Model/Config/Source/Group/MultiselectTest.php b/app/code/Magento/Customer/Test/Unit/Model/Config/Source/Group/MultiselectTest.php
index 3b8bea594ed523e268e13f5297cd00db2fdae0c0..bc64351f3d01591cea94892b759e08cefeb7d81d 100644
--- a/app/code/Magento/Customer/Test/Unit/Model/Config/Source/Group/MultiselectTest.php
+++ b/app/code/Magento/Customer/Test/Unit/Model/Config/Source/Group/MultiselectTest.php
@@ -6,6 +6,8 @@
  */
 namespace Magento\Customer\Test\Unit\Model\Config\Source\Group;
 
+use Magento\Customer\Model\Customer\Attribute\Source\GroupSourceLoggedInOnlyInterface;
+
 class MultiselectTest extends \PHPUnit_Framework_TestCase
 {
     /**
@@ -23,22 +25,29 @@ class MultiselectTest extends \PHPUnit_Framework_TestCase
      */
     protected $converterMock;
 
+    /**
+     * @var GroupSourceLoggedInOnlyInterface|\PHPUnit_Framework_MockObject_MockObject
+     */
+    private $groupSourceLoggedInOnly;
+
     protected function setUp()
     {
         $this->groupServiceMock = $this->getMock(\Magento\Customer\Api\GroupManagementInterface::class);
         $this->converterMock = $this->getMock(\Magento\Framework\Convert\DataObject::class, [], [], '', false);
-        $this->model =
-            new \Magento\Customer\Model\Config\Source\Group\Multiselect($this->groupServiceMock, $this->converterMock);
+        $this->groupSourceLoggedInOnly = $this->getMockBuilder(GroupSourceLoggedInOnlyInterface::class)->getMock();
+        $this->model = new \Magento\Customer\Model\Config\Source\Group\Multiselect(
+            $this->groupServiceMock,
+            $this->converterMock,
+            $this->groupSourceLoggedInOnly
+        );
     }
 
     public function testToOptionArray()
     {
         $expectedValue = ['General', 'Retail'];
-        $this->groupServiceMock->expects($this->once())
-            ->method('getLoggedInGroups')
-            ->will($this->returnValue($expectedValue));
-        $this->converterMock->expects($this->once())->method('toOptionArray')
-            ->with($expectedValue, 'id', 'code')->will($this->returnValue($expectedValue));
+        $this->groupServiceMock->expects($this->never())->method('getLoggedInGroups');
+        $this->converterMock->expects($this->never())->method('toOptionArray');
+        $this->groupSourceLoggedInOnly->expects($this->once())->method('toOptionArray')->willReturn($expectedValue);
         $this->assertEquals($expectedValue, $this->model->toOptionArray());
     }
 }
diff --git a/app/code/Magento/Customer/Test/Unit/Model/Config/Source/GroupTest.php b/app/code/Magento/Customer/Test/Unit/Model/Config/Source/GroupTest.php
index edb3405451ecab918ca046f91db5a7ce1dcc037d..55b495f9c419b4d2a208f2209f9c7049564fa367 100644
--- a/app/code/Magento/Customer/Test/Unit/Model/Config/Source/GroupTest.php
+++ b/app/code/Magento/Customer/Test/Unit/Model/Config/Source/GroupTest.php
@@ -6,10 +6,21 @@
  */
 namespace Magento\Customer\Test\Unit\Model\Config\Source;
 
+use Magento\Customer\Api\GroupManagementInterface;
+use Magento\Customer\Model\Config\Source\Group;
+use Magento\Customer\Model\Customer\Attribute\Source\GroupSourceLoggedInOnlyInterface;
+use Magento\Framework\Convert\DataObject;
+use Magento\Framework\TestFramework\Unit\Helper\ObjectManager;
+
 class GroupTest extends \PHPUnit_Framework_TestCase
 {
     /**
-     * @var \Magento\Customer\Model\Config\Source\Group
+     * @var GroupSourceLoggedInOnlyInterface|\PHPUnit_Framework_MockObject_MockObject
+     */
+    private $groupSource;
+
+    /**
+     * @var Group
      */
     protected $model;
 
@@ -25,20 +36,30 @@ class GroupTest extends \PHPUnit_Framework_TestCase
 
     protected function setUp()
     {
-        $this->groupServiceMock = $this->getMock(\Magento\Customer\Api\GroupManagementInterface::class);
-        $this->converterMock = $this->getMock(\Magento\Framework\Convert\DataObject::class, [], [], '', false);
-        $this->model =
-            new \Magento\Customer\Model\Config\Source\Group($this->groupServiceMock, $this->converterMock);
+        $this->groupServiceMock = $this->getMock(GroupManagementInterface::class);
+        $this->converterMock = $this->getMock(DataObject::class, [], [], '', false);
+        $this->groupSource = $this->getMockBuilder(GroupSourceLoggedInOnlyInterface::class)
+            ->getMockForAbstractClass();
+        $this->model = (new ObjectManager($this))->getObject(
+            Group::class,
+            [
+                'groupManagement' => $this->groupServiceMock,
+                'converter' => $this->converterMock,
+                'groupSourceForLoggedInCustomers' => $this->groupSource,
+            ]
+        );
     }
 
     public function testToOptionArray()
     {
         $expectedValue = ['General', 'Retail'];
-        $this->groupServiceMock->expects($this->once())
-            ->method('getLoggedInGroups')
-            ->will($this->returnValue($expectedValue));
-        $this->converterMock->expects($this->once())->method('toOptionArray')
-            ->with($expectedValue, 'id', 'code')->will($this->returnValue($expectedValue));
+        $this->groupServiceMock->expects($this->never())->method('getLoggedInGroups');
+        $this->converterMock->expects($this->never())->method('toOptionArray');
+
+        $this->groupSource->expects($this->once())
+            ->method('toOptionArray')
+            ->willReturn($expectedValue);
+
         array_unshift($expectedValue, ['value' => '', 'label' => __('-- Please Select --')]);
         $this->assertEquals($expectedValue, $this->model->toOptionArray());
     }
diff --git a/app/code/Magento/Customer/etc/di.xml b/app/code/Magento/Customer/etc/di.xml
index d8f320161ab060b9cce690848fb00a402f030bb4..ebde958f75bc1f678aafd7ccd677c1536f76ef69 100644
--- a/app/code/Magento/Customer/etc/di.xml
+++ b/app/code/Magento/Customer/etc/di.xml
@@ -51,6 +51,10 @@
                 type="Magento\Customer\Helper\View" />
     <preference for="Magento\Customer\Model\Address\CustomAttributeListInterface"
                 type="Magento\Customer\Model\Address\CustomAttributeList" />
+    <preference for="Magento\Customer\Model\Customer\Source\GroupSourceInterface"
+                type="Magento\Customer\Model\Customer\Source\Group" />
+    <preference for="Magento\Customer\Model\Customer\Attribute\Source\GroupSourceLoggedInOnlyInterface"
+                type="Magento\Customer\Model\Customer\Attribute\Source\Group"/>
     <type name="Magento\Customer\Model\Session">
         <arguments>
             <argument name="configShare" xsi:type="object">Magento\Customer\Model\Config\Share\Proxy</argument>
diff --git a/app/code/Magento/Sales/Block/Adminhtml/Order/Create/Form/AbstractForm.php b/app/code/Magento/Sales/Block/Adminhtml/Order/Create/Form/AbstractForm.php
index 6287af52bb8896cbdb0dbf38a4756493a26cde8f..45d120db4755bb7c461cd575473810baf0077198 100644
--- a/app/code/Magento/Sales/Block/Adminhtml/Order/Create/Form/AbstractForm.php
+++ b/app/code/Magento/Sales/Block/Adminhtml/Order/Create/Form/AbstractForm.php
@@ -194,12 +194,17 @@ abstract class AbstractForm extends \Magento\Sales\Block\Adminhtml\Order\Create\
                 if ($inputType == 'select' || $inputType == 'multiselect') {
                     $options = [];
                     foreach ($attribute->getOptions() as $optionData) {
-                        $options[] = ConvertArray::toFlatArray(
-                            $this->dataObjectProcessor->buildOutputDataArray(
-                                $optionData,
-                                \Magento\Customer\Api\Data\OptionInterface::class
-                            )
+                        $data = $this->dataObjectProcessor->buildOutputDataArray(
+                            $optionData,
+                            \Magento\Customer\Api\Data\OptionInterface::class
                         );
+                        foreach ($data as $key => $value) {
+                            if (is_array($value)) {
+                                unset($data[$key]);
+                                $data['value'] = $value;
+                            }
+                        }
+                        $options[] = $data;
                     }
                     $element->setValues($options);
                 } elseif ($inputType == 'date') {
diff --git a/app/code/Magento/Ui/view/base/web/js/form/components/insert-form.js b/app/code/Magento/Ui/view/base/web/js/form/components/insert-form.js
index f68e322a49dea5256093efded3fd9feaf827461d..11643d075a7965641617abc33440116bb4fd6fa1 100644
--- a/app/code/Magento/Ui/view/base/web/js/form/components/insert-form.js
+++ b/app/code/Magento/Ui/view/base/web/js/form/components/insert-form.js
@@ -38,7 +38,10 @@ define([
 
         el.innerHTML = elem;
         actions = el.getElementsByClassName(actionsClass)[0];
-        el.removeChild(actions);
+
+        if (actions) {
+            el.removeChild(actions);
+        }
 
         return el.innerHTML;
     }
diff --git a/app/code/Magento/Ui/view/base/web/js/lib/validation/validator.js b/app/code/Magento/Ui/view/base/web/js/lib/validation/validator.js
index 764b11dfbfea202bf40c8e631fdbcfdc0da1285e..7823871fd694c362912e0479a408d7befcf25450 100644
--- a/app/code/Magento/Ui/view/base/web/js/lib/validation/validator.js
+++ b/app/code/Magento/Ui/view/base/web/js/lib/validation/validator.js
@@ -27,12 +27,16 @@ define([
                 message: ''
             };
 
+        if (_.isObject(params)) {
+            message = params.message || '';
+        }
+
         if (!rulesList[id]) {
             return result;
         }
 
         rule    = rulesList[id];
-        message = rule.message;
+        message = message || rule.message;
         valid   = rule.handler(value, params, additionalParams);
 
         if (!valid) {
@@ -68,7 +72,7 @@ define([
             };
 
             _.every(rules, function (ruleParams, id) {
-                if (ruleParams !== false || additionalParams) {
+                if (ruleParams.validate || ruleParams !== false || additionalParams) {
                     result = validate(id, value, ruleParams, additionalParams);
 
                     return result.passed;
diff --git a/app/design/adminhtml/Magento/backend/Magento_AdminNotification/web/css/source/_module.less b/app/design/adminhtml/Magento/backend/Magento_AdminNotification/web/css/source/_module.less
index 585269e14d685dd8bc3e2f4ebcd90e1b500881da..bdb94e3a8202f9dc0142d5e81c5d6941e1567835 100644
--- a/app/design/adminhtml/Magento/backend/Magento_AdminNotification/web/css/source/_module.less
+++ b/app/design/adminhtml/Magento/backend/Magento_AdminNotification/web/css/source/_module.less
@@ -8,7 +8,16 @@
 //  _____________________________________________
 
 @message-system__background-color: @color-lazy-sun;
-@message-system-short-message__padding-vertical: 1.8rem;
+@message-system__border-color: @color-gray82;
+@message-system__border-width: .1rem;
+@message-system__color: @color-gray20;
+@message-system-short__padding-vertical: 1.5rem;
+@message-system-short-wrapper__height: 5rem;
+
+//  Triangle marker
+@message-system-triangle__height: .5rem;
+@message-system-triangle__padding-right: 3rem;
+@message-system-triangle__width: .8rem;
 
 //
 //  Message system
@@ -17,34 +26,124 @@
 .message-system-inner {
     &:extend(.abs-clearfix all);
     background: @message-system__background-color;
+    border: solid @message-system__border-color;
+    border-width: 0 @message-system__border-width @message-system__border-width;
+    position: relative;
+
+    .message-error {
+        background: none;
+    }
+
+    .message {
+        background: none;
+        margin: 0 0 -3px;
+        overflow: hidden;
+        padding: @message-system-short__padding-vertical 0 @message-system-short__padding-vertical 3.3rem;
+
+        &:before {
+            left: .3rem;
+        }
+    }
 
-    .message-system-list {
-        float: left;
-        width: 75%;
+    .action-menu-item {
+        &.action-close-wrapper {
+            width: 3.5rem;
+        }
+
+        .action-close {
+            float: right;
+        }
+
+        float: right;
+        padding: @message-system-short__padding-vertical 0 0;
+        vertical-align: top;
     }
 }
 
 .message-system-list {
+    border-bottom: 1px solid @message-system__border-color;
+    border-top: 1px solid @message-system__border-color;
     list-style: none;
-    margin: 0;
-    padding: 0;
+    margin: 0 0 1.5rem;
+
+    li {
+        + li {
+            border-top: 1px dashed @message-system__border-color;
+        }
+    }
 }
 
 .message-system-short {
+    min-height: @message-system-short-wrapper__height;
+
+    .action-close-wrapper {
+        display: none;
+    }
+}
+
+.message-system-short-wrapper {
     overflow: hidden;
-    text-align: right;
+    padding: 0 1.5rem 0 @indent__l;
+}
 
-    .message-system-short-label {
-        display: inline-block;
-        padding: @message-system-short-message__padding-vertical .3rem @message-system-short-message__padding-vertical 1rem;
+.message-system-collapsible {
+    background: @message-system__background-color;
+    border: @message-system__border-width solid @message-system__border-color;
+    border-top: 0;
+    display: none;
+    left: -1px;
+    padding: 0 @indent__l @message-system-short__padding-vertical;
+    position: absolute;
+    right: -1px;
+    top: 100%;
+    z-index: @z-index-5 - 2;
+
+    ._active & {
+        display: block;
     }
+}
 
-    .message {
+.message-system-action-dropdown {
+    .lib-button-reset();
+    float: right;
+    margin: @message-system-short__padding-vertical 0;
+    position: relative;
+
+    .action-toggle-triangle (
+        @_dropdown__padding-right: @message-system-triangle__padding-right;
+        @_triangle__height: @message-system-triangle__height;
+        @_triangle__width: @message-system-triangle__width;
+        @_triangle__color: @message-system__color;
+        @_triangle__hover__color: darken(@message-system__color, 10%);
+        @_triangle__right: (@message-system-triangle__padding-right / 2) - (@message-system-triangle__width/ 2);
+    );
+}
+
+.message-system-summary {
+    text-align: right;
+
+    .action__message-log {
+        border-right: 1px solid @message-system__border-color;
         display: inline-block;
-        padding: @message-system-short-message__padding-vertical 2rem @message-system-short-message__padding-vertical 3.3rem;
+        margin: 0 .2rem 0 @indent__xs;
+        padding-right: @indent__xs;
 
-        &:before {
-            left: .3rem;
+        &:last-child {
+            border-right: 0;
+            margin: 0;
+            padding: 0;
         }
     }
 }
+
+.notices-wrapper {
+    .admin__data-grid-loading-mask {
+        display: none;
+        min-height: @message-system-short-wrapper__height + @message-system__border-width;
+        z-index: @z-index-5 - 1;
+    }
+
+    .admin__data-grid-outer-wrap {
+        min-height: 0;
+    }
+}
diff --git a/app/design/adminhtml/Magento/backend/web/css/source/components/_messages.less b/app/design/adminhtml/Magento/backend/web/css/source/components/_messages.less
index 9d9a4cab990477f594601edefce714b2e1ad90ef..e955affe3fb4394a9daa38eeba643b64d8c1e658 100644
--- a/app/design/adminhtml/Magento/backend/web/css/source/components/_messages.less
+++ b/app/design/adminhtml/Magento/backend/web/css/source/components/_messages.less
@@ -28,6 +28,9 @@
 @alert-icon__warning__content: @icon-warning__content;
 @alert-icon__warning__color: @color-phoenix;
 
+@alert-icon__progress__content: @icon-refresh__content;
+@alert-icon__progress__color: @color-blue-dodger;
+
 @alert-icon__success__content: @icon-check-mage__content;
 @alert-icon__success__color: @color-green-apple;
 
@@ -93,6 +96,13 @@
     }
 }
 
+.message-progress {
+    &:before {
+        color: @alert-icon__progress__color;
+        content: @alert-icon__progress__content;
+    }
+}
+
 .message-error {
     background: @alert__error__background-color;
 
@@ -128,4 +138,4 @@
 .message-in-rating-edit {
     margin-left: 1.8rem;
     margin-right: 1.8rem;
-}
+}
\ No newline at end of file
diff --git a/dev/tests/api-functional/testsuite/Magento/Catalog/Api/ProductTierPriceManagementTest.php b/dev/tests/api-functional/testsuite/Magento/Catalog/Api/ProductTierPriceManagementTest.php
index b1a1e7a36858db51016f7282e5bad9ddde2e9259..ff7968292874eaf63f7c3970c11b3216868606a1 100644
--- a/dev/tests/api-functional/testsuite/Magento/Catalog/Api/ProductTierPriceManagementTest.php
+++ b/dev/tests/api-functional/testsuite/Magento/Catalog/Api/ProductTierPriceManagementTest.php
@@ -51,7 +51,7 @@ class ProductTierPriceManagementTest extends WebapiAbstract
     public function getListDataProvider()
     {
         return [
-            [0, 1, 5, 3],
+            [0, 2, 5, 3],
             [1, 0, null, null],
             ['all', 2, 8, 2],
         ];
diff --git a/dev/tests/functional/tests/app/Magento/Braintree/Test/Page/Adminhtml/BraintreeSettlementReport.xml b/dev/tests/functional/tests/app/Magento/Braintree/Test/Page/Adminhtml/BraintreeSettlementReport.xml
index 20359c41bd55fb148c4dd407a0b75c4e2cda8f93..bd2a0e60b20ab032b034a2930f1bd1e24f54f7fc 100644
--- a/dev/tests/functional/tests/app/Magento/Braintree/Test/Page/Adminhtml/BraintreeSettlementReport.xml
+++ b/dev/tests/functional/tests/app/Magento/Braintree/Test/Page/Adminhtml/BraintreeSettlementReport.xml
@@ -7,6 +7,6 @@
  -->
 <config xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="../../../../../../../vendor/magento/mtf/etc/pages.xsd">
     <page name="BraintreeSettlementReportIndex" area="Adminhtml" mca="braintree/report/index" module="Magento_Braintree">
-        <block name="settlementReportGrid" class="Magento\Braintree\Test\Block\Adminhtml\Report\Grid" locator=".admin__data-grid-outer-wrap" strategy="css selector"/>
+        <block name="settlementReportGrid" class="Magento\Braintree\Test\Block\Adminhtml\Report\Grid" locator="//div[contains(@data-bind, 'braintree_report')]" strategy="xpath"/>
     </page>
 </config>
diff --git a/dev/tests/functional/tests/app/Magento/Catalog/Test/Handler/CatalogProductSimple/Webapi.php b/dev/tests/functional/tests/app/Magento/Catalog/Test/Handler/CatalogProductSimple/Webapi.php
index f3d839ecf8a8e5e9f71d5617c2e62f2b90559dd8..4ec372ab7163bb627bdf1353618f81231bed504d 100644
--- a/dev/tests/functional/tests/app/Magento/Catalog/Test/Handler/CatalogProductSimple/Webapi.php
+++ b/dev/tests/functional/tests/app/Magento/Catalog/Test/Handler/CatalogProductSimple/Webapi.php
@@ -347,8 +347,16 @@ class Webapi extends AbstractWebApi implements CatalogProductSimpleInterface
                 $priceInfo['customer_group_id'] = $priceInfo['cust_group'];
                 unset($priceInfo['cust_group']);
 
-                $priceInfo['value'] = $priceInfo['price'];
-                unset($priceInfo['price']);
+                if (isset($priceInfo['price'])) {
+                    $priceInfo['value'] = $priceInfo['price'];
+                    unset($priceInfo['price']);
+                }
+                unset($priceInfo['value_type']);
+
+                if (isset($priceInfo['percentage_value'])) {
+                    $priceInfo['extension_attributes']['percentage_value'] = $priceInfo['percentage_value'];
+                    unset($priceInfo['percentage_value']);
+                }
 
                 $priceInfo['qty'] = $priceInfo['price_qty'];
                 unset($priceInfo['price_qty']);
diff --git a/dev/tests/functional/tests/app/Magento/Catalog/Test/Page/Adminhtml/CatalogProductIndex.xml b/dev/tests/functional/tests/app/Magento/Catalog/Test/Page/Adminhtml/CatalogProductIndex.xml
index a1de0d5599564a5693604efea577ae2a14e0d76e..c6a125bfd4ed236e4c8a6db38f6bfd7fb11a3e85 100644
--- a/dev/tests/functional/tests/app/Magento/Catalog/Test/Page/Adminhtml/CatalogProductIndex.xml
+++ b/dev/tests/functional/tests/app/Magento/Catalog/Test/Page/Adminhtml/CatalogProductIndex.xml
@@ -7,7 +7,7 @@
  -->
 <config xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="../../../../../../../vendor/magento/mtf/etc/pages.xsd">
     <page name="CatalogProductIndex" area="Adminhtml" mca="catalog/product/index" module="Magento_Catalog">
-        <block name="productGrid" class="Magento\Catalog\Test\Block\Adminhtml\Product\Grid" locator=".admin__data-grid-outer-wrap" strategy="css selector"/>
+        <block name="productGrid" class="Magento\Catalog\Test\Block\Adminhtml\Product\Grid" locator="//div[contains(@data-bind, 'product_listing')]" strategy="xpath"/>
         <block name="messagesBlock" class="Magento\Backend\Test\Block\Messages" locator="#messages" strategy="css selector"/>
         <block name="gridPageActionBlock" class="Magento\Catalog\Test\Block\Adminhtml\Product\GridPageAction" locator="#add_new_product" strategy="css selector"/>
     </page>
diff --git a/dev/tests/functional/tests/app/Magento/Cms/Test/Page/Adminhtml/CmsBlockIndex.xml b/dev/tests/functional/tests/app/Magento/Cms/Test/Page/Adminhtml/CmsBlockIndex.xml
index e23472508e83a83ca56d6e738bed43f3ca65bd88..c369a950df315087b09ff9528aa9c9227d258d18 100644
--- a/dev/tests/functional/tests/app/Magento/Cms/Test/Page/Adminhtml/CmsBlockIndex.xml
+++ b/dev/tests/functional/tests/app/Magento/Cms/Test/Page/Adminhtml/CmsBlockIndex.xml
@@ -9,6 +9,6 @@
     <page name="CmsBlockIndex" area="Adminhtml" mca="cms/block/index" module="Magento_Cms">
         <block name="messagesBlock" class="Magento\Backend\Test\Block\Messages" locator=".messages .message" strategy="css selector" />
         <block name="gridPageActions" class="Magento\Backend\Test\Block\GridPageActions" locator=".page-main-actions" strategy="css selector" />
-        <block name="cmsBlockGrid" class="Magento\Cms\Test\Block\Adminhtml\Block\CmsGrid" locator=".admin__data-grid-outer-wrap" strategy="css selector" />
+        <block name="cmsBlockGrid" class="Magento\Cms\Test\Block\Adminhtml\Block\CmsGrid" locator="//div[contains(@data-bind, 'cms_block_listing')]" strategy="xpath" />
     </page>
 </config>
diff --git a/dev/tests/functional/tests/app/Magento/Cms/Test/Page/Adminhtml/CmsPageIndex.xml b/dev/tests/functional/tests/app/Magento/Cms/Test/Page/Adminhtml/CmsPageIndex.xml
index ed2037cf893a39a14d6eee047f14c69da04f3dea..467d8f8a2ffe8309ef7964d4cdf925ab3544a347 100644
--- a/dev/tests/functional/tests/app/Magento/Cms/Test/Page/Adminhtml/CmsPageIndex.xml
+++ b/dev/tests/functional/tests/app/Magento/Cms/Test/Page/Adminhtml/CmsPageIndex.xml
@@ -8,7 +8,7 @@
 <config xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="../../../../../../../vendor/magento/mtf/etc/pages.xsd">
     <page name="CmsPageIndex" area="Adminhtml" mca="cms/page/index" module="Magento_Cms">
         <block name="pageActionsBlock" class="Magento\Backend\Test\Block\GridPageActions" locator=".page-main-actions" strategy="css selector" />
-        <block name="cmsPageGridBlock" class="Magento\Cms\Test\Block\Adminhtml\Page\Grid" locator=".admin__data-grid-outer-wrap" strategy="css selector" />
+        <block name="cmsPageGridBlock" class="Magento\Cms\Test\Block\Adminhtml\Page\Grid" locator="//div[contains(@data-bind, 'cms_page_listing')]" strategy="xpath" />
         <block name="messagesBlock" class="Magento\Backend\Test\Block\Messages" locator=".messages .message" strategy="css selector" />
     </page>
 </config>
diff --git a/dev/tests/functional/tests/app/Magento/Customer/Test/Page/Adminhtml/CustomerIndex.xml b/dev/tests/functional/tests/app/Magento/Customer/Test/Page/Adminhtml/CustomerIndex.xml
index d0247118d2d3b4261da1ba0e288f270f7ebd8ab3..7b908210b6e7f58de28dcf8188da38748b9a6e05 100644
--- a/dev/tests/functional/tests/app/Magento/Customer/Test/Page/Adminhtml/CustomerIndex.xml
+++ b/dev/tests/functional/tests/app/Magento/Customer/Test/Page/Adminhtml/CustomerIndex.xml
@@ -9,6 +9,6 @@
   <page name="CustomerIndex" area="Adminhtml" mca="customer/index" module="Magento_Customer">
     <block name="messagesBlock" class="Magento\Backend\Test\Block\Messages" locator="#messages" strategy="css selector"/>
     <block name="pageActionsBlock" class="Magento\Backend\Test\Block\GridPageActions" locator=".page-main-actions" strategy="css selector"/>
-    <block name="customerGridBlock" class="Magento\Customer\Test\Block\Adminhtml\CustomerGrid" locator=".admin__data-grid-outer-wrap" strategy="css selector"/>
+    <block name="customerGridBlock" class="Magento\Customer\Test\Block\Adminhtml\CustomerGrid" locator="//div[contains(@data-bind, 'customer_listing')]" strategy="xpath"/>
   </page>
 </config>
diff --git a/dev/tests/functional/tests/app/Magento/Sales/Test/Block/Adminhtml/Order/Grid.php b/dev/tests/functional/tests/app/Magento/Sales/Test/Block/Adminhtml/Order/Grid.php
index bd7d10a7cf04e5f4a1995edad8cbaec1d7d28fa1..3c2a9ed9c9b0fd2b09ea9435ce8cb0b43b46a58e 100644
--- a/dev/tests/functional/tests/app/Magento/Sales/Test/Block/Adminhtml/Order/Grid.php
+++ b/dev/tests/functional/tests/app/Magento/Sales/Test/Block/Adminhtml/Order/Grid.php
@@ -96,7 +96,7 @@ class Grid extends DataGrid
         $this->openFilterBlock();
 
         $storeGroupElements = $this->_rootElement->find($this->filters['purchase_point']['selector'])
-            ->getElements('//option/preceding-sibling::optgroup[1]', Locator::SELECTOR_XPATH);
+            ->getElements('.//option/preceding-sibling::optgroup[1]', Locator::SELECTOR_XPATH);
         $result = [];
 
         foreach ($storeGroupElements as $storeGroupElement) {
diff --git a/dev/tests/functional/tests/app/Magento/Sales/Test/Page/Adminhtml/OrderIndex.xml b/dev/tests/functional/tests/app/Magento/Sales/Test/Page/Adminhtml/OrderIndex.xml
index 1382f1c2567ddc1aac85673369703e632676bd11..beee22011c69e80d0ab7d20516a2193518ae0371 100644
--- a/dev/tests/functional/tests/app/Magento/Sales/Test/Page/Adminhtml/OrderIndex.xml
+++ b/dev/tests/functional/tests/app/Magento/Sales/Test/Page/Adminhtml/OrderIndex.xml
@@ -8,7 +8,7 @@
 <config xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="../../../../../../../vendor/magento/mtf/etc/pages.xsd">
     <page name="OrderIndex" area="Adminhtml" mca="sales/order/index" module="Magento_Sales">
         <block name="gridPageActions" class="Magento\Backend\Test\Block\GridPageActions" locator=".page-main-actions" strategy="css selector"/>
-        <block name="salesOrderGrid" class="Magento\Sales\Test\Block\Adminhtml\Order\Grid" locator=".admin__data-grid-outer-wrap" strategy="css selector"/>
+        <block name="salesOrderGrid" class="Magento\Sales\Test\Block\Adminhtml\Order\Grid" locator="//div[contains(@data-bind, 'sales_order_grid')]" strategy="xpath"/>
         <block name="messagesBlock" class="Magento\Backend\Test\Block\Messages" locator="#messages" strategy="css selector"/>
     </page>
 </config>
diff --git a/dev/tests/functional/tests/app/Magento/Search/Test/Page/Adminhtml/SynonymGroupIndex.xml b/dev/tests/functional/tests/app/Magento/Search/Test/Page/Adminhtml/SynonymGroupIndex.xml
index 85cc69ea56584e326ffabd656e694b11a1127681..a979454142f42adf17f8a566de68efd80b083bd6 100644
--- a/dev/tests/functional/tests/app/Magento/Search/Test/Page/Adminhtml/SynonymGroupIndex.xml
+++ b/dev/tests/functional/tests/app/Magento/Search/Test/Page/Adminhtml/SynonymGroupIndex.xml
@@ -9,6 +9,6 @@
     <page name="synonymGroupIndex" area="Adminhtml" mca="search/synonyms/index" module="Magento_Search">
         <block name="messagesBlock" class="Magento\Backend\Test\Block\Messages" locator=".messages .message" strategy="css selector" />
         <block name="gridPageActions" class="Magento\Backend\Test\Block\GridPageActions" locator=".page-main-actions" strategy="css selector" />
-        <block name="synonymGroupGrid" class="Magento\Search\Test\Block\Adminhtml\Block\SynonymGroupGrid" locator=".admin__data-grid-outer-wrap" strategy="css selector" />
+        <block name="synonymGroupGrid" class="Magento\Search\Test\Block\Adminhtml\Block\SynonymGroupGrid" locator="//div[contains(@data-bind, 'search_synonyms_grid')]" strategy="xpath" />
     </page>
 </config>
diff --git a/dev/tests/functional/tests/app/Magento/Shipping/Test/Page/Adminhtml/ShipmentIndex.xml b/dev/tests/functional/tests/app/Magento/Shipping/Test/Page/Adminhtml/ShipmentIndex.xml
index 81667dbbcc54dd5bf66c0c600a615d5709a01759..e9d5e1d21175ddb56d016c12af6c89e6c9fd20d0 100644
--- a/dev/tests/functional/tests/app/Magento/Shipping/Test/Page/Adminhtml/ShipmentIndex.xml
+++ b/dev/tests/functional/tests/app/Magento/Shipping/Test/Page/Adminhtml/ShipmentIndex.xml
@@ -7,7 +7,7 @@
  -->
 <config xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="../../../../../../../vendor/magento/mtf/etc/pages.xsd">
   <page name="ShipmentIndex" area="Adminhtml" mca="sales/shipment" module="Magento_Shipping">
-    <block name="shipmentsGrid" class="Magento\Shipping\Test\Block\Adminhtml\Shipment\Grid" locator=".admin__data-grid-outer-wrap" strategy="css selector"/>
+    <block name="shipmentsGrid" class="Magento\Shipping\Test\Block\Adminhtml\Shipment\Grid" locator="//div[contains(@data-bind, 'sales_order_shipment_grid')]" strategy="xpath"/>
     <block name="messagesBlock" class="Magento\Backend\Test\Block\Messages" locator="#messages" strategy="css selector"/>
   </page>
 </config>
diff --git a/dev/tests/integration/testsuite/Magento/AdvancedPricingImportExport/Model/Import/AdvancedPricingTest.php b/dev/tests/integration/testsuite/Magento/AdvancedPricingImportExport/Model/Import/AdvancedPricingTest.php
index e32939b195b7dfde6a9dd3a09c55dc14af2efed2..e5fd4cc0d9630080eb7c06f2558b02be889f8a57 100644
--- a/dev/tests/integration/testsuite/Magento/AdvancedPricingImportExport/Model/Import/AdvancedPricingTest.php
+++ b/dev/tests/integration/testsuite/Magento/AdvancedPricingImportExport/Model/Import/AdvancedPricingTest.php
@@ -124,7 +124,11 @@ class AdvancedPricingTest extends \PHPUnit_Framework_TestCase
             $this->assertEquals(3, count($tierPriceCollection));
             /** @var \Magento\Catalog\Model\Product\TierPrice $tierPrice */
             foreach ($tierPriceCollection as $tierPrice) {
-                $this->assertContains($tierPrice->getData(), $this->expectedTierPrice[$sku]);
+                $this->assertEquals(0, $tierPrice->getExtensionAttributes()->getPercentageValue());
+                $this->assertEquals(0, $tierPrice->getExtensionAttributes()->getWebsiteId());
+                $tierPriceData = $tierPrice->getData();
+                unset($tierPriceData['extension_attributes']);
+                $this->assertContains($tierPriceData, $this->expectedTierPrice[$sku]);
             }
         }
     }
@@ -240,7 +244,11 @@ class AdvancedPricingTest extends \PHPUnit_Framework_TestCase
             $this->assertEquals(3, count($tierPriceCollection));
             /** @var \Magento\Catalog\Model\Product\TierPrice $tierPrice */
             foreach ($tierPriceCollection as $tierPrice) {
-                $this->assertContains($tierPrice->getData(), $this->expectedTierPrice[$sku]);
+                $this->assertEquals(0, $tierPrice->getExtensionAttributes()->getPercentageValue());
+                $this->assertEquals(0, $tierPrice->getExtensionAttributes()->getWebsiteId());
+                $tierPriceData = $tierPrice->getData();
+                unset($tierPriceData['extension_attributes']);
+                $this->assertContains($tierPriceData, $this->expectedTierPrice[$sku]);
             }
         }
     }
diff --git a/dev/tests/integration/testsuite/Magento/Catalog/Model/Product/Attribute/Backend/TierpriceTest.php b/dev/tests/integration/testsuite/Magento/Catalog/Model/Product/Attribute/Backend/TierpriceTest.php
index c69bdc1738bd471b14ac31a3ddeb432b632ed441..89dd932f8a023c4adb2c7cc17cc04ad2eddfd6c6 100644
--- a/dev/tests/integration/testsuite/Magento/Catalog/Model/Product/Attribute/Backend/TierpriceTest.php
+++ b/dev/tests/integration/testsuite/Magento/Catalog/Model/Product/Attribute/Backend/TierpriceTest.php
@@ -95,6 +95,21 @@ class TierpriceTest extends \PHPUnit_Framework_TestCase
         $this->_model->validate($product);
     }
 
+    /**
+     * @expectedException \Magento\Framework\Exception\LocalizedException
+     */
+    public function testValidatePercentage()
+    {
+        $product = new \Magento\Framework\DataObject();
+        $product->setTierPrice(
+            [
+                ['website_id' => 0, 'cust_group' => 1, 'price_qty' => 2, 'percentage_value' => 101],
+            ]
+        );
+
+        $this->_model->validate($product);
+    }
+
     public function testPreparePriceData()
     {
         $data = [
@@ -122,7 +137,7 @@ class TierpriceTest extends \PHPUnit_Framework_TestCase
         $this->_model->afterLoad($product);
         $price = $product->getTierPrice();
         $this->assertNotEmpty($price);
-        $this->assertEquals(3, count($price));
+        $this->assertEquals(4, count($price));
     }
 
     /**
diff --git a/dev/tests/integration/testsuite/Magento/Catalog/Model/ResourceModel/Product/CollectionTest.php b/dev/tests/integration/testsuite/Magento/Catalog/Model/ResourceModel/Product/CollectionTest.php
index f0a665d8be8b7eea90480af5fad416b2659337d3..7f13f6c9c52962d5876f62f2182205e825e6aa20 100644
--- a/dev/tests/integration/testsuite/Magento/Catalog/Model/ResourceModel/Product/CollectionTest.php
+++ b/dev/tests/integration/testsuite/Magento/Catalog/Model/ResourceModel/Product/CollectionTest.php
@@ -17,6 +17,11 @@ class CollectionTest extends \PHPUnit_Framework_TestCase
      */
     protected $processor;
 
+    /**
+     * @var \Magento\Catalog\Api\ProductRepositoryInterface
+     */
+    private $productRepository;
+
     /**
      * Sets up the fixture, for example, opens a network connection.
      * This method is called before a test is executed.
@@ -30,6 +35,10 @@ class CollectionTest extends \PHPUnit_Framework_TestCase
         $this->processor = \Magento\TestFramework\Helper\Bootstrap::getObjectManager()->create(
             \Magento\Catalog\Model\Indexer\Product\Price\Processor::class
         );
+
+        $this->productRepository = \Magento\TestFramework\Helper\Bootstrap::getObjectManager()->create(
+            \Magento\Catalog\Api\ProductRepositoryInterface::class
+        );
     }
 
     /**
@@ -100,4 +109,19 @@ class CollectionTest extends \PHPUnit_Framework_TestCase
         $this->assertCount(2, $items);
         $this->assertEquals(15, $product->getPrice());
     }
+
+    /**
+     * @magentoDataFixture Magento/Catalog/Model/ResourceModel/_files/product_simple.php
+     * @magentoDbIsolation enabled
+     */
+    public function testGetProductsWithTierPrice()
+    {
+        $product = $this->productRepository->get('simple products');
+        $items = $this->collection->addIdFilter($product->getId())->addAttributeToSelect('price')
+            ->load()->addTierPriceData();
+        $tierPrices = $items->getFirstItem()->getTierPrices();
+        $this->assertCount(3, $tierPrices);
+        $this->assertEquals(50, $tierPrices[2]->getExtensionAttributes()->getPercentageValue());
+        $this->assertEquals(5, $tierPrices[2]->getValue());
+    }
 }
diff --git a/dev/tests/integration/testsuite/Magento/Catalog/Model/ResourceModel/_files/product_simple.php b/dev/tests/integration/testsuite/Magento/Catalog/Model/ResourceModel/_files/product_simple.php
index 60e5ad4f8b76a5789de5ca476a3f7cb453f35fa8..c1a7ef37dacc5c3bfc169af3b4f868371433c055 100644
--- a/dev/tests/integration/testsuite/Magento/Catalog/Model/ResourceModel/_files/product_simple.php
+++ b/dev/tests/integration/testsuite/Magento/Catalog/Model/ResourceModel/_files/product_simple.php
@@ -10,7 +10,6 @@ $product = \Magento\TestFramework\Helper\Bootstrap::getObjectManager()
     ->create(\Magento\Catalog\Model\Product::class);
 $product->isObjectNew(true);
 $product->setTypeId(\Magento\Catalog\Model\Product\Type::TYPE_SIMPLE)
-    ->setId(2)
     ->setAttributeSetId(4)
     ->setWebsiteIds([1])
     ->setName('Simple Products')
@@ -33,6 +32,12 @@ $product->setTypeId(\Magento\Catalog\Model\Product\Type::TYPE_SIMPLE)
                 'price_qty'  => 21,
                 'price'      => 81,
             ],
+            [
+                'website_id' => 0,
+                'cust_group' => Group::CUST_GROUP_ALL,
+                'price_qty'  => 30,
+                'percentage_value' => 50,
+            ],
         ]
     )
     ->setDescription('Description with <b>html tag</b>')
diff --git a/dev/tests/integration/testsuite/Magento/Catalog/_files/product_simple.php b/dev/tests/integration/testsuite/Magento/Catalog/_files/product_simple.php
index 794a2f9087a8ec7035f92e042008c35624b62653..aadf1e74a883ce823d574178cba29ca617f5abf0 100644
--- a/dev/tests/integration/testsuite/Magento/Catalog/_files/product_simple.php
+++ b/dev/tests/integration/testsuite/Magento/Catalog/_files/product_simple.php
@@ -4,6 +4,8 @@
  * See COPYING.txt for license details.
  */
 
+use Magento\Catalog\Api\Data\ProductTierPriceExtensionFactory;
+
 \Magento\TestFramework\Helper\Bootstrap::getInstance()->reinitialize();
 
 /** @var \Magento\TestFramework\ObjectManager $objectManager */
@@ -12,6 +14,92 @@ $objectManager = \Magento\TestFramework\Helper\Bootstrap::getObjectManager();
 /** @var \Magento\Catalog\Api\CategoryLinkManagementInterface $categoryLinkManagement */
 $categoryLinkManagement = $objectManager->create(\Magento\Catalog\Api\CategoryLinkManagementInterface::class);
 
+$tierPrices = [];
+/** @var \Magento\Catalog\Api\Data\ProductTierPriceInterfaceFactory $tierPriceFactory */
+$tierPriceFactory = $objectManager->get(\Magento\Catalog\Api\Data\ProductTierPriceInterfaceFactory::class);
+$tierPrices[] = $tierPriceFactory->create(
+    [
+        'data' => [
+            'customer_group_id' => \Magento\Customer\Model\Group::CUST_GROUP_ALL,
+            'qty' => 2,
+            'value' => 8
+        ]
+    ]
+);
+$tierPrices[] = $tierPriceFactory->create(
+    [
+        'data' => [
+            'customer_group_id' => \Magento\Customer\Model\Group::CUST_GROUP_ALL,
+            'qty' => 5,
+            'value' => 5
+        ]
+    ]
+);
+$tierPrices[] = $tierPriceFactory->create(
+    [
+        'data' => [
+            'customer_group_id' => \Magento\Customer\Model\Group::NOT_LOGGED_IN_ID,
+            'qty' => 3,
+            'value' => 5
+        ]
+    ]
+);
+/** @var  $tpExtensionAttributes */
+$tpExtensionAttributesFactory = $objectManager->create(ProductTierPriceExtensionFactory::class);
+$tpExtensionAttributes = $tpExtensionAttributesFactory->create()->setPercentageValue(50);
+
+$tierPrices[] = $tierPriceFactory->create(
+    [
+        'data' => [
+            'customer_group_id' => \Magento\Customer\Model\Group::NOT_LOGGED_IN_ID,
+            'qty' => 10
+        ]
+    ]
+)->setExtensionAttributes($tpExtensionAttributes);
+
+$tierPrices = [];
+/** @var \Magento\Catalog\Api\Data\ProductTierPriceInterfaceFactory $tierPriceFactory */
+$tierPriceFactory = $objectManager->get(\Magento\Catalog\Api\Data\ProductTierPriceInterfaceFactory::class);
+$tierPrices[] = $tierPriceFactory->create(
+    [
+        'data' => [
+            'customer_group_id' => \Magento\Customer\Model\Group::CUST_GROUP_ALL,
+            'qty' => 2,
+            'value' => 8
+        ]
+    ]
+);
+$tierPrices[] = $tierPriceFactory->create(
+    [
+        'data' => [
+            'customer_group_id' => \Magento\Customer\Model\Group::CUST_GROUP_ALL,
+            'qty' => 5,
+            'value' => 5
+        ]
+    ]
+);
+$tierPrices[] = $tierPriceFactory->create(
+    [
+        'data' => [
+            'customer_group_id' => \Magento\Customer\Model\Group::NOT_LOGGED_IN_ID,
+            'qty' => 3,
+            'value' => 5
+        ]
+    ]
+);
+/** @var  $tpExtensionAttributes */
+$tpExtensionAttributesFactory = $objectManager->create(ProductTierPriceExtensionFactory::class);
+$tpExtensionAttributes = $tpExtensionAttributesFactory->create()->setPercentageValue(50);
+
+$tierPrices[] = $tierPriceFactory->create(
+    [
+        'data' => [
+            'customer_group_id' => \Magento\Customer\Model\Group::NOT_LOGGED_IN_ID,
+            'qty' => 10
+        ]
+    ]
+)->setExtensionAttributes($tpExtensionAttributes);
+
 /** @var $product \Magento\Catalog\Model\Product */
 $product = $objectManager->create(\Magento\Catalog\Model\Product::class);
 $product->isObjectNew(true);
@@ -25,28 +113,7 @@ $product->setTypeId(\Magento\Catalog\Model\Product\Type::TYPE_SIMPLE)
     ->setWeight(1)
     ->setShortDescription("Short description")
     ->setTaxClassId(0)
-    ->setTierPrice(
-        [
-            [
-                'website_id' => 0,
-                'cust_group' => \Magento\Customer\Model\Group::CUST_GROUP_ALL,
-                'price_qty'  => 2,
-                'price'      => 8,
-            ],
-            [
-                'website_id' => 0,
-                'cust_group' => \Magento\Customer\Model\Group::CUST_GROUP_ALL,
-                'price_qty'  => 5,
-                'price'      => 5,
-            ],
-            [
-                'website_id' => 0,
-                'cust_group' => \Magento\Customer\Model\Group::NOT_LOGGED_IN_ID,
-                'price_qty'  => 3,
-                'price'      => 5,
-            ],
-        ]
-    )
+    ->setTierPrices($tierPrices)
     ->setDescription('Description with <b>html tag</b>')
     ->setMetaTitle('meta title')
     ->setMetaKeyword('meta keyword')
diff --git a/dev/tests/integration/testsuite/Magento/Customer/Model/Config/Source/Group/MultiselectTest.php b/dev/tests/integration/testsuite/Magento/Customer/Model/Config/Source/Group/MultiselectTest.php
index d337d9a764265d8d98f7e2d20a503d98fd0342fe..899572a500e6ba281912575973757065c823738f 100644
--- a/dev/tests/integration/testsuite/Magento/Customer/Model/Config/Source/Group/MultiselectTest.php
+++ b/dev/tests/integration/testsuite/Magento/Customer/Model/Config/Source/Group/MultiselectTest.php
@@ -18,13 +18,24 @@ class MultiselectTest extends \PHPUnit_Framework_TestCase
         $multiselect = Bootstrap::getObjectManager()->get(
             \Magento\Customer\Model\Config\Source\Group\Multiselect::class
         );
+
+        $options = $multiselect->toOptionArray();
+        $optionsToCompare = [];
+        foreach ($options as $option) {
+            if (is_array($option['value'])) {
+                $optionsToCompare = array_merge($optionsToCompare, $option['value']);
+            } else {
+                $optionsToCompare[] = $option;
+            }
+        }
+        sort($optionsToCompare);
         $this->assertEquals(
             [
                 ['value' => 1, 'label' => 'General'],
                 ['value' => 2, 'label' => 'Wholesale'],
                 ['value' => 3, 'label' => 'Retailer'],
             ],
-            $multiselect->toOptionArray()
+            $optionsToCompare
         );
     }
 }
diff --git a/dev/tests/integration/testsuite/Magento/Customer/Model/Config/Source/GroupTest.php b/dev/tests/integration/testsuite/Magento/Customer/Model/Config/Source/GroupTest.php
index b4674c7b6aae33b309777b1db4c78662a098a7ce..3d0776d058f44f1f22a46bf224c4233e7c91f4d7 100644
--- a/dev/tests/integration/testsuite/Magento/Customer/Model/Config/Source/GroupTest.php
+++ b/dev/tests/integration/testsuite/Magento/Customer/Model/Config/Source/GroupTest.php
@@ -16,14 +16,33 @@ class GroupTest extends \PHPUnit_Framework_TestCase
     {
         /** @var Group $group */
         $group = Bootstrap::getObjectManager()->get(\Magento\Customer\Model\Config\Source\Group::class);
-        $this->assertEquals(
-            [
-                ['value' => '', 'label' => '-- Please Select --'],
-                ['value' => 1, 'label' => 'General'],
-                ['value' => 2, 'label' => 'Wholesale'],
-                ['value' => 3, 'label' => 'Retailer'],
-            ],
-            $group->toOptionArray()
+        $options = $group->toOptionArray();
+        $this->assertContainsOptionRecursive('', '-- Please Select --', $options);
+    }
+
+    private function assertContainsOptionRecursive($expectedValue, $expectedLabel, array $values)
+    {
+        $this->assertTrue(
+            $this->hasOptionLabelRecursive($expectedValue, $expectedLabel, $values),
+            'Label ' . $expectedLabel . ' not found'
         );
     }
+
+    private function hasOptionLabelRecursive($value, $label, array $values)
+    {
+        $hasLabel = false;
+        foreach ($values as $option) {
+            $this->assertArrayHasKey('label', $option);
+            $this->assertArrayHasKey('value', $option);
+            if (strpos((string)$option['label'], (string)$label) !== false) {
+                $this->assertEquals($value, $option['value']);
+                $hasLabel = true;
+                break;
+            } elseif (is_array($option['value'])) {
+                $hasLabel |= $this->hasOptionLabelRecursive($value, $label, $option['value']);
+            }
+        }
+
+        return (bool)$hasLabel;
+    }
 }
diff --git a/dev/tests/integration/testsuite/Magento/ImportExport/Model/Import/Entity/EavAbstractTest.php b/dev/tests/integration/testsuite/Magento/ImportExport/Model/Import/Entity/EavAbstractTest.php
index c1e9be87a9ab6a1f5e4bb6ce6896dc23594b601e..e3810fc0766bd7a1028cbc209a092a655b5f5ca0 100644
--- a/dev/tests/integration/testsuite/Magento/ImportExport/Model/Import/Entity/EavAbstractTest.php
+++ b/dev/tests/integration/testsuite/Magento/ImportExport/Model/Import/Entity/EavAbstractTest.php
@@ -52,11 +52,17 @@ class EavAbstractTest extends \PHPUnit_Framework_TestCase
             $index = $attribute->getAttributeCode() == $indexAttributeCode ? 'value' : 'label';
             $expectedOptions = [];
             foreach ($attribute->getSource()->getAllOptions(false) as $option) {
-                $expectedOptions[strtolower($option[$index])] = $option['value'];
+                if (is_array($option['value'])) {
+                    foreach ($option['value'] as $value) {
+                        $expectedOptions[strtolower($value[$index])] = $value['value'];
+                    }
+                } else {
+                    $expectedOptions[strtolower($option[$index])] = $option['value'];
+                }
             }
             $actualOptions = $this->_model->getAttributeOptions($attribute, [$indexAttributeCode]);
-            sort($expectedOptions);
-            sort($actualOptions);
+            asort($expectedOptions);
+            asort($actualOptions);
             $this->assertEquals($expectedOptions, $actualOptions);
         }
     }
diff --git a/lib/internal/Magento/Framework/Module/Dir/Reader.php b/lib/internal/Magento/Framework/Module/Dir/Reader.php
index 7a8b1f11ef184654ee6e8318438610f552907809..353f7e51c590687823303f68aef2cd7899d2452f 100644
--- a/lib/internal/Magento/Framework/Module/Dir/Reader.php
+++ b/lib/internal/Magento/Framework/Module/Dir/Reader.php
@@ -46,6 +46,13 @@ class Reader
      */
     protected $readFactory;
 
+    /**
+     * Found configuration files grouped by configuration types (filename).
+     *
+     * @var array
+     */
+    private $fileIterators = [];
+
     /**
      * @param Dir $moduleDirs
      * @param ModuleListInterface $moduleList
@@ -65,24 +72,42 @@ class Reader
     }
 
     /**
-     * Go through all modules and find configuration files of active modules
+     * Go through all modules and find configuration files of active modules.
      *
      * @param string $filename
      * @return FileIterator
      */
     public function getConfigurationFiles($filename)
     {
-        return $this->fileIteratorFactory->create($this->getFiles($filename, Dir::MODULE_ETC_DIR));
+        return $this->getFilesIterator($filename, Dir::MODULE_ETC_DIR);
     }
 
     /**
-     * Go through all modules and find composer.json files of active modules
+     * Go through all modules and find composer.json files of active modules.
      *
      * @return FileIterator
      */
     public function getComposerJsonFiles()
     {
-        return $this->fileIteratorFactory->create($this->getFiles('composer.json'));
+        return $this->getFilesIterator('composer.json');
+    }
+
+    /**
+     * Retrieve iterator for files with $filename from components located in component $subDir.
+     *
+     * @param string $filename
+     * @param string $subDir
+     *
+     * @return FileIterator
+     */
+    private function getFilesIterator($filename, $subDir = '')
+    {
+        if (!isset($this->fileIterators[$subDir][$filename])) {
+            $this->fileIterators[$subDir][$filename] = $this->fileIteratorFactory->create(
+                $this->getFiles($filename, $subDir)
+            );
+        }
+        return $this->fileIterators[$subDir][$filename];
     }
 
     /**
@@ -96,9 +121,9 @@ class Reader
     {
         $result = [];
         foreach ($this->modulesList->getNames() as $moduleName) {
-            $moduleEtcDir = $this->getModuleDir($subDir, $moduleName);
-            $file = $moduleEtcDir . '/' . $filename;
-            $directoryRead = $this->readFactory->create($moduleEtcDir);
+            $moduleSubDir = $this->getModuleDir($subDir, $moduleName);
+            $file = $moduleSubDir . '/' . $filename;
+            $directoryRead = $this->readFactory->create($moduleSubDir);
             $path = $directoryRead->getRelativePath($file);
             if ($directoryRead->isExist($path)) {
                 $result[] = $file;
@@ -159,5 +184,6 @@ class Reader
     public function setModuleDir($moduleName, $type, $path)
     {
         $this->customModuleDirs[$moduleName][$type] = $path;
+        $this->fileIterators = [];
     }
 }
diff --git a/setup/src/Magento/Setup/Console/Command/DiCompileCommand.php b/setup/src/Magento/Setup/Console/Command/DiCompileCommand.php
index 65c0b090d6334a0ef6f6ffaa2a3c8b229389d073..3f577d9b3118755c88a575954c317d7ef140fb5e 100644
--- a/setup/src/Magento/Setup/Console/Command/DiCompileCommand.php
+++ b/setup/src/Magento/Setup/Console/Command/DiCompileCommand.php
@@ -140,17 +140,9 @@ class DiCompileCommand extends Command
             'library' => $libraryPaths,
             'generated_helpers' => $generationPath
         ];
-        $excludedModulePaths = [];
-        foreach ($modulePaths as $appCodePath) {
-            $excludedModulePaths[] = '#^' . $appCodePath . '/Test#';
-        }
-        $excludedLibraryPaths = [];
-        foreach ($libraryPaths as $libraryPath) {
-            $excludedLibraryPaths[] = '#^' . $libraryPath . '/([\\w]+/)?Test#';
-        }
         $this->excludedPathsList = [
-            'application' => $excludedModulePaths,
-            'framework' => $excludedLibraryPaths
+            'application' => $this->getExcludedModulePaths($modulePaths),
+            'framework' => $this->getExcludedLibraryPaths($libraryPaths),
         ];
         $this->configureObjectManager($output);
 
@@ -205,6 +197,54 @@ class DiCompileCommand extends Command
         }
     }
 
+    /**
+     * Build list of module path regexps which should be excluded from compilation
+     *
+     * @param string[] $modulePaths
+     * @return string[]
+     */
+    private function getExcludedModulePaths(array $modulePaths)
+    {
+        $modulesByBasePath = [];
+        foreach ($modulePaths as $modulePath) {
+            $moduleDir = basename($modulePath);
+            $vendorPath = dirname($modulePath);
+            $vendorDir = basename($vendorPath);
+            $basePath = dirname($vendorPath);
+            $modulesByBasePath[$basePath][$vendorDir][] = $moduleDir;
+        }
+
+        $basePathsRegExps = [];
+        foreach ($modulesByBasePath as $basePath => $vendorPaths) {
+            $vendorPathsRegExps = [];
+            foreach ($vendorPaths as $vendorDir => $vendorModules) {
+                $vendorPathsRegExps[] = $vendorDir
+                    . '/(?:' . join('|', $vendorModules) . ')';
+            }
+            $basePathsRegExps[] = $basePath
+                . '/(?:' . join('|', $vendorPathsRegExps) . ')';
+        }
+
+        $excludedModulePaths = [
+            '#^(?:' . join('|', $basePathsRegExps) . ')/Test#',
+        ];
+        return $excludedModulePaths;
+    }
+
+    /**
+     * Build list of library path regexps which should be excluded from compilation
+     *
+     * @param string[] $libraryPaths
+     * @return string[]
+     */
+    private function getExcludedLibraryPaths(array $libraryPaths)
+    {
+        $excludedLibraryPaths = [
+            '#^(?:' . join('|', $libraryPaths) . ')/([\\w]+/)?Test#',
+        ];
+        return $excludedLibraryPaths;
+    }
+
     /**
      * Delete directories by their code from "var" directory
      *