diff --git a/app/code/Magento/Bundle/Api/Data/LinkInterface.php b/app/code/Magento/Bundle/Api/Data/LinkInterface.php
index e16fad7f555be..4e2268eae8f9a 100644
--- a/app/code/Magento/Bundle/Api/Data/LinkInterface.php
+++ b/app/code/Magento/Bundle/Api/Data/LinkInterface.php
@@ -9,6 +9,24 @@
 
 interface LinkInterface extends \Magento\Framework\Api\ExtensibleDataInterface
 {
+    const PRICE_TYPE_FIXED = 0;
+    const PRICE_TYPE_PERCENT = 1;
+
+    /**
+     * Get the identifier
+     *
+     * @return string|null
+     */
+    public function getId();
+
+    /**
+     * Set id
+     *
+     * @param string $id
+     * @return $this
+     */
+    public function setId($id);
+
     /**
      * Get linked product sku
      *
@@ -69,21 +87,6 @@ public function getPosition();
      */
     public function setPosition($position);
 
-    /**
-     * Get is defined
-     *
-     * @return bool|null
-     */
-    public function getIsDefined();
-
-    /**
-     * Set is defined
-     *
-     * @param bool $isDefined
-     * @return $this
-     */
-    public function setIsDefined($isDefined);
-
     /**
      * Get is default
      *
diff --git a/app/code/Magento/Bundle/Api/ProductLinkManagementInterface.php b/app/code/Magento/Bundle/Api/ProductLinkManagementInterface.php
index 2109cde0f95d4..0529831f4094f 100644
--- a/app/code/Magento/Bundle/Api/ProductLinkManagementInterface.php
+++ b/app/code/Magento/Bundle/Api/ProductLinkManagementInterface.php
@@ -11,12 +11,13 @@ interface ProductLinkManagementInterface
     /**
      * Get all children for Bundle product
      *
-     * @param string $productId
+     * @param string $productSku
+     * @param int $optionId
      * @return \Magento\Bundle\Api\Data\LinkInterface[]
      * @throws \Magento\Framework\Exception\NoSuchEntityException
      * @throws \Magento\Framework\Exception\InputException
      */
-    public function getChildren($productId);
+    public function getChildren($productSku, $optionId = null);
 
     /**
      * Add child product to specified Bundle option by product sku
@@ -31,10 +32,23 @@ public function getChildren($productId);
      */
     public function addChildByProductSku($sku, $optionId, \Magento\Bundle\Api\Data\LinkInterface $linkedProduct);
 
+    /**
+     * @param string $sku
+     * @param \Magento\Bundle\Api\Data\LinkInterface $linkedProduct
+     * @throws \Magento\Framework\Exception\NoSuchEntityException
+     * @throws \Magento\Framework\Exception\CouldNotSaveException
+     * @throws \Magento\Framework\Exception\InputException
+     * @return bool
+     */
+    public function saveChild(
+        $sku,
+        \Magento\Bundle\Api\Data\LinkInterface $linkedProduct
+    );
+
     /**
      * @param \Magento\Catalog\Api\Data\ProductInterface $product
      * @param int $optionId
-     * @param Data\LinkInterface $linkedProduct
+     * @param \Magento\Bundle\Api\Data\LinkInterface $linkedProduct
      * @throws \Magento\Framework\Exception\NoSuchEntityException
      * @throws \Magento\Framework\Exception\CouldNotSaveException
      * @throws \Magento\Framework\Exception\InputException
diff --git a/app/code/Magento/Bundle/Model/Link.php b/app/code/Magento/Bundle/Model/Link.php
index eb99eff55e334..fef35d3261b03 100644
--- a/app/code/Magento/Bundle/Model/Link.php
+++ b/app/code/Magento/Bundle/Model/Link.php
@@ -16,17 +16,34 @@ class Link extends \Magento\Framework\Model\AbstractExtensibleModel implements
     /**#@+
      * Constants
      */
+    const KEY_ID = 'id';
     const KEY_SKU = 'sku';
     const KEY_OPTION_ID = 'option_id';
     const KEY_QTY = 'qty';
     const KEY_POSITION = 'position';
-    const KEY_IS_DEFINED = 'is_defined';
     const KEY_IS_DEFAULT = 'is_default';
     const KEY_PRICE = 'price';
     const KEY_PRICE_TYPE = 'price_type';
-    const KEY_CAN_CHANGE_QUANTITY = 'can_change_quantity';
+    const KEY_CAN_CHANGE_QUANTITY = 'selection_can_change_quantity';
     /**#@-*/
 
+    /**
+     * {@inheritdoc}
+     */
+    public function getId()
+    {
+        return $this->getData(self::KEY_ID);
+    }
+
+    /**
+     * {@inheritdoc}
+     */
+    public function setId($id)
+    {
+        return $this->setData(self::KEY_ID, $id);
+    }
+
+
     /**
      * {@inheritdoc}
      */
@@ -59,14 +76,6 @@ public function getPosition()
         return $this->getData(self::KEY_POSITION);
     }
 
-    /**
-     * {@inheritdoc}
-     */
-    public function getIsDefined()
-    {
-        return $this->getData(self::KEY_IS_DEFINED);
-    }
-
     /**
      * {@inheritdoc}
      */
@@ -143,17 +152,6 @@ public function setPosition($position)
         return $this->setData(self::KEY_POSITION, $position);
     }
 
-    /**
-     * Set is defined
-     *
-     * @param bool $isDefined
-     * @return $this
-     */
-    public function setIsDefined($isDefined)
-    {
-        return $this->setData(self::KEY_IS_DEFINED, $isDefined);
-    }
-
     /**
      * Set is default
      *
diff --git a/app/code/Magento/Bundle/Model/LinkManagement.php b/app/code/Magento/Bundle/Model/LinkManagement.php
index fe2c0d85d7b16..5d240c0517e59 100644
--- a/app/code/Magento/Bundle/Model/LinkManagement.php
+++ b/app/code/Magento/Bundle/Model/LinkManagement.php
@@ -75,15 +75,18 @@ public function __construct(
     /**
      * {@inheritdoc}
      */
-    public function getChildren($productId)
+    public function getChildren($productSku, $optionId = null)
     {
-        $product = $this->productRepository->get($productId);
+        $product = $this->productRepository->get($productSku);
         if ($product->getTypeId() != \Magento\Catalog\Model\Product\Type::TYPE_BUNDLE) {
             throw new InputException(__('Only implemented for bundle product'));
         }
 
         $childrenList = [];
         foreach ($this->getOptions($product) as $option) {
+            if ($optionId !== null && $option->getOptionId() != $optionId) {
+                continue;
+            }
             /** @var \Magento\Catalog\Model\Product $selection */
             foreach ($option->getSelections() as $selection) {
                 $childrenList[] = $this->buildLink($selection, $product);
@@ -107,6 +110,93 @@ public function addChildByProductSku($sku, $optionId, \Magento\Bundle\Api\Data\L
      * @SuppressWarnings(PHPMD.CyclomaticComplexity)
      * @SuppressWarnings(PHPMD.NPathComplexity)
      */
+    public function saveChild(
+        $sku,
+        \Magento\Bundle\Api\Data\LinkInterface $linkedProduct
+    ) {
+        $product = $this->productRepository->get($sku);
+        if ($product->getTypeId() != \Magento\Catalog\Model\Product\Type::TYPE_BUNDLE) {
+            throw new InputException(
+                __('Product with specified sku: "%1" is not a bundle product', [$product->getSku()])
+            );
+        }
+
+        /** @var \Magento\Catalog\Model\Product $linkProductModel */
+        $linkProductModel = $this->productRepository->get($linkedProduct->getSku());
+        if ($linkProductModel->isComposite()) {
+            throw new InputException(__('Bundle product could not contain another composite product'));
+        }
+
+        if (!$linkedProduct->getId()) {
+            throw new InputException(__('Id field of product link is required'));
+        }
+
+        /** @var \Magento\Bundle\Model\Selection $selectionModel */
+        $selectionModel = $this->bundleSelection->create();
+        $selectionModel->load($linkedProduct->getId());
+        if (!$selectionModel->getId()) {
+            throw new InputException(__('Can not find product link with id "%1"', [$linkedProduct->getId()]));
+        }
+
+        $selectionModel = $this->mapProductLinkToSelectionModel(
+            $selectionModel,
+            $linkedProduct,
+            $linkProductModel->getId(),
+            $product->getId()
+        );
+
+        try {
+            $selectionModel->save();
+        } catch (\Exception $e) {
+            throw new CouldNotSaveException(__('Could not save child: "%1"', $e->getMessage()), $e);
+        }
+
+        return true;
+    }
+
+    /**
+     * @param \Magento\Bundle\Model\Selection $selectionModel
+     * @param \Magento\Bundle\Api\Data\LinkInterface $productLink
+     * @param string $linkedProductId
+     * @param string $parentProductId
+     * @return \Magento\Bundle\Model\Selection
+     */
+    protected function mapProductLinkToSelectionModel(
+        \Magento\Bundle\Model\Selection $selectionModel,
+        \Magento\Bundle\Api\Data\LinkInterface $productLink,
+        $linkedProductId,
+        $parentProductId
+    ) {
+        $selectionModel->setProductId($linkedProductId);
+        $selectionModel->setParentProductId($parentProductId);
+        if (($productLink->getOptionId() !== null)) {
+            $selectionModel->setOptionId($productLink->getOptionId());
+        }
+        if ($productLink->getPosition() !== null) {
+            $selectionModel->setPosition($productLink->getPosition());
+        }
+        if ($productLink->getQty() !== null) {
+            $selectionModel->setSelectionQty($productLink->getQty());
+        }
+        if ($productLink->getPriceType() !== null) {
+            $selectionModel->setSelectionPriceType($productLink->getPriceType());
+        }
+        if ($productLink->getPrice() !== null) {
+            $selectionModel->setSelectionPriceValue($productLink->getPrice());
+        }
+        if ($productLink->getCanChangeQuantity() !== null) {
+            $selectionModel->setSelectionCanChangeQty($productLink->getCanChangeQuantity());
+        }
+        if ($productLink->getIsDefault() !== null) {
+            $selectionModel->setIsDefault($productLink->getIsDefault());
+        }
+
+        return $selectionModel;
+    }
+
+    /**
+     * {@inheritdoc}
+     */
     public function addChild(
         \Magento\Catalog\Api\Data\ProductInterface $product,
         $optionId,
@@ -119,17 +209,10 @@ public function addChild(
         }
 
         $options = $this->optionCollection->create();
-        $options->setProductIdFilter($product->getId())->joinValues($this->storeManager->getStore()->getId());
-        $isNewOption = true;
-        /** @var \Magento\Bundle\Model\Option $option */
-        foreach ($options as $option) {
-            if ($option->getOptionId() == $optionId) {
-                $isNewOption = false;
-                break;
-            }
-        }
+        $options->setIdFilter($optionId);
+        $existingOption = $options->getFirstItem();
 
-        if ($isNewOption) {
+        if (!$existingOption->getId()) {
             throw new InputException(
                 __(
                     'Product with specified sku: "%1" does not contain option: "%2"',
@@ -161,16 +244,13 @@ public function addChild(
         }
 
         $selectionModel = $this->bundleSelection->create();
-        $selectionModel->setOptionId($optionId)
-            ->setPosition($linkedProduct->getPosition())
-            ->setSelectionQty($linkedProduct->getQty())
-            ->setSelectionPriceType($linkedProduct->getPriceType())
-            ->setSelectionPriceValue($linkedProduct->getPrice())
-            ->setSelectionCanChangeQty($linkedProduct->getCanChangeQuantity())
-            ->setProductId($linkProductModel->getId())
-            ->setParentProductId($product->getId())
-            ->setIsDefault($linkedProduct->getIsDefault())
-            ->setWebsiteId($this->storeManager->getStore()->getWebsiteId());
+        $selectionModel = $this->mapProductLinkToSelectionModel(
+            $selectionModel,
+            $linkedProduct,
+            $linkProductModel->getId(),
+            $product->getId()
+        );
+        $selectionModel->setOptionId($optionId);
 
         try {
             $selectionModel->save();
@@ -242,8 +322,9 @@ private function buildLink(\Magento\Catalog\Model\Product $selection, \Magento\C
             '\Magento\Bundle\Api\Data\LinkInterface'
         );
         $link->setIsDefault($selection->getIsDefault())
+            ->setId($selection->getSelectionId())
             ->setQty($selection->getSelectionQty())
-            ->setIsDefined($selection->getSelectionCanChangeQty())
+            ->setCanChangeQuantity($selection->getSelectionCanChangeQty())
             ->setPrice($selectionPrice)
             ->setPriceType($selectionPriceType);
         return $link;
@@ -251,7 +332,7 @@ private function buildLink(\Magento\Catalog\Model\Product $selection, \Magento\C
 
     /**
      * @param \Magento\Catalog\Api\Data\ProductInterface $product
-     * @return \Magento\Bundle\Api\Data\OptionTypeInterface[]
+     * @return \Magento\Bundle\Api\Data\OptionInterface[]
      */
     private function getOptions(\Magento\Catalog\Api\Data\ProductInterface $product)
     {
diff --git a/app/code/Magento/Bundle/Model/OptionRepository.php b/app/code/Magento/Bundle/Model/OptionRepository.php
index 024b6f1cd2482..78caa83e801b3 100644
--- a/app/code/Magento/Bundle/Model/OptionRepository.php
+++ b/app/code/Magento/Bundle/Model/OptionRepository.php
@@ -161,7 +161,6 @@ public function deleteById($sku, $optionId)
 
     /**
      * {@inheritdoc}
-     * @SuppressWarnings(PHPMD.NPathComplexity)
      */
     public function save(
         \Magento\Catalog\Api\Data\ProductInterface $product,
@@ -170,35 +169,25 @@ public function save(
         $option->setStoreId($this->storeManager->getStore()->getId());
         $option->setParentId($product->getId());
 
-        if (!$option->getOptionId()) {
+        $optionId = $option->getOptionId();
+        $linksToAdd = [];
+        if (!$optionId) {
             $option->setDefaultTitle($option->getTitle());
-            $linksToAdd = is_array($option->getProductLinks()) ? $option->getProductLinks() : [];
+            if (is_array($option->getProductLinks())) {
+                $linksToAdd = $option->getProductLinks();
+            }
         } else {
             $optionCollection = $this->type->getOptionsCollection($product);
-            $optionCollection->setIdFilter($option->getOptionId());
 
             /** @var \Magento\Bundle\Model\Option $existingOption */
-            $existingOption = $optionCollection->getFirstItem();
+            $existingOption = $optionCollection->getItemById($option->getOptionId());
 
-            if (!$existingOption->getOptionId()) {
+            if (!isset($existingOption) || !$existingOption->getOptionId()) {
                 throw new NoSuchEntityException(__('Requested option doesn\'t exist'));
             }
 
             $option->setData(array_merge($existingOption->getData(), $option->getData()));
-
-            /** @var \Magento\Bundle\Api\Data\LinkInterface[] $existingLinks */
-            $existingLinks = is_array($existingOption->getProductLinks()) ? $existingOption->getProductLinks() : [];
-
-            /** @var \Magento\Bundle\Api\Data\LinkInterface[] $newProductLinks */
-            $newProductLinks = is_array($option->getProductLinks()) ? $option->getProductLinks() : [];
-
-            /** @var \Magento\Bundle\Api\Data\LinkInterface[] $linksToDelete */
-            $linksToDelete = array_udiff($existingLinks, $newProductLinks, [$this, 'compareLinks']);
-            foreach ($linksToDelete as $link) {
-                $this->linkManagement->removeChild($product->getSku(), $option->getOptionId(), $link->getSku());
-            }
-            /** @var \Magento\Bundle\Api\Data\LinkInterface[] $linksToAdd */
-            $linksToAdd = array_udiff($newProductLinks, $existingLinks, [$this, 'compareLinks']);
+            $this->updateOptionSelection($product, $option);
         }
 
         try {
@@ -215,6 +204,50 @@ public function save(
         return $option->getOptionId();
     }
 
+    /**
+     * Update option selections
+     *
+     * @param \Magento\Catalog\Api\Data\ProductInterface $product
+     * @param \Magento\Bundle\Api\Data\OptionInterface $option
+     * @return $this
+     */
+    protected function updateOptionSelection(
+        \Magento\Catalog\Api\Data\ProductInterface $product,
+        \Magento\Bundle\Api\Data\OptionInterface $option
+    ) {
+        $optionId = $option->getOptionId();
+        $existingLinks = $this->linkManagement->getChildren($product->getSku(), $optionId);
+        $linksToAdd = [];
+        $linksToUpdate = [];
+        $linksToDelete = [];
+        if (is_array($option->getProductLinks())) {
+            $productLinks = $option->getProductLinks();
+            foreach ($productLinks as $productLink) {
+                if (!$productLink->getId()) {
+                    $linksToAdd[] = $productLink;
+                } else {
+                    $linksToUpdate[] = $productLink;
+                }
+            }
+            /** @var \Magento\Bundle\Api\Data\LinkInterface[] $linksToDelete */
+            $linksToDelete = array_udiff($existingLinks, $linksToUpdate, [$this, 'compareLinks']);
+        }
+        foreach ($linksToUpdate as $linkedProduct) {
+            $this->linkManagement->saveChild($product->getSku(), $linkedProduct);
+        }
+        foreach ($linksToDelete as $linkedProduct) {
+            $this->linkManagement->removeChild(
+                $product->getSku(),
+                $option->getOptionId(),
+                $linkedProduct->getSku()
+            );
+        }
+        foreach ($linksToAdd as $linkedProduct) {
+            $this->linkManagement->addChild($product, $option->getOptionId(), $linkedProduct);
+        }
+        return $this;
+    }
+
     /**
      * @param string $sku
      * @return \Magento\Catalog\Api\Data\ProductInterface
@@ -241,7 +274,7 @@ private function compareLinks(
         \Magento\Bundle\Api\Data\LinkInterface $firstLink,
         \Magento\Bundle\Api\Data\LinkInterface $secondLink
     ) {
-        if ($firstLink->getSku() == $secondLink->getSku()) {
+        if ($firstLink->getId() == $secondLink->getId()) {
             return 0;
         } else {
             return 1;
diff --git a/app/code/Magento/Bundle/Model/Plugin/BundleLoadOptions.php b/app/code/Magento/Bundle/Model/Plugin/BundleLoadOptions.php
index 76b5dd84b3620..16bce777104a2 100644
--- a/app/code/Magento/Bundle/Model/Plugin/BundleLoadOptions.php
+++ b/app/code/Magento/Bundle/Model/Plugin/BundleLoadOptions.php
@@ -52,7 +52,10 @@ public function aroundLoad(
             return $product;
         }
 
-        $productExtension = $this->productExtensionFactory->create();
+        $productExtension = $product->getExtensionAttributes();
+        if ($productExtension === null) {
+            $productExtension = $this->productExtensionFactory->create();
+        }
         $productExtension->setBundleProductOptions($this->productOptionList->getItems($product));
 
         $product->setExtensionAttributes($productExtension);
diff --git a/app/code/Magento/Bundle/Model/Plugin/BundleSaveOptions.php b/app/code/Magento/Bundle/Model/Plugin/BundleSaveOptions.php
index b0e9ee6de4700..ccfd78d105480 100644
--- a/app/code/Magento/Bundle/Model/Plugin/BundleSaveOptions.php
+++ b/app/code/Magento/Bundle/Model/Plugin/BundleSaveOptions.php
@@ -17,8 +17,9 @@ class BundleSaveOptions
     /**
      * @param \Magento\Bundle\Api\ProductOptionRepositoryInterface $optionRepository
      */
-    public function __construct(\Magento\Bundle\Api\ProductOptionRepositoryInterface $optionRepository)
-    {
+    public function __construct(
+        \Magento\Bundle\Api\ProductOptionRepositoryInterface $optionRepository
+    ) {
         $this->optionRepository = $optionRepository;
     }
 
@@ -45,13 +46,37 @@ public function aroundSave(
         }
 
         /* @var \Magento\Bundle\Api\Data\OptionInterface[] $options */
-        $bundleProductOptions = $product->getExtensionAttributes()->getBundleProductOptions();
+        $extendedAttributes = $product->getExtensionAttributes();
+        if ($extendedAttributes === null) {
+            return $result;
+        }
+        $bundleProductOptions = $extendedAttributes->getBundleProductOptions();
+        if ($bundleProductOptions == null) {
+            return $result;
+        }
 
-        if (is_array($bundleProductOptions)) {
-            foreach ($bundleProductOptions as $option) {
-                $this->optionRepository->save($result, $option);
+        /** @var \Magento\Bundle\Api\Data\OptionInterface[] $bundleProductOptions */
+        $existingOptions = $this->optionRepository->getList($product->getSku());
+        $existingOptionsMap = [];
+        foreach ($existingOptions as $existingOption) {
+            $existingOptionsMap[$existingOption->getOptionId()] = $existingOption;
+        }
+        $updatedOptionIds = [];
+        foreach ($bundleProductOptions as $bundleOption) {
+            $optionId = $bundleOption->getOptionId();
+            if ($optionId) {
+                $updatedOptionIds[] = $optionId;
             }
         }
-        return $result;
+        $optionIdsToDelete = array_diff(array_keys($existingOptionsMap), $updatedOptionIds);
+        //Handle new and existing options
+        foreach ($bundleProductOptions as $option) {
+            $this->optionRepository->save($result, $option);
+        }
+        //Delete options that are not in the list
+        foreach ($optionIdsToDelete as $optionId) {
+            $this->optionRepository->delete($existingOptionsMap[$optionId]);
+        }
+        return $subject->get($result->getSku(), false, $result->getStoreId(), true);
     }
 }
diff --git a/app/code/Magento/Bundle/Model/Product/LinksList.php b/app/code/Magento/Bundle/Model/Product/LinksList.php
index d0b6a78635b6a..2d0076f67847b 100644
--- a/app/code/Magento/Bundle/Model/Product/LinksList.php
+++ b/app/code/Magento/Bundle/Model/Product/LinksList.php
@@ -61,8 +61,9 @@ public function getItems(\Magento\Catalog\Api\Data\ProductInterface $product, $o
                 '\Magento\Bundle\Api\Data\LinkInterface'
             );
             $productLink->setIsDefault($selection->getIsDefault())
+                ->setId($selection->getSelectionId())
                 ->setQty($selection->getSelectionQty())
-                ->setIsDefined($selection->getSelectionCanChangeQty())
+                ->setCanChangeQuantity($selection->getSelectionCanChangeQty())
                 ->setPrice($selectionPrice)
                 ->setPriceType($selectionPriceType);
             $productLinks[] = $productLink;
diff --git a/app/code/Magento/Bundle/Model/Selection.php b/app/code/Magento/Bundle/Model/Selection.php
index 919c54051566f..1f68f9795dd2b 100644
--- a/app/code/Magento/Bundle/Model/Selection.php
+++ b/app/code/Magento/Bundle/Model/Selection.php
@@ -8,6 +8,8 @@
 /**
  * Bundle Selection Model
  *
+ * @method int getSelectionId()
+ * @method \Magento\Bundle\Model\Selection setSelectionId(int $value)
  * @method int getOptionId()
  * @method \Magento\Bundle\Model\Selection setOptionId(int $value)
  * @method int getParentProductId()
diff --git a/app/code/Magento/Bundle/Model/Source/Option/Selection/Price/Type.php b/app/code/Magento/Bundle/Model/Source/Option/Selection/Price/Type.php
index 706de8be46be2..a541ac48c1248 100644
--- a/app/code/Magento/Bundle/Model/Source/Option/Selection/Price/Type.php
+++ b/app/code/Magento/Bundle/Model/Source/Option/Selection/Price/Type.php
@@ -5,6 +5,8 @@
  */
 namespace Magento\Bundle\Model\Source\Option\Selection\Price;
 
+use Magento\Bundle\Api\Data\LinkInterface;
+
 /**
  * Extended Attributes Source Model
  *
@@ -17,6 +19,9 @@ class Type implements \Magento\Framework\Option\ArrayInterface
      */
     public function toOptionArray()
     {
-        return [['value' => '0', 'label' => __('Fixed')], ['value' => '1', 'label' => __('Percent')]];
+        return [
+            ['value' => LinkInterface::PRICE_TYPE_FIXED, 'label' => __('Fixed')],
+            ['value' => LinkInterface::PRICE_TYPE_PERCENT, 'label' => __('Percent')]
+        ];
     }
 }
diff --git a/app/code/Magento/Bundle/Test/Unit/Model/LinkManagementTest.php b/app/code/Magento/Bundle/Test/Unit/Model/LinkManagementTest.php
index a1d9391b3e112..e4f49d48adcf3 100644
--- a/app/code/Magento/Bundle/Test/Unit/Model/LinkManagementTest.php
+++ b/app/code/Magento/Bundle/Test/Unit/Model/LinkManagementTest.php
@@ -111,7 +111,7 @@ protected function setUp()
             ->disableOriginalConstructor()
             ->getMock();
         $this->option = $this->getMockBuilder('Magento\Bundle\Model\Option')
-            ->setMethods(['getSelections', '__wakeup'])
+            ->setMethods(['getSelections', 'getOptionId', '__wakeup'])
             ->disableOriginalConstructor()
             ->getMock();
         $this->optionCollection = $this->getMockBuilder('Magento\Bundle\Model\Resource\Option\Collection')
@@ -193,14 +193,48 @@ public function testGetChildren()
             ->willReturnSelf();
         $this->link->expects($this->once())->method('setIsDefault')->willReturnSelf();
         $this->link->expects($this->once())->method('setQty')->willReturnSelf();
-        $this->link->expects($this->once())->method('setIsDefined')->willReturnSelf();
+        $this->link->expects($this->once())->method('setCanChangeQuantity')->willReturnSelf();
         $this->link->expects($this->once())->method('setPrice')->willReturnSelf();
         $this->link->expects($this->once())->method('setPriceType')->willReturnSelf();
+        $this->link->expects($this->once())->method('setId')->willReturnSelf();
         $this->linkFactory->expects($this->once())->method('create')->willReturn($this->link);
 
         $this->assertEquals([$this->link], $this->model->getChildren($productSku));
     }
 
+    public function testGetChildrenWithOptionId()
+    {
+        $productSku = 'productSku';
+
+        $this->getOptions();
+
+        $this->productRepository->expects($this->any())->method('get')->with($this->equalTo($productSku))
+            ->will($this->returnValue($this->product));
+
+        $this->product->expects($this->once())->method('getTypeId')->will($this->returnValue('bundle'));
+
+        $this->productType->expects($this->once())->method('setStoreFilter')->with(
+            $this->equalTo($this->storeId),
+            $this->product
+        );
+        $this->productType->expects($this->once())->method('getSelectionsCollection')
+            ->with($this->equalTo($this->optionIds), $this->equalTo($this->product))
+            ->will($this->returnValue($this->selectionCollection));
+        $this->productType->expects($this->once())->method('getOptionsIds')->with($this->equalTo($this->product))
+            ->will($this->returnValue($this->optionIds));
+
+        $this->optionCollection->expects($this->once())->method('appendSelections')
+            ->with($this->equalTo($this->selectionCollection))
+            ->will($this->returnValue([$this->option]));
+
+        $this->option->expects($this->any())->method('getOptionId')->will($this->returnValue(10));
+        $this->option->expects($this->never())->method('getSelections');
+
+        $this->dataObjectHelperMock->expects($this->never())->method('populateWithArray');
+
+        $this->assertEquals([], $this->model->getChildren($productSku, 1));
+    }
+
     /**
      * @expectedException \Magento\Framework\Exception\InputException
      */
@@ -243,31 +277,29 @@ public function testAddChildNonExistingOption()
         $productMock->expects($this->once())->method('getTypeId')->will($this->returnValue(
             \Magento\Catalog\Model\Product\Type::TYPE_BUNDLE
         ));
-        $productMock->expects($this->once())->method('getId')->will($this->returnValue('product_id'));
 
         $store = $this->getMock('\Magento\Store\Model\Store', [], [], '', false);
         $this->storeManagerMock->expects($this->any())->method('getStore')->will($this->returnValue($store));
         $store->expects($this->any())->method('getId')->will($this->returnValue(0));
 
-        $option = $this->getMockBuilder('\Magento\Bundle\Model\Option')->disableOriginalConstructor()
-            ->setMethods(['getOptionId', '__wakeup'])
+        $emptyOption = $this->getMockBuilder('\Magento\Bundle\Model\Option')->disableOriginalConstructor()
+            ->setMethods(['getId', '__wakeup'])
             ->getMock();
-        $option->expects($this->once())->method('getOptionId')->will($this->returnValue(2));
+        $emptyOption->expects($this->once())
+            ->method('getId')
+            ->will($this->returnValue(null));
 
         $optionsCollectionMock = $this->getMock(
             '\Magento\Bundle\Model\Resource\Option\Collection', [], [], '', false
         );
         $optionsCollectionMock->expects($this->once())
-            ->method('setProductIdFilter')
-            ->with($this->equalTo('product_id'))
+            ->method('setIdFilter')
+            ->with($this->equalTo(1))
             ->will($this->returnSelf());
         $optionsCollectionMock->expects($this->once())
-            ->method('joinValues')
-            ->with($this->equalTo(0))
-            ->will($this->returnSelf());
-        $optionsCollectionMock->expects($this->any())->method('getIterator')->will(
-            $this->returnValue(new \ArrayIterator([$option]))
-        );
+            ->method('getFirstItem')
+           ->will($this->returnValue($emptyOption));
+
         $this->optionCollectionFactoryMock->expects($this->any())->method('create')->will(
             $this->returnValue($optionsCollectionMock)
         );
@@ -304,22 +336,18 @@ public function testAddChildLinkedProductIsComposite()
         $store->expects($this->any())->method('getId')->will($this->returnValue(0));
 
         $option = $this->getMockBuilder('\Magento\Bundle\Model\Option')->disableOriginalConstructor()
-            ->setMethods(['getOptionId', '__wakeup'])
+            ->setMethods(['getId', '__wakeup'])
             ->getMock();
-        $option->expects($this->once())->method('getOptionId')->will($this->returnValue(1));
+        $option->expects($this->once())->method('getId')->will($this->returnValue(1));
 
         $optionsCollectionMock = $this->getMock('\Magento\Bundle\Model\Resource\Option\Collection', [], [], '', false);
         $optionsCollectionMock->expects($this->once())
-            ->method('setProductIdFilter')
-            ->with($this->equalTo('product_id'))
+            ->method('setIdFilter')
+            ->with($this->equalTo('1'))
             ->will($this->returnSelf());
         $optionsCollectionMock->expects($this->once())
-            ->method('joinValues')
-            ->with($this->equalTo(0))
-            ->will($this->returnSelf());
-        $optionsCollectionMock->expects($this->any())->method('getIterator')->will(
-            $this->returnValue(new \ArrayIterator([$option]))
-        );
+            ->method('getFirstItem')
+            ->will($this->returnValue($option));
         $this->optionCollectionFactoryMock->expects($this->any())->method('create')->will(
             $this->returnValue($optionsCollectionMock)
         );
@@ -359,22 +387,18 @@ public function testAddChildProductAlreadyExistsInOption()
         $store->expects($this->any())->method('getId')->will($this->returnValue(0));
 
         $option = $this->getMockBuilder('\Magento\Bundle\Model\Option')->disableOriginalConstructor()
-            ->setMethods(['getOptionId', '__wakeup'])
+            ->setMethods(['getId', '__wakeup'])
             ->getMock();
-        $option->expects($this->once())->method('getOptionId')->will($this->returnValue(1));
+        $option->expects($this->once())->method('getId')->will($this->returnValue(1));
 
         $optionsCollectionMock = $this->getMock('\Magento\Bundle\Model\Resource\Option\Collection', [], [], '', false);
         $optionsCollectionMock->expects($this->once())
-            ->method('setProductIdFilter')
-            ->with($this->equalTo('product_id'))
+            ->method('setIdFilter')
+            ->with($this->equalTo(1))
             ->will($this->returnSelf());
         $optionsCollectionMock->expects($this->once())
-            ->method('joinValues')
-            ->with($this->equalTo(0))
-            ->will($this->returnSelf());
-        $optionsCollectionMock->expects($this->any())->method('getIterator')->will(
-            $this->returnValue(new \ArrayIterator([$option]))
-        );
+            ->method('getFirstItem')
+            ->will($this->returnValue($option));
         $this->optionCollectionFactoryMock->expects($this->any())->method('create')->will(
             $this->returnValue($optionsCollectionMock)
         );
@@ -420,24 +444,20 @@ public function testAddChildCouldNotSave()
         $store->expects($this->any())->method('getId')->will($this->returnValue(0));
 
         $option = $this->getMockBuilder('\Magento\Bundle\Model\Option')->disableOriginalConstructor()
-            ->setMethods(['getOptionId', '__wakeup'])
+            ->setMethods(['getId', '__wakeup'])
             ->getMock();
-        $option->expects($this->once())->method('getOptionId')->will($this->returnValue(1));
+        $option->expects($this->once())->method('getId')->will($this->returnValue(1));
 
         $optionsCollectionMock = $this->getMock(
             '\Magento\Bundle\Model\Resource\Option\Collection', [], [], '', false
         );
         $optionsCollectionMock->expects($this->once())
-            ->method('setProductIdFilter')
-            ->with($this->equalTo('product_id'))
+            ->method('setIdFilter')
+            ->with($this->equalTo(1))
             ->will($this->returnSelf());
         $optionsCollectionMock->expects($this->once())
-            ->method('joinValues')
-            ->with($this->equalTo(0))
-            ->will($this->returnSelf());
-        $optionsCollectionMock->expects($this->any())->method('getIterator')->will(
-            $this->returnValue(new \ArrayIterator([$option]))
-        );
+            ->method('getFirstItem')
+            ->will($this->returnValue($option));
         $this->optionCollectionFactoryMock->expects($this->any())->method('create')->will(
             $this->returnValue($optionsCollectionMock)
         );
@@ -452,7 +472,7 @@ public function testAddChildCouldNotSave()
             ->will($this->returnValue($selections));
         $this->bundleFactoryMock->expects($this->once())->method('create')->will($this->returnValue($bundle));
 
-        $selection = $this->getMock('\Magento\Framework\Object', ['save'], [], '', false);
+        $selection = $this->getMock('\Magento\Bundle\Model\Selection', ['save'], [], '', false);
         $selection->expects($this->once())->method('save')
             ->will(
                 $this->returnCallback(
@@ -491,24 +511,20 @@ public function testAddChild()
         $store->expects($this->any())->method('getId')->will($this->returnValue(0));
 
         $option = $this->getMockBuilder('\Magento\Bundle\Model\Option')->disableOriginalConstructor()
-            ->setMethods(['getOptionId', '__wakeup'])
+            ->setMethods(['getId', '__wakeup'])
             ->getMock();
-        $option->expects($this->once())->method('getOptionId')->will($this->returnValue(1));
+        $option->expects($this->once())->method('getId')->will($this->returnValue(1));
 
         $optionsCollectionMock = $this->getMock(
             '\Magento\Bundle\Model\Resource\Option\Collection', [], [], '', false
         );
         $optionsCollectionMock->expects($this->once())
-            ->method('setProductIdFilter')
-            ->with($this->equalTo('product_id'))
+            ->method('setIdFilter')
+            ->with($this->equalTo(1))
             ->will($this->returnSelf());
         $optionsCollectionMock->expects($this->once())
-            ->method('joinValues')
-            ->with($this->equalTo(0))
-            ->will($this->returnSelf());
-        $optionsCollectionMock->expects($this->any())->method('getIterator')->will(
-            $this->returnValue(new \ArrayIterator([$option]))
-        );
+            ->method('getFirstItem')
+            ->will($this->returnValue($option));
         $this->optionCollectionFactoryMock->expects($this->any())->method('create')->will(
             $this->returnValue($optionsCollectionMock)
         );
@@ -523,7 +539,7 @@ public function testAddChild()
             ->will($this->returnValue($selections));
         $this->bundleFactoryMock->expects($this->once())->method('create')->will($this->returnValue($bundle));
 
-        $selection = $this->getMock('\Magento\Framework\Object', ['save', 'getId'], [], '', false);
+        $selection = $this->getMock('\Magento\Bundle\Model\Selection', ['save', 'getId'], [], '', false);
         $selection->expects($this->once())->method('save');
         $selection->expects($this->once())->method('getId')->will($this->returnValue(42));
         $this->bundleSelectionMock->expects($this->once())->method('create')->will($this->returnValue($selection));
@@ -531,6 +547,298 @@ public function testAddChild()
         $this->assertEquals(42, $result);
     }
 
+    public function testSaveChild()
+    {
+        $id = 12;
+        $optionId = 1;
+        $position = 3;
+        $qty = 2;
+        $priceType = 1;
+        $price = 10.5;
+        $canChangeQuantity = true;
+        $isDefault = true;
+        $linkProductId = 45;
+        $parentProductId = 32;
+        $bundleProductSku = 'bundleProductSku';
+
+        $productLink = $this->getMock('\Magento\Bundle\Api\Data\LinkInterface');
+        $productLink->expects($this->any())->method('getSku')->will($this->returnValue('linked_product_sku'));
+        $productLink->expects($this->any())->method('getId')->will($this->returnValue($id));
+        $productLink->expects($this->any())->method('getOptionId')->will($this->returnValue($optionId));
+        $productLink->expects($this->any())->method('getPosition')->will($this->returnValue($position));
+        $productLink->expects($this->any())->method('getQty')->will($this->returnValue($qty));
+        $productLink->expects($this->any())->method('getPriceType')->will($this->returnValue($priceType));
+        $productLink->expects($this->any())->method('getPrice')->will($this->returnValue($price));
+        $productLink->expects($this->any())->method('getCanChangeQuantity')->will($this->returnValue($canChangeQuantity));
+        $productLink->expects($this->any())->method('getIsDefault')->will($this->returnValue($isDefault));
+
+        $productMock = $this->getMock('\Magento\Catalog\Model\Product', [], [], '', false);
+        $productMock->expects($this->once())->method('getTypeId')->will($this->returnValue(
+            \Magento\Catalog\Model\Product\Type::TYPE_BUNDLE
+        ));
+        $productMock->expects($this->any())->method('getId')->will($this->returnValue($parentProductId));
+
+        $linkedProductMock = $this->getMock('\Magento\Catalog\Model\Product', [], [], '', false);
+        $linkedProductMock->expects($this->any())->method('getId')->will($this->returnValue($linkProductId));
+        $linkedProductMock->expects($this->once())->method('isComposite')->will($this->returnValue(false));
+        $this->productRepository
+            ->expects($this->at(0))
+            ->method('get')
+            ->with($bundleProductSku)
+            ->will($this->returnValue($productMock));
+        $this->productRepository
+            ->expects($this->at(1))
+            ->method('get')
+            ->with('linked_product_sku')
+            ->will($this->returnValue($linkedProductMock));
+
+        $store = $this->getMock('\Magento\Store\Model\Store', [], [], '', false);
+        $this->storeManagerMock->expects($this->any())->method('getStore')->will($this->returnValue($store));
+        $store->expects($this->any())->method('getId')->will($this->returnValue(0));
+
+        $selection = $this->getMock(
+            '\Magento\Bundle\Model\Selection',
+            [
+                'save',
+                'getId',
+                'load',
+                'setProductId',
+                'setParentProductId',
+                'setOptionId',
+                'setPosition',
+                'setSelectionQty',
+                'setSelectionPriceType',
+                'setSelectionPriceValue',
+                'setSelectionCanChangeQty',
+                'setIsDefault'
+            ],
+            [],
+            '',
+            false
+        );
+        $selection->expects($this->once())->method('save');
+        $selection->expects($this->once())->method('load')->with($id)->will($this->returnSelf());
+        $selection->expects($this->any())->method('getId')->will($this->returnValue($id));
+        $selection->expects($this->once())->method('setProductId')->with($linkProductId);
+        $selection->expects($this->once())->method('setParentProductId')->with($parentProductId);
+        $selection->expects($this->once())->method('setOptionId')->with($optionId);
+        $selection->expects($this->once())->method('setPosition')->with($position);
+        $selection->expects($this->once())->method('setSelectionQty')->with($qty);
+        $selection->expects($this->once())->method('setSelectionPriceType')->with($priceType);
+        $selection->expects($this->once())->method('setSelectionPriceValue')->with($price);
+        $selection->expects($this->once())->method('setSelectionCanChangeQty')->with($canChangeQuantity);
+        $selection->expects($this->once())->method('setIsDefault')->with($isDefault);
+
+        $this->bundleSelectionMock->expects($this->once())->method('create')->will($this->returnValue($selection));
+        $this->assertTrue($this->model->saveChild($bundleProductSku, $productLink));
+    }
+
+    /**
+     * @expectedException \Magento\Framework\Exception\CouldNotSaveException
+     */
+    public function testSaveChildFailedToSave()
+    {
+        $id = 12;
+        $linkProductId = 45;
+        $parentProductId = 32;
+        $productLink = $this->getMock('\Magento\Bundle\Api\Data\LinkInterface');
+        $productLink->expects($this->any())->method('getSku')->will($this->returnValue('linked_product_sku'));
+        $productLink->expects($this->any())->method('getId')->will($this->returnValue($id));
+        $bundleProductSku = 'bundleProductSku';
+
+        $productMock = $this->getMock('\Magento\Catalog\Model\Product', [], [], '', false);
+        $productMock->expects($this->once())->method('getTypeId')->will($this->returnValue(
+            \Magento\Catalog\Model\Product\Type::TYPE_BUNDLE
+        ));
+        $productMock->expects($this->any())->method('getId')->will($this->returnValue($parentProductId));
+
+        $linkedProductMock = $this->getMock('\Magento\Catalog\Model\Product', [], [], '', false);
+        $linkedProductMock->expects($this->any())->method('getId')->will($this->returnValue($linkProductId));
+        $linkedProductMock->expects($this->once())->method('isComposite')->will($this->returnValue(false));
+        $this->productRepository
+            ->expects($this->at(0))
+            ->method('get')
+            ->with($bundleProductSku)
+            ->will($this->returnValue($productMock));
+        $this->productRepository
+            ->expects($this->at(1))
+            ->method('get')
+            ->with('linked_product_sku')
+            ->will($this->returnValue($linkedProductMock));
+
+        $store = $this->getMock('\Magento\Store\Model\Store', [], [], '', false);
+        $this->storeManagerMock->expects($this->any())->method('getStore')->will($this->returnValue($store));
+        $store->expects($this->any())->method('getId')->will($this->returnValue(0));
+
+        $selection = $this->getMock(
+            '\Magento\Bundle\Model\Selection',
+            [
+                'save',
+                'getId',
+                'load',
+                'setProductId',
+                'setParentProductId',
+                'setSelectionId',
+                'setOptionId',
+                'setPosition',
+                'setSelectionQty',
+                'setSelectionPriceType',
+                'setSelectionPriceValue',
+                'setSelectionCanChangeQty',
+                'setIsDefault'
+            ],
+            [],
+            '',
+            false
+        );
+        $mockException = $this->getMock('\Exception');
+        $selection->expects($this->once())->method('save')->will($this->throwException($mockException));
+        $selection->expects($this->once())->method('load')->with($id)->will($this->returnSelf());
+        $selection->expects($this->any())->method('getId')->will($this->returnValue($id));
+        $selection->expects($this->once())->method('setProductId')->with($linkProductId);
+
+        $this->bundleSelectionMock->expects($this->once())->method('create')->will($this->returnValue($selection));
+        $this->model->saveChild($bundleProductSku, $productLink);
+    }
+
+    /**
+     * @expectedException \Magento\Framework\Exception\InputException
+     */
+    public function testSaveChildWithoutId()
+    {
+        $bundleProductSku = "bundleSku";
+        $linkedProductSku = 'simple';
+        $productLink = $this->getMock('\Magento\Bundle\Api\Data\LinkInterface');
+        $productLink->expects($this->any())->method('getId')->will($this->returnValue(null));
+        $productLink->expects($this->any())->method('getSku')->will($this->returnValue($linkedProductSku));
+
+        $productMock = $this->getMock('\Magento\Catalog\Model\Product', [], [], '', false);
+        $productMock->expects($this->once())->method('getTypeId')->will($this->returnValue(
+            \Magento\Catalog\Model\Product\Type::TYPE_BUNDLE
+        ));
+
+        $linkedProductMock = $this->getMock('\Magento\Catalog\Model\Product', [], [], '', false);
+        $linkedProductMock->expects($this->once())->method('isComposite')->will($this->returnValue(false));
+        $this->productRepository
+            ->expects($this->at(0))
+            ->method('get')
+            ->with($bundleProductSku)
+            ->will($this->returnValue($productMock));
+        $this->productRepository
+            ->expects($this->at(1))
+            ->method('get')
+            ->with($linkedProductSku)
+            ->will($this->returnValue($linkedProductMock));
+
+        $this->model->saveChild($bundleProductSku, $productLink);
+    }
+
+    /**
+     * @expectedException \Magento\Framework\Exception\InputException
+     * @expectedExceptionMessage Can not find product link with id "12345"
+     */
+    public function testSaveChildWithInvalidId()
+    {
+        $id = 12345;
+        $linkedProductSku = 'simple';
+        $bundleProductSku = "bundleProductSku";
+        $productLink = $this->getMock('\Magento\Bundle\Api\Data\LinkInterface');
+        $productLink->expects($this->any())->method('getId')->will($this->returnValue($id));
+        $productLink->expects($this->any())->method('getSku')->will($this->returnValue($linkedProductSku));
+
+        $productMock = $this->getMock('\Magento\Catalog\Model\Product', [], [], '', false);
+        $productMock->expects($this->once())->method('getTypeId')->will($this->returnValue(
+            \Magento\Catalog\Model\Product\Type::TYPE_BUNDLE
+        ));
+
+        $linkedProductMock = $this->getMock('\Magento\Catalog\Model\Product', [], [], '', false);
+        $linkedProductMock->expects($this->once())->method('isComposite')->will($this->returnValue(false));
+        $this->productRepository
+            ->expects($this->at(0))
+            ->method('get')
+            ->with($bundleProductSku)
+            ->will($this->returnValue($productMock));
+        $this->productRepository
+            ->expects($this->at(1))
+            ->method('get')
+            ->with($linkedProductSku)
+            ->will($this->returnValue($linkedProductMock));
+
+        $selection = $this->getMock(
+            '\Magento\Bundle\Model\Selection',
+            [
+                'getId',
+                'load',
+            ],
+            [],
+            '',
+            false
+        );
+        $selection->expects($this->once())->method('load')->with($id)->will($this->returnSelf());
+        $selection->expects($this->any())->method('getId')->will($this->returnValue(null));
+
+        $this->bundleSelectionMock->expects($this->once())->method('create')->will($this->returnValue($selection));
+
+        $this->model->saveChild($bundleProductSku, $productLink);
+    }
+
+    /**
+     * @expectedException \Magento\Framework\Exception\InputException
+     */
+    public function testSaveChildWithCompositeProductLink()
+    {
+        $bundleProductSku = "bundleProductSku";
+        $id = 12;
+        $linkedProductSku = 'simple';
+        $productLink = $this->getMock('\Magento\Bundle\Api\Data\LinkInterface');
+        $productLink->expects($this->any())->method('getId')->will($this->returnValue($id));
+        $productLink->expects($this->any())->method('getSku')->will($this->returnValue($linkedProductSku));
+
+        $productMock = $this->getMock('\Magento\Catalog\Model\Product', [], [], '', false);
+        $productMock->expects($this->once())->method('getTypeId')->will($this->returnValue(
+            \Magento\Catalog\Model\Product\Type::TYPE_BUNDLE
+        ));
+
+        $linkedProductMock = $this->getMock('\Magento\Catalog\Model\Product', [], [], '', false);
+        $linkedProductMock->expects($this->once())->method('isComposite')->will($this->returnValue(true));
+        $this->productRepository
+            ->expects($this->at(0))
+            ->method('get')
+            ->with($bundleProductSku)
+            ->will($this->returnValue($productMock));
+        $this->productRepository
+            ->expects($this->at(1))
+            ->method('get')
+            ->with($linkedProductSku)
+            ->will($this->returnValue($linkedProductMock));
+
+        $this->model->saveChild($bundleProductSku, $productLink);
+    }
+
+    /**
+     * @expectedException \Magento\Framework\Exception\InputException
+     */
+    public function testSaveChildWithSimpleProduct()
+    {
+        $id = 12;
+        $linkedProductSku = 'simple';
+        $bundleProductSku = "bundleProductSku";
+
+        $productLink = $this->getMock('\Magento\Bundle\Api\Data\LinkInterface');
+        $productLink->expects($this->any())->method('getId')->will($this->returnValue($id));
+        $productLink->expects($this->any())->method('getSku')->will($this->returnValue($linkedProductSku));
+
+        $productMock = $this->getMock('\Magento\Catalog\Model\Product', [], [], '', false);
+        $productMock->expects($this->once())->method('getTypeId')->will($this->returnValue(
+            \Magento\Catalog\Model\Product\Type::TYPE_SIMPLE
+        ));
+
+        $this->productRepository->expects($this->once())->method('get')->with($bundleProductSku)
+            ->willReturn($productMock);
+
+        $this->model->saveChild($bundleProductSku, $productLink);
+    }
+
     public function testRemoveChild()
     {
         $this->productRepository->expects($this->any())->method('get')->will($this->returnValue($this->product));
diff --git a/app/code/Magento/Bundle/Test/Unit/Model/OptionRepositoryTest.php b/app/code/Magento/Bundle/Test/Unit/Model/OptionRepositoryTest.php
index 7b91140a3bfe6..6c4fc418f0390 100644
--- a/app/code/Magento/Bundle/Test/Unit/Model/OptionRepositoryTest.php
+++ b/app/code/Magento/Bundle/Test/Unit/Model/OptionRepositoryTest.php
@@ -335,8 +335,9 @@ public function testUpdateIfOptionDoesNotExist()
             ->willReturn($optCollectionMock);
 
         $existingOptionMock = $this->getMock('\Magento\Bundle\Model\Option', ['getOptionId'], [], '', false);
-        $optCollectionMock->expects($this->once())->method('setIdFilter')->with($optionId)->willReturnSelf();
-        $optCollectionMock->expects($this->once())->method('getFirstItem')->willReturn($existingOptionMock);
+        $optCollectionMock->expects($this->once())->method('getItemById')
+            ->with($optionId)
+            ->willReturn($existingOptionMock);
         $existingOptionMock->expects($this->once())->method('getOptionId')->willReturn(null);
 
         $this->assertEquals($optionId, $this->model->save($productMock, $optionMock));
@@ -345,13 +346,16 @@ public function testUpdateIfOptionDoesNotExist()
     /**
      * @SuppressWarnings(PHPMD.UnusedLocalVariable)
      */
-    public function testUpdate()
+    public function testSaveExistingOption()
     {
         $productId = 1;
+        $productSku = 'bundle_sku';
         $storeId = 2;
         $optionId = 5;
         $existingOptionId = 5;
-        $existingOptionTitle = 'option_title';
+        $existingLinkToUpdateId = '23';
+        $existingLinkToDeleteId = '24';
+        $productSkuToDelete = 'simple2';
 
         $storeMock = $this->getMock('\Magento\Store\Model\Store', ['getId'], [], '', false);
         $storeMock->expects($this->once())->method('getId')->willReturn($storeId);
@@ -359,6 +363,7 @@ public function testUpdate()
 
         $productMock = $this->getMock('\Magento\Catalog\Model\Product', [], [], '', false);
         $productMock->expects($this->once())->method('getId')->willReturn($productId);
+        $productMock->expects($this->any())->method('getSku')->willReturn($productSku);
         $optionMock = $this->getMock(
             '\Magento\Bundle\Model\Option',
             [
@@ -378,6 +383,17 @@ public function testUpdate()
         $optionMock->expects($this->once())->method('setParentId')->with($productId)->willReturnSelf();
         $optionMock->expects($this->any())->method('getOptionId')->willReturn($optionId);
 
+        $existingLinkToUpdate = $this->getMock('\Magento\Bundle\Api\Data\LinkInterface');
+        $existingLinkToUpdate->expects($this->any())->method('getId')->willReturn($existingLinkToUpdateId);
+        $existingLinkToDelete = $this->getMock('\Magento\Bundle\Api\Data\LinkInterface');
+        $existingLinkToDelete->expects($this->any())->method('getId')->willReturn($existingLinkToDeleteId);
+        $existingLinkToDelete->expects($this->once())->method('getSku')->willReturn($productSkuToDelete);
+        $existingLinks = [$existingLinkToUpdate, $existingLinkToDelete];
+        $this->linkManagementMock->expects($this->once())
+            ->method('getChildren')
+            ->with($productSku, $optionId)
+            ->willReturn($existingLinks);
+
         $optCollectionMock = $this->getMock('\Magento\Bundle\Model\Resource\Option\Collection', [], [], '', false);
         $this->typeMock->expects($this->once())
             ->method('getOptionsCollection')
@@ -390,22 +406,183 @@ public function testUpdate()
             '',
             false
         );
-        $optCollectionMock->expects($this->once())->method('setIdFilter')->with($optionId)->willReturnSelf();
-        $optCollectionMock->expects($this->once())->method('getFirstItem')->willReturn($existingOptionMock);
+        $optCollectionMock->expects($this->once())->method('getItemById')
+            ->with($optionId)
+            ->willReturn($existingOptionMock);
         $existingOptionMock->expects($this->any())->method('getOptionId')->willReturn($existingOptionId);
-        $existingOptionMock->expects($this->once())->method('getProductLinks')->willReturn(null);
 
-        $linkedProductMock = $this->getMock('\Magento\Bundle\Api\Data\LinkInterface');
-        $optionMock->expects($this->exactly(2))->method('getProductLinks')->willReturn([$linkedProductMock]);
+        $productLinkUpdate = $this->getMock('\Magento\Bundle\Api\Data\LinkInterface');
+        $productLinkUpdate->expects($this->any())->method('getId')->willReturn($existingLinkToUpdateId);
+        $productLinkNew = $this->getMock('\Magento\Bundle\Api\Data\LinkInterface');
+        $productLinkNew->expects($this->any())->method('getId')->willReturn(null);
+        $optionMock->expects($this->exactly(2))
+            ->method('getProductLinks')
+            ->willReturn([$productLinkUpdate, $productLinkNew]);
 
         $this->optionResourceMock->expects($this->once())->method('save')->with($optionMock)->willReturnSelf();
         $this->linkManagementMock->expects($this->once())
             ->method('addChild')
-            ->with($productMock, $optionId, $linkedProductMock)
-            ->willReturn(1);
+            ->with($productMock, $optionId, $productLinkNew);
+        $this->linkManagementMock->expects($this->once())
+            ->method('saveChild')
+            ->with($productSku, $productLinkUpdate);
+        $this->linkManagementMock->expects($this->once())
+            ->method('removeChild')
+            ->with($productSku, $optionId, $productSkuToDelete);
+        $this->assertEquals($optionId, $this->model->save($productMock, $optionMock));
+    }
+
+    /**
+     * @expectedException \Magento\Framework\Exception\NoSuchEntityException
+     * @expectedExceptionMessage Requested option doesn't exist
+     */
+    public function testSaveExistingOptionNoSuchOption()
+    {
+        $productId = 1;
+        $productSku = 'bundle_sku';
+        $storeId = 2;
+        $optionId = 5;
+
+        $storeMock = $this->getMock('\Magento\Store\Model\Store', ['getId'], [], '', false);
+        $storeMock->expects($this->once())->method('getId')->willReturn($storeId);
+        $this->storeManagerMock->expects($this->once())->method('getStore')->willReturn($storeMock);
+
+        $productMock = $this->getMock('\Magento\Catalog\Model\Product', [], [], '', false);
+        $productMock->expects($this->once())->method('getId')->willReturn($productId);
+        $productMock->expects($this->any())->method('getSku')->willReturn($productSku);
+        $optionMock = $this->getMock(
+            '\Magento\Bundle\Model\Option',
+            [
+                'setStoreId',
+                'setParentId',
+                'getOptionId',
+            ],
+            [],
+            '',
+            false
+        );
+        $optionMock->expects($this->once())->method('setStoreId')->with($storeId)->willReturnSelf();
+        $optionMock->expects($this->once())->method('setParentId')->with($productId)->willReturnSelf();
+        $optionMock->expects($this->any())->method('getOptionId')->willReturn($optionId);
+
+        $optCollectionMock = $this->getMock('\Magento\Bundle\Model\Resource\Option\Collection', [], [], '', false);
+        $this->typeMock->expects($this->once())
+            ->method('getOptionsCollection')
+            ->with($productMock)
+            ->willReturn($optCollectionMock);
+        $existingOptionMock = $this->getMock(
+            '\Magento\Bundle\Model\Option',
+            ['getOptionId', 'getTitle', 'getProductLinks'],
+            [],
+            '',
+            false
+        );
+        $optCollectionMock->expects($this->once())->method('getItemById')
+            ->with($optionId)
+            ->willReturn($existingOptionMock);
+        $existingOptionMock->expects($this->any())->method('getOptionId')->willReturn(null);
+
+        $this->model->save($productMock, $optionMock);
+    }
+
+    public function testSaveNewOption()
+    {
+        $productId = 1;
+        $productSku = 'bundle_sku';
+        $storeId = 2;
+        $optionId = 5;
+
+        $storeMock = $this->getMock('\Magento\Store\Model\Store', ['getId'], [], '', false);
+        $storeMock->expects($this->once())->method('getId')->willReturn($storeId);
+        $this->storeManagerMock->expects($this->once())->method('getStore')->willReturn($storeMock);
+
+        $productMock = $this->getMock('\Magento\Catalog\Model\Product', [], [], '', false);
+        $productMock->expects($this->once())->method('getId')->willReturn($productId);
+        $productMock->expects($this->any())->method('getSku')->willReturn($productSku);
+        $optionMock = $this->getMock(
+            '\Magento\Bundle\Model\Option',
+            [
+                'setStoreId',
+                'setParentId',
+                'getProductLinks',
+                'getOptionId',
+                'setOptionId',
+                'setDefaultTitle',
+                'getTitle'
+            ],
+            [],
+            '',
+            false
+        );
+        $optionMock->expects($this->once())->method('setStoreId')->with($storeId)->willReturnSelf();
+        $optionMock->expects($this->once())->method('setParentId')->with($productId)->willReturnSelf();
+        $optionMock->method('getOptionId')
+            ->will($this->onConsecutiveCalls(null, $optionId, $optionId, $optionId, $optionId));
+
+        $productLink1 = $this->getMock('\Magento\Bundle\Api\Data\LinkInterface');
+        $productLink2 = $this->getMock('\Magento\Bundle\Api\Data\LinkInterface');
+        $optionMock->expects($this->exactly(2))
+            ->method('getProductLinks')
+            ->willReturn([$productLink1, $productLink2]);
+
+        $this->optionResourceMock->expects($this->once())->method('save')->with($optionMock)->willReturnSelf();
+        $this->linkManagementMock->expects($this->at(0))
+            ->method('addChild')
+            ->with($productMock, $optionId, $productLink1);
+        $this->linkManagementMock->expects($this->at(1))
+            ->method('addChild')
+            ->with($productMock, $optionId, $productLink2);
         $this->assertEquals($optionId, $this->model->save($productMock, $optionMock));
     }
 
+    /**
+     * @expectedException \Magento\Framework\Exception\CouldNotSaveException
+     * @expectedExceptionMessage Could not save option
+     */
+    public function testSaveCanNotSave()
+    {
+        $productId = 1;
+        $productSku = 'bundle_sku';
+        $storeId = 2;
+        $optionId = 5;
+
+        $storeMock = $this->getMock('\Magento\Store\Model\Store', ['getId'], [], '', false);
+        $storeMock->expects($this->once())->method('getId')->willReturn($storeId);
+        $this->storeManagerMock->expects($this->once())->method('getStore')->willReturn($storeMock);
+
+        $productMock = $this->getMock('\Magento\Catalog\Model\Product', [], [], '', false);
+        $productMock->expects($this->once())->method('getId')->willReturn($productId);
+        $productMock->expects($this->any())->method('getSku')->willReturn($productSku);
+        $optionMock = $this->getMock(
+            '\Magento\Bundle\Model\Option',
+            [
+                'setStoreId',
+                'setParentId',
+                'getProductLinks',
+                'getOptionId',
+                'setOptionId',
+                'setDefaultTitle',
+                'getTitle'
+            ],
+            [],
+            '',
+            false
+        );
+        $optionMock->expects($this->once())->method('setStoreId')->with($storeId)->willReturnSelf();
+        $optionMock->expects($this->once())->method('setParentId')->with($productId)->willReturnSelf();
+        $optionMock->method('getOptionId')->will($this->onConsecutiveCalls(null, $optionId, $optionId, $optionId));
+
+        $productLink1 = $this->getMock('\Magento\Bundle\Api\Data\LinkInterface');
+        $productLink2 = $this->getMock('\Magento\Bundle\Api\Data\LinkInterface');
+        $optionMock->expects($this->exactly(2))
+            ->method('getProductLinks')
+            ->willReturn([$productLink1, $productLink2]);
+
+        $this->optionResourceMock->expects($this->once())->method('save')->with($optionMock)
+            ->willThrowException($this->getMock('\Exception'));
+        $this->model->save($productMock, $optionMock);
+    }
+
     public function testGetList()
     {
         $productSku = 'simple';
diff --git a/app/code/Magento/Bundle/Test/Unit/Model/Plugin/BundleLoadOptionsTest.php b/app/code/Magento/Bundle/Test/Unit/Model/Plugin/BundleLoadOptionsTest.php
index e4050d99040ca..77d92ff634334 100644
--- a/app/code/Magento/Bundle/Test/Unit/Model/Plugin/BundleLoadOptionsTest.php
+++ b/app/code/Magento/Bundle/Test/Unit/Model/Plugin/BundleLoadOptionsTest.php
@@ -60,7 +60,6 @@ public function testAroundLoadIfProductTypeNotBundle()
 
     public function testAroundLoad()
     {
-        $this->markTestSkipped('MAGETWO-34577');
         $productMock = $this->getMock(
             '\Magento\Catalog\Model\Product',
             ['getTypeId', 'setExtensionAttributes'],
@@ -82,6 +81,7 @@ public function testAroundLoad()
             ->willReturn([$optionMock]);
         $productExtensionMock = $this->getMockBuilder('\Magento\Catalog\Api\Data\ProductExtension')
             ->disableOriginalConstructor()
+            ->setMethods(['setBundleProductOptions', 'getBundleProductOptions'])
             ->getMock();
         $this->productExtensionFactory->expects($this->once())
             ->method('create')
diff --git a/app/code/Magento/Bundle/Test/Unit/Model/Plugin/BundleSaveOptionsTest.php b/app/code/Magento/Bundle/Test/Unit/Model/Plugin/BundleSaveOptionsTest.php
index 1615760e6dbee..62095af664f2b 100644
--- a/app/code/Magento/Bundle/Test/Unit/Model/Plugin/BundleSaveOptionsTest.php
+++ b/app/code/Magento/Bundle/Test/Unit/Model/Plugin/BundleSaveOptionsTest.php
@@ -43,6 +43,11 @@ class BundleSaveOptionsTest extends \PHPUnit_Framework_TestCase
      */
     protected $productBundleOptionsMock;
 
+    /**
+     * @var \PHPUnit_Framework_MockObject_MockObject
+     */
+    protected $productInterfaceFactoryMock;
+
     /**
      * @var \Closure
      */
@@ -54,7 +59,7 @@ protected function setUp()
         $this->productOptionRepositoryMock = $this->getMock('Magento\Bundle\Api\ProductOptionRepositoryInterface');
         $this->productMock = $this->getMock(
             'Magento\Catalog\Model\Product',
-            ['getExtensionAttributes', 'getTypeId'],
+            ['getExtensionAttributes', 'getTypeId', 'getSku', 'getStoreId'],
             [],
             '',
             false
@@ -62,10 +67,12 @@ protected function setUp()
         $this->closureMock = function () {
             return $this->productMock;
         };
-        $this->plugin = new BundleSaveOptions($this->productOptionRepositoryMock);
+        $this->plugin = new BundleSaveOptions(
+            $this->productOptionRepositoryMock
+        );
         $this->productExtensionMock = $this->getMock(
             'Magento\Catalog\Api\Data\ProductExtension',
-            ['getBundleProductOptions'],
+            ['getBundleProductOptions', 'setBundleProductOptions'],
             [],
             '',
             false
@@ -98,7 +105,7 @@ public function testAroundSaveWhenProductIsBundleWithoutOptions()
             ->willReturn($this->productExtensionMock);
         $this->productExtensionMock->expects($this->once())
             ->method('getBundleProductOptions')
-            ->willReturn([]);
+            ->willReturn(null);
 
         $this->productOptionRepositoryMock->expects($this->never())->method('save');
 
@@ -110,20 +117,105 @@ public function testAroundSaveWhenProductIsBundleWithoutOptions()
 
     public function testAroundSaveWhenProductIsBundleWithOptions()
     {
+        $productSku = "bundle_sku";
+        $option = $this->getMock('\Magento\Bundle\Api\Data\OptionInterface');
+        $this->productMock->expects($this->once())->method('getTypeId')->willReturn('bundle');
+        $this->productMock->expects($this->once())
+            ->method('getExtensionAttributes')
+            ->willReturn($this->productExtensionMock);
+        $this->productExtensionMock->expects($this->once())
+            ->method('getBundleProductOptions')
+            ->willReturn([$option]);
+
+        $this->productOptionRepositoryMock->expects($this->once())->method('save')->with($this->productMock, $option);
+
+        $this->productMock->expects($this->exactly(2))->method('getSku')
+            ->will($this->returnValue($productSku));
+
+        $this->productOptionRepositoryMock->expects($this->once())
+            ->method('getList')
+            ->with($productSku)
+            ->will($this->returnValue([]));
+
+        $newProductMock = $this->getMockBuilder('Magento\Catalog\Api\Data\ProductInterface')
+            ->disableOriginalConstructor()->getMock();
+        $this->productRepositoryMock->expects($this->once())
+            ->method('get')
+            ->with($productSku, false, null, true)
+            ->willReturn($newProductMock);
+
+        $this->assertEquals(
+            $newProductMock,
+            $this->plugin->aroundSave($this->productRepositoryMock, $this->closureMock, $this->productMock)
+        );
+    }
+
+    /**
+     * Test the case where the product has existing options
+     */
+    public function testAroundSaveWhenProductIsBundleWithOptionsAndExistingOptions()
+    {
+        $existOption1Id = 10;
+        $existOption2Id = 11;
+        $productSku = 'bundle_sku';
+        $existingOption1 = $this->getMock('\Magento\Bundle\Api\Data\OptionInterface');
+        $existingOption1->expects($this->once())
+            ->method('getOptionId')
+            ->will($this->returnValue($existOption1Id));
+        $existingOption2 = $this->getMock('\Magento\Bundle\Api\Data\OptionInterface');
+        $existingOption2->expects($this->once())
+            ->method('getOptionId')
+            ->will($this->returnValue($existOption2Id));
+
+        $bundleOptionExisting = $this->getMock('\Magento\Bundle\Api\Data\OptionInterface');
+        $bundleOptionExisting->expects($this->once())
+            ->method('getOptionId')
+            ->will($this->returnValue($existOption1Id));
+
+        $bundleOptionNew = $this->getMock('\Magento\Bundle\Api\Data\OptionInterface');
+        $bundleOptionNew->expects($this->once())
+            ->method('getOptionId')
+            ->will($this->returnValue(null));
+
         $this->productMock->expects($this->once())->method('getTypeId')->willReturn('bundle');
         $this->productMock->expects($this->once())
             ->method('getExtensionAttributes')
             ->willReturn($this->productExtensionMock);
         $this->productExtensionMock->expects($this->once())
             ->method('getBundleProductOptions')
-            ->willReturn([$this->productBundleOptionsMock]);
+            ->willReturn([$bundleOptionExisting, $bundleOptionNew]);
+        $this->productMock->expects($this->exactly(2))->method('getSku')
+            ->will($this->returnValue($productSku));
 
         $this->productOptionRepositoryMock->expects($this->once())
+            ->method('getList')
+            ->with($productSku)
+            ->will($this->returnValue([$existingOption1, $existingOption2]));
+
+        $this->productOptionRepositoryMock
+            ->expects($this->at(1))
+            ->method('save')
+            ->with($this->productMock, $bundleOptionExisting);
+
+        $this->productOptionRepositoryMock
+            ->expects($this->at(1))
             ->method('save')
-            ->with($this->productMock, $this->productBundleOptionsMock);
+            ->with($this->productMock, $bundleOptionNew);
+
+        $this->productOptionRepositoryMock
+            ->expects($this->once())
+            ->method('delete')
+            ->with($existingOption2);
+
+        $newProductMock = $this->getMockBuilder('Magento\Catalog\Api\Data\ProductInterface')
+            ->disableOriginalConstructor()->getMock();
+        $this->productRepositoryMock->expects($this->once())
+            ->method('get')
+            ->with($productSku, false, null, true)
+            ->willReturn($newProductMock);
 
         $this->assertEquals(
-            $this->productMock,
+            $newProductMock,
             $this->plugin->aroundSave($this->productRepositoryMock, $this->closureMock, $this->productMock)
         );
     }
diff --git a/app/code/Magento/Bundle/Test/Unit/Model/Product/LinksListTest.php b/app/code/Magento/Bundle/Test/Unit/Model/Product/LinksListTest.php
index 86c409a3de09e..0909834ce5914 100644
--- a/app/code/Magento/Bundle/Test/Unit/Model/Product/LinksListTest.php
+++ b/app/code/Magento/Bundle/Test/Unit/Model/Product/LinksListTest.php
@@ -64,6 +64,7 @@ protected function setUp()
                 'getIsDefault',
                 'getSelectionQty',
                 'getSelectionCanChangeQty',
+                'getSelectionId',
                 '__wakeup'
             ],
             [],
@@ -89,6 +90,7 @@ protected function setUp()
     public function testLinksList()
     {
         $optionId = 665;
+        $selectionId = 1345;
         $this->productTypeMock->expects($this->once())
             ->method('getSelectionsCollection')
             ->with([$optionId], $this->productMock)
@@ -99,6 +101,7 @@ public function testLinksList()
             ->willReturn('selection_price_type');
         $this->selectionMock->expects($this->once())->method('getSelectionPriceValue')->willReturn(12);
         $this->selectionMock->expects($this->once())->method('getData')->willReturn(['some data']);
+        $this->selectionMock->expects($this->once())->method('getSelectionId')->willReturn($selectionId);
         $this->selectionMock->expects($this->once())->method('getIsDefault')->willReturn(true);
         $this->selectionMock->expects($this->once())->method('getSelectionQty')->willReturn(66);
         $this->selectionMock->expects($this->once())->method('getSelectionCanChangeQty')->willReturn(22);
@@ -108,8 +111,9 @@ public function testLinksList()
             ->with($linkMock, ['some data'], '\Magento\Bundle\Api\Data\LinkInterface')->willReturnSelf();
         $linkMock->expects($this->once())->method('setIsDefault')->with(true)->willReturnSelf();
         $linkMock->expects($this->once())->method('setQty')->with(66)->willReturnSelf();
-        $linkMock->expects($this->once())->method('setIsDefined')->with(22)->willReturnSelf();
+        $linkMock->expects($this->once())->method('setCanChangeQuantity')->with(22)->willReturnSelf();
         $linkMock->expects($this->once())->method('setPrice')->with(12)->willReturnSelf();
+        $linkMock->expects($this->once())->method('setId')->with($selectionId)->willReturnSelf();
         $linkMock->expects($this->once())
             ->method('setPriceType')->with('selection_price_type')->willReturnSelf();
         $this->linkFactoryMock->expects($this->once())->method('create')->willReturn($linkMock);
diff --git a/app/code/Magento/Bundle/etc/data_object.xml b/app/code/Magento/Bundle/etc/data_object.xml
index 88e317dafc78c..2b3da013978f9 100644
--- a/app/code/Magento/Bundle/etc/data_object.xml
+++ b/app/code/Magento/Bundle/etc/data_object.xml
@@ -8,7 +8,5 @@
 <config xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="../../../../../lib/internal/Magento/Framework/Api/etc/data_object.xsd">
     <custom_attributes for="Magento\Catalog\Api\Data\ProductInterface">
         <attribute code="bundle_product_options" type="Magento\Bundle\Api\Data\OptionInterface[]" />
-        <attribute code="price_type" type="integer" />
-        <attribute code="price_view" type="string" />
     </custom_attributes>
 </config>
diff --git a/app/code/Magento/Bundle/etc/webapi.xml b/app/code/Magento/Bundle/etc/webapi.xml
index ec0dffcf1049e..550987ba13e66 100644
--- a/app/code/Magento/Bundle/etc/webapi.xml
+++ b/app/code/Magento/Bundle/etc/webapi.xml
@@ -13,7 +13,13 @@
             <resource ref="Magento_Catalog::products"/>
         </resources>
     </route>
-    <route url="/V1/bundle-products/:productId/children" method="GET">
+    <route url="/V1/bundle-products/:sku/links/:id" method="PUT">
+        <service class="Magento\Bundle\Api\ProductLinkManagementInterface" method="saveChild"/>
+        <resources>
+            <resource ref="Magento_Catalog::products"/>
+        </resources>
+    </route>
+    <route url="/V1/bundle-products/:productSku/children" method="GET">
         <service class="Magento\Bundle\Api\ProductLinkManagementInterface" method="getChildren"/>
         <resources>
             <resource ref="Magento_Catalog::products"/>
diff --git a/app/code/Magento/Catalog/Api/ProductRepositoryInterface.php b/app/code/Magento/Catalog/Api/ProductRepositoryInterface.php
index 8205f68fca609..7fb25a7c046f5 100644
--- a/app/code/Magento/Catalog/Api/ProductRepositoryInterface.php
+++ b/app/code/Magento/Catalog/Api/ProductRepositoryInterface.php
@@ -27,10 +27,11 @@ public function save(\Magento\Catalog\Api\Data\ProductInterface $product, $saveO
      * @param string $sku
      * @param bool $editMode
      * @param null|int $storeId
+     * @param bool $forceReload
      * @return \Magento\Catalog\Api\Data\ProductInterface
      * @throws \Magento\Framework\Exception\NoSuchEntityException
      */
-    public function get($sku, $editMode = false, $storeId = null);
+    public function get($sku, $editMode = false, $storeId = null, $forceReload = false);
 
     /**
      * Get info about product by product id
@@ -38,10 +39,11 @@ public function get($sku, $editMode = false, $storeId = null);
      * @param int $productId
      * @param bool $editMode
      * @param null|int $storeId
+     * @param bool $forceReload
      * @return \Magento\Catalog\Api\Data\ProductInterface
      * @throws \Magento\Framework\Exception\NoSuchEntityException
      */
-    public function getById($productId, $editMode = false, $storeId = null);
+    public function getById($productId, $editMode = false, $storeId = null, $forceReload = false);
 
     /**
      * Delete product
diff --git a/app/code/Magento/Catalog/Controller/Adminhtml/Product/Initialization/Helper.php b/app/code/Magento/Catalog/Controller/Adminhtml/Product/Initialization/Helper.php
index 9dbbddcee68f8..318fad719e5c0 100644
--- a/app/code/Magento/Catalog/Controller/Adminhtml/Product/Initialization/Helper.php
+++ b/app/code/Magento/Catalog/Controller/Adminhtml/Product/Initialization/Helper.php
@@ -64,6 +64,8 @@ public function __construct(
     public function initialize(\Magento\Catalog\Model\Product $product)
     {
         $productData = $this->request->getPost('product');
+        unset($productData['custom_attributes']);
+        unset($productData['extension_attributes']);
 
         if ($productData) {
             $stockData = isset($productData['stock_data']) ? $productData['stock_data'] : [];
diff --git a/app/code/Magento/Catalog/Model/Plugin/ProductRepository/TransactionWrapper.php b/app/code/Magento/Catalog/Model/Plugin/ProductRepository/TransactionWrapper.php
new file mode 100644
index 0000000000000..1d940a5d7aee5
--- /dev/null
+++ b/app/code/Magento/Catalog/Model/Plugin/ProductRepository/TransactionWrapper.php
@@ -0,0 +1,102 @@
+<?php
+/**
+ * Plugin for \Magento\Catalog\Api\ProductRepositoryInterface
+ *
+ * Copyright © 2015 Magento. All rights reserved.
+ * See COPYING.txt for license details.
+ */
+namespace Magento\Catalog\Model\Plugin\ProductRepository;
+
+class TransactionWrapper
+{
+    /**
+     * @var \Magento\Catalog\Model\Resource\Product
+     */
+    protected $resourceModel;
+
+    /**
+     * @param \Magento\Catalog\Model\Resource\Product $resourceModel
+     */
+    public function __construct(
+        \Magento\Catalog\Model\Resource\Product $resourceModel
+    ) {
+        $this->resourceModel = $resourceModel;
+    }
+
+    /**
+     * @param \Magento\Catalog\Api\ProductRepositoryInterface $subject
+     * @param callable $proceed
+     * @param \Magento\Catalog\Api\Data\ProductInterface $product
+     * @param bool $saveOptions
+     * @return \Magento\Catalog\Api\Data\ProductInterface
+     * @throws \Exception
+     * @SuppressWarnings(PHPMD.UnusedFormalParameter)
+     */
+    public function aroundSave(
+        \Magento\Catalog\Api\ProductRepositoryInterface $subject,
+        \Closure $proceed,
+        \Magento\Catalog\Api\Data\ProductInterface $product,
+        $saveOptions = false
+    ) {
+        $this->resourceModel->beginTransaction();
+        try {
+            /** @var \Magento\Catalog\Api\Data\ProductInterface $result */
+            $result = $proceed($product, $saveOptions);
+            $this->resourceModel->commit();
+            return $result;
+        } catch (\Exception $e) {
+            $this->resourceModel->rollBack();
+            throw $e;
+        }
+    }
+
+    /**
+     * @param \Magento\Catalog\Api\ProductRepositoryInterface $subject
+     * @param callable $proceed
+     * @param \Magento\Catalog\Api\Data\ProductInterface $product
+     * @return bool
+     * @throws \Exception
+     * @SuppressWarnings(PHPMD.UnusedFormalParameter)
+     */
+    public function aroundDelete(
+        \Magento\Catalog\Api\ProductRepositoryInterface $subject,
+        \Closure $proceed,
+        \Magento\Catalog\Api\Data\ProductInterface $product
+    ) {
+        $this->resourceModel->beginTransaction();
+        try {
+            /** @var bool $result */
+            $result = $proceed($product);
+            $this->resourceModel->commit();
+            return $result;
+        } catch (\Exception $e) {
+            $this->resourceModel->rollBack();
+            throw $e;
+        }
+    }
+
+    /**
+     * @param \Magento\Catalog\Api\ProductRepositoryInterface $subject
+     * @param callable $proceed
+     * @param string $productSku
+     * @return bool
+     * @throws \Exception
+     * @SuppressWarnings(PHPMD.UnusedFormalParameter)
+     */
+    public function aroundDeleteById(
+        \Magento\Catalog\Api\ProductRepositoryInterface $subject,
+        \Closure $proceed,
+        $productSku
+    ) {
+        $this->resourceModel->beginTransaction();
+        try {
+            /** @var bool $result */
+            $result = $proceed($productSku);
+            $this->resourceModel->commit();
+            return $result;
+        } catch (\Exception $e) {
+            $this->resourceModel->rollBack();
+            throw $e;
+        }
+    }
+}
diff --git a/app/code/Magento/Catalog/Model/ProductRepository.php b/app/code/Magento/Catalog/Model/ProductRepository.php
index 43fa06093960b..40ecc5d2cb341 100644
--- a/app/code/Magento/Catalog/Model/ProductRepository.php
+++ b/app/code/Magento/Catalog/Model/ProductRepository.php
@@ -181,10 +181,10 @@ public function __construct(
     /**
      * {@inheritdoc}
      */
-    public function get($sku, $editMode = false, $storeId = null)
+    public function get($sku, $editMode = false, $storeId = null, $forceReload = false)
     {
         $cacheKey = $this->getCacheKey(func_get_args());
-        if (!isset($this->instances[$sku][$cacheKey])) {
+        if (!isset($this->instances[$sku][$cacheKey]) || $forceReload) {
             $product = $this->productFactory->create();
 
             $productId = $this->resourceModel->getIdBySku($sku);
@@ -204,10 +204,10 @@ public function get($sku, $editMode = false, $storeId = null)
     /**
      * {@inheritdoc}
      */
-    public function getById($productId, $editMode = false, $storeId = null)
+    public function getById($productId, $editMode = false, $storeId = null, $forceReload = false)
     {
         $cacheKey = $this->getCacheKey(func_get_args());
-        if (!isset($this->instancesById[$productId][$cacheKey])) {
+        if (!isset($this->instancesById[$productId][$cacheKey]) || $forceReload) {
             $product = $this->productFactory->create();
 
             if ($editMode) {
@@ -235,6 +235,7 @@ public function getById($productId, $editMode = false, $storeId = null)
     protected function getCacheKey($data)
     {
         unset($data[0]);
+        unset($data['forceReload']);
         $serializeData = [];
         foreach ($data as $key => $value) {
             if (is_object($value)) {
diff --git a/app/code/Magento/Catalog/Test/Unit/Model/Plugin/ProductRepository/TransactionWrapperTest.php b/app/code/Magento/Catalog/Test/Unit/Model/Plugin/ProductRepository/TransactionWrapperTest.php
new file mode 100644
index 0000000000000..681af582d1344
--- /dev/null
+++ b/app/code/Magento/Catalog/Test/Unit/Model/Plugin/ProductRepository/TransactionWrapperTest.php
@@ -0,0 +1,141 @@
+<?php
+/**
+ * Copyright © 2015 Magento. All rights reserved.
+ * See COPYING.txt for license details.
+ */
+namespace Magento\Catalog\Test\Unit\Model\Plugin\ProductRepository;
+
+class TransactionWrapperTest extends \PHPUnit_Framework_TestCase
+{
+    /**
+     * @var \Magento\Catalog\Model\Plugin\ProductRepository\TransactionWrapper
+     */
+    protected $model;
+
+    /**
+     * @var \PHPUnit_Framework_MockObject_MockObject|\Magento\Catalog\Model\Resource\Product
+     */
+    protected $resourceMock;
+
+    /**
+     * @var \PHPUnit_Framework_MockObject_MockObject|\Magento\Catalog\Api\ProductRepositoryInterface
+     */
+    protected $subjectMock;
+
+    /**
+     * @var \Closure
+     */
+    protected $closureMock;
+
+    /**
+     * @var \Closure
+     */
+    protected $rollbackClosureMock;
+
+    /**
+     * @var \PHPUnit_Framework_MockObject_MockObject
+     */
+    protected $productMock;
+
+    /**
+     * @var bool
+     */
+    protected $saveOption = true;
+
+    const ERROR_MSG = "error occurred";
+
+    protected function setUp()
+    {
+        $this->resourceMock = $this->getMock('Magento\Catalog\Model\Resource\Product', [], [], '', false);
+        $this->subjectMock = $this->getMock('Magento\Catalog\Api\ProductRepositoryInterface', [], [], '', false);
+        $this->productMock = $this->getMock('Magento\Catalog\Api\Data\ProductInterface', [], [], '', false);
+        $productMock = $this->productMock;
+        $this->closureMock = function () use ($productMock) {
+            return $productMock;
+        };
+        $this->rollbackClosureMock = function () use ($productMock) {
+            throw new \Exception(self::ERROR_MSG);
+        };
+
+        $this->model = new \Magento\Catalog\Model\Plugin\ProductRepository\TransactionWrapper($this->resourceMock);
+    }
+
+    public function testAroundSaveCommit()
+    {
+        $this->resourceMock->expects($this->once())->method('beginTransaction');
+        $this->resourceMock->expects($this->once())->method('commit');
+
+        $this->assertEquals(
+            $this->productMock,
+            $this->model->aroundSave($this->subjectMock, $this->closureMock, $this->productMock, $this->saveOption)
+        );
+    }
+
+    /**
+     * @expectedException \Exception
+     * @expectedExceptionMessage error occurred
+     */
+    public function testAroundSaveRollBack()
+    {
+        $this->resourceMock->expects($this->once())->method('beginTransaction');
+        $this->resourceMock->expects($this->once())->method('rollBack');
+
+        $this->model->aroundSave($this->subjectMock, $this->rollbackClosureMock, $this->productMock, $this->saveOption);
+    }
+
+    public function testAroundDeleteCommit()
+    {
+        $this->resourceMock->expects($this->once())->method('beginTransaction');
+        $this->resourceMock->expects($this->once())->method('commit');
+
+        $this->assertEquals(
+            $this->productMock,
+            $this->model->aroundDelete($this->subjectMock, $this->closureMock, $this->productMock, $this->saveOption)
+        );
+    }
+
+    /**
+     * @expectedException \Exception
+     * @expectedExceptionMessage error occurred
+     */
+    public function testAroundDeleteRollBack()
+    {
+        $this->resourceMock->expects($this->once())->method('beginTransaction');
+        $this->resourceMock->expects($this->once())->method('rollBack');
+
+        $this->model->aroundDelete(
+            $this->subjectMock,
+            $this->rollbackClosureMock,
+            $this->productMock,
+            $this->saveOption
+        );
+    }
+
+    public function testAroundDeleteByIdCommit()
+    {
+        $this->resourceMock->expects($this->once())->method('beginTransaction');
+        $this->resourceMock->expects($this->once())->method('commit');
+
+        $this->assertEquals(
+            $this->productMock,
+            $this->model->aroundDelete($this->subjectMock, $this->closureMock, $this->productMock, $this->saveOption)
+        );
+    }
+
+    /**
+     * @expectedException \Exception
+     * @expectedExceptionMessage error occurred
+     */
+    public function testAroundDeleteByIdRollBack()
+    {
+        $this->resourceMock->expects($this->once())->method('beginTransaction');
+        $this->resourceMock->expects($this->once())->method('rollBack');
+
+        $this->model->aroundDelete(
+            $this->subjectMock,
+            $this->rollbackClosureMock,
+            $this->productMock,
+            $this->saveOption
+        );
+    }
+}
diff --git a/app/code/Magento/Catalog/Test/Unit/Model/ProductLink/RepositoryTest.php b/app/code/Magento/Catalog/Test/Unit/Model/ProductLink/RepositoryTest.php
index affded52e9586..26f70422f0e60 100644
--- a/app/code/Magento/Catalog/Test/Unit/Model/ProductLink/RepositoryTest.php
+++ b/app/code/Magento/Catalog/Test/Unit/Model/ProductLink/RepositoryTest.php
@@ -75,8 +75,8 @@ public function testSave()
         $linkedProductMock = $this->getMock('\Magento\Catalog\Model\Product', [], [], '', false);
         $this->productRepositoryMock->expects($this->exactly(2))->method('get')->will($this->returnValueMap(
             [
-                ['product', false, null, $productMock],
-                ['linkedProduct', false, null, $linkedProductMock],
+                ['product', false, null, false, $productMock],
+                ['linkedProduct', false, null, false, $linkedProductMock],
             ]
         ));
         $entityMock->expects($this->once())->method('getLinkedProductSku')->willReturn('linkedProduct');
@@ -102,8 +102,8 @@ public function testSaveWithException()
         $linkedProductMock = $this->getMock('\Magento\Catalog\Model\Product', [], [], '', false);
         $this->productRepositoryMock->expects($this->exactly(2))->method('get')->will($this->returnValueMap(
             [
-                ['product', false, null, $productMock],
-                ['linkedProduct', false, null, $linkedProductMock],
+                ['product', false, null, false, $productMock],
+                ['linkedProduct', false, null, false, $linkedProductMock],
             ]
         ));
         $entityMock->expects($this->once())->method('getLinkedProductSku')->willReturn('linkedProduct');
@@ -129,8 +129,8 @@ public function testDelete()
         $linkedProductMock = $this->getMock('\Magento\Catalog\Model\Product', [], [], '', false);
         $this->productRepositoryMock->expects($this->exactly(2))->method('get')->will($this->returnValueMap(
             [
-                ['product', false, null, $productMock],
-                ['linkedProduct', false, null, $linkedProductMock],
+                ['product', false, null, false, $productMock],
+                ['linkedProduct', false, null, false, $linkedProductMock],
             ]
         ));
         $entityMock->expects($this->once())->method('getLinkedProductSku')->willReturn('linkedProduct');
@@ -157,8 +157,8 @@ public function testDeleteWithInvalidDataException()
         $linkedProductMock = $this->getMock('\Magento\Catalog\Model\Product', [], [], '', false);
         $this->productRepositoryMock->expects($this->exactly(2))->method('get')->will($this->returnValueMap(
             [
-                ['product', false, null, $productMock],
-                ['linkedProduct', false, null, $linkedProductMock],
+                ['product', false, null, false, $productMock],
+                ['linkedProduct', false, null, false, $linkedProductMock],
             ]
         ));
         $entityMock->expects($this->once())->method('getLinkedProductSku')->willReturn('linkedProduct');
@@ -186,8 +186,8 @@ public function testDeleteWithNoSuchEntityException()
         $linkedProductMock = $this->getMock('\Magento\Catalog\Model\Product', [], [], '', false);
         $this->productRepositoryMock->expects($this->exactly(2))->method('get')->will($this->returnValueMap(
             [
-                ['product', false, null, $productMock],
-                ['linkedProduct', false, null, $linkedProductMock],
+                ['product', false, null, false, $productMock],
+                ['linkedProduct', false, null, false, $linkedProductMock],
             ]
         ));
         $entityMock->expects($this->exactly(2))->method('getLinkedProductSku')->willReturn('linkedProduct');
diff --git a/app/code/Magento/Catalog/Test/Unit/Model/ProductRepositoryTest.php b/app/code/Magento/Catalog/Test/Unit/Model/ProductRepositoryTest.php
index d50868fc2306b..a8ba184519d79 100644
--- a/app/code/Magento/Catalog/Test/Unit/Model/ProductRepositoryTest.php
+++ b/app/code/Magento/Catalog/Test/Unit/Model/ProductRepositoryTest.php
@@ -298,6 +298,55 @@ public function testGetByIdForCacheKeyGenerate($identifier, $editMode, $storeId)
         $this->productMock->expects($this->once())->method('load')->with($identifier);
         $this->productMock->expects($this->once())->method('getId')->willReturn($identifier);
         $this->assertEquals($this->productMock, $this->model->getById($identifier, $editMode, $storeId));
+        //Second invocation should just return from cache
+        $this->assertEquals($this->productMock, $this->model->getById($identifier, $editMode, $storeId));
+    }
+
+    /**
+     * Test the forceReload parameter
+     *
+     * @throws \Magento\Framework\Exception\NoSuchEntityException
+     */
+    public function testGetByIdForcedReload()
+    {
+        $identifier = "23";
+        $editMode = false;
+        $storeId = 0;
+
+        $this->productFactoryMock->expects($this->exactly(2))->method('create')
+            ->will($this->returnValue($this->productMock));
+        $this->productMock->expects($this->exactly(2))->method('load');
+        $this->productMock->expects($this->exactly(2))->method('getId')->willReturn($identifier);
+        $this->assertEquals($this->productMock, $this->model->getById($identifier, $editMode, $storeId));
+        //second invocation should just return from cache
+        $this->assertEquals($this->productMock, $this->model->getById($identifier, $editMode, $storeId));
+        //force reload
+        $this->assertEquals($this->productMock, $this->model->getById($identifier, $editMode, $storeId, true));
+    }
+
+    /**
+     * Test forceReload parameter
+     *
+     * @throws \Magento\Framework\Exception\NoSuchEntityException
+     */
+    public function testGetForcedReload()
+    {
+        $sku = "sku";
+        $id = "23";
+        $editMode = false;
+        $storeId = 0;
+
+        $this->productFactoryMock->expects($this->exactly(2))->method('create')
+            ->will($this->returnValue($this->productMock));
+        $this->productMock->expects($this->exactly(2))->method('load');
+        $this->productMock->expects($this->exactly(2))->method('getId')->willReturn($sku);
+        $this->resourceModelMock->expects($this->exactly(2))->method('getIdBySku')
+            ->with($sku)->willReturn($id);
+        $this->assertEquals($this->productMock, $this->model->get($sku, $editMode, $storeId));
+        //second invocation should just return from cache
+        $this->assertEquals($this->productMock, $this->model->get($sku, $editMode, $storeId));
+        //force reload
+        $this->assertEquals($this->productMock, $this->model->get($sku, $editMode, $storeId, true));
     }
 
     public function testGetByIdWithSetStoreId()
diff --git a/app/code/Magento/Catalog/etc/di.xml b/app/code/Magento/Catalog/etc/di.xml
index 971bebcac3226..acd741fdbbe66 100644
--- a/app/code/Magento/Catalog/etc/di.xml
+++ b/app/code/Magento/Catalog/etc/di.xml
@@ -461,4 +461,7 @@
     <preference for="Magento\Catalog\Api\Data\ProductCustomOptionValuesInterface" type="\Magento\Catalog\Model\Product\Option\Value" />
     <virtualType name="Magento\Catalog\Model\Resource\Attribute\Collection" type="Magento\Eav\Model\Resource\Entity\Attribute\Collection">
     </virtualType>
+    <type name="Magento\Catalog\Api\ProductRepositoryInterface">
+        <plugin name="transactionWrapper" type="\Magento\Catalog\Model\Plugin\ProductRepository\TransactionWrapper" sortOrder="-1"/>
+    </type>
 </config>
diff --git a/app/code/Magento/Sales/Model/Order/Creditmemo/Item.php b/app/code/Magento/Sales/Model/Order/Creditmemo/Item.php
index 163a03903f878..35d522427429f 100644
--- a/app/code/Magento/Sales/Model/Order/Creditmemo/Item.php
+++ b/app/code/Magento/Sales/Model/Order/Creditmemo/Item.php
@@ -219,7 +219,7 @@ public function calcRowTotal()
         $rowTotalInclTax = $orderItem->getRowTotalInclTax();
         $baseRowTotalInclTax = $orderItem->getBaseRowTotalInclTax();
 
-        if (!$this->isLast() && $orderItemQtyInvoiced > 0 && $this->getQty() > 0) {
+        if (!$this->isLast() && $orderItemQtyInvoiced > 0 && $this->getQty() >= 0) {
             $availableQty = $orderItemQtyInvoiced - $orderItem->getQtyRefunded();
             $rowTotal = $creditmemo->roundPrice($rowTotal / $availableQty * $this->getQty());
             $baseRowTotal = $creditmemo->roundPrice($baseRowTotal / $availableQty * $this->getQty(), 'base');
diff --git a/app/code/Magento/Sales/Test/Unit/Model/Order/Creditmemo/ItemTest.php b/app/code/Magento/Sales/Test/Unit/Model/Order/Creditmemo/ItemTest.php
index afcf792c718dd..0673aa120a217 100644
--- a/app/code/Magento/Sales/Test/Unit/Model/Order/Creditmemo/ItemTest.php
+++ b/app/code/Magento/Sales/Test/Unit/Model/Order/Creditmemo/ItemTest.php
@@ -264,43 +264,53 @@ public function testCancel()
         $this->assertInstanceOf('Magento\Sales\Model\Order\Creditmemo\Item', $result);
     }
 
-    public function testCalcRowTotal()
+    /**
+     * @dataProvider calcRowTotalDataProvider
+     */
+    public function testCalcRowTotal($qty)
     {
         $creditmemoMock = $this->getMockBuilder('\Magento\Sales\Model\Order\Creditmemo')
             ->disableOriginalConstructor()
             ->getMock();
         $creditmemoMock->expects($this->exactly(4))
             ->method('roundPrice')
-            ->willReturnMap(
-                [
-                    [0.375, 'regular', false, 0.4],
-                    [0.375, 'base', false, 0.4],
-                    [1, 'including', false, 1.0],
-                    [1, 'including_base', false, 1.0]
-                ]
-            );
+            ->will($this->returnCallback(
+                function ($arg) {
+                    return round($arg, 2);
+                }
+            ));
+
+        $qtyInvoiced = 10;
+        $qtyRefunded = 2;
+        $qtyAvailable = $qtyInvoiced - $qtyRefunded;
+
+        $rowInvoiced = 5;
+        $amountRefunded = 2;
+
+        $expectedRowTotal = ($rowInvoiced - $amountRefunded) / $qtyAvailable * $qty;
+        $expectedRowTotal = round($expectedRowTotal, 2);
 
         $orderItemMock = $this->getMockBuilder('Magento\Sales\Model\Order\Item')
             ->disableOriginalConstructor()
             ->getMock();
         $orderItemMock->expects($this->once())
             ->method('getQtyInvoiced')
-            ->willReturn(10);
+            ->willReturn($qtyInvoiced);
         $orderItemMock->expects($this->once())
             ->method('getQtyRefunded')
-            ->willReturn(2);
+            ->willReturn($qtyRefunded);
         $orderItemMock->expects($this->once())
             ->method('getRowInvoiced')
-            ->willReturn(5);
+            ->willReturn($rowInvoiced);
         $orderItemMock->expects($this->once())
             ->method('getAmountRefunded')
-            ->willReturn(2);
+            ->willReturn($amountRefunded);
         $orderItemMock->expects($this->once())
             ->method('getBaseRowInvoiced')
-            ->willReturn(5);
+            ->willReturn($rowInvoiced);
         $orderItemMock->expects($this->once())
             ->method('getBaseAmountRefunded')
-            ->willReturn(2);
+            ->willReturn($amountRefunded);
         $orderItemMock->expects($this->once())
             ->method('getRowTotalInclTax')
             ->willReturn(1);
@@ -313,11 +323,28 @@ public function testCalcRowTotal()
         $orderItemMock->expects($this->once())
             ->method('getQtyOrdered')
             ->willReturn(1);
+        $orderItemMock->expects($this->any())
+            ->method('getQtyToRefund')
+            ->willReturn($qtyAvailable);
 
-        $this->item->setData('qty', 1);
+        $this->item->setData('qty', $qty);
         $this->item->setCreditmemo($creditmemoMock);
         $this->item->setOrderItem($orderItemMock);
         $result = $this->item->calcRowTotal();
+
         $this->assertInstanceOf('Magento\Sales\Model\Order\Creditmemo\Item', $result);
+        $this->assertEquals($expectedRowTotal, $this->item->getData('row_total'));
+        $this->assertEquals($expectedRowTotal, $this->item->getData('base_row_total'));
+    }
+
+    /**
+     * @return array
+     */
+    public function calcRowTotalDataProvider()
+    {
+        return [
+            'qty 1' => [1],
+            'qty 0' => [0],
+        ];
     }
 }
diff --git a/app/code/Magento/Weee/Model/Total/Creditmemo/Weee.php b/app/code/Magento/Weee/Model/Total/Creditmemo/Weee.php
index d45eab656ca2a..7ddf6d66a7d0a 100644
--- a/app/code/Magento/Weee/Model/Total/Creditmemo/Weee.php
+++ b/app/code/Magento/Weee/Model/Total/Creditmemo/Weee.php
@@ -49,36 +49,30 @@ public function collect(Creditmemo $creditmemo)
 
         $totalWeeeAmount = 0;
         $baseTotalWeeeAmount = 0;
-
         $totalWeeeAmountInclTax = 0;
         $baseTotalWeeeAmountInclTax = 0;
-
-        $totalTaxAmount = $totalWeeeAmountInclTax - $totalWeeeAmount;
-        $baseTotalTaxAmount = $baseTotalWeeeAmountInclTax - $baseTotalWeeeAmount;
+        $totalTaxAmount = 0;
+        $baseTotalTaxAmount = 0;
 
         foreach ($creditmemo->getAllItems() as $item) {
             $orderItem = $item->getOrderItem();
-            if ($orderItem->isDummy() || $item->getQty() <= 0) {
+            $orderItemQty = $orderItem->getQtyOrdered();
+
+            if (!$orderItemQty || $orderItem->isDummy() || $item->getQty() < 0) {
                 continue;
             }
 
-            $ratio = $item->getQty() / $orderItem->getQtyOrdered();
+            $ratio = $item->getQty() / $orderItemQty;
 
             $orderItemWeeeAmountExclTax = $orderItem->getWeeeTaxAppliedRowAmount();
             $orderItemBaseWeeeAmountExclTax = $orderItem->getBaseWeeeTaxAppliedRowAmnt();
             $weeeAmountExclTax = $creditmemo->roundPrice($orderItemWeeeAmountExclTax * $ratio);
-            $baseWeeeAmountExclTax = $creditmemo->roundPrice(
-                $orderItemBaseWeeeAmountExclTax * $ratio,
-                'base'
-            );
+            $baseWeeeAmountExclTax = $creditmemo->roundPrice($orderItemBaseWeeeAmountExclTax * $ratio, 'base');
 
             $orderItemWeeeAmountInclTax = $this->_weeeData->getRowWeeeTaxInclTax($orderItem);
             $orderItemBaseWeeeAmountInclTax = $this->_weeeData->getBaseRowWeeeTaxInclTax($orderItem);
             $weeeAmountInclTax = $creditmemo->roundPrice($orderItemWeeeAmountInclTax * $ratio);
-            $baseWeeeAmountInclTax = $creditmemo->roundPrice(
-                $orderItemBaseWeeeAmountInclTax * $ratio,
-                'base'
-            );
+            $baseWeeeAmountInclTax = $creditmemo->roundPrice($orderItemBaseWeeeAmountInclTax * $ratio, 'base');
 
             $itemTaxAmount = $weeeAmountInclTax - $weeeAmountExclTax;
             $itemBaseTaxAmount = $baseWeeeAmountInclTax - $baseWeeeAmountExclTax;
diff --git a/app/code/Magento/Weee/Model/Total/Invoice/Weee.php b/app/code/Magento/Weee/Model/Total/Invoice/Weee.php
index 457a08ccd8669..a5bed91ec0c36 100644
--- a/app/code/Magento/Weee/Model/Total/Invoice/Weee.php
+++ b/app/code/Magento/Weee/Model/Total/Invoice/Weee.php
@@ -57,11 +57,12 @@ public function collect(\Magento\Sales\Model\Order\Invoice $invoice)
             $orderItem = $item->getOrderItem();
             $orderItemQty = $orderItem->getQtyOrdered();
 
-            if (!$orderItemQty || $orderItem->isDummy() || $item->getQty() <= 0) {
+            if (!$orderItemQty || $orderItem->isDummy() || $item->getQty() < 0) {
                 continue;
             }
 
             $ratio = $item->getQty() / $orderItemQty;
+
             $orderItemWeeeAmount = $orderItem->getWeeeTaxAppliedRowAmount();
             $orderItemBaseWeeeAmount = $orderItem->getBaseWeeeTaxAppliedRowAmnt();
             $weeeAmount = $invoice->roundPrice($orderItemWeeeAmount * $ratio);
diff --git a/app/code/Magento/Weee/Test/Unit/Model/Total/Creditmemo/WeeeTest.php b/app/code/Magento/Weee/Test/Unit/Model/Total/Creditmemo/WeeeTest.php
index 47aa7692a51d9..0fdd0ad9bdab1 100644
--- a/app/code/Magento/Weee/Test/Unit/Model/Total/Creditmemo/WeeeTest.php
+++ b/app/code/Magento/Weee/Test/Unit/Model/Total/Creditmemo/WeeeTest.php
@@ -169,6 +169,7 @@ function ($price, $type) use (&$roundingDelta) {
     public function collectDataProvider()
     {
         $result = [];
+
         // scenario 1: 3 item_1, $100 with $weee, 8.25 tax rate, 3 items invoiced, full creditmemo
         $result['complete_creditmemo'] = [
             'creditmemo_data' => [
@@ -236,7 +237,6 @@ public function collectDataProvider()
                         'tax_ratio' => serialize(['weee' => 1.0]),
                         'weee_tax_applied_row_amount' => 30,
                         'base_weee_tax_applied_row_amount' => 30,
-
                     ],
                 ],
                 'creditmemo_data' => [
@@ -248,7 +248,6 @@ public function collectDataProvider()
                     'base_subtotal' => 300,
                     'subtotal_incl_tax' => 357.22,
                     'base_subtotal_incl_tax' => 357.22,
-
                 ],
             ],
         ];
@@ -320,7 +319,6 @@ public function collectDataProvider()
                         'tax_ratio' => serialize(['weee' => 1.65 / 2.47]),
                         'weee_tax_applied_row_amount' => 20,
                         'base_weee_tax_applied_row_amount' => 20,
-
                     ],
                 ],
                 'creditmemo_data' => [
@@ -332,7 +330,6 @@ public function collectDataProvider()
                     'base_subtotal' => 200,
                     'subtotal_incl_tax' => 238.15,
                     'base_subtotal_incl_tax' => 238.15,
-
                 ],
             ],
         ];
@@ -404,7 +401,6 @@ public function collectDataProvider()
                         'tax_ratio' => serialize(['weee' => 0.83 / 2.47]),
                         'weee_tax_applied_row_amount' => 10,
                         'base_weee_tax_applied_row_amount' => 10,
-
                     ],
                 ],
                 'creditmemo_data' => [
@@ -416,7 +412,79 @@ public function collectDataProvider()
                     'base_subtotal' => 100,
                     'subtotal_incl_tax' => 119.07,
                     'base_subtotal_incl_tax' => 119.07,
+                ],
+            ],
+        ];
 
+        // scenario 4: 3 item_1, $100 with $weee, 8.25 tax rate.  Returning qty 0.
+        $result['zero_return'] = [
+            'creditmemo_data' => [
+                'items' => [
+                    'item_1' => [
+                        'order_item' => [
+                            'qty_ordered' => 3,
+                            'weee_tax_applied_row_amount' => 30,
+                            'base_weee_tax_applied_row_amnt' => 30,
+                            'row_weee_tax_incl_tax' => 32.47,
+                            'base_row_weee_tax_incl_tax' => 32.47,
+                            'weee_amount_invoiced' => 30,
+                            'base_weee_amount_invoiced' => 30,
+                            'weee_amount_refunded' => 0,
+                            'base_weee_amount_refunded' => 0,
+                            'weee_tax_amount_invoiced' => 2.47,
+                            'base_weee_tax_amount_invoiced' => 2.47,
+                            'weee_tax_amount_refunded' => 0,
+                            'base_weee_tax_amount_refunded' => 0,
+                            'applied_weee' => [
+                                [
+                                    'title' => 'recycling_fee',
+                                    'base_row_amount' => 30,
+                                    'row_amount' => 30,
+                                    'base_row_amount_incl_tax' => 32.47,
+                                    'row_amount_incl_tax' => 32.47,
+                                ],
+                            ],
+                            'qty_invoiced' => 3,
+                        ],
+                        'is_last' => true,
+                        'data_fields' => [
+                            'qty' => 0,
+                            'applied_weee' => [
+                                [
+                                ],
+                            ],
+                        ],
+                    ],
+                ],
+                'include_in_subtotal' => false,
+                'data_fields' => [
+                    'grand_total' => 300,
+                    'base_grand_total' => 300,
+                    'subtotal' => 300,
+                    'base_subtotal' => 300,
+                    'subtotal_incl_tax' => 324.75,
+                    'base_subtotal_incl_tax' => 324.75,
+                    'tax_amount' => 0,
+                    'base_tax_amount' => 0,
+                ],
+            ],
+            'expected_results' => [
+                'creditmemo_items' => [
+                    'item_1' => [
+                        'applied_weee' => [
+                            [
+                                'title' => 'recycling_fee',
+                                'base_row_amount' => 0,
+                                'row_amount' => 0,
+                                'base_row_amount_incl_tax' => 0,
+                                'row_amount_incl_tax' => 0,
+                            ],
+                        ],
+                    ],
+                ],
+                'creditmemo_data' => [
+                    'subtotal' => 300,
+                    'base_subtotal' => 300,
                 ],
             ],
         ];
diff --git a/app/code/Magento/Weee/Test/Unit/Model/Total/Invoice/WeeeTest.php b/app/code/Magento/Weee/Test/Unit/Model/Total/Invoice/WeeeTest.php
index 4d91954b70fa2..d034947c48d71 100644
--- a/app/code/Magento/Weee/Test/Unit/Model/Total/Invoice/WeeeTest.php
+++ b/app/code/Magento/Weee/Test/Unit/Model/Total/Invoice/WeeeTest.php
@@ -172,6 +172,7 @@ function ($price, $type) use (&$roundingDelta) {
     public function collectDataProvider()
     {
         $result = [];
+
         // 3 item_1, $100 with $weee, 8.25 tax rate, full invoice
         $result['complete_invoice'] = [
             'order_data' => [
@@ -269,7 +270,6 @@ public function collectDataProvider()
                     'base_subtotal' => 300,
                     'subtotal_incl_tax' => 344.85,
                     'base_subtotal_incl_tax' => 344.85,
-
                 ],
             ],
         ];
@@ -360,7 +360,6 @@ public function collectDataProvider()
                         'tax_ratio' => serialize(['weee' => 1.65 / 2.47]),
                         'weee_tax_applied_row_amount' => 20,
                         'base_weee_tax_applied_row_amount' => 20,
-
                     ],
                 ],
                 'invoice_data' => [
@@ -372,7 +371,6 @@ public function collectDataProvider()
                     'base_subtotal' => 200,
                     'subtotal_incl_tax' => 238.15,
                     'base_subtotal_incl_tax' => 238.15,
-
                 ],
             ],
         ];
@@ -464,7 +462,6 @@ public function collectDataProvider()
                         'tax_ratio' => serialize(['weee' => 0.82 / 2.47]),
                         'weee_tax_applied_row_amount' => 10,
                         'base_weee_tax_applied_row_amount' => 10,
-
                     ],
                 ],
                 'invoice_data' => [
@@ -476,7 +473,6 @@ public function collectDataProvider()
                     'base_subtotal' => 100,
                     'subtotal_incl_tax' => 119.07,
                     'base_subtotal_incl_tax' => 119.07,
-
                 ],
             ],
         ];
@@ -580,7 +576,98 @@ public function collectDataProvider()
                     'base_subtotal' => 100,
                     'subtotal_incl_tax' => 114.95,
                     'base_subtotal_incl_tax' => 114.95,
+                ],
+            ],
+        ];
 
+        // 3 item_1, $100 with $weee, 8.25 tax rate. Invoicing qty 0.
+        $result['zero_invoice'] = [
+            'order_data' => [
+                'previous_invoices' => [
+                ],
+                'data_fields' => [
+                    'shipping_tax_amount' => 1.24,
+                    'base_shipping_tax_amount' => 1.24,
+                    'shipping_hidden_tax_amount' => 0,
+                    'base_shipping_hidden_tax_amount' => 0,
+                    'tax_amount' => 16.09,
+                    'tax_invoiced' => 0,
+                    'base_tax_amount' => 16.09,
+                    'base_tax_amount_invoiced' => 0,
+                    'subtotal' => '300',
+                    'base_subtotal' => '300',
+                ],
+            ],
+            'invoice_data' => [
+                'items' => [
+                    'item_1' => [
+                        'order_item' => [
+                            'qty_ordered' => 3,
+                            'weee_tax_applied_row_amount' => 30,
+                            'base_weee_tax_applied_row_amnt' => 30,
+                            'row_weee_tax_incl_tax' => 32.47,
+                            'base_row_weee_tax_incl_tax' => 32.47,
+                            'weee_amount_invoiced' => 0,
+                            'base_weee_amount_invoiced' => 0,
+                            'weee_tax_amount_invoiced' => 0,
+                            'base_weee_tax_amount_invoiced' => 0,
+                            'applied_weee' => [
+                                [
+                                    'title' => 'recycling_fee',
+                                    'base_row_amount' => 30,
+                                    'row_amount' => 30,
+                                    'base_row_amount_incl_tax' => 32.47,
+                                    'row_amount_incl_tax' => 32.47,
+                                ],
+                            ],
+                            'applied_weee_updated' => [
+                                'base_row_amount_invoiced' => 30,
+                                'row_amount_invoiced' => 30,
+                                'base_tax_amount_invoiced' => 2.47,
+                                'tax_amount_invoiced' => 2.47,
+                            ],
+                            'qty_invoiced' => 0,
+                        ],
+                        'is_last' => true,
+                        'data_fields' => [
+                            'qty' => 0,
+                            'applied_weee' => [
+                                [
+                                ],
+                            ],
+                        ],
+                    ],
+                ],
+                'is_last' => true,
+                'include_in_subtotal' => false,
+                'data_fields' => [
+                    'grand_total' => 181.09,
+                    'base_grand_total' => 181.09,
+                    'subtotal' => 300,
+                    'base_subtotal' => 300,
+                    'subtotal_incl_tax' => 314.85,
+                    'base_subtotal_incl_tax' => 314.85,
+                    'tax_amount' => 16.09,
+                    'base_tax_amount' => 16.09,
+                ],
+            ],
+            'expected_results' => [
+                'invoice_items' => [
+                    'item_1' => [
+                        'applied_weee' => [
+                            [
+                                'title' => 'recycling_fee',
+                                'base_row_amount' => 0,
+                                'row_amount' => 0,
+                                'base_row_amount_incl_tax' => 0,
+                                'row_amount_incl_tax' => 0,
+                            ],
+                        ],
+                    ],
+                ],
+                'invoice_data' => [
+                    'subtotal' => 300,
+                    'base_subtotal' => 300,
                 ],
             ],
         ];
diff --git a/dev/tests/api-functional/testsuite/Magento/Bundle/Api/ProductLinkManagementTest.php b/dev/tests/api-functional/testsuite/Magento/Bundle/Api/ProductLinkManagementTest.php
index 463f490480ce0..caf1b320ef82c 100644
--- a/dev/tests/api-functional/testsuite/Magento/Bundle/Api/ProductLinkManagementTest.php
+++ b/dev/tests/api-functional/testsuite/Magento/Bundle/Api/ProductLinkManagementTest.php
@@ -33,12 +33,13 @@ public function testGetChildren()
         $this->assertArrayHasKey(0, $result);
         $this->assertArrayHasKey('option_id', $result[0]);
         $this->assertArrayHasKey('is_default', $result[0]);
-        $this->assertArrayHasKey('is_defined', $result[0]);
+        $this->assertArrayHasKey('can_change_quantity', $result[0]);
         $this->assertArrayHasKey('price', $result[0]);
         $this->assertArrayHasKey('price_type', $result[0]);
+        $this->assertNotNull($result[0]['id']);
 
-        unset($result[0]['option_id'], $result[0]['is_default'], $result[0]['is_defined']);
-        unset($result[0]['price'], $result[0]['price_type']);
+        unset($result[0]['option_id'], $result[0]['is_default'], $result[0]['can_change_quantity']);
+        unset($result[0]['price'], $result[0]['price_type'], $result[0]['id']);
 
         ksort($result[0]);
         ksort($expected[0]);
@@ -83,6 +84,55 @@ public function testAddChild()
         $this->assertGreaterThan(0, $childId);
     }
 
+    /**
+     * @magentoApiDataFixture Magento/Bundle/_files/product.php
+     * @magentoApiDataFixture Magento/Catalog/_files/product_virtual.php
+     */
+    public function testSaveChild()
+    {
+        $productSku = 'bundle-product';
+        $children = $this->getChildren($productSku);
+
+        $linkedProduct = $children[0];
+
+        //Modify a few fields
+        $linkedProduct['is_default'] = true;
+        $linkedProduct['qty'] = 2;
+
+        $this->assertTrue($this->saveChild($productSku, $linkedProduct));
+        $children = $this->getChildren($productSku);
+        $this->assertEquals($linkedProduct, $children[0]);
+    }
+
+    /**
+     * @param string $productSku
+     * @param array $linkedProduct
+     * @return string
+     */
+    private function saveChild($productSku, $linkedProduct)
+    {
+        $resourcePath = self::RESOURCE_PATH . '/:sku/links/:id';
+        $serviceInfo = [
+            'rest' => [
+                'resourcePath' => str_replace(
+                    [':sku', ':id'],
+                    [$productSku, $linkedProduct['id']],
+                    $resourcePath
+                ),
+                'httpMethod' => \Magento\Framework\Webapi\Rest\Request::HTTP_METHOD_PUT,
+            ],
+            'soap' => [
+                'service' => self::SERVICE_NAME,
+                'serviceVersion' => self::SERVICE_VERSION,
+                'operation' => self::SERVICE_NAME . 'SaveChild',
+            ],
+        ];
+        return $this->_webApiCall(
+            $serviceInfo,
+            ['sku' => $productSku, 'linkedProduct' => $linkedProduct]
+        );
+    }
+
     /**
      * @param string $productSku
      * @param int $optionId
@@ -158,6 +208,6 @@ protected function getChildren($productSku)
                 'operation' => self::SERVICE_NAME . 'getChildren',
             ],
         ];
-        return $this->_webApiCall($serviceInfo, ['productId' => $productSku]);
+        return $this->_webApiCall($serviceInfo, ['productSku' => $productSku]);
     }
 }
diff --git a/dev/tests/api-functional/testsuite/Magento/Bundle/Api/ProductOptionRepositoryTest.php b/dev/tests/api-functional/testsuite/Magento/Bundle/Api/ProductOptionRepositoryTest.php
index 325e35ee82910..d402aea3d67a7 100644
--- a/dev/tests/api-functional/testsuite/Magento/Bundle/Api/ProductOptionRepositoryTest.php
+++ b/dev/tests/api-functional/testsuite/Magento/Bundle/Api/ProductOptionRepositoryTest.php
@@ -29,7 +29,7 @@ public function testGet()
                     'sku' => 'simple',
                     'qty' => 1,
                     'position' => 0,
-                    'is_defined' => true,
+                    'can_change_quantity' => 1,
                     'is_default' => false,
                     'price' => null,
                     'price_type' => null,
@@ -42,9 +42,13 @@ public function testGet()
         $this->assertArrayHasKey('option_id', $result);
         $expected['product_links'][0]['option_id'] = $result['option_id'];
         unset($result['option_id']);
+        $this->assertNotNull($result['product_links'][0]['id']);
+        unset($result['product_links'][0]['id']);
 
         ksort($expected);
         ksort($result);
+        ksort($expected['product_links'][0]);
+        ksort($result['product_links'][0]);
         $this->assertEquals($expected, $result);
     }
 
@@ -66,7 +70,7 @@ public function testGetList()
                         'sku' => 'simple',
                         'qty' => 1,
                         'position' => 0,
-                        'is_defined' => true,
+                        'can_change_quantity' => 1,
                         'is_default' => false,
                         'price' => null,
                         'price_type' => null,
@@ -80,9 +84,13 @@ public function testGetList()
         $this->assertArrayHasKey('option_id', $result[0]);
         $expected[0]['product_links'][0]['option_id'] = $result[0]['option_id'];
         unset($result[0]['option_id']);
+        $this->assertNotNull($result[0]['product_links'][0]['id']);
+        unset($result[0]['product_links'][0]['id']);
 
         ksort($expected[0]);
         ksort($result[0]);
+        ksort($expected[0]['product_links'][0]);
+        ksort($result[0]['product_links'][0]);
         $this->assertEquals($expected, $result);
     }
 
diff --git a/dev/tests/api-functional/testsuite/Magento/Bundle/Api/ProductServiceTest.php b/dev/tests/api-functional/testsuite/Magento/Bundle/Api/ProductServiceTest.php
index 1a817b18d3b59..80fbe1999d334 100644
--- a/dev/tests/api-functional/testsuite/Magento/Bundle/Api/ProductServiceTest.php
+++ b/dev/tests/api-functional/testsuite/Magento/Bundle/Api/ProductServiceTest.php
@@ -10,6 +10,7 @@
 use Magento\Framework\Api\ExtensibleDataInterface;
 use Magento\TestFramework\Helper\Bootstrap;
 use Magento\TestFramework\TestCase\WebapiAbstract;
+use Magento\Bundle\Api\Data\LinkInterface;
 
 /**
  * Class ProductServiceTest for testing Bundle Product API
@@ -19,6 +20,7 @@ class ProductServiceTest extends WebapiAbstract
     const SERVICE_NAME = 'catalogProductRepositoryV1';
     const SERVICE_VERSION = 'V1';
     const RESOURCE_PATH = '/V1/products';
+    const BUNDLE_PRODUCT_ID = 'sku-test-product-bundle';
 
     /**
      * @var \Magento\Catalog\Model\Resource\Product\Collection
@@ -39,20 +41,7 @@ public function setUp()
      */
     public function tearDown()
     {
-        /** @var \Magento\Framework\Registry $registry */
-        $registry = \Magento\TestFramework\Helper\Bootstrap::getObjectManager()->get('Magento\Framework\Registry');
-
-        $registry->unregister('isSecureArea');
-        $registry->register('isSecureArea', true);
-
-        $this->productCollection->addFieldToFilter(
-            'sku',
-            ['in' => ['sku-test-product-bundle']]
-        )->delete();
-        unset($this->productCollection);
-
-        $registry->unregister('isSecureArea');
-        $registry->register('isSecureArea', false);
+        $this->deleteProductBySku(self::BUNDLE_PRODUCT_ID);
         parent::tearDown();
     }
 
@@ -61,7 +50,6 @@ public function tearDown()
      */
     public function testCreateBundle()
     {
-        $this->markTestSkipped('Processing of custom attributes has been changed in MAGETWO-34448.');
         $bundleProductOptions = [
             [
                 "title" => "test option",
@@ -73,38 +61,365 @@ public function testCreateBundle()
                         "qty" => 1,
                         'is_default' => false,
                         'price' => 1.0,
-                        'price_type' => 1
+                        'price_type' => LinkInterface::PRICE_TYPE_FIXED,
                     ],
                 ],
             ],
         ];
 
-        $uniqueId = 'sku-test-product-bundle';
         $product = [
-            "sku" => $uniqueId,
-            "name" => $uniqueId,
+            "sku" => self::BUNDLE_PRODUCT_ID,
+            "name" => self::BUNDLE_PRODUCT_ID,
             "type_id" => "bundle",
             "price" => 50,
             'attribute_set_id' => 4,
+            "custom_attributes" => [
+                [
+                    "attribute_code" => "price_type",
+                    "value" => \Magento\Bundle\Model\Product\Price::PRICE_TYPE_FIXED,
+                ],
+                [
+                    "attribute_code" => "price_view",
+                    "value" => 1,
+                ],
+            ],
             "extension_attributes" => [
-                "price_type" => \Magento\Bundle\Model\Product\Price::PRICE_TYPE_DYNAMIC,
                 "bundle_product_options" => $bundleProductOptions,
-                "price_view" => "test"
             ],
         ];
 
         $response = $this->createProduct($product);
 
-        $this->assertEquals($uniqueId, $response[ProductInterface::SKU]);
+        $this->assertEquals(self::BUNDLE_PRODUCT_ID, $response[ProductInterface::SKU]);
+        $this->assertEquals(50, $response['price']);
+        $this->assertTrue(
+            isset($response[ExtensibleDataInterface::EXTENSION_ATTRIBUTES_KEY]["bundle_product_options"])
+        );
+        $resultBundleProductOptions
+            = $response[ExtensibleDataInterface::EXTENSION_ATTRIBUTES_KEY]["bundle_product_options"];
+        $this->assertTrue(isset($resultBundleProductOptions[0]["product_links"][0]["sku"]));
+        $this->assertEquals('simple', $resultBundleProductOptions[0]["product_links"][0]["sku"]);
+
+        $response = $this->getProduct(self::BUNDLE_PRODUCT_ID);
+        $this->assertTrue(
+            isset($response[ExtensibleDataInterface::EXTENSION_ATTRIBUTES_KEY]["bundle_product_options"])
+        );
+        $resultBundleProductOptions
+            = $response[ExtensibleDataInterface::EXTENSION_ATTRIBUTES_KEY]["bundle_product_options"];
+        $this->assertTrue(isset($resultBundleProductOptions[0]["product_links"][0]["sku"]));
+        $this->assertEquals('simple', $resultBundleProductOptions[0]["product_links"][0]["sku"]);
+    }
+
+    /**
+     * @magentoApiDataFixture Magento/Catalog/_files/products_new.php
+     * @magentoApiDataFixture Magento/Catalog/_files/second_product_simple.php
+     */
+    public function testUpdateBundleModifyExistingSelection()
+    {
+        $bundleProduct = $this->createFixedPriceBundleProduct();
+        $bundleProductOptions = $this->getBundleProductOptions($bundleProduct);
+
+        $existingSelectionId = $bundleProductOptions[0]['product_links'][0]['id'];
+
+        //Change the type of existing option
+        $bundleProductOptions[0]['type'] = 'select';
+        //Change the sku of existing link and qty
+        $bundleProductOptions[0]['product_links'][0]['sku'] = 'simple2';
+        $bundleProductOptions[0]['product_links'][0]['qty'] = 2;
+        $bundleProductOptions[0]['product_links'][0]['price'] = 10;
+        $bundleProductOptions[0]['product_links'][0]['price_type'] = 1;
+        $this->setBundleProductOptions($bundleProduct, $bundleProductOptions);
+
+        $updatedProduct = $this->saveProduct($bundleProduct);
+
+        $bundleOptions = $this->getBundleProductOptions($updatedProduct);
+        $this->assertEquals('select', $bundleOptions[0]['type']);
+        $this->assertEquals('simple2', $bundleOptions[0]['product_links'][0]['sku']);
+        $this->assertEquals(2, $bundleOptions[0]['product_links'][0]['qty']);
+        $this->assertEquals($existingSelectionId, $bundleOptions[0]['product_links'][0]['id']);
+        $this->assertEquals(10, $bundleOptions[0]['product_links'][0]['price']);
+        $this->assertEquals(1, $bundleOptions[0]['product_links'][0]['price_type']);
+    }
+
+    /**
+     * @magentoApiDataFixture Magento/Catalog/_files/products_new.php
+     * @magentoApiDataFixture Magento/Catalog/_files/second_product_simple.php
+     */
+    public function testUpdateBundleModifyExistingOptionOnly()
+    {
+        $bundleProduct = $this->createFixedPriceBundleProduct();
+        $bundleProductOptions = $this->getBundleProductOptions($bundleProduct);
+
+        $existingSelectionId = $bundleProductOptions[0]['product_links'][0]['id'];
+
+        //Change the type of existing option
+        $bundleProductOptions[0]['type'] = 'select';
+        //unset product_links attribute
+        unset($bundleProductOptions[0]['product_links']);
+        $this->setBundleProductOptions($bundleProduct, $bundleProductOptions);
+
+        $updatedProduct = $this->saveProduct($bundleProduct);
+
+        $bundleOptions = $this->getBundleProductOptions($updatedProduct);
+        $this->assertEquals('select', $bundleOptions[0]['type']);
+        $this->assertEquals('simple', $bundleOptions[0]['product_links'][0]['sku']);
+        $this->assertEquals(1, $bundleOptions[0]['product_links'][0]['qty']);
+        $this->assertEquals($existingSelectionId, $bundleOptions[0]['product_links'][0]['id']);
+        $this->assertEquals(20, $bundleOptions[0]['product_links'][0]['price']);
+        $this->assertEquals(1, $bundleOptions[0]['product_links'][0]['price_type']);
+    }
+
+    /**
+     * @magentoApiDataFixture Magento/Catalog/_files/products_new.php
+     * @magentoApiDataFixture Magento/Catalog/_files/second_product_simple.php
+     */
+    public function testUpdateProductWithoutBundleOptions()
+    {
+        $bundleProduct = $this->createFixedPriceBundleProduct();
+        $bundleProductOptions = $this->getBundleProductOptions($bundleProduct);
+
+        $existingSelectionId = $bundleProductOptions[0]['product_links'][0]['id'];
+
+        //unset bundle_product_options
+        unset($bundleProductOptions[0]['product_links']);
+        $this->setBundleProductOptions($bundleProduct, null);
+
+        $updatedProduct = $this->saveProduct($bundleProduct);
+
+        $bundleOptions = $this->getBundleProductOptions($updatedProduct);
+        $this->assertEquals('checkbox', $bundleOptions[0]['type']);
+        $this->assertEquals('simple', $bundleOptions[0]['product_links'][0]['sku']);
+        $this->assertEquals(1, $bundleOptions[0]['product_links'][0]['qty']);
+        $this->assertEquals($existingSelectionId, $bundleOptions[0]['product_links'][0]['id']);
+        $this->assertEquals(20, $bundleOptions[0]['product_links'][0]['price']);
+        $this->assertEquals(1, $bundleOptions[0]['product_links'][0]['price_type']);
+    }
+
+    /**
+     * @magentoApiDataFixture Magento/Catalog/_files/products_new.php
+     * @magentoApiDataFixture Magento/Catalog/_files/second_product_simple.php
+     */
+    public function testUpdateBundleAddSelection()
+    {
+        $bundleProduct = $this->createDynamicBundleProduct();
+        $bundleProductOptions = $this->getBundleProductOptions($bundleProduct);
+
+        //Add a selection to existing option
+        $bundleProductOptions[0]['product_links'][] = [
+            'sku' => 'simple2',
+            'qty' => 2,
+            "price" => 20,
+            "price_type" => 1,
+            "is_default" => false,
+        ];
+        $this->setBundleProductOptions($bundleProduct, $bundleProductOptions);
+        $updatedProduct = $this->saveProduct($bundleProduct);
+
+        $bundleOptions = $this->getBundleProductOptions($updatedProduct);
+        $this->assertEquals('simple', $bundleOptions[0]['product_links'][0]['sku']);
+        $this->assertEquals('simple2', $bundleOptions[0]['product_links'][1]['sku']);
+        $this->assertEquals(2, $bundleOptions[0]['product_links'][1]['qty']);
+        $this->assertGreaterThan(
+            $bundleOptions[0]['product_links'][0]['id'],
+            $bundleOptions[0]['product_links'][1]['id']
+        );
+    }
+
+    /**
+     * @magentoApiDataFixture Magento/Catalog/_files/products_new.php
+     * @magentoApiDataFixture Magento/Catalog/_files/second_product_simple.php
+     */
+    public function testUpdateBundleAddAndDeleteOption()
+    {
+        $bundleProduct = $this->createDynamicBundleProduct();
+
+        $bundleProductOptions = $this->getBundleProductOptions($bundleProduct);
+
+        $oldOptionId = $bundleProductOptions[0]['option_id'];
+        //replace current option with a new option
+        $bundleProductOptions[0] = [
+            'title' => 'new option',
+            'required' => true,
+            'type' => 'select',
+            'product_links' => [
+                [
+                    'sku' => 'simple2',
+                    'qty' => 2,
+                    "price" => 20,
+                    "price_type" => 1,
+                    "is_default" => false,
+                ],
+            ],
+        ];
+        $this->setBundleProductOptions($bundleProduct, $bundleProductOptions);
+        $this->saveProduct($bundleProduct);
+
+        $updatedProduct = $this->getProduct(self::BUNDLE_PRODUCT_ID);
+        $bundleOptions = $this->getBundleProductOptions($updatedProduct);
+        $this->assertEquals('new option', $bundleOptions[0]['title']);
+        $this->assertTrue($bundleOptions[0]['required']);
+        $this->assertEquals('select', $bundleOptions[0]['type']);
+        $this->assertGreaterThan($oldOptionId, $bundleOptions[0]['option_id']);
+        $this->assertFalse(isset($bundleOptions[1]));
+        $this->assertEquals('simple2', $bundleOptions[0]['product_links'][0]['sku']);
+        $this->assertEquals(2, $bundleOptions[0]['product_links'][0]['qty']);
+    }
+
+    /**
+     * Get the bundle_product_options custom attribute from product, null if the attribute is not set
+     *
+     * @param array $product
+     * @return array|null
+     */
+    protected function getBundleProductOptions($product)
+    {
+        if (isset($product[ExtensibleDataInterface::EXTENSION_ATTRIBUTES_KEY]["bundle_product_options"])) {
+            return $product[ExtensibleDataInterface::EXTENSION_ATTRIBUTES_KEY]["bundle_product_options"];
+        } else {
+            return null;
+        }
+    }
+
+    /**
+     * Set the bundle_product_options custom attribute, replace existing attribute if exists
+     *
+     * @param array $product
+     * @param array $bundleProductOptions
+     */
+    protected function setBundleProductOptions(&$product, $bundleProductOptions)
+    {
+        $product["extension_attributes"]["bundle_product_options"] = $bundleProductOptions;
+        return;
+    }
+
+    /**
+     * Create dynamic bundle product with one option
+     *
+     * @return array
+     */
+    protected function createDynamicBundleProduct()
+    {
+        $bundleProductOptions = [
+            [
+                "title" => "test option",
+                "type" => "checkbox",
+                "required" => 1,
+                "product_links" => [
+                    [
+                        "sku" => 'simple',
+                        "qty" => 1,
+                        "is_default" => true,
+                        "price" => 10,
+                        "price_type" => 1,
+                    ],
+                ],
+            ],
+        ];
+
+        $uniqueId = self::BUNDLE_PRODUCT_ID;
+        $product = [
+            "sku" => $uniqueId,
+            "name" => $uniqueId,
+            "type_id" => "bundle",
+            'attribute_set_id' => 4,
+            "custom_attributes" => [
+                "price_type" => [
+                    'attribute_code' => 'price_type',
+                    'value' => \Magento\Bundle\Model\Product\Price::PRICE_TYPE_DYNAMIC
+                ],
+                "price_view" => [
+                    "attribute_code" => "price_view",
+                    "value" => "1",
+                ],
+            ],
+            "extension_attributes" => [
+                "bundle_product_options" => $bundleProductOptions,
+            ],
+        ];
+
+        $response = $this->createProduct($product);
+        $this->assertTrue(
+            isset($response[ExtensibleDataInterface::EXTENSION_ATTRIBUTES_KEY]["bundle_product_options"])
+        );
         $resultBundleProductOptions
             = $response[ExtensibleDataInterface::EXTENSION_ATTRIBUTES_KEY]["bundle_product_options"];
-        $this->assertEquals($bundleProductOptions, $resultBundleProductOptions);
+        $this->assertTrue(isset($resultBundleProductOptions[0]["product_links"][0]["sku"]));
         $this->assertEquals('simple', $resultBundleProductOptions[0]["product_links"][0]["sku"]);
+        $this->assertTrue(isset($response['custom_attributes']));
+        $customAttributes = $this->convertCustomAttributes($response['custom_attributes']);
+        $this->assertTrue(isset($customAttributes['price_type']));
+        $this->assertEquals(\Magento\Bundle\Model\Product\Price::PRICE_TYPE_DYNAMIC, $customAttributes['price_type']);
+        $this->assertTrue(isset($customAttributes['price_view']));
+        $this->assertEquals(1, $customAttributes['price_view']);
+        return $response;
+    }
+
+    /**
+     * Create fixed price bundle product with one option
+     *
+     * @return array
+     */
+    protected function createFixedPriceBundleProduct()
+    {
+        $bundleProductOptions = [
+            [
+                "title" => "test option",
+                "type" => "checkbox",
+                "required" => 1,
+                "product_links" => [
+                    [
+                        "sku" => 'simple',
+                        "qty" => 1,
+                        "price" => 20,
+                        "price_type" => 1,
+                        "is_default" => true,
+                    ],
+                ],
+            ],
+        ];
+
+        $uniqueId = self::BUNDLE_PRODUCT_ID;
+        $product = [
+            "sku" => $uniqueId,
+            "name" => $uniqueId,
+            "type_id" => "bundle",
+            "price" => 50,
+            'attribute_set_id' => 4,
+            "custom_attributes" => [
+                "price_type" => [
+                    'attribute_code' => 'price_type',
+                    'value' => \Magento\Bundle\Model\Product\Price::PRICE_TYPE_FIXED
+                ],
+                "price_view" => [
+                    "attribute_code" => "price_view",
+                    "value" => "1",
+                ],
+            ],
+            "extension_attributes" => [
+                "bundle_product_options" => $bundleProductOptions,
+            ],
+        ];
 
-        $response = $this->getProduct($uniqueId);
+        $response = $this->createProduct($product);
         $resultBundleProductOptions
             = $response[ExtensibleDataInterface::EXTENSION_ATTRIBUTES_KEY]["bundle_product_options"];
         $this->assertEquals('simple', $resultBundleProductOptions[0]["product_links"][0]["sku"]);
+        $this->assertTrue(isset($response['custom_attributes']));
+        $customAttributes = $this->convertCustomAttributes($response['custom_attributes']);
+        $this->assertTrue(isset($customAttributes['price_type']));
+        $this->assertEquals(\Magento\Bundle\Model\Product\Price::PRICE_TYPE_FIXED, $customAttributes['price_type']);
+        $this->assertTrue(isset($customAttributes['price_view']));
+        $this->assertEquals(1, $customAttributes['price_view']);
+        return $response;
+    }
+
+    protected function convertCustomAttributes($customAttributes)
+    {
+        $convertedCustomAttribute = [];
+        foreach ($customAttributes as $customAttribute) {
+            $convertedCustomAttribute[$customAttribute['attribute_code']] = $customAttribute['value'];
+        }
+        return $convertedCustomAttribute;
     }
 
     /**
@@ -154,7 +469,56 @@ protected function createProduct($product)
         ];
         $requestData = ['product' => $product];
         $response = $this->_webApiCall($serviceInfo, $requestData);
-        $product[ProductInterface::SKU] = $response[ProductInterface::SKU];
-        return $product;
+        return $response;
+    }
+
+    /**
+     * Delete a product by sku
+     *
+     * @param $productSku
+     * @return bool
+     */
+    protected function deleteProductBySku($productSku)
+    {
+        $resourcePath = self::RESOURCE_PATH . '/' . $productSku;
+        $serviceInfo = [
+            'rest' => [
+                'resourcePath' => $resourcePath,
+                'httpMethod' => \Magento\Framework\Webapi\Rest\Request::HTTP_METHOD_DELETE
+            ],
+            'soap' => [
+                'service' => self::SERVICE_NAME,
+                'serviceVersion' => self::SERVICE_VERSION,
+                'operation' => self::SERVICE_NAME . 'deleteById',
+            ],
+        ];
+        $requestData = ["sku" => $productSku];
+        $response = $this->_webApiCall($serviceInfo, $requestData);
+        return $response;
+    }
+
+    /**
+     * Save product
+     *
+     * @param array $product
+     * @return array the created product data
+     */
+    protected function saveProduct($product)
+    {
+        $resourcePath = self::RESOURCE_PATH . '/' . $product['sku'];
+        $serviceInfo = [
+            'rest' => [
+                'resourcePath' => $resourcePath,
+                'httpMethod' => \Magento\Framework\Webapi\Rest\Request::HTTP_METHOD_PUT
+            ],
+            'soap' => [
+                'service' => self::SERVICE_NAME,
+                'serviceVersion' => self::SERVICE_VERSION,
+                'operation' => self::SERVICE_NAME . 'Save',
+            ],
+        ];
+        $requestData = ['product' => $product];
+        $response = $this->_webApiCall($serviceInfo, $requestData);
+        return $response;
     }
 }
diff --git a/dev/tests/integration/testsuite/Magento/Bundle/Model/Plugin/BundleSaveOptionsTest.php b/dev/tests/integration/testsuite/Magento/Bundle/Model/Plugin/BundleSaveOptionsTest.php
new file mode 100644
index 0000000000000..b46a6544e18aa
--- /dev/null
+++ b/dev/tests/integration/testsuite/Magento/Bundle/Model/Plugin/BundleSaveOptionsTest.php
@@ -0,0 +1,84 @@
+<?php
+/**
+ * Copyright © 2015 Magento. All rights reserved.
+ * See COPYING.txt for license details.
+ */
+
+namespace Magento\Bundle\Model\Plugin;
+
+class BundleSaveOptionsTest extends \PHPUnit_Framework_TestCase
+{
+    /**
+     * @var \Magento\Catalog\Model\Product
+     */
+    protected $_model;
+
+    /**
+     * @var \Magento\Catalog\Api\ProductRepositoryInterface
+     */
+    protected $productRepository;
+
+    protected function setUp()
+    {
+        $objectManager = \Magento\TestFramework\Helper\Bootstrap::getObjectManager();
+        $this->productRepository = $objectManager->get('Magento\Catalog\Api\ProductRepositoryInterface');
+    }
+
+    /**
+     * @magentoDataFixture Magento/Bundle/_files/product.php
+     * @magentoDbIsolation enabled
+     */
+    public function testSaveSuccess()
+    {
+        $title = "new title";
+        $bundleProductSku = 'bundle-product';
+        $product = $this->productRepository->get($bundleProductSku);
+        $bundleExtensionAttributes = $product->getExtensionAttributes()->getBundleProductOptions();
+        $bundleOption = $bundleExtensionAttributes[0];
+        $this->assertEquals(true, $bundleOption->getRequired());
+        $bundleOption->setTitle($title);
+
+        $oldDescription = $product->getDescription();
+        $description = $oldDescription . "hello";
+        $product->setDescription($description);
+        $product->getExtensionAttributes()->setBundleProductOptions([$bundleOption]);
+        $product = $this->productRepository->save($product);
+
+        $this->assertEquals($description, $product->getDescription());
+        $this->assertEquals($title, $product->getExtensionAttributes()->getBundleProductOptions()[0]->getTitle());
+    }
+
+    /**
+     * @magentoDataFixture Magento/Bundle/_files/product.php
+     * @magentoDbIsolation enabled
+     */
+    public function testSaveFailure()
+    {
+        $this->markTestSkipped("When MAGETWO-36510 is fixed, need to change Dbisolation to disabled");
+        $bundleProductSku = 'bundle-product';
+        $product = $this->productRepository->get($bundleProductSku);
+        $bundleExtensionAttributes = $product->getExtensionAttributes()->getBundleProductOptions();
+        $bundleOption = $bundleExtensionAttributes[0];
+        $this->assertEquals(true, $bundleOption->getRequired());
+        $bundleOption->setRequired(false);
+        //set an incorrect option id to trigger exception
+        $bundleOption->setOptionId(-1);
+
+        $description = "hello";
+
+        $product->setDescription($description);
+        $product->getExtensionAttributes()->setBundleProductOptions([$bundleOption]);
+        $caughtException = false;
+        try {
+            $this->productRepository->save($product);
+        } catch (\Magento\Framework\Exception\NoSuchEntityException $e) {
+            $caughtException = true;
+        }
+
+        $this->assertTrue($caughtException);
+        /** @var \Magento\Catalog\Model\Product $product */
+        $product = \Magento\TestFramework\Helper\Bootstrap::getObjectManager()
+            ->create('Magento\Catalog\Model\Product')->load($product->getId());
+        $this->assertEquals(null, $product->getDescription());
+    }
+}
diff --git a/dev/tests/integration/testsuite/Magento/Bundle/_files/product.php b/dev/tests/integration/testsuite/Magento/Bundle/_files/product.php
index 1bb799689c7ec..663c2d0225742 100644
--- a/dev/tests/integration/testsuite/Magento/Bundle/_files/product.php
+++ b/dev/tests/integration/testsuite/Magento/Bundle/_files/product.php
@@ -31,6 +31,12 @@
     \Magento\Catalog\Model\Product\Attribute\Source\Status::STATUS_ENABLED
 )->setStockData(
     ['use_config_manage_stock' => 1, 'qty' => 100, 'is_qty_decimal' => 0, 'is_in_stock' => 1]
+)->setPriceView(
+    1
+)->setPriceType(
+    1
+)->setPrice(
+    10.0
 )->setBundleOptionsData(
     [
         [
diff --git a/dev/tests/performance/benchmark.jmx b/dev/tests/performance/benchmark.jmx
index cd9ed6d2ce32c..34b8617830f41 100644
--- a/dev/tests/performance/benchmark.jmx
+++ b/dev/tests/performance/benchmark.jmx
@@ -864,7 +864,7 @@
             <collectionProp name="Arguments.arguments">
               <elementProp name="billing[address_id]" elementType="HTTPArgument">
                 <boolProp name="HTTPArgument.always_encode">true</boolProp>
-                <stringProp name="Argument.value">37</stringProp>
+                <stringProp name="Argument.value"></stringProp>
                 <stringProp name="Argument.metadata">=</stringProp>
                 <boolProp name="HTTPArgument.use_equals">true</boolProp>
                 <stringProp name="Argument.name">billing[address_id]</stringProp>
@@ -962,7 +962,7 @@
               </elementProp>
               <elementProp name="billing[save_in_address_book]" elementType="HTTPArgument">
                 <boolProp name="HTTPArgument.always_encode">true</boolProp>
-                <stringProp name="Argument.value">1</stringProp>
+                <stringProp name="Argument.value">0</stringProp>
                 <stringProp name="Argument.metadata">=</stringProp>
                 <boolProp name="HTTPArgument.use_equals">true</boolProp>
                 <stringProp name="Argument.name">billing[save_in_address_book]</stringProp>
diff --git a/lib/internal/Magento/Framework/Model/AbstractExtensibleModel.php b/lib/internal/Magento/Framework/Model/AbstractExtensibleModel.php
index b90ff7acb3929..7b70fa5d35e61 100644
--- a/lib/internal/Magento/Framework/Model/AbstractExtensibleModel.php
+++ b/lib/internal/Magento/Framework/Model/AbstractExtensibleModel.php
@@ -205,6 +205,21 @@ public function unsetData($key = null)
         return parent::unsetData($key);
     }
 
+    /**
+     * Convert custom values if necessary
+     *
+     * @param array $customAttributes
+     * @return void
+     */
+    protected function convertCustomAttributeValues(array &$customAttributes)
+    {
+        foreach ($customAttributes as $attributeCode => $attributeValue) {
+            if ($attributeValue instanceof \Magento\Framework\Api\AttributeValue) {
+                $customAttributes[$attributeCode] = $attributeValue->getValue();
+            }
+        }
+    }
+
     /**
      * Object data getter
      *
@@ -231,6 +246,7 @@ public function getData($key = '', $index = null)
             $customAttributes = isset($this->_data[self::CUSTOM_ATTRIBUTES])
                 ? $this->_data[self::CUSTOM_ATTRIBUTES]
                 : [];
+            $this->convertCustomAttributeValues($customAttributes);
             $data = array_merge($this->_data, $customAttributes);
             unset($data[self::CUSTOM_ATTRIBUTES]);
         } else {
@@ -238,6 +254,9 @@ public function getData($key = '', $index = null)
             if ($data === null) {
                 /** Try to find necessary data in custom attributes */
                 $data = parent::getData(self::CUSTOM_ATTRIBUTES . "/{$key}", $index);
+                if ($data instanceof \Magento\Framework\Api\AttributeValue) {
+                    $data = $data->getValue();
+                }
             }
         }
         return $data;
diff --git a/lib/internal/Magento/Framework/Webapi/ServiceInputProcessor.php b/lib/internal/Magento/Framework/Webapi/ServiceInputProcessor.php
index 14caad463c95a..e232546d6ac1e 100644
--- a/lib/internal/Magento/Framework/Webapi/ServiceInputProcessor.php
+++ b/lib/internal/Magento/Framework/Webapi/ServiceInputProcessor.php
@@ -137,8 +137,7 @@ protected function _createFromArray($className, $data)
         if (is_subclass_of($className, self::EXTENSION_ATTRIBUTES_TYPE)) {
             $className = substr($className, 0, -strlen('Interface'));
         }
-        $factory = $this->objectManager->get($className . 'Factory');
-        $object = $factory->create();
+        $object = $this->objectManager->create($className);
 
         foreach ($data as $propertyName => $value) {
             // Converts snake_case to uppercase CamelCase to help form getter/setter method names
diff --git a/lib/internal/Magento/Framework/Webapi/Test/Unit/ServiceInputProcessorTest.php b/lib/internal/Magento/Framework/Webapi/Test/Unit/ServiceInputProcessorTest.php
index 41cdf8184ba37..ac75d2c131fb1 100644
--- a/lib/internal/Magento/Framework/Webapi/Test/Unit/ServiceInputProcessorTest.php
+++ b/lib/internal/Magento/Framework/Webapi/Test/Unit/ServiceInputProcessorTest.php
@@ -42,6 +42,14 @@ public function setUp()
         $this->objectManagerMock = $this->getMockBuilder('\Magento\Framework\ObjectManagerInterface')
             ->disableOriginalConstructor()
             ->getMock();
+        $this->objectManagerMock->expects($this->any())
+            ->method('create')
+            ->willReturnCallback(
+                function ($className) use ($objectManager) {
+                    return $objectManager->getObject($className);
+                }
+            );
+
         /** @var \Magento\Framework\Reflection\TypeProcessor $typeProcessor */
         $typeProcessor = $objectManager->getObject('Magento\Framework\Reflection\TypeProcessor');
         $cache = $this->getMockBuilder('Magento\Framework\App\Cache\Type\Webapi')
@@ -119,12 +127,6 @@ public function testNonExistentPropertiesWithoutDefaultArgumentValue()
 
     public function testNestedDataProperties()
     {
-        $this->setupFactory(
-            [
-                'Magento\Framework\Webapi\Test\Unit\ServiceInputProcessor\Nested',
-                '\Magento\Framework\Webapi\Test\Unit\ServiceInputProcessor\Simple',
-            ]
-        );
         $data = ['nested' => ['details' => ['entityId' => 15, 'name' => 'Test']]];
         $result = $this->serviceInputProcessor->process(
             'Magento\Framework\Webapi\Test\Unit\ServiceInputProcessor\TestService',
@@ -167,7 +169,6 @@ public function testSimpleArrayProperties()
 
     public function testAssociativeArrayProperties()
     {
-        $this->setupFactory(['Magento\Framework\Webapi\Test\Unit\ServiceInputProcessor\Simple']);
         $data = ['associativeArray' => ['key' => 'value', 'key_two' => 'value_two']];
         $result = $this->serviceInputProcessor->process(
             'Magento\Framework\Webapi\Test\Unit\ServiceInputProcessor\TestService',
@@ -186,7 +187,6 @@ public function testAssociativeArrayProperties()
 
     public function testAssociativeArrayPropertiesWithItem()
     {
-        $this->setupFactory(['Magento\Framework\Webapi\Test\Unit\ServiceInputProcessor\AssociativeArray']);
         $data = ['associativeArray' => ['item' => 'value']];
         $result = $this->serviceInputProcessor->process(
             'Magento\Framework\Webapi\Test\Unit\ServiceInputProcessor\TestService',
@@ -204,7 +204,6 @@ public function testAssociativeArrayPropertiesWithItem()
 
     public function testAssociativeArrayPropertiesWithItemArray()
     {
-        $this->setupFactory(['Magento\Framework\Webapi\Test\Unit\ServiceInputProcessor\AssociativeArray']);
         $data = ['associativeArray' => ['item' => ['value1','value2']]];
         $result = $this->serviceInputProcessor->process(
             'Magento\Framework\Webapi\Test\Unit\ServiceInputProcessor\TestService',
@@ -223,11 +222,6 @@ public function testAssociativeArrayPropertiesWithItemArray()
 
     public function testArrayOfDataObjectProperties()
     {
-        $this->setupFactory(
-            [
-                '\Magento\Framework\Webapi\Test\Unit\ServiceInputProcessor\Simple'
-            ]
-        );
         $data = [
             'dataObjects' => [
                 ['entityId' => 14, 'name' => 'First'],
@@ -259,7 +253,6 @@ public function testArrayOfDataObjectProperties()
 
     public function testNestedSimpleArrayProperties()
     {
-        $this->setupFactory(['Magento\Framework\Webapi\Test\Unit\ServiceInputProcessor\SimpleArray']);
         $data = ['arrayData' => ['ids' => [1, 2, 3, 4]]];
         $result = $this->serviceInputProcessor->process(
             'Magento\Framework\Webapi\Test\Unit\ServiceInputProcessor\TestService',
@@ -281,7 +274,6 @@ public function testNestedSimpleArrayProperties()
 
     public function testNestedAssociativeArrayProperties()
     {
-        $this->setupFactory(['Magento\Framework\Webapi\Test\Unit\ServiceInputProcessor\AssociativeArray']);
         $data = [
             'associativeArrayData' => ['associativeArray' => ['key' => 'value', 'key2' => 'value2']],
         ];
@@ -305,12 +297,6 @@ public function testNestedAssociativeArrayProperties()
 
     public function testNestedArrayOfDataObjectProperties()
     {
-        $this->setupFactory(
-            [
-                'Magento\Framework\Webapi\Test\Unit\ServiceInputProcessor\DataArray',
-                '\Magento\Framework\Webapi\Test\Unit\ServiceInputProcessor\Simple',
-            ]
-        );
         $data = [
             'dataObjects' => [
                 'items' => [['entityId' => 1, 'name' => 'First'], ['entityId' => 2, 'name' => 'Second']],
@@ -352,14 +338,6 @@ public function testNestedArrayOfDataObjectProperties()
      */
     public function testCustomAttributesProperties($customAttributeType, $inputData, $expectedObject)
     {
-        $this->setupFactory(
-            [
-                'Magento\Framework\Webapi\Test\Unit\ServiceInputProcessor\ObjectWithCustomAttributes',
-                '\Magento\Framework\Webapi\Test\Unit\ServiceInputProcessor\Simple',
-                'Magento\Framework\Webapi\Test\Unit\ServiceInputProcessor\Simple',
-                'Magento\Framework\Webapi\Test\Unit\ServiceInputProcessor\SimpleArray',
-            ]
-        );
         $this->customAttributeTypeLocator->expects($this->any())->method('getType')->willReturn($customAttributeType);
 
         $result = $this->serviceInputProcessor->process(
@@ -521,27 +499,4 @@ protected function getObjectWithCustomAttributes($type, $value = [])
             ]]
         );
     }
-    protected function setupFactory(array $classNames)
-    {
-        $objectManager = new \Magento\Framework\TestFramework\Unit\Helper\ObjectManager($this);
-
-        $returnValueMap = [];
-        foreach ($classNames as $className) {
-            $factoryMock = $this->getMockBuilder($className . 'Factory')
-                ->setMethods(['create'])
-                ->disableOriginalConstructor()
-                ->getMock();
-            $factoryMock->expects($this->any())
-                ->method('create')
-                ->willReturnCallback(
-                    function () use ($objectManager, $className) {
-                        return $objectManager->getObject($className);
-                    }
-                );
-            $returnValueMap[] = [$className . 'Factory', $factoryMock];
-        }
-        $this->objectManagerMock->expects($this->any())
-            ->method('get')
-            ->will($this->returnValueMap($returnValueMap));
-    }
 }