Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Merge pull request #7957 from axw/global-clock-leases
Introduce and use global clock in lease manager ## Description of change This PR introduces a global clock document to the database. Each controller machine agent runs a worker that will periodically (1s) attempt to advance the global time by the same (period) amount. Concurrent updates will be prevented, and unsuccessful workers will back off for a short (30s) delay. We guarantee that the global time is monotonically increasing, and increases at a rate no faster than wall clock time. We pass a global clock reader into the state/lease client, so that it can compute expiry times based on the global time. Lease documents store the start time as a global time, and the request duration; we then make the expiry time relative to the local time for use by clients. The local time is expected to contain a monotonic component (i.e. Go 1.9+), so it can be compared to time.Now(). We refresh the global clock time when a claim or extension is made, to guarantee that the lease is held for at least the specified duration. When a lease is to be expired, we first attempt to use the most recent global clock time, and then refresh if the lease expires after the cached time, only failing if the lease expires after the refreshed time. ## QA steps 1. juju bootstrap localhost 2. juju enable-ha 3. juju deploy ubuntu -n 3 Identify the lease holders for "singular-controller" and "ubuntu", and stop those agents (controller machine agent and unit agent respectively). Another agent should eventually claim the leases. Also: 1. juju bootstrap localhost (with juju 2.2.5) 2. juju deploy postgresql -n 3 (wait for postgresql units to become idle, take note of the leader) 3. juju enable-ha (wait for additional controllers to be voting) 4. juju upgrade-juju -m controller (with this branch) Verify that: - there are no old docs in the leases collection - the leases are kept by the previous holders across upgrade ## Documentation changes None. ## Bug reference Fixes https://bugs.launchpad.net/juju/+bug/1706340
- Loading branch information
Showing
44 changed files
with
1,722 additions
and
791 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,34 @@ | ||
// Copyright 2017 Canonical Ltd. | ||
// Licensed under the AGPLv3, see LICENCE file for details. | ||
|
||
package globalclock | ||
|
||
import ( | ||
"time" | ||
|
||
"github.com/juju/errors" | ||
) | ||
|
||
var ( | ||
// ErrConcurrentUpdate is returned by Updater.Advance when the | ||
// clock value has been changed since the last read. | ||
ErrConcurrentUpdate = errors.New("clock was updated concurrently, retry") | ||
) | ||
|
||
// Updater provides a means of updating the global clock time. | ||
type Updater interface { | ||
// Advance adds the given duration to the global clock, ensuring | ||
// that the clock has not been updated concurrently. | ||
// | ||
// Advance will return ErrConcurrentUpdate if another updater | ||
// updates the clock concurrently. In this case, the updater | ||
// will refresh its view of the clock, and the caller can | ||
// attempt Advance later. | ||
// | ||
// If Advance returns any error other than ErrConcurrentUpdate, | ||
// the Updater should be considered invalid, and the caller | ||
// should obtain a new Updater. Failing to do so could lead | ||
// to non-monotonic time, since there is no way of knowing in | ||
// general whether or not the database was updated. | ||
Advance(d time.Duration) error | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,51 @@ | ||
// Copyright 2017 Canonical Ltd. | ||
// Licensed under the AGPLv3, see LICENCE file for details. | ||
|
||
package globalclock | ||
|
||
import ( | ||
"github.com/juju/errors" | ||
|
||
"github.com/juju/juju/mongo" | ||
) | ||
|
||
// UpdaterConfig contains the resources and information required to | ||
// create an Updater. | ||
type UpdaterConfig struct { | ||
Config | ||
} | ||
|
||
// ReaderConfig contains the resources and information required to | ||
// create a Reader. | ||
type ReaderConfig struct { | ||
Config | ||
} | ||
|
||
// Config contains the common resources and information required to | ||
// create an Updater or Reader. | ||
type Config struct { | ||
// Collection names the MongoDB collection in which the clock | ||
// documents are stored. | ||
Collection string | ||
|
||
// Mongo exposes the mgo capabilities required by a Client | ||
// for updating and reading the clock. | ||
Mongo Mongo | ||
} | ||
|
||
// Mongo exposes MongoDB operations for use by the globalclock package. | ||
type Mongo interface { | ||
// GetCollection should probably call the mongo.CollectionFromName func | ||
GetCollection(name string) (collection mongo.Collection, closer func()) | ||
} | ||
|
||
// validate returns an error if the supplied config is not valid. | ||
func (config Config) validate() error { | ||
if config.Collection == "" { | ||
return errors.New("missing collection") | ||
} | ||
if config.Mongo == nil { | ||
return errors.New("missing mongo client") | ||
} | ||
return nil | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,23 @@ | ||
// Copyright 2017 Canonical Ltd. | ||
// Licensed under the AGPLv3, see LICENCE file for details. | ||
|
||
/* | ||
Package globalclock provides clients for updating and reading the | ||
global virtual time, stored in the MongoDB database. | ||
Multiple global clock updaters may run concurrently, but concurrent | ||
updates will fail. This simplifies failover in a multi-node controller, | ||
while preserving the invariant that a global clock second is at least | ||
as long as a wall-clock second. | ||
Schema design | ||
------------- | ||
We maintain a single collection, with a single document containing | ||
the current global time. Whenever time is to be advanced, we update | ||
the document while ensuring that the global time has not advanced by | ||
any other updater. | ||
*/ | ||
package globalclock |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,14 @@ | ||
// Copyright 2017 Canonical Ltd. | ||
// Licensed under the AGPLv3, see LICENCE file for details. | ||
|
||
package globalclock_test | ||
|
||
import ( | ||
"testing" | ||
|
||
coretesting "github.com/juju/juju/testing" | ||
) | ||
|
||
func TestPackage(t *testing.T) { | ||
coretesting.MgoTestPackage(t) | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,45 @@ | ||
// Copyright 2017 Canonical Ltd. | ||
// Licensed under the AGPLv3, see LICENCE file for details. | ||
|
||
package globalclock | ||
|
||
import ( | ||
"time" | ||
|
||
"github.com/juju/errors" | ||
"gopkg.in/mgo.v2" | ||
) | ||
|
||
// Reader provides a means of reading the global clock time. | ||
// | ||
// Reader is not goroutine-safe. | ||
type Reader struct { | ||
config ReaderConfig | ||
} | ||
|
||
// NewReader returns a new Reader using the supplied config, or an error. | ||
// | ||
// Readers will not function past the lifetime of their configured Mongo. | ||
func NewReader(config ReaderConfig) (*Reader, error) { | ||
if err := config.validate(); err != nil { | ||
return nil, errors.Trace(err) | ||
} | ||
r := &Reader{config: config} | ||
return r, nil | ||
} | ||
|
||
// Now returns the current global time. | ||
func (r *Reader) Now() (time.Time, error) { | ||
coll, closer := r.config.Mongo.GetCollection(r.config.Collection) | ||
defer closer() | ||
|
||
t, err := readClock(coll) | ||
if errors.Cause(err) == mgo.ErrNotFound { | ||
// No time written yet. When it is written | ||
// for the first time, it'll be globalEpoch. | ||
t = globalEpoch | ||
} else if err != nil { | ||
return time.Time{}, errors.Trace(err) | ||
} | ||
return t, nil | ||
} |
Oops, something went wrong.