Skip to content

Commit

Permalink
feat(mongo): install juju-fb from local snap
Browse files Browse the repository at this point in the history
This feature was supposedly added 5 years ago here:
https://github.com/juju/juju/pull/9760/files

However, 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

Fixes: https://bugs.launchpad.net/juju/+bug/2075182
  • Loading branch information
jack-w-shaw committed Sep 2, 2024
1 parent 020c376 commit 3195627
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 3195627

Please sign in to comment.