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

Orca 679 global atlantis lock new release branch #49

Conversation

msarvar
Copy link

@msarvar msarvar commented Mar 4, 2021

Added support to create a command specific lock entries to the db/bolt_db, LockCommand, UnlockCommand, and CheckCommandLock.

Implemented a new ApplyLocker interface for locking.go#Client that manages apply lock creation/deletion and lock retrieval.

LocksController has 2 handlers to create and delete apply locks.
Server#Index renders the UI to create/delete the locks.
ApplyCommandRunner expects an object that implements ApplyCommandLocker which boils down to object having IsDisabled function that returns boolean value.
DefaultApplyCommandLocker implements the interface.

In UI it is just a AJAX request that will create/delete the locks for apply. To create a new lock or delete existing one do to atlantis main page.

Recreated #48 against new release branch.

@msarvar
Copy link
Author

msarvar commented Mar 4, 2021

/ptal @nishkrishnan

Copy link
Contributor

@nishkrishnan nishkrishnan left a comment

Choose a reason for hiding this comment

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

Nice work, have a few comments

transactionErr := b.db.Update(func(tx *bolt.Tx) error {
bucket := tx.Bucket(b.globalLocksBucketName)

currLockSerialized := bucket.Get([]byte(b.commandLockKey(cmdName)))
Copy link
Contributor

Choose a reason for hiding this comment

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

Do we want to make this idempotent? Personally, would rather error out if the lock exists. Think about TFE returning a 409 in a similar situation.

func (b *BoltDB) LockCommand(cmdName models.CommandName, lockTime time.Time) (*models.CommandLock, error) {
lock := models.CommandLock{
CommandName: cmdName,
Time: lockTime,
Copy link
Contributor

Choose a reason for hiding this comment

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

I'd lean to having like a LockMetadata struct which contains the time in addition to a description. Eventually we might want to add user there for example and just makes it easy to add without changing the interface.

//go:generate pegomock generate -m --use-experimental-model-gen --package mocks -o mocks/mock_apply_command_locker.go ApplyCommandLocker

type ApplyCommandLocker interface {
IsDisabled(ctx *CommandContext) bool
Copy link
Contributor

Choose a reason for hiding this comment

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

ie. This struct isn't responsible for performing the locking, it's responsible for checking whether a lock exists. Confusing naming imo.

Id suggest renaming this to GlobalApplyLockChecker with the method isLocked (disabled also doesn't fit well if the name of the struct doesn't include that)


func NewApplyCommandLocker(
applyLockChecker locking.ApplyLockChecker,
disableApply bool,
Copy link
Contributor

Choose a reason for hiding this comment

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

can we rename this to disableApplyFlag or something that hints at this being a global server flag?

// IsDisabled returns true if there is a global apply command lock or
// DisableApply flag is set to true
func (a *DefaultApplyCommandLocker) IsDisabled(ctx *CommandContext) bool {
lock, err := a.ApplyLockChecker.CheckApplyLock()
Copy link
Contributor

Choose a reason for hiding this comment

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

Looks like command context is barely used here. The logger embedded in there is useful if we want to debug per-pull issues. However, the global apply lock fetching error doesn't seem necessary to log at that level, you can just keep that logger embedded in the struct.

If you do the above, you can just use a single interface delegation pattern for:

  1. fetching the global lock
  2. comparing the result of that to the global server config flag.

@@ -53,7 +88,7 @@ func (a *ApplyCommandRunner) Run(ctx *CommandContext, cmd *CommentCommand) {
baseRepo := ctx.Pull.BaseRepo
pull := ctx.Pull

if a.DisableApply {
if a.locker.IsDisabled(ctx) {
ctx.Log.Info("ignoring apply command since apply disabled globally")
if err := a.vcsClient.CreateComment(baseRepo, pull.Num, applyDisabledComment, models.ApplyCommand.String()); err != nil {
Copy link
Contributor

Choose a reason for hiding this comment

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

The applyDisabledComment is very generic, would be good to think about how to make that more useful depending on how the apply command lock is configured. Not in scope for this PR though.

}

// CheckApplyLock retrieves an apply command lock if present.
func (c *Client) CheckApplyLock() (ApplyCommandLockResponse, error) {
Copy link
Contributor

Choose a reason for hiding this comment

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

Sounds to me like this should also follow the same logic of checking the server configuration as well. We should also probably not allow unlocking of atlantis if it's configured to disable applies through server configuration both.

Right now the implementation doesn't do that which is good, however, it'll say it's locked and unlocked it, but applies still won't work if the server configuration has it defined.

func (c *Client) LockApply() (ApplyCommandLockResponse, error) {
response := ApplyCommandLockResponse{}

applyCmdLock, err := c.backend.LockCommand(models.ApplyCommand, time.Now().Local())
Copy link
Contributor

Choose a reason for hiding this comment

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

Best practice is to use unix timestamps if we are storing data.

nishkrishnan
nishkrishnan previously approved these changes Mar 10, 2021
"github.com/runatlantis/atlantis/server/events/models/fixtures"
)

// func setupApplyCmd(t *testing.T) *events.ApplyCommandRunner {
Copy link
Contributor

Choose a reason for hiding this comment

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

Commented?

When(logger.GetLevel()).ThenReturn(logging.Info)
When(logger.NewLogger("runatlantis/atlantis#1", true, logging.Info)).
ThenReturn(pullLogger)

scope := stats.NewStore(stats.NewNullSink(), false)
Copy link
Contributor

Choose a reason for hiding this comment

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

Was this added for fixing the tests you thought were broken? lol do we still need it?

Copy link
Author

Choose a reason for hiding this comment

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

No, this is to remove the logging warnings about statsd failing to reach the localhost. Just to reduce the noise :)

pullKeySeparator = "::"
locksBucketName = "runLocks"
pullsBucketName = "pulls"
globalLocksBucketName = "global"
Copy link
Contributor

Choose a reason for hiding this comment

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

missed this earlier but let's name it globalLocks

Comment on lines 175 to 176
// If the lock already exists, it will just return pointer to the existing lock
// If lock creation fails it will return nil
Copy link
Contributor

Choose a reason for hiding this comment

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

Probably should update this to reflect the new changes.

@msarvar
Copy link
Author

msarvar commented Mar 10, 2021

💨

@msarvar msarvar merged commit 5db4734 into release-v0.17.0-beta-lyft.1 Mar 12, 2021
nishkrishnan pushed a commit that referenced this pull request Apr 6, 2021
* Orca 679 global atlantis lock new release branch (#49)

* Adding CommandLocker to boltDB and exposing it through locker interface

* Apply lock ui and apply command lock controller

* Minor comments

* Adding more tests and refactorinng

* Linting fixes

* creating applyLockingClient variable to fix interface error

* nullsink for stats

* Addressing PR comments

* fixing e2e tests

* linting fix fml

* Update outdated function descriptions

Address PR comments

* revert stats sink changes

* remove unnecessary dependencies
nishkrishnan pushed a commit that referenced this pull request Jun 28, 2021
* Adding CommandLocker to boltDB and exposing it through locker interface

* Apply lock ui and apply command lock controller

* Minor comments

* Adding more tests and refactorinng

* Linting fixes

* creating applyLockingClient variable to fix interface error

* nullsink for stats

* Addressing PR comments

* fixing e2e tests

* linting fix fml

* Update outdated function descriptions

Address PR comments

* revert stats sink changes
msarvar added a commit that referenced this pull request Sep 27, 2021
* Adding CommandLocker to boltDB and exposing it through locker interface

* Apply lock ui and apply command lock controller

* Minor comments

* Adding more tests and refactorinng

* Linting fixes

* creating applyLockingClient variable to fix interface error

* nullsink for stats

* Addressing PR comments

* fixing e2e tests

* linting fix fml

* Update outdated function descriptions

Address PR comments

* revert stats sink changes
@mikecutalo mikecutalo deleted the ORCA-679-global-atlantis-lock-new-release-branch branch June 8, 2023 18:29
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