Skip to content

Commit 5a3b206

Browse files
committed
add mock for azcopy ut
1 parent 8e21965 commit 5a3b206

11 files changed

+552
-194
lines changed
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
/*
2+
Copyright The Kubernetes Authors.
3+
4+
Licensed under the Apache License, Version 2.0 (the "License");
5+
you may not use this file except in compliance with the License.
6+
You may obtain a copy of the License at
7+
8+
http://www.apache.org/licenses/LICENSE-2.0
9+
10+
Unless required by applicable law or agreed to in writing, software
11+
distributed under the License is distributed on an "AS IS" BASIS,
12+
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
See the License for the specific language governing permissions and
14+
limitations under the License.
15+
*/
+15
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
// /*
2+
// Copyright The Kubernetes Authors.
3+
//
4+
// Licensed under the Apache License, Version 2.0 (the "License");
5+
// you may not use this file except in compliance with the License.
6+
// You may obtain a copy of the License at
7+
//
8+
// http://www.apache.org/licenses/LICENSE-2.0
9+
//
10+
// Unless required by applicable law or agreed to in writing, software
11+
// distributed under the License is distributed on an "AS IS" BASIS,
12+
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
// See the License for the specific language governing permissions and
14+
// limitations under the License.
15+
// */

hack/update-mock.sh

+28
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
#!/bin/bash
2+
3+
# Copyright 2020 The Kubernetes Authors.
4+
#
5+
# Licensed under the Apache License, Version 2.0 (the "License");
6+
# you may not use this file except in compliance with the License.
7+
# You may obtain a copy of the License at
8+
#
9+
# http://www.apache.org/licenses/LICENSE-2.0
10+
#
11+
# Unless required by applicable law or agreed to in writing, software
12+
# distributed under the License is distributed on an "AS IS" BASIS,
13+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14+
# See the License for the specific language governing permissions and
15+
# limitations under the License.
16+
17+
set -euo pipefail
18+
19+
REPO_ROOT=$(realpath $(dirname ${BASH_SOURCE})/..)
20+
COPYRIGHT_FILE="${REPO_ROOT}/hack/boilerplate/boilerplate.generatego.txt"
21+
22+
if ! type mockgen &> /dev/null; then
23+
echo "mockgen not exist, install it"
24+
go install github.com/golang/mock/[email protected]
25+
fi
26+
27+
echo "Updating mocks for util.go"
28+
mockgen -copyright_file=$COPYRIGHT_FILE -source=pkg/util/util.go -package=util -destination=pkg/util/util_mock.go

pkg/azurefile/azurefile.go

+10-5
Original file line numberDiff line numberDiff line change
@@ -46,6 +46,7 @@ import (
4646

4747
csicommon "sigs.k8s.io/azurefile-csi-driver/pkg/csi-common"
4848
"sigs.k8s.io/azurefile-csi-driver/pkg/mounter"
49+
fileutil "sigs.k8s.io/azurefile-csi-driver/pkg/util"
4950
"sigs.k8s.io/cloud-provider-azure/pkg/azureclients/fileclient"
5051
azcache "sigs.k8s.io/cloud-provider-azure/pkg/cache"
5152
azure "sigs.k8s.io/cloud-provider-azure/pkg/provider"
@@ -269,6 +270,8 @@ type Driver struct {
269270
volStatsCache azcache.Resource
270271
// sas expiry time for azcopy in volume clone
271272
sasTokenExpirationMinutes int
273+
// azcopyFunc for provide exec mock for ut
274+
azcopyFunc *fileutil.AzcopyFunc
272275
}
273276

274277
// NewDriver Creates a NewCSIDriver object. Assumes vendor version is equal to driver version &
@@ -300,6 +303,7 @@ func NewDriver(options *DriverOptions) *Driver {
300303
driver.volLockMap = newLockMap()
301304
driver.subnetLockMap = newLockMap()
302305
driver.volumeLocks = newVolumeLocks()
306+
driver.azcopyFunc = &fileutil.AzcopyFunc{}
303307

304308
var err error
305309
getter := func(key string) (interface{}, error) { return nil, nil }
@@ -928,21 +932,22 @@ func (d *Driver) copyFileShare(ctx context.Context, req *csi.CreateVolumeRequest
928932
srcPath := fmt.Sprintf("https://%s.file.%s/%s%s", accountName, storageEndpointSuffix, srcFileShareName, accountSasToken)
929933
dstPath := fmt.Sprintf("https://%s.file.%s/%s%s", accountName, storageEndpointSuffix, dstFileShareName, accountSasToken)
930934

931-
jobState, percent, err := getAzcopyJob(dstFileShareName)
935+
aFunc := d.azcopyFunc
936+
jobState, percent, err := aFunc.GetAzcopyJob(dstFileShareName)
932937
klog.V(2).Infof("azcopy job status: %s, copy percent: %s%%, error: %v", jobState, percent, err)
933-
if jobState == AzcopyJobError || jobState == AzcopyJobCompleted {
938+
if jobState == fileutil.AzcopyJobError || jobState == fileutil.AzcopyJobCompleted {
934939
return err
935940
}
936941
klog.V(2).Infof("begin to copy fileshare %s to %s", srcFileShareName, dstFileShareName)
937942
for {
938943
select {
939944
case <-timeTick:
940-
jobState, percent, err := getAzcopyJob(dstFileShareName)
945+
jobState, percent, err := aFunc.GetAzcopyJob(dstFileShareName)
941946
klog.V(2).Infof("azcopy job status: %s, copy percent: %s%%, error: %v", jobState, percent, err)
942947
switch jobState {
943-
case AzcopyJobError, AzcopyJobCompleted:
948+
case fileutil.AzcopyJobError, fileutil.AzcopyJobCompleted:
944949
return err
945-
case AzcopyJobNotFound:
950+
case fileutil.AzcopyJobNotFound:
946951
klog.V(2).Infof("copy fileshare %s to %s", srcFileShareName, dstFileShareName)
947952
out, copyErr := exec.Command("azcopy", "copy", srcPath, dstPath, "--recursive", "--check-length=false").CombinedOutput()
948953
if copyErr != nil {

pkg/azurefile/controllerserver.go

+2-1
Original file line numberDiff line numberDiff line change
@@ -108,7 +108,8 @@ func (d *Driver) CreateVolume(ctx context.Context, req *csi.CreateVolumeRequest)
108108
if acquired := d.volumeLocks.TryAcquire(volName); !acquired {
109109
// logging the job status if it's volume cloning
110110
if req.GetVolumeContentSource() != nil {
111-
jobState, percent, err := getAzcopyJob(volName)
111+
aFunc := d.azcopyFunc
112+
jobState, percent, err := aFunc.GetAzcopyJob(volName)
112113
klog.V(2).Infof("azcopy job status: %s, copy percent: %s%%, error: %v", jobState, percent, err)
113114
}
114115
return nil, status.Errorf(codes.Aborted, volumeOperationAlreadyExistsFmt, volName)

pkg/azurefile/controllerserver_test.go

+92-1
Original file line numberDiff line numberDiff line change
@@ -23,12 +23,14 @@ import (
2323
"net/http"
2424
"net/url"
2525
"reflect"
26-
"sigs.k8s.io/cloud-provider-azure/pkg/azureclients/fileclient"
2726
"strings"
2827
"sync"
2928
"testing"
3029
"time"
3130

31+
"sigs.k8s.io/azurefile-csi-driver/pkg/util"
32+
"sigs.k8s.io/cloud-provider-azure/pkg/azureclients/fileclient"
33+
3234
"github.com/Azure/azure-sdk-for-go/services/network/mgmt/2022-07-01/network"
3335
"sigs.k8s.io/cloud-provider-azure/pkg/azureclients/subnetclient/mocksubnetclient"
3436
azcache "sigs.k8s.io/cloud-provider-azure/pkg/cache"
@@ -1783,6 +1785,95 @@ func TestCopyVolume(t *testing.T) {
17831785
}
17841786
},
17851787
},
1788+
{
1789+
name: "azcopy job is already completed",
1790+
testFunc: func(t *testing.T) {
1791+
d := NewFakeDriver()
1792+
mp := map[string]string{}
1793+
1794+
volumeSource := &csi.VolumeContentSource_VolumeSource{
1795+
VolumeId: "vol_1#f5713de20cde511e8ba4900#fileshare#",
1796+
}
1797+
volumeContentSourceVolumeSource := &csi.VolumeContentSource_Volume{
1798+
Volume: volumeSource,
1799+
}
1800+
volumecontensource := csi.VolumeContentSource{
1801+
Type: volumeContentSourceVolumeSource,
1802+
}
1803+
1804+
req := &csi.CreateVolumeRequest{
1805+
Name: "unit-test",
1806+
VolumeCapabilities: stdVolCap,
1807+
Parameters: mp,
1808+
VolumeContentSource: &volumecontensource,
1809+
}
1810+
1811+
ctrl := gomock.NewController(t)
1812+
defer ctrl.Finish()
1813+
1814+
m := util.NewMockEXEC(ctrl)
1815+
listStr := "JobId: ed1c3833-eaff-fe42-71d7-513fb065a9d9\nStart Time: Monday, 07-Aug-23 03:29:54 UTC\nStatus: Completed\nCommand: copy https://{accountName}.file.core.windows.net/{srcFileshare}{SAStoken} https://{accountName}.file.core.windows.net/{dstFileshare}{SAStoken} --recursive --check-length=false"
1816+
m.EXPECT().RunCommand(gomock.Eq("azcopy jobs list | grep dstFileshare -B 3")).Return(listStr, nil)
1817+
// if test.enableShow {
1818+
// m.EXPECT().RunCommand(gomock.Not("azcopy jobs list | grep dstContainer -B 3")).Return(test.showStr, test.showErr)
1819+
// }
1820+
1821+
d.azcopyFunc.ExecCmd = m
1822+
1823+
ctx := context.Background()
1824+
1825+
var expectedErr error
1826+
err := d.copyVolume(ctx, req, "", &fileclient.ShareOptions{Name: "dstFileshare"}, "core.windows.net")
1827+
if !reflect.DeepEqual(err, expectedErr) {
1828+
t.Errorf("Unexpected error: %v", err)
1829+
}
1830+
},
1831+
},
1832+
{
1833+
name: "azcopy job is first in progress and then be completed",
1834+
testFunc: func(t *testing.T) {
1835+
d := NewFakeDriver()
1836+
mp := map[string]string{}
1837+
1838+
volumeSource := &csi.VolumeContentSource_VolumeSource{
1839+
VolumeId: "vol_1#f5713de20cde511e8ba4900#fileshare#",
1840+
}
1841+
volumeContentSourceVolumeSource := &csi.VolumeContentSource_Volume{
1842+
Volume: volumeSource,
1843+
}
1844+
volumecontensource := csi.VolumeContentSource{
1845+
Type: volumeContentSourceVolumeSource,
1846+
}
1847+
1848+
req := &csi.CreateVolumeRequest{
1849+
Name: "unit-test",
1850+
VolumeCapabilities: stdVolCap,
1851+
Parameters: mp,
1852+
VolumeContentSource: &volumecontensource,
1853+
}
1854+
1855+
ctrl := gomock.NewController(t)
1856+
defer ctrl.Finish()
1857+
1858+
m := util.NewMockEXEC(ctrl)
1859+
listStr1 := "JobId: ed1c3833-eaff-fe42-71d7-513fb065a9d9\nStart Time: Monday, 07-Aug-23 03:29:54 UTC\nStatus: InProgress\nCommand: copy https://{accountName}.file.core.windows.net/{srcFileshare}{SAStoken} https://{accountName}.file.core.windows.net/{dstFileshare}{SAStoken} --recursive --check-length=false"
1860+
listStr2 := "JobId: ed1c3833-eaff-fe42-71d7-513fb065a9d9\nStart Time: Monday, 07-Aug-23 03:29:54 UTC\nStatus: Completed\nCommand: copy https://{accountName}.file.core.windows.net/{srcFileshare}{SAStoken} https://{accountName}.file.core.windows.net/{dstFileshare}{SAStoken} --recursive --check-length=false"
1861+
o1 := m.EXPECT().RunCommand(gomock.Eq("azcopy jobs list | grep dstFileshare -B 3")).Return(listStr1, nil).Times(1)
1862+
m.EXPECT().RunCommand(gomock.Not("azcopy jobs list | grep dstFileshare -B 3")).Return("Percent Complete (approx): 50.0", nil)
1863+
o2 := m.EXPECT().RunCommand(gomock.Eq("azcopy jobs list | grep dstFileshare -B 3")).Return(listStr2, nil)
1864+
gomock.InOrder(o1, o2)
1865+
1866+
d.azcopyFunc.ExecCmd = m
1867+
1868+
ctx := context.Background()
1869+
1870+
var expectedErr error
1871+
err := d.copyVolume(ctx, req, "", &fileclient.ShareOptions{Name: "dstFileshare"}, "core.windows.net")
1872+
if !reflect.DeepEqual(err, expectedErr) {
1873+
t.Errorf("Unexpected error: %v", err)
1874+
}
1875+
},
1876+
},
17861877
}
17871878

17881879
for _, tc := range testCases {

pkg/azurefile/utils.go

-94
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,6 @@ package azurefile
1919
import (
2020
"fmt"
2121
"os"
22-
"os/exec"
2322
"strconv"
2423
"strings"
2524
"sync"
@@ -35,15 +34,6 @@ const (
3534
tagKeyValueDelimiter = "="
3635
)
3736

38-
type AzcopyJobState string
39-
40-
const (
41-
AzcopyJobError AzcopyJobState = "Error"
42-
AzcopyJobNotFound AzcopyJobState = "NotFound"
43-
AzcopyJobRunning AzcopyJobState = "Running"
44-
AzcopyJobCompleted AzcopyJobState = "Completed"
45-
)
46-
4737
// lockMap used to lock on entries
4838
type lockMap struct {
4939
sync.Mutex
@@ -275,87 +265,3 @@ func replaceWithMap(str string, m map[string]string) string {
275265
}
276266
return str
277267
}
278-
279-
// getAzcopyJob get the azcopy job status if job existed
280-
func getAzcopyJob(dstFileshare string) (AzcopyJobState, string, error) {
281-
cmdStr := fmt.Sprintf("azcopy jobs list | grep %s -B 3", dstFileshare)
282-
// cmd output example:
283-
// JobId: ed1c3833-eaff-fe42-71d7-513fb065a9d9
284-
// Start Time: Monday, 07-Aug-23 03:29:54 UTC
285-
// Status: Completed (or Cancelled, InProgress)
286-
// Command: copy https://{accountName}.file.core.windows.net/{srcFileshare}{SAStoken} https://{accountName}.file.core.windows.net/{dstFileshare}{SAStoken} --recursive --check-length=false
287-
// --
288-
// JobId: b598cce3-9aa9-9640-7793-c2bf3c385a9a
289-
// Start Time: Wednesday, 09-Aug-23 09:09:03 UTC
290-
// Status: Cancelled
291-
// Command: copy https://{accountName}.file.core.windows.net/{srcFileshare}{SAStoken} https://{accountName}.file.core.windows.net/{dstFileshare}{SAStoken} --recursive --check-length=false
292-
out, err := exec.Command("sh", "-c", cmdStr).CombinedOutput()
293-
// if grep command returns nothing, the exec will return exit status 1 error, so filter this error
294-
if err != nil && err.Error() != "exit status 1" {
295-
klog.Warningf("failed to get azcopy job with error: %v, jobState: %v", err, AzcopyJobError)
296-
return AzcopyJobError, "", fmt.Errorf("couldn't list jobs in azcopy %v", err)
297-
}
298-
jobid, jobState, err := parseAzcopyJobList(string(out), dstFileshare)
299-
if err != nil || jobState == AzcopyJobError {
300-
klog.Warningf("failed to get azcopy job with error: %v, jobState: %v", err, jobState)
301-
return AzcopyJobError, "", fmt.Errorf("couldn't parse azcopy job list in azcopy %v", err)
302-
}
303-
if jobState == AzcopyJobCompleted {
304-
return jobState, "100.0", err
305-
}
306-
if jobid == "" {
307-
return jobState, "", err
308-
}
309-
cmdPercentStr := fmt.Sprintf("azcopy jobs show %s | grep Percent", jobid)
310-
// cmd out example:
311-
// Percent Complete (approx): 100.0
312-
summary, err := exec.Command("sh", "-c", cmdPercentStr).CombinedOutput()
313-
if err != nil {
314-
klog.Warningf("failed to get azcopy job with error: %v, jobState: %v", err, AzcopyJobError)
315-
return AzcopyJobError, "", fmt.Errorf("couldn't show jobs summary in azcopy %v", err)
316-
}
317-
jobState, percent, err := parseAzcopyJobShow(string(summary))
318-
if err != nil || jobState == AzcopyJobError {
319-
klog.Warningf("failed to get azcopy job with error: %v, jobState: %v", err, jobState)
320-
return AzcopyJobError, "", fmt.Errorf("couldn't parse azcopy job show in azcopy %v", err)
321-
}
322-
return jobState, percent, nil
323-
}
324-
325-
func parseAzcopyJobList(joblist string, dstFileShareName string) (string, AzcopyJobState, error) {
326-
jobid := ""
327-
jobSegments := strings.Split(joblist, "JobId: ")
328-
if len(jobSegments) < 2 {
329-
return jobid, AzcopyJobNotFound, nil
330-
}
331-
jobSegments = jobSegments[1:]
332-
for _, job := range jobSegments {
333-
segments := strings.Split(job, "\n")
334-
if len(segments) < 4 {
335-
return jobid, AzcopyJobError, fmt.Errorf("error parsing jobs list: %s", job)
336-
}
337-
statusSegments := strings.Split(segments[2], ": ")
338-
if len(statusSegments) < 2 {
339-
return jobid, AzcopyJobError, fmt.Errorf("error parsing jobs list status: %s", segments[2])
340-
}
341-
status := statusSegments[1]
342-
switch status {
343-
case "InProgress":
344-
jobid = segments[0]
345-
case "Completed":
346-
return jobid, AzcopyJobCompleted, nil
347-
}
348-
}
349-
if jobid == "" {
350-
return jobid, AzcopyJobNotFound, nil
351-
}
352-
return jobid, AzcopyJobRunning, nil
353-
}
354-
355-
func parseAzcopyJobShow(jobshow string) (AzcopyJobState, string, error) {
356-
segments := strings.Split(jobshow, ": ")
357-
if len(segments) < 2 {
358-
return AzcopyJobError, "", fmt.Errorf("error parsing jobs summary: %s in Percent Complete (approx)", jobshow)
359-
}
360-
return AzcopyJobRunning, strings.ReplaceAll(segments[1], "\n", ""), nil
361-
}

0 commit comments

Comments
 (0)