Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Feature/state machine changes #454

Closed
wants to merge 11 commits into from

Conversation

franmoore05
Copy link
Contributor

What

Draft PR for state machine implementation. Tests have not been updated yet.

How to review

Ensure the changes make sense.

Who can review

@janderson2

Copy link
Contributor

@janderson2 janderson2 left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Have added a few thoughts on the structure.

Comment on lines 30 to 31
//nolint:gosec // This is not a hardcoded credential.
downloadServiceToken = "X-Download-Service-Token"
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Would the following address the lint error without need for the comment? (haven't tested it, so just a theory)

Suggested change
//nolint:gosec // This is not a hardcoded credential.
downloadServiceToken = "X-Download-Service-Token"
//nolint:gosec // This is not a hardcoded credential.
downloadServiceTokenHeader = "X-Download-Service-Token"

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'll address this in the overall PR for this change.

application/orchestrator.go Outdated Show resolved Hide resolved
application/orchestrator.go Outdated Show resolved Hide resolved
application/states.go Outdated Show resolved Hide resolved
application/states.go Outdated Show resolved Hide resolved
application/state_machine.go Outdated Show resolved Hide resolved
Copy link
Contributor

@janderson2 janderson2 left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Shaping up nicely. I think this will definitely improve readability and maintainability immensely. Have left some thoughts and comments.

service/service.go Outdated Show resolved Hide resolved
application/state_machine.go Outdated Show resolved Hide resolved
service/service.go Outdated Show resolved Hide resolved
return nil
}

func castStateToState(state string) State {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It looks like this could be handled by the state machine itself as it has the map[string]State state map so can complete this cast internally. See comment on Transition function.

application/state_machine.go Outdated Show resolved Hide resolved
application/state_machine.go Outdated Show resolved Hide resolved
application/orchestrator.go Outdated Show resolved Hide resolved
application/orchestrator.go Outdated Show resolved Hide resolved
Comment on lines 92 to 108
if vars[hasDownloads] != trueStringified {
data["updated_state"] = versionUpdate.State
if versionUpdate.State == models.PublishedState {
if err := smDS.publishVersion(ctx, currentDataset, currentVersion, versionUpdate, versionDetails); err != nil {
log.Error(ctx, "putVersion endpoint: failed publishing version", err)
return err
}
}

if versionUpdate.State == models.AssociatedState && currentVersion.State != models.AssociatedState {

if err := smDS.associateVersion(ctx, currentVersion, versionUpdate, versionDetails); err != nil {
log.Error(ctx, "putVersion endpoint: failed associating version", err)
return err
}
}
}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This logic should form part of the transition into published and associated states respectively and should therefore be called from the Enter functions of those states.

Is the thinking is to complete the refactor in two steps to minimise the scope of refactoring in a single PR?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This has been moved in to the enter function, let me know if this looks right to you, if it is I'll implement for the associateVersion also.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

One comment about the locking, but looks right to me.

application/states.go Outdated Show resolved Hide resolved
for i := 0; i < len(transitions); i++ {
if previousState == transitions[i] {
match = true
nextState = castStateToState(newState)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This call to the hard coded cast in the castStateToState() could be replaced with:

Suggested change
nextState = castStateToState(newState)
nextState = sm.states[newState]

This would improve the maintainability of the code by preventing the need to extend the hard coded cast when changing the states.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This has been amended

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I am not seeing this change. Maybe it hasn't been pushed?

application/state_machine.go Outdated Show resolved Hide resolved
application/states.go Outdated Show resolved Hide resolved
application/states.go Outdated Show resolved Hide resolved
application/states.go Outdated Show resolved Hide resolved
application/states.go Outdated Show resolved Hide resolved
application/states.go Outdated Show resolved Hide resolved
application/states.go Outdated Show resolved Hide resolved
application/states.go Outdated Show resolved Hide resolved
fmt.Println("Validation Failed")
fmt.Println(errModel)
return errModel
} else {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

else is redundant here as the above will have returned on error.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This has now been removed

Comment on lines 484 to 490
lockID, error := smDS.DataStore.Backend.AcquireInstanceLock(ctx, currentVersion.ID)
if error != nil {
return error
}
defer func() {
smDS.DataStore.Backend.UnlockInstance(ctx, lockID)
}()
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think we may need to move the locking to the orchestrator outside of the enter function (e.g. call it in the the AmendVersion function) so that the lock is acquired before the current version data is read to prevent a race condition. This will also mean that the lock will be implemented across the different transitions.

It looks like we might need to consider a dataset level lock as well, but as I don't believe that currently exists it is a later concern.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I have now moved where the lock happens

}

// This is only applicable to CMD datasets
if currentVersion.Type == "v4" {
Copy link
Contributor

@janderson2 janderson2 Nov 19, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It appears that CMD could have multiple string values here. Suggest calling

func GetDatasetType(datasetType string) (DatasetType, error) {
switch datasetType {
case "filterable", "v4", "":
return Filterable, nil
and Filterable rather than a string literal to avoid any issues.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This check has now been amended

Comment on lines 92 to 108
if vars[hasDownloads] != trueStringified {
data["updated_state"] = versionUpdate.State
if versionUpdate.State == models.PublishedState {
if err := smDS.publishVersion(ctx, currentDataset, currentVersion, versionUpdate, versionDetails); err != nil {
log.Error(ctx, "putVersion endpoint: failed publishing version", err)
return err
}
}

if versionUpdate.State == models.AssociatedState && currentVersion.State != models.AssociatedState {

if err := smDS.associateVersion(ctx, currentVersion, versionUpdate, versionDetails); err != nil {
log.Error(ctx, "putVersion endpoint: failed associating version", err)
return err
}
}
}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

One comment about the locking, but looks right to me.

return nil
}

func UpdateVersionInfo(smDS *StateMachineDatasetAPI, ctx context.Context,
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Consider whether this needs to be exported.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I've now amended this

Comment on lines +573 to +581
if err := generator.Generate(ctx, versionDetails.datasetID, versionUpdate.ID, versionDetails.edition, versionDetails.version); err != nil {
data["instance_id"] = versionUpdate.ID
data["state"] = versionUpdate.State
data["type"] = t.String()
log.Error(ctx, "State Machine: Publish: PublishDataset: error while attempting to generate full dataset version downloads on version publish", err, data)
return err
// TODO - TECH DEBT - need to add an error event for this. Kafka message perhaps.
}
log.Info(ctx, "State Machine: Publish: PublishDataset: generated full dataset version downloads:", data)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Need to check that generator is not nil. Something like:

Suggested change
if err := generator.Generate(ctx, versionDetails.datasetID, versionUpdate.ID, versionDetails.edition, versionDetails.version); err != nil {
data["instance_id"] = versionUpdate.ID
data["state"] = versionUpdate.State
data["type"] = t.String()
log.Error(ctx, "State Machine: Publish: PublishDataset: error while attempting to generate full dataset version downloads on version publish", err, data)
return err
// TODO - TECH DEBT - need to add an error event for this. Kafka message perhaps.
}
log.Info(ctx, "State Machine: Publish: PublishDataset: generated full dataset version downloads:", data)
if generator == nil {
log.Info(ctx, "PublishDataset: no download generation needed for dataset type", data)
} else {
if err := generator.Generate(ctx, versionDetails.datasetID, versionUpdate.ID, versionDetails.edition, versionDetails.version); err != nil {
data["instance_id"] = versionUpdate.ID
data["state"] = versionUpdate.State
data["type"] = t.String()
log.Error(ctx, "State Machine: Publish: PublishDataset: error while attempting to generate full dataset version downloads on version publish", err, data)
return err
// TODO - TECH DEBT - need to add an error event for this. Kafka message perhaps.
}
log.Info(ctx, "State Machine: Publish: PublishDataset: generated full dataset version downloads:", data)
}

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The nil check has now been added

Comment on lines 77 to 80
nextState = castStateToState(versionUpdate.State)
if nextState == nil {
return errors.New("incorrect state value")
}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This function call can be simplified as something like:

Suggested change
nextState = castStateToState(versionUpdate.State)
if nextState == nil {
return errors.New("incorrect state value")
}
nextState, ok = castStateToState(versionUpdate.State)
if !ok {
return errors.New("incorrect state value")
}

Thereby removing the need for a hard coded cast function.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This has now been amended

Comment on lines +497 to +503
// lockID, error := smDS.DataStore.Backend.AcquireInstanceLock(ctx, currentVersion.ID)
// if error != nil {
// return error
// }
// defer func() {
// smDS.DataStore.Backend.UnlockInstance(ctx, lockID)
// }()
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

A little tidy up needed

@franmoore05
Copy link
Contributor Author

Closing this PR - new one to follow once unit tests and component tests are fixed.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

2 participants