Fetch calendar by name on 409 ErrorFolderExist (#3318)
Handle 409, `ErrorFolderExists` error in restore path while creating destination calendar. We need to fix this for all m365 services. This PR is focused on calendar only. Context: `CreateCalendar()` may fail with ErrorFolderExists under certain error conditions. For e.g. consider below scenario. 1. `CreateCalendar()` does a POST to graph to create restore destination calendar but this fails with 5xx. 2. It's possible that step 1 may have left some dirty state in graph. For e.g. it's possible that the destination folder in step 1 was actually created, but 5xx was returned due to other reasons. 3. So when we reattempt POST in such a scenario, we sometimes observe ErrorFolderExists error . 4. Corso should be resilient to such errors. To fix this, when we encounter such an error, we will do a GET to fetch the restore destination folder and add it to folder cache. --- #### Does this PR need a docs update or release note? - [ ] ✅ Yes, it's included - [ ] 🕐 Yes, but in a later PR - [x] ⛔ No #### Type of change <!--- Please check the type of change your PR introduces: ---> - [ ] 🌻 Feature - [x] 🐛 Bugfix - [ ] 🗺️ Documentation - [ ] 🤖 Supportability/Tests - [ ] 💻 CI/Deployment - [ ] 🧹 Tech Debt/Cleanup #### Issue(s) <!-- Can reference multiple issues. Use one of the following "magic words" - "closes, fixes" to auto-close the Github issue. --> * #<issue> #### Test Plan <!-- How will this be tested prior to merging.--> Integration tests and mock tests will be added in a follow up PR. - [x] 💪 Manual - [ ] ⚡ Unit test - [ ] 💚 E2E
This commit is contained in:
parent
ef2083bc20
commit
5b9cd69e29
@ -99,6 +99,45 @@ func (c Events) GetContainerByID(
|
||||
return graph.CalendarDisplayable{Calendarable: cal}, nil
|
||||
}
|
||||
|
||||
// GetContainerByName fetches a calendar by name
|
||||
func (c Events) GetContainerByName(
|
||||
ctx context.Context,
|
||||
userID, name string,
|
||||
) (models.Calendarable, error) {
|
||||
filter := fmt.Sprintf("name eq '%s'", name)
|
||||
options := &users.ItemCalendarsRequestBuilderGetRequestConfiguration{
|
||||
QueryParameters: &users.ItemCalendarsRequestBuilderGetQueryParameters{
|
||||
Filter: &filter,
|
||||
},
|
||||
}
|
||||
|
||||
ctx = clues.Add(ctx, "calendar_name", name)
|
||||
|
||||
resp, err := c.Stable.Client().UsersById(userID).Calendars().Get(ctx, options)
|
||||
if err != nil {
|
||||
return nil, graph.Stack(ctx, err).WithClues(ctx)
|
||||
}
|
||||
|
||||
// We only allow the api to match one calendar with provided name.
|
||||
// Return an error if multiple calendars exist (unlikely) or if no calendar
|
||||
// is found.
|
||||
if len(resp.GetValue()) != 1 {
|
||||
err = clues.New("unexpected number of calendars returned").
|
||||
With("returned_calendar_count", len(resp.GetValue()))
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// Sanity check ID and name
|
||||
cal := resp.GetValue()[0]
|
||||
cd := CalendarDisplayable{Calendarable: cal}
|
||||
|
||||
if err := checkIDAndName(cd); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return cal, nil
|
||||
}
|
||||
|
||||
// GetItem retrieves an Eventable item.
|
||||
func (c Events) GetItem(
|
||||
ctx context.Context,
|
||||
|
||||
@ -689,9 +689,19 @@ func establishEventsRestoreLocation(
|
||||
ctx = clues.Add(ctx, "is_new_cache", isNewCache)
|
||||
|
||||
temp, err := ac.Events().CreateCalendar(ctx, user, folders[0])
|
||||
if err != nil && !graph.IsErrFolderExists(err) {
|
||||
return "", err
|
||||
}
|
||||
|
||||
// 409 handling: Fetch folder if it exists and add to cache.
|
||||
// This is rare, but may happen if CreateCalendar() POST fails with 5xx,
|
||||
// potentially leaving dirty state in graph.
|
||||
if graph.IsErrFolderExists(err) {
|
||||
temp, err = ac.Events().GetContainerByName(ctx, user, folders[0])
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
}
|
||||
|
||||
folderID := ptr.Val(temp.GetId())
|
||||
|
||||
|
||||
@ -41,6 +41,10 @@ const (
|
||||
syncFolderNotFound errorCode = "ErrorSyncFolderNotFound"
|
||||
syncStateInvalid errorCode = "SyncStateInvalid"
|
||||
syncStateNotFound errorCode = "SyncStateNotFound"
|
||||
// This error occurs when an attempt is made to create a folder that has
|
||||
// the same name as another folder in the same parent. Such duplicate folder
|
||||
// names are not allowed by graph.
|
||||
folderExists errorCode = "ErrorFolderExists"
|
||||
)
|
||||
|
||||
type errorMessage string
|
||||
@ -178,6 +182,10 @@ func IsMalwareResp(ctx context.Context, resp *http.Response) bool {
|
||||
return false
|
||||
}
|
||||
|
||||
func IsErrFolderExists(err error) bool {
|
||||
return hasErrorCode(err, folderExists)
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// error parsers
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
@ -300,3 +300,48 @@ func (suite *GraphErrorsUnitSuite) TestMalwareInfo() {
|
||||
|
||||
assert.Equal(suite.T(), expect, ItemInfo(&i))
|
||||
}
|
||||
|
||||
func (suite *GraphErrorsUnitSuite) TestIsErrFolderExists() {
|
||||
table := []struct {
|
||||
name string
|
||||
err error
|
||||
expect assert.BoolAssertionFunc
|
||||
}{
|
||||
{
|
||||
name: "nil",
|
||||
err: nil,
|
||||
expect: assert.False,
|
||||
},
|
||||
{
|
||||
name: "non-matching",
|
||||
err: assert.AnError,
|
||||
expect: assert.False,
|
||||
},
|
||||
{
|
||||
name: "non-matching oDataErr",
|
||||
err: odErr("folder doesn't exist"),
|
||||
expect: assert.False,
|
||||
},
|
||||
{
|
||||
name: "matching oDataErr",
|
||||
err: odErr(string(folderExists)),
|
||||
expect: assert.True,
|
||||
},
|
||||
// next two tests are to make sure the checks are case insensitive
|
||||
{
|
||||
name: "oDataErr camelcase",
|
||||
err: odErr("ErrorFolderExists"),
|
||||
expect: assert.True,
|
||||
},
|
||||
{
|
||||
name: "oDataErr lowercase",
|
||||
err: odErr("errorfolderexists"),
|
||||
expect: assert.True,
|
||||
},
|
||||
}
|
||||
for _, test := range table {
|
||||
suite.Run(test.name, func() {
|
||||
test.expect(suite.T(), IsErrFolderExists(test.err))
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user