Skip to content

Commit

Permalink
Merge pull request juju#17999 from jack-w-shaw/JUJU-6609_install_loca…
Browse files Browse the repository at this point in the history
…l_mongo_snap

juju#17999

When this feature was originally implemented some 5 years ago, it was only half-implemented

Complete this process. Allow users to deploy juju with a local juju-db snap using --db-snap and --db-snap-asserts.

This involved fixing the mongo snap service code to look in the correct directory for the .snap file.

We also completely neglected the .assert file. Pick that up at the same time.

Our snap service code, as well, also needed some serious refactoring to support:
- installing local snaps from .snap files
- acknowledging the assert file of local snaps

We should probably add an integration test for this, but that is out of scope.

## Checklist

- [x] Code style: imports ordered, good names, simple structure, etc
- [x] Comments saying why design decisions were made
- [x] Go unit tests, with comments saying what you're testing
- [ ] [Integration tests](https://github.com/juju/juju/tree/main/tests), with comments saying what you're testing

## QA steps

### Happy path

```
$ snap download juju-db --channel 4.4/stable
Fetching snap "juju-db"
Fetching assertions for "juju-db"
Install the snap with:
 snap ack juju-db_178.assert
 snap install juju-db_178.snap

$ juju bootstrap lxd lxd --db-snap ./juju-db_178.snap --db-snap-asserts ./juju-db_178.assert
```
Then check `juju debug-log -m controller --replay` for:
```
INFO juju.mongo Ensuring mongo server is running; data directory /var/snap/juju-db/common; port 37017
INFO juju.service.snap running snap command: [ack /var/lib/juju/snap/juju-db_178.assert]
INFO juju.service.snap running snap command: [install /var/lib/juju/snap/juju-db_178.snap]
```

### Sad path

```
$ rm ./juju-db_178.snap
$ rm ./juju-db_178.assert
$ touch ./juju-db_178.snap
$ touch ./juju-db_178.assert

$ juju bootstrap lxd lxd --db-snap ./juju-db_178.snap --db-snap-asserts ./juju-db_178.assert
...
INFO juju.service.snap snap.go:418 running snap command: [install /var/lib/juju/snap/juju-db_178.snap]
ERROR failed to start mongo: cannot install mongod: output: : attempt count exceeded: exit status 1
ERROR failed to bootstrap model: subprocess encountered error code 1
```

## Links

**Launchpad bug:** https://bugs.launchpad.net/juju/+bug/2075182
  • Loading branch information
jujubot authored Sep 2, 2024
2 parents 020c376 + 3195627 commit 5717831
Show file tree
Hide file tree
Showing 10 changed files with 394 additions and 108 deletions.
6 changes: 3 additions & 3 deletions cmd/jujud/agent/agenttest/agent.go
Original file line number Diff line number Diff line change
Expand Up @@ -60,7 +60,7 @@ func InstallFakeEnsureMongo(suite patchingSuite, dataDir string) *FakeEnsureMong
suite.PatchValue(&cmdutil.NewEnsureMongoParams, func(agentConfig agent.Config) (mongo.EnsureServerParams, error) {
params, err := ensureParams(agentConfig)
if err == nil {
params.DataDir = dataDir
params.MongoDataDir = dataDir
}
return params, err
})
Expand All @@ -72,7 +72,7 @@ func InstallFakeEnsureMongo(suite patchingSuite, dataDir string) *FakeEnsureMong
type FakeEnsureMongo struct {
EnsureCount int
InitiateCount int
DataDir string
MongoDataDir string
OplogSize int
Info controller.StateServingInfo
InitiateParams peergrouper.InitiateMongoParams
Expand All @@ -89,7 +89,7 @@ func (f *FakeEnsureMongo) CurrentConfig(*mgo.Session) (*replicaset.Config, error

func (f *FakeEnsureMongo) EnsureMongo(ctx context.Context, args mongo.EnsureServerParams) error {
f.EnsureCount++
f.DataDir, f.OplogSize = args.DataDir, args.OplogSize
f.MongoDataDir, f.OplogSize = args.MongoDataDir, args.OplogSize
f.Info = controller.StateServingInfo{
APIPort: args.APIPort,
StatePort: args.StatePort,
Expand Down
2 changes: 1 addition & 1 deletion cmd/jujud/agent/bootstrap_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -396,7 +396,7 @@ func (s *BootstrapSuite) TestInitializeModel(c *gc.C) {
err = cmd.Run(nil)
c.Assert(err, jc.ErrorIsNil)

c.Assert(s.fakeEnsureMongo.DataDir, gc.Equals, s.dataDir)
c.Assert(s.fakeEnsureMongo.MongoDataDir, gc.Equals, s.dataDir)
c.Assert(s.fakeEnsureMongo.InitiateCount, gc.Equals, 1)
c.Assert(s.fakeEnsureMongo.EnsureCount, gc.Equals, 1)
c.Assert(s.fakeEnsureMongo.OplogSize, gc.Equals, 1234)
Expand Down
74 changes: 49 additions & 25 deletions mongo/mongo.go
Original file line number Diff line number Diff line change
Expand Up @@ -187,8 +187,11 @@ type EnsureServerParams struct {
// SystemIdentity is the identity of the system.
SystemIdentity string

// DataDir is the machine agent data directory.
DataDir string
// MongoDataDir is the machine agent mongo data directory.
MongoDataDir string

// JujuDataDir is the directory where juju data is stored.
JujuDataDir string

// ConfigDir is where mongo config goes.
ConfigDir string
Expand Down Expand Up @@ -230,24 +233,27 @@ func ensureServer(ctx context.Context, args EnsureServerParams, mongoKernelTweak
}

mongoDep := dependency.Mongo(args.JujuDBSnapChannel)
if args.DataDir == "" {
args.DataDir = dataPathForJujuDbSnap
if args.MongoDataDir == "" {
args.MongoDataDir = dataPathForJujuDbSnap
}
if args.JujuDataDir == "" {
args.JujuDataDir = dataPathForJuju
}
if args.ConfigDir == "" {
args.ConfigDir = systemd.EtcSystemdDir
}

logger.Infof(
"Ensuring mongo server is running; data directory %s; port %d",
args.DataDir, args.StatePort,
args.MongoDataDir, args.StatePort,
)

if err := setupDataDirectory(args); err != nil {
return errors.Annotatef(err, "cannot set up data directory")
}

// TODO(wallyworld) - set up Numactl if requested in args.SetNUMAControlPolicy
svc, err := mongoSnapService(args.DataDir, args.ConfigDir, args.JujuDBSnapChannel)
svc, err := mongoSnapService(args.JujuDataDir, args.ConfigDir, args.JujuDBSnapChannel)
if err != nil {
return errors.Annotatef(err, "cannot create mongo snap service")
}
Expand All @@ -265,7 +271,7 @@ func ensureServer(ctx context.Context, args EnsureServerParams, mongoKernelTweak

oplogSizeMB := args.OplogSize
if oplogSizeMB == 0 {
oplogSizeMB, err = defaultOplogSize(dbDir(args.DataDir))
oplogSizeMB, err = defaultOplogSize(dbDir(args.MongoDataDir))
if err != nil {
return errors.Annotatef(err, "unable to calculate default oplog size")
}
Expand All @@ -275,11 +281,11 @@ func ensureServer(ctx context.Context, args EnsureServerParams, mongoKernelTweak

// Update snap configuration.
// TODO(tsm): refactor out to service.Configure
err = mongoArgs.writeConfig(configPath(args.DataDir))
err = mongoArgs.writeConfig(configPath(args.MongoDataDir))
if err != nil {
return errors.Annotatef(err, "unable to write config")
}
if err := snap.SetSnapConfig(ServiceName, "configpath", configPath(args.DataDir)); err != nil {
if err := snap.SetSnapConfig(ServiceName, "configpath", configPath(args.MongoDataDir)); err != nil {
return errors.Annotatef(err, "unable to set snap config")
}

Expand Down Expand Up @@ -355,19 +361,19 @@ func ensureMongoServiceRunning(ctx context.Context, svc MongoSnapService) error
}

func setupDataDirectory(args EnsureServerParams) error {
dbDir := dbDir(args.DataDir)
dbDir := dbDir(args.MongoDataDir)
if err := os.MkdirAll(dbDir, 0700); err != nil {
return errors.Annotate(err, "cannot create mongo database directory")
}

// TODO(fix): rather than copy, we should ln -s coz it could be changed later!!!
if err := UpdateSSLKey(args.DataDir, args.Cert, args.PrivateKey); err != nil {
if err := UpdateSSLKey(args.MongoDataDir, args.Cert, args.PrivateKey); err != nil {
return errors.Trace(err)
}

err := utils.AtomicWriteFile(sharedSecretPath(args.DataDir), []byte(args.SharedSecret), 0600)
err := utils.AtomicWriteFile(sharedSecretPath(args.MongoDataDir), []byte(args.SharedSecret), 0600)
if err != nil {
return errors.Annotatef(err, "cannot write mongod shared secret to %v", sharedSecretPath(args.DataDir))
return errors.Annotatef(err, "cannot write mongod shared secret to %v", sharedSecretPath(args.MongoDataDir))
}

if err := os.MkdirAll(logPath(dbDir), 0755); err != nil {
Expand Down Expand Up @@ -421,17 +427,26 @@ func logVersion(mongoPath string) {
}

func mongoSnapService(dataDir, configDir, snapChannel string) (MongoSnapService, error) {
snapName := JujuDbSnap
jujuDbLocalSnapPattern := regexp.MustCompile(`juju-db_[0-9]+\.snap`)
jujuDbLocalAssertsPattern := regexp.MustCompile(`juju-db_[0-9]+\.assert`)

// If we're installing a local snap, then provide an absolute path
// as a snap <name>. snap install <name> will then do the Right Thing (TM).
files, err := os.ReadDir(path.Join(dataDir, "snap"))
snapDir := path.Join(dataDir, "snap")
files, err := os.ReadDir(snapDir)

var (
snapPath string
snapAssertsPath string
)
if err == nil {
for _, fullFileName := range files {
_, fileName := path.Split(fullFileName.Name())
fileName := fullFileName.Name()
if jujuDbLocalSnapPattern.MatchString(fileName) {
snapName = fullFileName.Name()
snapPath = path.Join(snapDir, fileName)
}
if jujuDbLocalAssertsPattern.MatchString(fileName) {
snapAssertsPath = path.Join(snapDir, fileName)
}
}
}
Expand All @@ -447,8 +462,17 @@ func mongoSnapService(dataDir, configDir, snapChannel string) (MongoSnapService,
Desc: ServiceName + " snap",
Limit: mongoULimits,
}
svc, err := newSnapService(
snapName, ServiceName, conf, snap.Command, configDir, snapChannel, "", backgroundServices, []snap.Installable{})

svc, err := newSnapService(snap.ServiceConfig{
ServiceName: ServiceName,
SnapPath: snapPath,
SnapAssertsPath: snapAssertsPath,
Conf: conf,
SnapExecutable: snap.Command,
ConfigDir: configDir,
Channel: snapChannel,
BackgroundServices: backgroundServices,
})
return svc, errors.Trace(err)
}

Expand All @@ -457,13 +481,12 @@ var installMongo = packaging.InstallDependency

func installMongod(mongoDep packaging.Dependency, hostSeries string, snapSvc MongoSnapService) error {
// Do either a local snap install or a real install from the store.
if snapSvc.Name() == ServiceName {
// Store snap.
return installMongo(mongoDep, hostSeries)
} else {
if snapSvc.IsLocal() {
// Local snap.
return snapSvc.Install()
}
// Store snap.
return installMongo(mongoDep, hostSeries)
}

// dbDir returns the dir where mongo storage is.
Expand All @@ -478,13 +501,14 @@ type MongoSnapService interface {
Running() (bool, error)
ConfigOverride() error
Name() string
IsLocal() bool
Start() error
Restart() error
Install() error
}

var newSnapService = func(mainSnap, serviceName string, conf common.Conf, snapPath, configDir, channel string, confinementPolicy snap.ConfinementPolicy, backgroundServices []snap.BackgroundService, prerequisites []snap.Installable) (MongoSnapService, error) {
return snap.NewService(mainSnap, serviceName, conf, snapPath, configDir, channel, confinementPolicy, backgroundServices, prerequisites)
var newSnapService = func(config snap.ServiceConfig) (MongoSnapService, error) {
return snap.NewService(config)
}

// CurrentReplicasetConfig is overridden in tests.
Expand Down
42 changes: 36 additions & 6 deletions mongo/mongo_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,6 @@ import (
"github.com/juju/juju/mongo"
"github.com/juju/juju/mongo/mongotest"
"github.com/juju/juju/packaging"
"github.com/juju/juju/service/common"
"github.com/juju/juju/service/snap"
coretesting "github.com/juju/juju/testing"
)
Expand Down Expand Up @@ -59,7 +58,8 @@ func makeEnsureServerParams(dataDir, configDir string) mongo.EnsureServerParams
PrivateKey: testInfo.PrivateKey,
SharedSecret: testInfo.SharedSecret,

DataDir: dataDir,
MongoDataDir: dataDir,
JujuDataDir: dataDir,
ConfigDir: configDir,
JujuDBSnapChannel: "latest",

Expand Down Expand Up @@ -102,13 +102,25 @@ func (s *MongoSuite) setupMocks(c *gc.C) *gomock.Controller {

func (s *MongoSuite) expectInstallMongoSnap() {
mExp := s.mongoSnapService.EXPECT()
mExp.Name().Return("not-juju-db")
mExp.IsLocal().Return(false)
mExp.ConfigOverride().Return(nil)
mExp.Start().Return(nil)
mExp.Running().Return(true, nil).AnyTimes()

s.PatchValue(mongo.NewSnapService, func(config snap.ServiceConfig) (mongo.MongoSnapService, error) {
return s.mongoSnapService, nil
})
}

func (s *MongoSuite) expectInstallLocalMongoSnap() {
mExp := s.mongoSnapService.EXPECT()
mExp.IsLocal().Return(true)
mExp.Install().Return(nil)
mExp.ConfigOverride().Return(nil)
mExp.Start().Return(nil).AnyTimes()
mExp.Start().Return(nil)
mExp.Running().Return(true, nil).AnyTimes()

s.PatchValue(mongo.NewSnapService, func(mainSnap, serviceName string, conf common.Conf, snapPath, configDir, channel string, confinementPolicy snap.ConfinementPolicy, backgroundServices []snap.BackgroundService, prerequisites []snap.Installable) (mongo.MongoSnapService, error) {
s.PatchValue(mongo.NewSnapService, func(config snap.ServiceConfig) (mongo.MongoSnapService, error) {
return s.mongoSnapService, nil
})
}
Expand Down Expand Up @@ -217,6 +229,24 @@ func (s *MongoSuite) TestEnsureServerInstalledSetsSysctlValues(c *gc.C) {
c.Assert(string(contents), gc.Equals, "new value")
}

func (s *MongoSuite) TestEnsureServerInstalledLocalSnap(c *gc.C) {
defer s.setupMocks(c).Finish()
s.expectInstallLocalMongoSnap()

dataDir := c.MkDir()
configDir := c.MkDir()

testing.PatchExecutableAsEchoArgs(c, s, "snap")

s.PatchValue(mongo.InstallMongo, func(dep packaging.Dependency, series string) error {
c.Fatalf("unexpected call to InstallMongo")
return nil
})

err := mongo.EnsureServerInstalled(context.Background(), makeEnsureServerParams(dataDir, configDir))
c.Assert(err, jc.ErrorIsNil)
}

func (s *MongoSuite) TestEnsureServerInstalledError(c *gc.C) {
defer s.setupMocks(c).Finish()

Expand All @@ -231,7 +261,7 @@ func (s *MongoSuite) TestEnsureServerInstalledError(c *gc.C) {
})

err := mongo.EnsureServerInstalled(context.Background(), makeEnsureServerParams(dataDir, configDir))
c.Assert(errors.Cause(err), gc.Equals, failure)
c.Assert(errors.Cause(err), gc.Equals, failure, gc.Commentf("unexpected error: %v", err))
}

func (s *MongoSuite) assertEnsureServerIPv6(c *gc.C, ipv6 bool) string {
Expand Down
14 changes: 14 additions & 0 deletions mongo/mongotest/mongoservice_mock.go

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

13 changes: 8 additions & 5 deletions mongo/service.go
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ import (
"github.com/juju/errors"
"github.com/juju/utils/v3"

"github.com/juju/juju/core/paths"
"github.com/juju/juju/network"
)

Expand Down Expand Up @@ -46,6 +47,8 @@ const (
FileNameDBSSLKey = "server.pem"
)

var dataPathForJuju = paths.DataDir(paths.CurrentOS())

// See https://docs.mongodb.com/manual/reference/ulimit/.
var mongoULimits = map[string]string{
"fsize": "unlimited", // file size
Expand Down Expand Up @@ -244,9 +247,9 @@ func generateConfig(oplogSizeMB int, args EnsureServerParams) *ConfigArgs {

mongoArgs := &ConfigArgs{
Clock: clock.WallClock,
DataDir: args.DataDir,
DBDir: dbDir(args.DataDir),
LogPath: logPath(args.DataDir),
DataDir: args.MongoDataDir,
DBDir: dbDir(args.MongoDataDir),
LogPath: logPath(args.MongoDataDir),
Port: args.StatePort,
OplogSizeMB: oplogSizeMB,
IPv6: supportsIPv6(),
Expand All @@ -258,8 +261,8 @@ func generateConfig(oplogSizeMB int, args EnsureServerParams) *ConfigArgs {
SlowMS: 1000,
Quiet: true,
ReplicaSet: ReplicaSetName,
AuthKeyFile: sharedSecretPath(args.DataDir),
PEMKeyFile: sslKeyPath(args.DataDir),
AuthKeyFile: sharedSecretPath(args.MongoDataDir),
PEMKeyFile: sslKeyPath(args.MongoDataDir),
PEMKeyPassword: "ignored", // used as boilerplate later
TLSOnNormalPorts: false,
TLSMode: "requireTLS",
Expand Down
Loading

0 comments on commit 5717831

Please sign in to comment.