Skip to content

Commit 245d390

Browse files
authored
Merge pull request #2647 from gofr-dev/fix-file
2 parents 924c569 + f6f6607 commit 245d390

File tree

3 files changed

+119
-62
lines changed

3 files changed

+119
-62
lines changed

docs/advanced-guide/handling-file/page.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -186,7 +186,7 @@ func main() {
186186
ShareName: "myshare",
187187
// Endpoint is optional, defaults to https://{AccountName}.file.core.windows.net
188188
// Endpoint: "https://custom-endpoint.file.core.windows.net",
189-
}, app.Logger(), app.Metrics())
189+
})
190190

191191
if err != nil {
192192
app.Logger().Fatalf("Failed to initialize Azure File Storage: %v", err)

pkg/gofr/datasource/file/azure/fs.go

Lines changed: 22 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,6 @@ import (
55
"errors"
66
"time"
77

8-
"gofr.dev/pkg/gofr/datasource"
98
"gofr.dev/pkg/gofr/datasource/file"
109
)
1110

@@ -30,8 +29,9 @@ type Config struct {
3029
}
3130

3231
// New creates and validates a new Azure File Storage file system.
33-
// Returns error if connection fails.
34-
func New(config *Config, logger datasource.Logger, metrics file.StorageMetrics) (file.FileSystemProvider, error) {
32+
// Returns error if configuration is invalid.
33+
// Connection will be established when Connect() is called.
34+
func New(config *Config) (file.FileSystemProvider, error) {
3535
if config == nil {
3636
return nil, errInvalidConfig
3737
}
@@ -54,48 +54,42 @@ func New(config *Config, logger datasource.Logger, metrics file.StorageMetrics)
5454
CommonFileSystem: &file.CommonFileSystem{
5555
Provider: adapter,
5656
Location: config.ShareName,
57-
Logger: logger,
58-
Metrics: metrics,
5957
ProviderName: "Azure", // Set provider name for observability
6058
},
6159
}
6260

61+
return fs, nil
62+
}
63+
64+
// Connect tries a single immediate connect via provider; on failure it starts a background retry.
65+
func (f *azureFileSystem) Connect() {
66+
if f.CommonFileSystem.IsConnected() {
67+
return
68+
}
69+
6370
ctx, cancel := context.WithTimeout(context.Background(), defaultTimeout)
6471
defer cancel()
6572

66-
if logger != nil {
67-
logger.Debugf("Attempting to connect to Azure File Share %s (timeout: %v)", config.ShareName, defaultTimeout)
73+
if f.CommonFileSystem.Logger != nil {
74+
f.CommonFileSystem.Logger.Debugf("Attempting to connect to Azure File Share %s (timeout: %v)",
75+
f.CommonFileSystem.Location, defaultTimeout)
6876
}
6977

7078
// Use CommonFileSystem.Connect for bookkeeping
71-
if err := fs.CommonFileSystem.Connect(ctx); err != nil {
72-
if logger != nil {
73-
logger.Warnf("Azure File Share %s not available, starting background retry: %v", config.ShareName, err)
79+
if err := f.CommonFileSystem.Connect(ctx); err != nil {
80+
if f.CommonFileSystem.Logger != nil {
81+
f.CommonFileSystem.Logger.Warnf("Azure File Share %s not available, starting background retry: %v", f.CommonFileSystem.Location, err)
7482
}
7583

76-
go fs.startRetryConnect()
84+
go f.startRetryConnect()
7785

78-
return fs, nil
86+
return
7987
}
8088

8189
// Connected successfully
82-
if logger != nil {
83-
logger.Debugf("Successfully connected to Azure File Share %s", config.ShareName)
84-
}
85-
86-
return fs, nil
87-
}
88-
89-
// Connect tries a single immediate connect via provider; on failure it starts a background retry.
90-
func (f *azureFileSystem) Connect() {
91-
if f.CommonFileSystem.IsConnected() {
92-
return
90+
if f.CommonFileSystem.Logger != nil {
91+
f.CommonFileSystem.Logger.Debugf("Successfully connected to Azure File Share %s", f.CommonFileSystem.Location)
9392
}
94-
95-
ctx, cancel := context.WithTimeout(context.Background(), defaultTimeout)
96-
defer cancel()
97-
98-
_ = f.CommonFileSystem.Connect(ctx)
9993
}
10094

10195
// startRetryConnect repeatedly calls provider.Connect until success.

pkg/gofr/datasource/file/azure/fs_test.go

Lines changed: 96 additions & 33 deletions
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,7 @@ import (
1515
var errConnectionFailed = errors.New("connection failed")
1616

1717
func TestNew_NilConfig(t *testing.T) {
18-
fs, err := New(nil, nil, nil)
18+
fs, err := New(nil)
1919

2020
require.Error(t, err)
2121
assert.Nil(t, fs)
@@ -25,7 +25,7 @@ func TestNew_NilConfig(t *testing.T) {
2525
func TestNew_EmptyShareName(t *testing.T) {
2626
config := &Config{ShareName: ""}
2727

28-
fs, err := New(config, nil, nil)
28+
fs, err := New(config)
2929

3030
require.Error(t, err)
3131
assert.Nil(t, fs)
@@ -39,7 +39,7 @@ func TestNew_EmptyAccountName(t *testing.T) {
3939
AccountKey: "testkey",
4040
}
4141

42-
fs, err := New(config, nil, nil)
42+
fs, err := New(config)
4343

4444
require.Error(t, err)
4545
assert.Nil(t, fs)
@@ -53,7 +53,7 @@ func TestNew_EmptyAccountKey(t *testing.T) {
5353
AccountKey: "",
5454
}
5555

56-
fs, err := New(config, nil, nil)
56+
fs, err := New(config)
5757

5858
require.Error(t, err)
5959
assert.Nil(t, fs)
@@ -73,6 +73,14 @@ func TestNew_ConnectionFailure_StartsRetry(t *testing.T) {
7373
ShareName: "non-existent-share",
7474
}
7575

76+
fs, err := New(config)
77+
require.NoError(t, err)
78+
require.NotNil(t, fs)
79+
80+
// Set logger and metrics via UseLogger/UseMetrics
81+
fs.UseLogger(mockLogger)
82+
fs.UseMetrics(mockMetrics)
83+
7684
// Expect debug log for connection attempt
7785
mockLogger.EXPECT().Debugf(
7886
gomock.Any(), // Format string
@@ -99,10 +107,8 @@ func TestNew_ConnectionFailure_StartsRetry(t *testing.T) {
99107
gomock.Any(), // Share name
100108
).MaxTimes(1)
101109

102-
fs, err := New(config, mockLogger, mockMetrics)
103-
104-
require.NoError(t, err)
105-
require.NotNil(t, fs)
110+
// Now call Connect which will attempt connection and start retry
111+
fs.Connect()
106112

107113
time.Sleep(100 * time.Millisecond)
108114

@@ -115,25 +121,36 @@ func TestAzureFileSystem_Connect_AlreadyConnected(t *testing.T) {
115121

116122
mockLogger := file.NewMockLogger(ctrl)
117123
mockMetrics := file.NewMockMetrics(ctrl)
124+
mockProvider := file.NewMockStorageProvider(ctrl)
118125

119126
config := &Config{
120127
AccountName: "testaccount",
121128
AccountKey: "testkey",
122129
ShareName: "testshare",
123130
}
124131

125-
mockLogger.EXPECT().Debugf(gomock.Any(), gomock.Any(), gomock.Any()).AnyTimes()
126-
mockLogger.EXPECT().Debugf(gomock.Any(), gomock.Any()).AnyTimes()
132+
fs, err := New(config)
133+
require.NoError(t, err)
134+
135+
// Set logger and metrics via UseLogger/UseMetrics
136+
fs.UseLogger(mockLogger)
137+
fs.UseMetrics(mockMetrics)
138+
139+
// Replace provider with mock for successful connection
140+
fs.(*azureFileSystem).CommonFileSystem.Provider = mockProvider
141+
142+
mockLogger.EXPECT().Debugf("Attempting to connect to Azure File Share %s (timeout: %v)", "testshare", gomock.Any())
127143
mockMetrics.EXPECT().NewHistogram(file.AppFileStats, gomock.Any(), gomock.Any())
128-
mockLogger.EXPECT().Infof("connected to %s", "testshare").MaxTimes(1)
129-
mockLogger.EXPECT().Warnf(gomock.Any(), gomock.Any(), gomock.Any()).MaxTimes(1)
130-
mockLogger.EXPECT().Debug(gomock.Any()).AnyTimes()
131-
mockMetrics.EXPECT().RecordHistogram(gomock.Any(), file.AppFileStats, gomock.Any(), gomock.Any()).AnyTimes()
144+
mockProvider.EXPECT().Connect(gomock.Any()).Return(nil)
145+
mockLogger.EXPECT().Infof("connected to %s", "testshare")
146+
mockLogger.EXPECT().Debugf("Successfully connected to Azure File Share %s", "testshare")
147+
mockLogger.EXPECT().Debug(gomock.Any())
148+
mockMetrics.EXPECT().RecordHistogram(gomock.Any(), file.AppFileStats, gomock.Any(), gomock.Any())
132149

133-
fs, err := New(config, mockLogger, mockMetrics)
134-
require.NoError(t, err)
150+
// First Connect() call - will attempt connection and succeed
151+
fs.(*azureFileSystem).Connect()
135152

136-
// If already connected, Connect() should return immediately
153+
// If already connected, Connect() should return immediately (no more calls expected)
137154
fs.(*azureFileSystem).Connect()
138155

139156
fs.(*azureFileSystem).CommonFileSystem.SetDisableRetry(true)
@@ -152,21 +169,35 @@ func TestAzureFileSystem_Connect_NotConnected(t *testing.T) {
152169
ShareName: "testshare",
153170
}
154171

155-
mockLogger.EXPECT().Debugf(gomock.Any(), gomock.Any(), gomock.Any()).AnyTimes()
156-
mockLogger.EXPECT().Debugf(gomock.Any(), gomock.Any()).AnyTimes()
172+
fs, err := New(config)
173+
require.NoError(t, err)
174+
175+
// Set logger and metrics via UseLogger/UseMetrics
176+
fs.UseLogger(mockLogger)
177+
fs.UseMetrics(mockMetrics)
178+
179+
mockLogger.EXPECT().Debugf("Attempting to connect to Azure File Share %s (timeout: %v)", "testshare", gomock.Any())
157180
mockMetrics.EXPECT().NewHistogram(file.AppFileStats, gomock.Any(), gomock.Any())
158-
mockLogger.EXPECT().Warnf(gomock.Any(), gomock.Any(), gomock.Any()).MaxTimes(1)
159-
mockLogger.EXPECT().Debug(gomock.Any()).AnyTimes()
160-
mockMetrics.EXPECT().RecordHistogram(gomock.Any(), file.AppFileStats, gomock.Any(), gomock.Any()).AnyTimes()
181+
mockLogger.EXPECT().Debug(gomock.Any())
182+
mockMetrics.EXPECT().RecordHistogram(gomock.Any(), file.AppFileStats, gomock.Any(), gomock.Any())
183+
mockLogger.EXPECT().Warnf("Azure File Share %s not available, starting background retry: %v", "testshare", gomock.Any())
184+
// Expect logRetryStart from the goroutine (may or may not be called depending on timing)
185+
mockLogger.EXPECT().Debugf(
186+
"Starting background retry for Azure File Share %s (retry interval: 1 minute)",
187+
"testshare",
188+
).MaxTimes(1)
161189

162-
fs, err := New(config, mockLogger, mockMetrics)
163-
require.NoError(t, err)
190+
// Call Connect - should attempt to connect and start retry
191+
fs.(*azureFileSystem).Connect()
192+
193+
// Give goroutine time to start
194+
time.Sleep(50 * time.Millisecond)
164195

165-
// Mark as not connected
196+
// Mark as not connected and disable retry to stop the goroutine
166197
fs.(*azureFileSystem).CommonFileSystem.SetDisableRetry(true)
167198

168-
// Call Connect - should attempt to connect
169-
fs.(*azureFileSystem).Connect()
199+
// Give goroutine time to check the flag and exit
200+
time.Sleep(50 * time.Millisecond)
170201
}
171202

172203
func TestAzureFileSystem_startRetryConnect(t *testing.T) {
@@ -182,15 +213,22 @@ func TestAzureFileSystem_startRetryConnect(t *testing.T) {
182213
ShareName: "testshare",
183214
}
184215

216+
fs, err := New(config)
217+
require.NoError(t, err)
218+
219+
// Set logger and metrics via UseLogger/UseMetrics
220+
fs.UseLogger(mockLogger)
221+
fs.UseMetrics(mockMetrics)
222+
185223
mockLogger.EXPECT().Debugf(gomock.Any(), gomock.Any(), gomock.Any()).AnyTimes()
186224
mockLogger.EXPECT().Debugf(gomock.Any(), gomock.Any()).AnyTimes()
187225
mockMetrics.EXPECT().NewHistogram(file.AppFileStats, gomock.Any(), gomock.Any())
188226
mockLogger.EXPECT().Warnf(gomock.Any(), gomock.Any(), gomock.Any()).MaxTimes(1)
189227
mockLogger.EXPECT().Debug(gomock.Any())
190228
mockMetrics.EXPECT().RecordHistogram(gomock.Any(), file.AppFileStats, gomock.Any(), gomock.Any())
191229

192-
fs, err := New(config, mockLogger, mockMetrics)
193-
require.NoError(t, err)
230+
// Call Connect which will start retry on failure
231+
fs.Connect()
194232

195233
// Disable retry to stop the goroutine quickly
196234
fs.(*azureFileSystem).CommonFileSystem.SetDisableRetry(true)
@@ -212,19 +250,26 @@ func TestAzureFileSystem_startRetryConnect_RetryDisabled(t *testing.T) {
212250
ShareName: "testshare",
213251
}
214252

253+
fs, err := New(config)
254+
require.NoError(t, err)
255+
256+
// Set logger and metrics via UseLogger/UseMetrics
257+
fs.UseLogger(mockLogger)
258+
fs.UseMetrics(mockMetrics)
259+
215260
mockLogger.EXPECT().Debugf(gomock.Any(), gomock.Any(), gomock.Any()).AnyTimes()
216261
mockLogger.EXPECT().Debugf(gomock.Any(), gomock.Any()).AnyTimes()
217262
mockMetrics.EXPECT().NewHistogram(file.AppFileStats, gomock.Any(), gomock.Any())
218263
mockLogger.EXPECT().Warnf(gomock.Any(), gomock.Any(), gomock.Any()).MaxTimes(1)
219264
mockLogger.EXPECT().Debug(gomock.Any())
220265
mockMetrics.EXPECT().RecordHistogram(gomock.Any(), file.AppFileStats, gomock.Any(), gomock.Any())
221266

222-
fs, err := New(config, mockLogger, mockMetrics)
223-
require.NoError(t, err)
224-
225267
// Disable retry immediately - retry loop should exit
226268
fs.(*azureFileSystem).CommonFileSystem.SetDisableRetry(true)
227269

270+
// Call Connect which would start retry, but retry is disabled
271+
fs.Connect()
272+
228273
// Give it a moment to check the retry disabled flag
229274
time.Sleep(50 * time.Millisecond)
230275
}
@@ -321,7 +366,7 @@ func TestNew_NilLogger(t *testing.T) {
321366
ShareName: "testshare",
322367
}
323368

324-
fs, err := New(config, nil, nil)
369+
fs, err := New(config)
325370

326371
require.NoError(t, err)
327372
require.NotNil(t, fs)
@@ -344,11 +389,27 @@ func TestAzureFileSystem_Connect_NotConnected_WithLogger(t *testing.T) {
344389
},
345390
}
346391

392+
mockLogger.EXPECT().Debugf("Attempting to connect to Azure File Share %s (timeout: %v)", "testshare", gomock.Any())
347393
mockMetrics.EXPECT().NewHistogram(file.AppFileStats, gomock.Any(), gomock.Any())
348394
mockLogger.EXPECT().Debug(gomock.Any())
349395
mockMetrics.EXPECT().RecordHistogram(gomock.Any(), file.AppFileStats, gomock.Any(), gomock.Any())
396+
mockLogger.EXPECT().Warnf("Azure File Share %s not available, starting background retry: %v", "testshare", gomock.Any())
397+
// Expect logRetryStart from the goroutine
398+
mockLogger.EXPECT().Debugf(
399+
"Starting background retry for Azure File Share %s (retry interval: 1 minute)",
400+
"testshare",
401+
)
350402

351403
fs.Connect()
404+
405+
// Give goroutine time to start
406+
time.Sleep(100 * time.Millisecond)
407+
408+
// Disable retry to stop the goroutine
409+
fs.CommonFileSystem.SetDisableRetry(true)
410+
411+
// Give goroutine time to check the flag and exit
412+
time.Sleep(100 * time.Millisecond)
352413
}
353414

354415
func TestAzureFileSystem_logRetryStart_WithLogger(t *testing.T) {
@@ -617,9 +678,11 @@ func TestAzureFileSystem_Connect_WhenNotConnected(t *testing.T) {
617678
// Ensure not connected
618679
assert.False(t, fs.CommonFileSystem.IsConnected())
619680

681+
mockLogger.EXPECT().Debugf("Attempting to connect to Azure File Share %s (timeout: %v)", "testshare", gomock.Any())
620682
mockMetrics.EXPECT().NewHistogram(file.AppFileStats, gomock.Any(), gomock.Any())
621683
mockProvider.EXPECT().Connect(gomock.Any()).Return(nil)
622684
mockLogger.EXPECT().Infof("connected to %s", "testshare")
685+
mockLogger.EXPECT().Debugf("Successfully connected to Azure File Share %s", "testshare")
623686
mockLogger.EXPECT().Debug(gomock.Any())
624687
mockMetrics.EXPECT().RecordHistogram(gomock.Any(), file.AppFileStats, gomock.Any(), gomock.Any())
625688

0 commit comments

Comments
 (0)