Repository: dell/csi-baremetal
Pull request #916 (2022-08-04 00:19:30)

[ISSUE-915] reset source for ubuntu 21.04 (cherry-pick to release)
## Purpose
### Resolves #915

Changes only for loopback manager driver
Changed file `/etc/apt/sources.list`
Replace repos `archive.ubuntu.com` and `security.ubuntu.com` to `old-releases.ubuntu.com`
Add [trusted=yes] for ignore issue like:
```
E: The repository 'http://old-releases.ubuntu.com/ubuntu hirsute InRelease' is not signed.
```

## PR checklist
- [x] Add link to the issue
- [ ] Choose Project
- [x] Choose PR label
- [ ] New unit tests added
- [ ] Modified code has meaningful comments
- [ ] All TODOs are linked with the issues
- [ ] All comments are resolved

## Testing
Build and e2e


yurkov-anton: /ci
github-actions[bot]: @yurkov-anton Start CI.
Operator branch: 
Go Version: 1.17.7
GoLangCI Version: 1.44.0
Helm Version: 3.7.1
Kubectl Version: v1.22.2
github-actions[bot]: Versions using in CI
CSI Version: 1.1.0-1.982cfae
OPERATOR Version: 1.1.0-1.9e73361
github-actions[bot]: ## E2E Test Results
    1 files      1 suites   11m 26s [:stopwatch:](https://github.com/EnricoMi/publish-unit-test-result-action/blob/v1.20/README.md#the-symbols "duration of all tests")
149 tests     4 [:heavy_check_mark:](https://github.com/EnricoMi/publish-unit-test-result-action/blob/v1.20/README.md#the-symbols "passed tests") 145 [:zzz:](https://github.com/EnricoMi/publish-unit-test-result-action/blob/v1.20/README.md#the-symbols "skipped / disabled tests") 0 [:x:](https://github.com/EnricoMi/publish-unit-test-result-action/blob/v1.20/README.md#the-symbols "failed tests")
    4 runs  -141 [:heavy_check_mark:](https://github.com/EnricoMi/publish-unit-test-result-action/blob/v1.20/README.md#the-symbols "passed tests") 145 [:zzz:](https://github.com/EnricoMi/publish-unit-test-result-action/blob/v1.20/README.md#the-symbols "skipped / disabled tests") 0 [:x:](https://github.com/EnricoMi/publish-unit-test-result-action/blob/v1.20/README.md#the-symbols "failed tests")

Results for commit 982cfae2.

[test-results]:data:application/gzip;base64,H4sIAHYW62IC/02MXQrDIBAGrxJ8bqHKxmovUxZ1QZrE4s9T6N1rJbG+7cy3zM7ILy6xx8QvE0vF5w62RMw+bBWlklXUKbcR9EnPVIypCv7i5d/tZ+6K0C9V3bpwMYZ4mFi2dAR+5xm8cuBdjclmhmLjMWjCuvpcgWklDKETEpUTYACs4kjcEp8FkLtrQSgsSvb5AjWZV3MGAQAA

github-actions[bot]: @yurkov-anton, CI tests passed Run: https://github.com/dell/csi-baremetal/actions/runs/2793306982

Patch:
diff --git a/pkg/base/k8s/cr_helper.go b/pkg/base/k8s/cr_helper.go
index 513d86e..f08cf6f 100644
--- a/pkg/base/k8s/cr_helper.go
+++ b/pkg/base/k8s/cr_helper.go
@@ -19,7 +19,6 @@ package k8s
 import (
 	"context"
 	"errors"
-	"fmt"
 	"strings"
 
 	"github.com/sirupsen/logrus"
@@ -36,16 +35,38 @@ import (
 	errTypes "github.com/dell/csi-baremetal/pkg/base/error"
 )
 
-// CRHelper is able to collect different CRs by different criteria
-type CRHelper struct {
+// CRHelper is holds idempotent methods that consists of WrapFS methods
+type CRHelper interface {
+	SetReader(reader CRReader) CRHelper
+	GetACByLocation(location string) (*accrd.AvailableCapacity, error)
+	DeleteACsByNodeID(nodeID string) error
+	GetVolumesByLocation(ctx context.Context, location string) ([]*volumecrd.Volume, error)
+	GetLVGByDrive(ctx context.Context, driveUUID string) (*lvgcrd.LogicalVolumeGroup, error)
+	UpdateVolumesOpStatusOnNode(nodeID, opStatus string) error
+	GetVolumeByID(volID string) (*volumecrd.Volume, error)
+	GetVolumeCRs(node ...string) ([]volumecrd.Volume, error)
+	UpdateDrivesStatusOnNode(nodeID, status string) error
+	GetDriveCRs(node ...string) ([]drivecrd.Drive, error)
+	GetACCRs(node ...string) ([]accrd.AvailableCapacity, error)
+	GetDriveCRByVolume(volume *volumecrd.Volume) (*drivecrd.Drive, error)
+	GetDriveCRAndLVGCRByVolume(volume *volumecrd.Volume) (*drivecrd.Drive, *lvgcrd.LogicalVolumeGroup, error)
+	GetVGNameByLVGCRName(lvgCRName string) (string, error)
+	GetLVGCRs(node ...string) ([]lvgcrd.LogicalVolumeGroup, error)
+	UpdateVolumeCRSpec(volName string, namespace string, newSpec api.Volume) error
+	DeleteObjectByName(ctx context.Context, name string, namespace string, obj k8sCl.Object) error
+	UpdateVolumeOpStatus(ctx context.Context, volume *volumecrd.Volume, opStatus string) error
+}
+
+// CRHelperImpl is implementation of CRHelper
+type CRHelperImpl struct {
 	k8sClient *KubeClient
 	reader    CRReader
 	log       *logrus.Entry
 }
 
-// NewCRHelper is a constructor for CRHelper instance
-func NewCRHelper(k *KubeClient, logger *logrus.Logger) *CRHelper {
-	return &CRHelper{
+// NewCRHelperImpl is a constructor for CRHelperImpl instance
+func NewCRHelperImpl(k *KubeClient, logger *logrus.Logger) *CRHelperImpl {
+	return &CRHelperImpl{
 		k8sClient: k,
 		reader:    k,
 		log:       logger.WithField("component", "CRHelper"),
@@ -53,7 +74,7 @@ func NewCRHelper(k *KubeClient, logger *logrus.Logger) *CRHelper {
 }
 
 // SetReader allow to set separate CRReader for CRHelper, by default k8sClient will be used
-func (cs *CRHelper) SetReader(reader CRReader) *CRHelper {
+func (cs *CRHelperImpl) SetReader(reader CRReader) CRHelper {
 	cs.reader = reader
 	return cs
 }
@@ -61,7 +82,7 @@ func (cs *CRHelper) SetReader(reader CRReader) *CRHelper {
 // GetACByLocation reads the whole list of AC CRs from a cluster and searches the AC with provided location
 // Receive context and location name which should be equal to AvailableCapacity.Spec.Location
 // Returns a pointer to the instance of accrd.AvailableCapacity or nil
-func (cs *CRHelper) GetACByLocation(location string) (*accrd.AvailableCapacity, error) {
+func (cs *CRHelperImpl) GetACByLocation(location string) (*accrd.AvailableCapacity, error) {
 	ll := cs.log.WithFields(logrus.Fields{
 		"method":   "GetACByLocation",
 		"location": location,
@@ -87,7 +108,7 @@ func (cs *CRHelper) GetACByLocation(location string) (*accrd.AvailableCapacity,
 // DeleteACsByNodeID deletes AC CRs for specific node ID
 // Receives unique identifier of the node
 // Returns error or nil
-func (cs *CRHelper) DeleteACsByNodeID(nodeID string) error {
+func (cs *CRHelperImpl) DeleteACsByNodeID(nodeID string) error {
 	ll := cs.log.WithFields(logrus.Fields{"method": "DeleteACsByNodeID", "nodeID": nodeID})
 
 	acList := &accrd.AvailableCapacityList{}
@@ -119,7 +140,7 @@ func (cs *CRHelper) DeleteACsByNodeID(nodeID string) error {
 // GetVolumesByLocation reads the whole list of Volume CRs from a cluster and searches the volume with provided location
 // Receives golang context and location name which should be equal to Volume.Spec.Location
 // Returns a list of a pointers to volumes which are belong to the location and error
-func (cs *CRHelper) GetVolumesByLocation(ctx context.Context, location string) ([]*volumecrd.Volume, error) {
+func (cs *CRHelperImpl) GetVolumesByLocation(ctx context.Context, location string) ([]*volumecrd.Volume, error) {
 	ll := cs.log.WithFields(logrus.Fields{
 		"method":   "GetVolumesByLocation",
 		"location": location,
@@ -160,7 +181,7 @@ func (cs *CRHelper) GetVolumesByLocation(ctx context.Context, location string) (
 // GetLVGByDrive reads list of LogicalVolumeGroup CRs from a cluster and searches the lvg with provided location
 // Receives golang context and drive uuid
 // Returns found lvg and error
-func (cs *CRHelper) GetLVGByDrive(ctx context.Context, driveUUID string) (*lvgcrd.LogicalVolumeGroup, error) {
+func (cs *CRHelperImpl) GetLVGByDrive(ctx context.Context, driveUUID string) (*lvgcrd.LogicalVolumeGroup, error) {
 	ll := cs.log.WithFields(logrus.Fields{
 		"method":    "GetLVGByDrive",
 		"driveUUID": driveUUID,
@@ -181,7 +202,7 @@ func (cs *CRHelper) GetLVGByDrive(ctx context.Context, driveUUID string) (*lvgcr
 // UpdateVolumesOpStatusOnNode updates operational status of volumes on a node without taking into account current state
 // Receives unique identifier of the node and operational status to be set
 // Returns error or nil
-func (cs *CRHelper) UpdateVolumesOpStatusOnNode(nodeID, opStatus string) error {
+func (cs *CRHelperImpl) UpdateVolumesOpStatusOnNode(nodeID, opStatus string) error {
 	ll := cs.log.WithFields(logrus.Fields{"method": "UpdateVolumesOpStatus", "nodeID": nodeID})
 	// TODO: check that operational status is valid https://github.com/dell/csi-baremetal/issues/80
 	volumes, err := cs.GetVolumeCRs(nodeID)
@@ -211,7 +232,7 @@ func (cs *CRHelper) UpdateVolumesOpStatusOnNode(nodeID, opStatus string) error {
 }
 
 // GetVolumeByID reads volume CRs and returns volumes CR if it .Spec.Id == volId
-func (cs *CRHelper) GetVolumeByID(volID string) (*volumecrd.Volume, error) {
+func (cs *CRHelperImpl) GetVolumeByID(volID string) (*volumecrd.Volume, error) {
 	volumeCRs, err := cs.GetVolumeCRs()
 	if err != nil {
 		return nil, err
@@ -226,13 +247,13 @@ func (cs *CRHelper) GetVolumeByID(volID string) (*volumecrd.Volume, error) {
 		"method":   "GetVolumeByID",
 		"volumeID": volID,
 	}).Infof("Volume CR not found")
-	return nil, fmt.Errorf("volume wasn't found")
+	return nil, errTypes.ErrorNotFound
 }
 
 // GetVolumeCRs collect volume CRs that locate on node, use just node[0] element
 // if node isn't provided - return all volume CRs
 // if error occurs - return nil and error
-func (cs *CRHelper) GetVolumeCRs(node ...string) ([]volumecrd.Volume, error) {
+func (cs *CRHelperImpl) GetVolumeCRs(node ...string) ([]volumecrd.Volume, error) {
 	var (
 		vList = &volumecrd.VolumeList{}
 		err   error
@@ -259,7 +280,7 @@ func (cs *CRHelper) GetVolumeCRs(node ...string) ([]volumecrd.Volume, error) {
 // UpdateDrivesStatusOnNode updates status of drives on a node without taking into account current state
 // Receives unique identifier of the node and status to be set
 // Returns error or nil
-func (cs *CRHelper) UpdateDrivesStatusOnNode(nodeID, status string) error {
+func (cs *CRHelperImpl) UpdateDrivesStatusOnNode(nodeID, status string) error {
 	ll := cs.log.WithFields(logrus.Fields{"method": "UpdateDrivesStatusOnNode", "nodeID": nodeID})
 	// TODO: check that drive status is valid - https://github.com/dell/csi-baremetal/issues/80
 	drives, _ := cs.GetDriveCRs(nodeID)
@@ -291,7 +312,7 @@ func (cs *CRHelper) UpdateDrivesStatusOnNode(nodeID, status string) error {
 // GetDriveCRs collect Drives CR that locate on node, use just node[0] element
 // if node isn't provided - return all Drives CR
 // if error occurs - return nil and error
-func (cs *CRHelper) GetDriveCRs(node ...string) ([]drivecrd.Drive, error) {
+func (cs *CRHelperImpl) GetDriveCRs(node ...string) ([]drivecrd.Drive, error) {
 	var (
 		dList = &drivecrd.DriveList{}
 		err   error
@@ -318,7 +339,7 @@ func (cs *CRHelper) GetDriveCRs(node ...string) ([]drivecrd.Drive, error) {
 // GetACCRs collect ACs CR that locate on node, use just node[0] element
 // if node isn't provided - return all ACs CR
 // if error occurs - return nil and error
-func (cs *CRHelper) GetACCRs(node ...string) ([]accrd.AvailableCapacity, error) {
+func (cs *CRHelperImpl) GetACCRs(node ...string) ([]accrd.AvailableCapacity, error) {
 	var (
 		acsList = &accrd.AvailableCapacityList{}
 		err     error
@@ -343,13 +364,13 @@ func (cs *CRHelper) GetACCRs(node ...string) ([]accrd.AvailableCapacity, error)
 }
 
 // GetDriveCRByVolume reads drive CRs and returns CR for drive on which volume is located
-func (cs *CRHelper) GetDriveCRByVolume(volume *volumecrd.Volume) (*drivecrd.Drive, error) {
+func (cs *CRHelperImpl) GetDriveCRByVolume(volume *volumecrd.Volume) (*drivecrd.Drive, error) {
 	drive, _, err := cs.GetDriveCRAndLVGCRByVolume(volume)
 	return drive, err
 }
 
 // GetDriveCRAndLVGCRByVolume reads drive CRs and returns CR for drive and CR for lvg (if exist) on which volume is located
-func (cs *CRHelper) GetDriveCRAndLVGCRByVolume(volume *volumecrd.Volume) (*drivecrd.Drive, *lvgcrd.LogicalVolumeGroup, error) {
+func (cs *CRHelperImpl) GetDriveCRAndLVGCRByVolume(volume *volumecrd.Volume) (*drivecrd.Drive, *lvgcrd.LogicalVolumeGroup, error) {
 	ll := cs.log.WithFields(logrus.Fields{
 		"method": "GetDriveCRByVolume",
 		"volume": volume.Name,
@@ -388,7 +409,7 @@ func (cs *CRHelper) GetDriveCRAndLVGCRByVolume(volume *volumecrd.Volume) (*drive
 // GetVGNameByLVGCRName read LogicalVolumeGroup CR with name lvgCRName and returns LogicalVolumeGroup CR.Spec.Name
 // method is used for LogicalVolumeGroup based on system VG because system VG name != LogicalVolumeGroup CR name
 // in case of error returns empty string and error
-func (cs *CRHelper) GetVGNameByLVGCRName(lvgCRName string) (string, error) {
+func (cs *CRHelperImpl) GetVGNameByLVGCRName(lvgCRName string) (string, error) {
 	lvgCR := lvgcrd.LogicalVolumeGroup{}
 	if err := cs.reader.ReadCR(context.Background(), lvgCRName, "", &lvgCR); err != nil {
 		return "", err
@@ -399,7 +420,7 @@ func (cs *CRHelper) GetVGNameByLVGCRName(lvgCRName string) (string, error) {
 // GetLVGCRs collect LogicalVolumeGroup CRs that locate on node, use just node[0] element
 // if node isn't provided - return all volume CRs
 // if error occurs - return nil
-func (cs *CRHelper) GetLVGCRs(node ...string) ([]lvgcrd.LogicalVolumeGroup, error) {
+func (cs *CRHelperImpl) GetLVGCRs(node ...string) ([]lvgcrd.LogicalVolumeGroup, error) {
 	var (
 		lvgList = &lvgcrd.LogicalVolumeGroupList{}
 		err     error
@@ -425,7 +446,7 @@ func (cs *CRHelper) GetLVGCRs(node ...string) ([]lvgcrd.LogicalVolumeGroup, erro
 
 // UpdateVolumeCRSpec reads volume CR with name volName and update it's spec to newSpec
 // returns nil or error in case of error
-func (cs *CRHelper) UpdateVolumeCRSpec(volName string, namespace string, newSpec api.Volume) error {
+func (cs *CRHelperImpl) UpdateVolumeCRSpec(volName string, namespace string, newSpec api.Volume) error {
 	var (
 		volumeCR = &volumecrd.Volume{}
 		err      error
@@ -441,7 +462,7 @@ func (cs *CRHelper) UpdateVolumeCRSpec(volName string, namespace string, newSpec
 }
 
 // DeleteObjectByName read runtime.Object by its name and then delete it
-func (cs *CRHelper) DeleteObjectByName(ctx context.Context, name string, namespace string, obj k8sCl.Object) error {
+func (cs *CRHelperImpl) DeleteObjectByName(ctx context.Context, name string, namespace string, obj k8sCl.Object) error {
 	if err := cs.reader.ReadCR(ctx, name, namespace, obj); err != nil {
 		if k8sError.IsNotFound(err) {
 			return nil
@@ -454,7 +475,7 @@ func (cs *CRHelper) DeleteObjectByName(ctx context.Context, name string, namespa
 
 // UpdateVolumeOpStatus Update volume Operational status to opStatus
 // returns nil or error in case of error
-func (cs *CRHelper) UpdateVolumeOpStatus(ctx context.Context, volume *volumecrd.Volume, opStatus string) error {
+func (cs *CRHelperImpl) UpdateVolumeOpStatus(ctx context.Context, volume *volumecrd.Volume, opStatus string) error {
 	ll := cs.log.WithFields(logrus.Fields{
 		"method":   "UpdateVolumeOpStatus",
 		"volume":   volume.Name,
diff --git a/pkg/common/volume_operations.go b/pkg/common/volume_operations.go
index ab4c6b9..163dfd9 100644
--- a/pkg/common/volume_operations.go
+++ b/pkg/common/volume_operations.go
@@ -61,7 +61,7 @@ type VolumeOperationsImpl struct {
 	acProvider             AvailableCapacityOperations
 	k8sClient              *k8s.KubeClient
 	capacityManagerBuilder capacityplanner.CapacityManagerBuilder
-	crHelper               *k8s.CRHelper
+	crHelper               k8s.CRHelper
 
 	metrics        metrics.Statistic
 	cache          cache.Interface
@@ -91,7 +91,7 @@ func NewVolumeOperationsImpl(k8sClient *k8s.KubeClient, logger *logrus.Logger, c
 	}
 	vo := &VolumeOperationsImpl{
 		k8sClient:              k8sClient,
-		crHelper:               k8s.NewCRHelper(k8sClient, logger),
+		crHelper:               k8s.NewCRHelperImpl(k8sClient, logger),
 		acProvider:             NewACOperationsImpl(k8sClient, logger),
 		log:                    logger.WithField("component", "VolumeOperationsImpl"),
 		featureChecker:         featureConf,
diff --git a/pkg/controller/capacitycontroller/capacitycontroller.go b/pkg/controller/capacitycontroller/capacitycontroller.go
index 280c592..0e0faeb 100644
--- a/pkg/controller/capacitycontroller/capacitycontroller.go
+++ b/pkg/controller/capacitycontroller/capacitycontroller.go
@@ -35,9 +35,9 @@ const RequeueDriveTime = time.Second * 30
 // Controller reconciles drive custom resource
 type Controller struct {
 	client   *k8s.KubeClient
-	crHelper *k8s.CRHelper
+	crHelper k8s.CRHelper
 	// CRHelper instance which reads from cache
-	cachedCrHelper *k8s.CRHelper
+	cachedCrHelper k8s.CRHelper
 	log            *logrus.Entry
 }
 
@@ -47,8 +47,8 @@ type Controller struct {
 func NewCapacityController(client *k8s.KubeClient, k8sCache k8s.CRReader, log *logrus.Logger) *Controller {
 	return &Controller{
 		client:         client,
-		crHelper:       k8s.NewCRHelper(client, log),
-		cachedCrHelper: k8s.NewCRHelper(client, log).SetReader(k8sCache),
+		crHelper:       k8s.NewCRHelperImpl(client, log),
+		cachedCrHelper: k8s.NewCRHelperImpl(client, log).SetReader(k8sCache),
 		log:            log.WithField("component", "Controller"),
 	}
 }
diff --git a/pkg/controller/controller.go b/pkg/controller/controller.go
index 733d004..020155f 100644
--- a/pkg/controller/controller.go
+++ b/pkg/controller/controller.go
@@ -70,7 +70,7 @@ type CSIControllerService struct {
 
 	ready bool
 
-	crHelper *k8s.CRHelper
+	crHelper k8s.CRHelper
 
 	csi.IdentityServer
 	grpc_health_v1.HealthServer
@@ -87,7 +87,7 @@ func NewControllerService(k8sClient *k8s.KubeClient, logger *logrus.Logger,
 		svc:                      common.NewVolumeOperationsImpl(k8sClient, logger, cache.NewMemCache(), featureConf),
 		nodeServicesStateMonitor: node.NewNodeServicesStateMonitor(k8sClient, logger),
 		IdentityServer:           NewIdentityServer(base.PluginName, base.PluginVersion),
-		crHelper:                 k8s.NewCRHelper(k8sClient, logger),
+		crHelper:                 k8s.NewCRHelperImpl(k8sClient, logger),
 		reqLock:                  lock.NewCASMutex(),
 	}
 
diff --git a/pkg/controller/node/statemonitor.go b/pkg/controller/node/statemonitor.go
index dab161e..593cb29 100644
--- a/pkg/controller/node/statemonitor.go
+++ b/pkg/controller/node/statemonitor.go
@@ -62,7 +62,7 @@ type ServicesStateMonitor struct {
 	// logger. Can't we initialize it inside?
 	log *logrus.Entry
 	// helper to work with custom resource definition
-	crHelper *k8s.CRHelper
+	crHelper k8s.CRHelper
 	// nodeX ID -> <Ready/Unready/PermanentDown>
 	nodeHealthMap map[string]*serviceState
 	// mutex to protect map access
@@ -87,7 +87,7 @@ func NewNodeServicesStateMonitor(client *k8s.KubeClient, logger *logrus.Logger)
 	return &ServicesStateMonitor{
 		client:        client,
 		log:           logger.WithField("component", "ServicesStateMonitor"),
-		crHelper:      k8s.NewCRHelper(client, logger),
+		crHelper:      k8s.NewCRHelperImpl(client, logger),
 		nodeHealthMap: make(map[string]*serviceState),
 		lock:          &sync.RWMutex{},
 	}
diff --git a/pkg/crcontrollers/drive/drivecontroller.go b/pkg/crcontrollers/drive/drivecontroller.go
index a974dc9..e5cb0d2 100644
--- a/pkg/crcontrollers/drive/drivecontroller.go
+++ b/pkg/crcontrollers/drive/drivecontroller.go
@@ -27,7 +27,7 @@ import (
 // Controller to reconcile drive custom resource
 type Controller struct {
 	client         *k8s.KubeClient
-	crHelper       *k8s.CRHelper
+	crHelper       k8s.CRHelper
 	nodeID         string
 	driveMgrClient api.DriveServiceClient
 	eventRecorder  *events.Recorder
@@ -57,7 +57,7 @@ const (
 func NewController(client *k8s.KubeClient, nodeID string, serviceClient api.DriveServiceClient, eventRecorder *events.Recorder, log *logrus.Logger) *Controller {
 	return &Controller{
 		client:         client,
-		crHelper:       k8s.NewCRHelper(client, log),
+		crHelper:       k8s.NewCRHelperImpl(client, log),
 		nodeID:         nodeID,
 		driveMgrClient: serviceClient,
 		eventRecorder:  eventRecorder,
diff --git a/pkg/crcontrollers/lvg/lvgcontroller.go b/pkg/crcontrollers/lvg/lvgcontroller.go
index d990b51..daf2dba 100644
--- a/pkg/crcontrollers/lvg/lvgcontroller.go
+++ b/pkg/crcontrollers/lvg/lvgcontroller.go
@@ -51,7 +51,7 @@ const (
 // Controller is the LogicalVolumeGroup custom resource Controller for serving VG operations on Node side in Reconcile loop
 type Controller struct {
 	k8sClient *k8s.KubeClient
-	crHelper  *k8s.CRHelper
+	crHelper  k8s.CRHelper
 
 	listBlk lsblk.WrapLsblk
 	lvmOps  lvm.WrapLVM
@@ -68,7 +68,7 @@ func NewController(k8sClient *k8s.KubeClient, nodeID string, log *logrus.Logger)
 	e := command.NewExecutor(log)
 	return &Controller{
 		k8sClient: k8sClient,
-		crHelper:  k8s.NewCRHelper(k8sClient, log),
+		crHelper:  k8s.NewCRHelperImpl(k8sClient, log),
 		node:      nodeID,
 		log:       log.WithField("component", "Controller"),
 		e:         e,
diff --git a/pkg/drivemgr/loopbackmgr/Dockerfile.build b/pkg/drivemgr/loopbackmgr/Dockerfile.build
index 3895a3f..1a07173 100644
--- a/pkg/drivemgr/loopbackmgr/Dockerfile.build
+++ b/pkg/drivemgr/loopbackmgr/Dockerfile.build
@@ -1,5 +1,7 @@
 FROM    ubuntu:21.04
 
+RUN     sed -i -re 's/archive.ubuntu.com|security.ubuntu.com/old-releases.ubuntu.com/g' /etc/apt/sources.list \
+&&      sed -i -re 's/deb|deb-src/& \[trusted=yes\]/' /etc/apt/sources.list
 # Remove bash packet to get rid of related CVEs
 RUN     apt update --no-install-recommends -y -q && apt remove --no-install-recommends -y --allow-remove-essential -q bash
 
diff --git a/pkg/mocks/cr_helper.go b/pkg/mocks/cr_helper.go
new file mode 100644
index 0000000..82750e2
--- /dev/null
+++ b/pkg/mocks/cr_helper.go
@@ -0,0 +1,411 @@
+// Code generated by mockery v2.9.4. DO NOT EDIT.
+
+package mocks
+
+import (
+	accrd "github.com/dell/csi-baremetal/api/v1/availablecapacitycrd"
+	client "sigs.k8s.io/controller-runtime/pkg/client"
+
+	context "context"
+
+	drivecrd "github.com/dell/csi-baremetal/api/v1/drivecrd"
+
+	k8s "github.com/dell/csi-baremetal/pkg/base/k8s"
+
+	lvgcrd "github.com/dell/csi-baremetal/api/v1/lvgcrd"
+
+	mock "github.com/stretchr/testify/mock"
+
+	v1api "github.com/dell/csi-baremetal/api/generated/v1"
+
+	volumecrd "github.com/dell/csi-baremetal/api/v1/volumecrd"
+)
+
+// CRHelper is an autogenerated mock type for the CRHelper type
+type CRHelper struct {
+	mock.Mock
+}
+
+// DeleteACsByNodeID provides a mock function with given fields: nodeID
+func (_m *CRHelper) DeleteACsByNodeID(nodeID string) error {
+	ret := _m.Called(nodeID)
+
+	var r0 error
+	if rf, ok := ret.Get(0).(func(string) error); ok {
+		r0 = rf(nodeID)
+	} else {
+		r0 = ret.Error(0)
+	}
+
+	return r0
+}
+
+// DeleteObjectByName provides a mock function with given fields: ctx, name, namespace, obj
+func (_m *CRHelper) DeleteObjectByName(ctx context.Context, name string, namespace string, obj client.Object) error {
+	ret := _m.Called(ctx, name, namespace, obj)
+
+	var r0 error
+	if rf, ok := ret.Get(0).(func(context.Context, string, string, client.Object) error); ok {
+		r0 = rf(ctx, name, namespace, obj)
+	} else {
+		r0 = ret.Error(0)
+	}
+
+	return r0
+}
+
+// GetACByLocation provides a mock function with given fields: location
+func (_m *CRHelper) GetACByLocation(location string) (*accrd.AvailableCapacity, error) {
+	ret := _m.Called(location)
+
+	var r0 *accrd.AvailableCapacity
+	if rf, ok := ret.Get(0).(func(string) *accrd.AvailableCapacity); ok {
+		r0 = rf(location)
+	} else {
+		if ret.Get(0) != nil {
+			r0 = ret.Get(0).(*accrd.AvailableCapacity)
+		}
+	}
+
+	var r1 error
+	if rf, ok := ret.Get(1).(func(string) error); ok {
+		r1 = rf(location)
+	} else {
+		r1 = ret.Error(1)
+	}
+
+	return r0, r1
+}
+
+// GetACCRs provides a mock function with given fields: node
+func (_m *CRHelper) GetACCRs(node ...string) ([]accrd.AvailableCapacity, error) {
+	_va := make([]interface{}, len(node))
+	for _i := range node {
+		_va[_i] = node[_i]
+	}
+	var _ca []interface{}
+	_ca = append(_ca, _va...)
+	ret := _m.Called(_ca...)
+
+	var r0 []accrd.AvailableCapacity
+	if rf, ok := ret.Get(0).(func(...string) []accrd.AvailableCapacity); ok {
+		r0 = rf(node...)
+	} else {
+		if ret.Get(0) != nil {
+			r0 = ret.Get(0).([]accrd.AvailableCapacity)
+		}
+	}
+
+	var r1 error
+	if rf, ok := ret.Get(1).(func(...string) error); ok {
+		r1 = rf(node...)
+	} else {
+		r1 = ret.Error(1)
+	}
+
+	return r0, r1
+}
+
+// GetDriveCRAndLVGCRByVolume provides a mock function with given fields: volume
+func (_m *CRHelper) GetDriveCRAndLVGCRByVolume(volume *volumecrd.Volume) (*drivecrd.Drive, *lvgcrd.LogicalVolumeGroup, error) {
+	ret := _m.Called(volume)
+
+	var r0 *drivecrd.Drive
+	if rf, ok := ret.Get(0).(func(*volumecrd.Volume) *drivecrd.Drive); ok {
+		r0 = rf(volume)
+	} else {
+		if ret.Get(0) != nil {
+			r0 = ret.Get(0).(*drivecrd.Drive)
+		}
+	}
+
+	var r1 *lvgcrd.LogicalVolumeGroup
+	if rf, ok := ret.Get(1).(func(*volumecrd.Volume) *lvgcrd.LogicalVolumeGroup); ok {
+		r1 = rf(volume)
+	} else {
+		if ret.Get(1) != nil {
+			r1 = ret.Get(1).(*lvgcrd.LogicalVolumeGroup)
+		}
+	}
+
+	var r2 error
+	if rf, ok := ret.Get(2).(func(*volumecrd.Volume) error); ok {
+		r2 = rf(volume)
+	} else {
+		r2 = ret.Error(2)
+	}
+
+	return r0, r1, r2
+}
+
+// GetDriveCRByVolume provides a mock function with given fields: volume
+func (_m *CRHelper) GetDriveCRByVolume(volume *volumecrd.Volume) (*drivecrd.Drive, error) {
+	ret := _m.Called(volume)
+
+	var r0 *drivecrd.Drive
+	if rf, ok := ret.Get(0).(func(*volumecrd.Volume) *drivecrd.Drive); ok {
+		r0 = rf(volume)
+	} else {
+		if ret.Get(0) != nil {
+			r0 = ret.Get(0).(*drivecrd.Drive)
+		}
+	}
+
+	var r1 error
+	if rf, ok := ret.Get(1).(func(*volumecrd.Volume) error); ok {
+		r1 = rf(volume)
+	} else {
+		r1 = ret.Error(1)
+	}
+
+	return r0, r1
+}
+
+// GetDriveCRs provides a mock function with given fields: node
+func (_m *CRHelper) GetDriveCRs(node ...string) ([]drivecrd.Drive, error) {
+	_va := make([]interface{}, len(node))
+	for _i := range node {
+		_va[_i] = node[_i]
+	}
+	var _ca []interface{}
+	_ca = append(_ca, _va...)
+	ret := _m.Called(_ca...)
+
+	var r0 []drivecrd.Drive
+	if rf, ok := ret.Get(0).(func(...string) []drivecrd.Drive); ok {
+		r0 = rf(node...)
+	} else {
+		if ret.Get(0) != nil {
+			r0 = ret.Get(0).([]drivecrd.Drive)
+		}
+	}
+
+	var r1 error
+	if rf, ok := ret.Get(1).(func(...string) error); ok {
+		r1 = rf(node...)
+	} else {
+		r1 = ret.Error(1)
+	}
+
+	return r0, r1
+}
+
+// GetLVGByDrive provides a mock function with given fields: ctx, driveUUID
+func (_m *CRHelper) GetLVGByDrive(ctx context.Context, driveUUID string) (*lvgcrd.LogicalVolumeGroup, error) {
+	ret := _m.Called(ctx, driveUUID)
+
+	var r0 *lvgcrd.LogicalVolumeGroup
+	if rf, ok := ret.Get(0).(func(context.Context, string) *lvgcrd.LogicalVolumeGroup); ok {
+		r0 = rf(ctx, driveUUID)
+	} else {
+		if ret.Get(0) != nil {
+			r0 = ret.Get(0).(*lvgcrd.LogicalVolumeGroup)
+		}
+	}
+
+	var r1 error
+	if rf, ok := ret.Get(1).(func(context.Context, string) error); ok {
+		r1 = rf(ctx, driveUUID)
+	} else {
+		r1 = ret.Error(1)
+	}
+
+	return r0, r1
+}
+
+// GetLVGCRs provides a mock function with given fields: node
+func (_m *CRHelper) GetLVGCRs(node ...string) ([]lvgcrd.LogicalVolumeGroup, error) {
+	_va := make([]interface{}, len(node))
+	for _i := range node {
+		_va[_i] = node[_i]
+	}
+	var _ca []interface{}
+	_ca = append(_ca, _va...)
+	ret := _m.Called(_ca...)
+
+	var r0 []lvgcrd.LogicalVolumeGroup
+	if rf, ok := ret.Get(0).(func(...string) []lvgcrd.LogicalVolumeGroup); ok {
+		r0 = rf(node...)
+	} else {
+		if ret.Get(0) != nil {
+			r0 = ret.Get(0).([]lvgcrd.LogicalVolumeGroup)
+		}
+	}
+
+	var r1 error
+	if rf, ok := ret.Get(1).(func(...string) error); ok {
+		r1 = rf(node...)
+	} else {
+		r1 = ret.Error(1)
+	}
+
+	return r0, r1
+}
+
+// GetVGNameByLVGCRName provides a mock function with given fields: lvgCRName
+func (_m *CRHelper) GetVGNameByLVGCRName(lvgCRName string) (string, error) {
+	ret := _m.Called(lvgCRName)
+
+	var r0 string
+	if rf, ok := ret.Get(0).(func(string) string); ok {
+		r0 = rf(lvgCRName)
+	} else {
+		r0 = ret.Get(0).(string)
+	}
+
+	var r1 error
+	if rf, ok := ret.Get(1).(func(string) error); ok {
+		r1 = rf(lvgCRName)
+	} else {
+		r1 = ret.Error(1)
+	}
+
+	return r0, r1
+}
+
+// GetVolumeByID provides a mock function with given fields: volID
+func (_m *CRHelper) GetVolumeByID(volID string) (*volumecrd.Volume, error) {
+	ret := _m.Called(volID)
+
+	var r0 *volumecrd.Volume
+	if rf, ok := ret.Get(0).(func(string) *volumecrd.Volume); ok {
+		r0 = rf(volID)
+	} else {
+		if ret.Get(0) != nil {
+			r0 = ret.Get(0).(*volumecrd.Volume)
+		}
+	}
+
+	var r1 error
+	if rf, ok := ret.Get(1).(func(string) error); ok {
+		r1 = rf(volID)
+	} else {
+		r1 = ret.Error(1)
+	}
+
+	return r0, r1
+}
+
+// GetVolumeCRs provides a mock function with given fields: node
+func (_m *CRHelper) GetVolumeCRs(node ...string) ([]volumecrd.Volume, error) {
+	_va := make([]interface{}, len(node))
+	for _i := range node {
+		_va[_i] = node[_i]
+	}
+	var _ca []interface{}
+	_ca = append(_ca, _va...)
+	ret := _m.Called(_ca...)
+
+	var r0 []volumecrd.Volume
+	if rf, ok := ret.Get(0).(func(...string) []volumecrd.Volume); ok {
+		r0 = rf(node...)
+	} else {
+		if ret.Get(0) != nil {
+			r0 = ret.Get(0).([]volumecrd.Volume)
+		}
+	}
+
+	var r1 error
+	if rf, ok := ret.Get(1).(func(...string) error); ok {
+		r1 = rf(node...)
+	} else {
+		r1 = ret.Error(1)
+	}
+
+	return r0, r1
+}
+
+// GetVolumesByLocation provides a mock function with given fields: ctx, location
+func (_m *CRHelper) GetVolumesByLocation(ctx context.Context, location string) ([]*volumecrd.Volume, error) {
+	ret := _m.Called(ctx, location)
+
+	var r0 []*volumecrd.Volume
+	if rf, ok := ret.Get(0).(func(context.Context, string) []*volumecrd.Volume); ok {
+		r0 = rf(ctx, location)
+	} else {
+		if ret.Get(0) != nil {
+			r0 = ret.Get(0).([]*volumecrd.Volume)
+		}
+	}
+
+	var r1 error
+	if rf, ok := ret.Get(1).(func(context.Context, string) error); ok {
+		r1 = rf(ctx, location)
+	} else {
+		r1 = ret.Error(1)
+	}
+
+	return r0, r1
+}
+
+// SetReader provides a mock function with given fields: reader
+func (_m *CRHelper) SetReader(reader k8s.CRReader) k8s.CRHelper {
+	ret := _m.Called(reader)
+
+	var r0 k8s.CRHelper
+	if rf, ok := ret.Get(0).(func(k8s.CRReader) k8s.CRHelper); ok {
+		r0 = rf(reader)
+	} else {
+		if ret.Get(0) != nil {
+			r0 = ret.Get(0).(k8s.CRHelper)
+		}
+	}
+
+	return r0
+}
+
+// UpdateDrivesStatusOnNode provides a mock function with given fields: nodeID, status
+func (_m *CRHelper) UpdateDrivesStatusOnNode(nodeID string, status string) error {
+	ret := _m.Called(nodeID, status)
+
+	var r0 error
+	if rf, ok := ret.Get(0).(func(string, string) error); ok {
+		r0 = rf(nodeID, status)
+	} else {
+		r0 = ret.Error(0)
+	}
+
+	return r0
+}
+
+// UpdateVolumeCRSpec provides a mock function with given fields: volName, namespace, newSpec
+func (_m *CRHelper) UpdateVolumeCRSpec(volName string, namespace string, newSpec v1api.Volume) error {
+	ret := _m.Called(volName, namespace, newSpec)
+
+	var r0 error
+	if rf, ok := ret.Get(0).(func(string, string, v1api.Volume) error); ok {
+		r0 = rf(volName, namespace, newSpec)
+	} else {
+		r0 = ret.Error(0)
+	}
+
+	return r0
+}
+
+// UpdateVolumeOpStatus provides a mock function with given fields: ctx, volume, opStatus
+func (_m *CRHelper) UpdateVolumeOpStatus(ctx context.Context, volume *volumecrd.Volume, opStatus string) error {
+	ret := _m.Called(ctx, volume, opStatus)
+
+	var r0 error
+	if rf, ok := ret.Get(0).(func(context.Context, *volumecrd.Volume, string) error); ok {
+		r0 = rf(ctx, volume, opStatus)
+	} else {
+		r0 = ret.Error(0)
+	}
+
+	return r0
+}
+
+// UpdateVolumesOpStatusOnNode provides a mock function with given fields: nodeID, opStatus
+func (_m *CRHelper) UpdateVolumesOpStatusOnNode(nodeID string, opStatus string) error {
+	ret := _m.Called(nodeID, opStatus)
+
+	var r0 error
+	if rf, ok := ret.Get(0).(func(string, string) error); ok {
+		r0 = rf(nodeID, opStatus)
+	} else {
+		r0 = ret.Error(0)
+	}
+
+	return r0
+}
diff --git a/pkg/node/node.go b/pkg/node/node.go
index 3b14b63..97c8e4b 100644
--- a/pkg/node/node.go
+++ b/pkg/node/node.go
@@ -292,9 +292,13 @@ func (s *CSINodeService) NodeUnstageVolume(ctx context.Context, req *csi.NodeUns
 	volumeID := req.GetVolumeId()
 	volumeCR, err := s.crHelper.GetVolumeByID(volumeID)
 	if err != nil {
-		message := fmt.Sprintf("Unable to find volume with ID %s", volumeID)
-		ll.Error(message)
-		return nil, status.Error(codes.NotFound, message)
+		// If NodeUnstageVolume return error for not found Volume CR
+		// kubelet will be call NodeUnstageVolume again, it means infinity calls
+		if err == baseerr.ErrorNotFound {
+			return &csi.NodeUnstageVolumeResponse{}, nil
+		}
+		message := fmt.Sprintf("Unable to find volume with ID %s: %s", volumeID, err.Error())
+		return nil, status.Error(codes.Internal, message)
 	}
 
 	currStatus := volumeCR.Spec.CSIStatus
diff --git a/pkg/node/provisioners/drive_provisioner.go b/pkg/node/provisioners/drive_provisioner.go
index f4501f2..90b7cd6 100644
--- a/pkg/node/provisioners/drive_provisioner.go
+++ b/pkg/node/provisioners/drive_provisioner.go
@@ -53,7 +53,7 @@ type DriveProvisioner struct {
 	partOps uw.PartitionOperations
 
 	k8sClient *k8s.KubeClient
-	crHelper  *k8s.CRHelper
+	crHelper  k8s.CRHelper
 
 	log *logrus.Entry
 }
@@ -68,7 +68,7 @@ func NewDriveProvisioner(
 		fsOps:     uw.NewFSOperationsImpl(e, log),
 		partOps:   uw.NewPartitionOperationsImpl(e, log),
 		k8sClient: k,
-		crHelper:  k8s.NewCRHelper(k, log),
+		crHelper:  k8s.NewCRHelperImpl(k, log),
 		log:       log.WithField("component", "DriveProvisioner"),
 	}
 }
diff --git a/pkg/node/provisioners/lvm_provisioner.go b/pkg/node/provisioners/lvm_provisioner.go
index 13f0d02..1d93167 100644
--- a/pkg/node/provisioners/lvm_provisioner.go
+++ b/pkg/node/provisioners/lvm_provisioner.go
@@ -37,7 +37,7 @@ import (
 type LVMProvisioner struct {
 	lvmOps   lvm.WrapLVM
 	fsOps    uw.FSOperations
-	crHelper *k8s.CRHelper
+	crHelper k8s.CRHelper
 	log      *logrus.Entry
 }
 
@@ -46,7 +46,7 @@ func NewLVMProvisioner(e command.CmdExecutor, k *k8s.KubeClient, log *logrus.Log
 	return &LVMProvisioner{
 		lvmOps:   lvm.NewLVM(e, log),
 		fsOps:    uw.NewFSOperationsImpl(e, log),
-		crHelper: k8s.NewCRHelper(k, log),
+		crHelper: k8s.NewCRHelperImpl(k, log),
 		log:      log.WithField("component", "LVMProvisioner"),
 	}
 }
diff --git a/pkg/node/volumemgr.go b/pkg/node/volumemgr.go
index 360ef67..68f06f7 100644
--- a/pkg/node/volumemgr.go
+++ b/pkg/node/volumemgr.go
@@ -88,9 +88,9 @@ type VolumeManager struct {
 	// cache for kubernetes resources
 	k8sCache k8s.CRReader
 	// help to read/update particular CR
-	crHelper *k8s.CRHelper
+	crHelper k8s.CRHelper
 	// CRHelper instance which reads from cache
-	cachedCrHelper *k8s.CRHelper
+	cachedCrHelper k8s.CRHelper
 
 	// uses for communicating with hardware manager
 	driveMgrClient api.DriveServiceClient
@@ -212,8 +212,8 @@ func NewVolumeManager(
 	vm := &VolumeManager{
 		k8sClient:      k8sClient,
 		k8sCache:       k8sCache,
-		crHelper:       k8s.NewCRHelper(k8sClient, logger),
-		cachedCrHelper: k8s.NewCRHelper(k8sClient, logger).SetReader(k8sCache),
+		crHelper:       k8s.NewCRHelperImpl(k8sClient, logger),
+		cachedCrHelper: k8s.NewCRHelperImpl(k8sClient, logger).SetReader(k8sCache),
 		driveMgrClient: client,
 		acProvider:     common.NewACOperationsImpl(k8sClient, logger),
 		provisioners: map[p.VolumeType]p.Provisioner{


Test patch:
diff --git a/pkg/base/k8s/cr_helper_test.go b/pkg/base/k8s/cr_helper_test.go
index e003662..e57fac0 100644
--- a/pkg/base/k8s/cr_helper_test.go
+++ b/pkg/base/k8s/cr_helper_test.go
@@ -28,12 +28,12 @@ import (
 	errTypes "github.com/dell/csi-baremetal/pkg/base/error"
 )
 
-func setup() *CRHelper {
+func setup() *CRHelperImpl {
 	k, err := GetFakeKubeClient(testNs, testLogger)
 	if err != nil {
 		panic(err)
 	}
-	return NewCRHelper(k, testLogger)
+	return NewCRHelperImpl(k, testLogger)
 }
 
 func TestCRHelper_GetACByLocation(t *testing.T) {
diff --git a/pkg/node/node_test.go b/pkg/node/node_test.go
index 5c92e50..00b4e08 100644
--- a/pkg/node/node_test.go
+++ b/pkg/node/node_test.go
@@ -20,6 +20,7 @@ import (
 	"errors"
 	"fmt"
 	baseerr "github.com/dell/csi-baremetal/pkg/base/error"
+	mockProv "github.com/dell/csi-baremetal/pkg/mocks/provisioners"
 	"path"
 	"testing"
 	"time"
@@ -42,7 +43,6 @@ import (
 	csibmnodeconst "github.com/dell/csi-baremetal/pkg/crcontrollers/node/common"
 	"github.com/dell/csi-baremetal/pkg/mocks"
 	mocklu "github.com/dell/csi-baremetal/pkg/mocks/linuxutils"
-	mockProv "github.com/dell/csi-baremetal/pkg/mocks/provisioners"
 	p "github.com/dell/csi-baremetal/pkg/node/provisioners"
 	wbtcommon "github.com/dell/csi-baremetal/pkg/node/wbt/common"
 )
@@ -473,13 +473,24 @@ var _ = Describe("CSINodeService NodeUnStage()", func() {
 			Expect(err).NotTo(BeNil())
 			Expect(err.Error()).To(ContainSubstring("Stage Path missing in request"))
 		})
-		It("Should fail because Volume CR wasn't found", func() {
+		It("Shouldn't fail if Volume CR wasn't found", func() {
 			req := getNodeUnstageRequest("sone-none-existing-UUID", stagePath)
 			resp, err := node.NodeUnstageVolume(testCtx, req)
 
-			Expect(resp).To(BeNil())
+			Expect(err).To(BeNil())
+			Expect(resp).To(Equal(&csi.NodeUnstageVolumeResponse{}))
+		})
+		It("Should fail if Volume CR is unavailable by internal error", func() {
+			crHelper := &mocks.CRHelper{}
+			node.crHelper = crHelper
+			crHelper.On("GetVolumeByID", "internal-error-UUID").
+				Return(nil, errors.New("internal-error"))
+
+			req := getNodeUnstageRequest("internal-error-UUID", stagePath)
+			resp, err := node.NodeUnstageVolume(testCtx, req)
+
 			Expect(err).NotTo(BeNil())
-			Expect(status.Code(err)).To(Equal(codes.NotFound))
+			Expect(resp).To(BeNil())
 		})
 		It("Should fail with UnmountWithCheck() error", func() {
 			req := getNodeUnstageRequest(testV1ID, stagePath)