Skip to content

Commit db9b463

Browse files
authored
Add concurrent zapi SVM rename unit test
1 parent ce85645 commit db9b463

File tree

2 files changed

+143
-42
lines changed

2 files changed

+143
-42
lines changed

storage_drivers/ontap/api/azgo/common.go

Lines changed: 58 additions & 42 deletions
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,55 @@ type ZAPIResponseIterable interface {
3535
NextTag() string
3636
}
3737

38+
type httpClientGetter interface {
39+
getHttpClient() (*http.Client, error)
40+
}
41+
42+
func (o *ZapiRunner) getHttpClient() (*http.Client, error) {
43+
// Check to use cert/key and load the cert pair
44+
var cert tls.Certificate
45+
caCertPool := x509.NewCertPool()
46+
skipVerify := true
47+
if o.ClientCertificate != "" && o.ClientPrivateKey != "" {
48+
certDecode, err := base64.StdEncoding.DecodeString(o.ClientCertificate)
49+
if err != nil {
50+
return nil, errors.New("failed to decode client certificate from base64")
51+
}
52+
keyDecode, err := base64.StdEncoding.DecodeString(o.ClientPrivateKey)
53+
if err != nil {
54+
return nil, errors.New("failed to decode private key from base64")
55+
}
56+
cert, err = tls.X509KeyPair(certDecode, keyDecode)
57+
if err != nil {
58+
log.Debugf("error: %v", err)
59+
return nil, errors.New("cannot load certificate and key")
60+
}
61+
}
62+
63+
// Check to use trustedCACertificate to use InsecureSkipVerify or not
64+
if o.TrustedCACertificate != "" {
65+
trustedCACert, err := base64.StdEncoding.DecodeString(o.TrustedCACertificate)
66+
if err != nil {
67+
return nil, errors.New("failed to decode trusted CA certificate from base64")
68+
}
69+
skipVerify = false
70+
caCertPool.AppendCertsFromPEM(trustedCACert)
71+
}
72+
73+
tr := &http.Transport{
74+
TLSClientConfig: &tls.Config{
75+
InsecureSkipVerify: skipVerify, MinVersion: tridentconfig.MinClientTLSVersion,
76+
Certificates: []tls.Certificate{cert}, RootCAs: caCertPool,
77+
},
78+
}
79+
80+
client := &http.Client{
81+
Transport: tr,
82+
Timeout: time.Duration(tridentconfig.StorageAPITimeoutSeconds * time.Second),
83+
}
84+
return client, nil
85+
}
86+
3887
type ZapiRunner struct {
3988
ManagementLIF string
4089
svm string
@@ -47,12 +96,13 @@ type ZapiRunner struct {
4796
ontapApiVersion string
4897
DebugTraceFlags map[string]bool // Example: {"api":false, "method":true}
4998
m *sync.RWMutex
99+
clientGetter httpClientGetter
50100
}
51101

52102
func NewZapiRunner(managementLIF, svm, username, password, clientPrivateKey, clientCertificate,
53103
clientCACert string, secure bool, ontapApiVersion string, debugTraceFlags map[string]bool,
54104
) *ZapiRunner {
55-
return &ZapiRunner{
105+
zr := &ZapiRunner{
56106
ManagementLIF: managementLIF,
57107
svm: svm,
58108
Username: username,
@@ -65,6 +115,8 @@ func NewZapiRunner(managementLIF, svm, username, password, clientPrivateKey, cli
65115
DebugTraceFlags: debugTraceFlags,
66116
m: &sync.RWMutex{},
67117
}
118+
zr.clientGetter = zr // Use ZapiRunners own getter for HTTP clients
119+
return zr
68120
}
69121

70122
// CopyForNontunneledZapiRunner returns a clone of the ZapiRunner configured on this driver with the SVM field cleared
@@ -182,50 +234,14 @@ func (o *ZapiRunner) SendZapi(r ZAPIRequest) (*http.Response, error) {
182234
return nil, err
183235
}
184236
req.Header.Set("Content-Type", "application/xml")
185-
186-
// Check to use cert/key and load the cert pair
187-
var cert tls.Certificate
188-
caCertPool := x509.NewCertPool()
189-
skipVerify := true
190-
if o.ClientCertificate != "" && o.ClientPrivateKey != "" {
191-
certDecode, err := base64.StdEncoding.DecodeString(o.ClientCertificate)
192-
if err != nil {
193-
return nil, errors.New("failed to decode client certificate from base64")
194-
}
195-
keyDecode, err := base64.StdEncoding.DecodeString(o.ClientPrivateKey)
196-
if err != nil {
197-
return nil, errors.New("failed to decode private key from base64")
198-
}
199-
cert, err = tls.X509KeyPair(certDecode, keyDecode)
200-
if err != nil {
201-
log.Debugf("error: %v", err)
202-
return nil, errors.New("cannot load certificate and key")
203-
}
204-
} else {
237+
if o.ClientCertificate == "" || o.ClientPrivateKey == "" {
205238
req.SetBasicAuth(o.Username, o.Password)
206239
}
207-
208-
// Check to use trustedCACertificate to use InsecureSkipVerify or not
209-
if o.TrustedCACertificate != "" {
210-
trustedCACert, err := base64.StdEncoding.DecodeString(o.TrustedCACertificate)
211-
if err != nil {
212-
return nil, errors.New("failed to decode trusted CA certificate from base64")
213-
}
214-
skipVerify = false
215-
caCertPool.AppendCertsFromPEM(trustedCACert)
216-
}
217-
218-
tr := &http.Transport{
219-
TLSClientConfig: &tls.Config{
220-
InsecureSkipVerify: skipVerify, MinVersion: tridentconfig.MinClientTLSVersion,
221-
Certificates: []tls.Certificate{cert}, RootCAs: caCertPool,
222-
},
240+
client, err := o.clientGetter.getHttpClient()
241+
if err != nil {
242+
return nil, fmt.Errorf("failed to get HTTP client: %v", err)
223243
}
224244

225-
client := &http.Client{
226-
Transport: tr,
227-
Timeout: time.Duration(tridentconfig.StorageAPITimeoutSeconds * time.Second),
228-
}
229245
response, err := client.Do(req)
230246

231247
if err != nil {
@@ -276,7 +292,7 @@ func (o *ZapiRunner) ExecuteUsing(z ZAPIRequest, requestType string, v interface
276292
if strings.HasSuffix(svm, "-mc") {
277293
o.svm = strings.TrimSuffix(svm, "-mc")
278294
} else {
279-
o.svm += "-mc"
295+
o.svm = fmt.Sprintf("%s-mc", svm)
280296
}
281297

282298
return o.executeWithoutIteration(z, requestType, vCopy)

storage_drivers/ontap/api/azgo/common_test.go

Lines changed: 85 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,10 +3,14 @@ package azgo
33
import (
44
"bytes"
55
"encoding/xml"
6+
"fmt"
67
"io"
8+
"io/ioutil"
79
"net/http"
10+
"strings"
811
"sync"
912
"testing"
13+
"time"
1014

1115
"github.com/stretchr/testify/assert"
1216
)
@@ -104,3 +108,84 @@ func TestCopyForNontunneledZapiRunner(t *testing.T) {
104108
assert.NotSame(t, originalZRunner.m, copiedZRunner.m, "Mutexes should not be the same instance")
105109
assert.NotNil(t, copiedZRunner.m, "Copied ZapiRunner should have a non-nil mutex")
106110
}
111+
112+
func CreateTestZapiRunner(n int) *ZapiRunner {
113+
managementLIF := "1.2.3.4"
114+
svm := fmt.Sprintf("svm-%d", n)
115+
username := fmt.Sprintf("user%d", n)
116+
password := fmt.Sprintf("password%d", n)
117+
clientPrivateKey := ""
118+
clientCertificate := ""
119+
clientCACert := ""
120+
secure := false
121+
ontapApiVersion := "9.8"
122+
debugTraceFlags := map[string]bool{}
123+
return &ZapiRunner{
124+
ManagementLIF: managementLIF,
125+
svm: svm,
126+
Username: username,
127+
Password: password,
128+
ClientPrivateKey: clientPrivateKey,
129+
ClientCertificate: clientCertificate,
130+
TrustedCACertificate: clientCACert,
131+
Secure: secure,
132+
ontapApiVersion: ontapApiVersion,
133+
DebugTraceFlags: debugTraceFlags,
134+
m: &sync.RWMutex{},
135+
}
136+
}
137+
138+
type mockHttpClientGetter struct {
139+
tr http.RoundTripper
140+
timeout time.Duration
141+
}
142+
143+
func (m *mockHttpClientGetter) getHttpClient() (*http.Client, error) {
144+
client := &http.Client{
145+
Transport: m.tr,
146+
Timeout: m.timeout,
147+
}
148+
return client, nil
149+
}
150+
151+
type RoundTripFunc func(*http.Request) (*http.Response, error)
152+
153+
func (f RoundTripFunc) RoundTrip(r *http.Request) (*http.Response, error) {
154+
return f(r)
155+
}
156+
157+
var mockZapiErrorVserverNotFound string = `<?xml version="1.0" encoding="UTF-8"?>
158+
<netapp version="1.21" xmlns="http://www.netapp.com/filer/admin">
159+
<results status="failed" errno="15698" reason="Simulated error for testing"/>
160+
</netapp>`
161+
162+
// TestZapiRunner_ExecuteUsing_Parallel_MC tests the parallel execution of ExecuteUsing where the SVM name changes
163+
func TestZapiRunner_ExecuteUsing_Parallel_MC(t *testing.T) {
164+
// Test parallel calls to ExecuteUsing with MC name changes
165+
zRunner := CreateTestZapiRunner(1)
166+
mockTransport := RoundTripFunc(func(req *http.Request) (*http.Response, error) {
167+
return &http.Response{
168+
StatusCode: http.StatusOK,
169+
Body: ioutil.NopCloser(strings.NewReader(mockZapiErrorVserverNotFound)),
170+
Header: make(http.Header),
171+
}, nil
172+
})
173+
mockGetter := &mockHttpClientGetter{
174+
tr: mockTransport,
175+
timeout: 5 * time.Second,
176+
}
177+
zRunner.clientGetter = mockGetter
178+
179+
var wg sync.WaitGroup
180+
for i := 0; i < 50; i++ {
181+
wg.Add(1)
182+
go func(n int) {
183+
defer wg.Done()
184+
zRunner.ExecuteUsing(&VolumeCreateRequest{}, "VolumeCreateRequest", NewVolumeCreateResponse())
185+
}(i)
186+
}
187+
wg.Wait() // Wait for all goroutines to finish
188+
189+
// Check the SVM name, it should be one of the expected values
190+
assert.True(t, zRunner.svm == "svm-1-mc" || zRunner.svm == "svm-1")
191+
}

0 commit comments

Comments
 (0)