Skip to content

Commit

Permalink
Install mongo from snap in focal and later when bootstrapping controller
Browse files Browse the repository at this point in the history
  • Loading branch information
achilleasa committed Apr 16, 2020
1 parent 484fa89 commit c18a5bd
Show file tree
Hide file tree
Showing 9 changed files with 228 additions and 122 deletions.
1 change: 1 addition & 0 deletions cmd/jujud/agent/bootstrap.go
Original file line number Diff line number Diff line change
Expand Up @@ -270,6 +270,7 @@ func (c *BootstrapCommand) Run(_ *cmd.Context) error {
} else {
agentConfig.SetMongoMemoryProfile(mmprof)
}
agentConfig.SetMongoSnapChannel(args.ControllerConfig.MongoSnapChannel())
return nil
}); err != nil {
return fmt.Errorf("cannot write agent config: %v", err)
Expand Down
3 changes: 2 additions & 1 deletion cmd/jujud/util/util.go
Original file line number Diff line number Diff line change
Expand Up @@ -225,7 +225,8 @@ func NewEnsureServerParams(agentConfig agent.Config) (mongo.EnsureServerParams,
OplogSize: oplogSize,
SetNUMAControlPolicy: numaCtlPolicy,

MemoryProfile: agentConfig.MongoMemoryProfile(),
MemoryProfile: agentConfig.MongoMemoryProfile(),
MongoSnapChannel: agentConfig.MongoSnapChannel(),
}
return params, nil
}
2 changes: 1 addition & 1 deletion mongo/export_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -38,7 +38,7 @@ func PatchService(patchValue func(interface{}, interface{}), data *svctesting.Fa
svc.FakeServiceData = data
return svc, nil
})
patchValue(&newService, func(name string, conf common.Conf) (mongoService, error) {
patchValue(&newService, func(name string, _ bool, conf common.Conf) (mongoService, error) {
svc := svctesting.NewFakeService(name, conf)
svc.FakeServiceData = data
return svc, nil
Expand Down
141 changes: 82 additions & 59 deletions mongo/mongo.go
Original file line number Diff line number Diff line change
Expand Up @@ -443,6 +443,9 @@ type EnsureServerParams struct {
// MemoryProfile determines which value is going to be used by
// the cache and future memory tweaks.
MemoryProfile MemoryProfile

// The channel for installing the mongo snap in focal and later.
MongoSnapChannel string
}

// EnsureServer ensures that the MongoDB server is installed,
Expand All @@ -454,38 +457,19 @@ func EnsureServer(args EnsureServerParams) (Version, error) {
return ensureServer(args, mongoKernelTweaks)
}

func setupDataDirectory(args EnsureServerParams) error {
dbDir := DbDir(args.DataDir)
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 {
return errors.Trace(err)
}

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

if err := os.MkdirAll(logPath(dbDir), 0644); err != nil {
return errors.Annotate(err, "cannot create mongodb logging directory")
}

return nil
}

func ensureServer(args EnsureServerParams, mongoKernelTweaks map[string]string) (Version, error) {
var zeroVersion Version
tweakSysctlForMongo(mongoKernelTweaks)

hostSeries := series.MustHostSeries()
mongoDep := dependency.Mongo(args.SetNUMAControlPolicy, args.MongoSnapChannel)
usingMongoFromSnap := providesMongoAsSnap(mongoDep, hostSeries) || featureflag.Enabled(feature.MongoDbSnap)

// TODO(tsm): clean up the args.DataDir handling. When using a snap, args.DataDir should be
// set earlier in the bootstrapping process. An extra variable is needed here because
// cloudconfig sends local snaps to /var/lib/juju/
dataDir := args.DataDir
if featureflag.Enabled(feature.MongoDbSnap) {
if usingMongoFromSnap {
if args.DataDir != dataPathForJujuDbSnap {
logger.Warningf("overwriting args.dataDir (set to %v) to %v", args.DataDir, dataPathForJujuDbSnap)
args.DataDir = dataPathForJujuDbSnap
Expand All @@ -497,9 +481,9 @@ func ensureServer(args EnsureServerParams, mongoKernelTweaks map[string]string)
args.DataDir, args.StatePort,
)

setupDataDirectory(args)
setupDataDirectory(args, usingMongoFromSnap)

if err := installMongod(series.MustHostSeries(), args.SetNUMAControlPolicy, dataDir); err != nil {
if err := installMongod(mongoDep, hostSeries, dataDir); err != nil {
// This isn't treated as fatal because the Juju MongoDB
// package is likely to be already installed anyway. There
// could just be a temporary issue with apt-get/yum/whatever
Expand Down Expand Up @@ -536,17 +520,19 @@ func ensureServer(args EnsureServerParams, mongoKernelTweaks map[string]string)
}
}

mongoArgs := generateConfig(mongoPath, args.DataDir, args.StatePort, oplogSizeMB, args.SetNUMAControlPolicy, mongodVersion, args.MemoryProfile)
mongoArgs := generateConfig(mongoPath, oplogSizeMB, mongodVersion, usingMongoFromSnap, args)
logger.Debugf("creating mongo service configuration for mongo version: %d.%d.%d-%s at %q",
mongoArgs.Version.Major, mongoArgs.Version.Minor, mongoArgs.Version.Point, mongoArgs.Version.Patch, mongoArgs.MongoPath)

svc, err := mongoArgs.asService()
svc, err := mongoArgs.asService(usingMongoFromSnap)
if err != nil {
return zeroVersion, errors.Trace(err)
}

// Update configuration if we are using mongo from snap (focal+ or
// on an earlier series using the feature flag).
// TODO(tsm): refactor out to service.Configure
if featureflag.Enabled(feature.MongoDbSnap) {
if usingMongoFromSnap {
err = mongoArgs.writeConfig(configPath(args.DataDir))
if err != nil {
return zeroVersion, errors.Trace(err)
Expand Down Expand Up @@ -612,6 +598,29 @@ func ensureServer(args EnsureServerParams, mongoKernelTweaks map[string]string)
return mongodVersion, nil
}

func setupDataDirectory(args EnsureServerParams, usingMongoFromSnap bool) error {
dbDir := DbDir(args.DataDir)
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 {
return errors.Trace(err)
}

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

if err := os.MkdirAll(logPath(dbDir, usingMongoFromSnap), 0644); err != nil {
return errors.Annotate(err, "cannot create mongodb logging directory")
}

return nil
}

func truncateAndWriteIfExists(procFile, value string) error {
if _, err := os.Stat(procFile); os.IsNotExist(err) {
logger.Debugf("%q does not exist, will not set %q", procFile, value)
Expand Down Expand Up @@ -669,45 +678,59 @@ func logVersion(mongoPath string) {
logger.Debugf("using mongod: %s --version: %q", mongoPath, output)
}

func getSnapChannel() string {
return fmt.Sprintf("%s/%s", SnapTrack, SnapRisk)
}

func installMongod(operatingsystem string, numaCtl bool, dataDir string) error {
if featureflag.Enabled(feature.MongoDbSnap) {
snapName := ServiceName
jujuDbLocalSnapPattern := regexp.MustCompile(`juju-db_[0-9]+\.snap`)

// 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 := ioutil.ReadDir(path.Join(dataDir, "snap"))
if err == nil {
for _, fullFileName := range files {
_, fileName := path.Split(fullFileName.Name())
if jujuDbLocalSnapPattern.MatchString(fileName) {
snapName = fullFileName.Name()
}
func installMongod(mongoDep packaging.Dependency, hostSeries, dataDir string) error {
// If we are not forcing a mongo snap via a feature flag, install the
// package list (which may also include snaps for focal+) provided by
// the mongo dependency for our series.
if !featureflag.Enabled(feature.MongoDbSnap) {
return packaging.InstallDependency(mongoDep, hostSeries)
}

snapName := ServiceName
jujuDbLocalSnapPattern := regexp.MustCompile(`juju-db_[0-9]+\.snap`)

// 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 := ioutil.ReadDir(path.Join(dataDir, "snap"))
if err == nil {
for _, fullFileName := range files {
_, fileName := path.Split(fullFileName.Name())
if jujuDbLocalSnapPattern.MatchString(fileName) {
snapName = fullFileName.Name()
}
}

prerequisites := []snap.App{snap.NewApp("core")}
backgroundServices := []snap.BackgroundService{{"daemon", true}}
conf := common.Conf{Desc: ServiceName + " snap"}
service, err := snap.NewService(snapName, ServiceName, conf, snap.Command, getSnapChannel(), "", backgroundServices, prerequisites)
if err != nil {
return errors.Trace(err)
}
return service.Install()
}

if err := packaging.InstallDependency(dependency.Mongo(numaCtl), operatingsystem); err != nil {
return err
prerequisites := []snap.App{snap.NewApp("core")}
backgroundServices := []snap.BackgroundService{
{
Name: "daemon",
EnableAtStartup: true,
},
}
conf := common.Conf{Desc: ServiceName + " snap"}
snapChannel := fmt.Sprintf("%s/%s", SnapTrack, SnapRisk)
service, err := snap.NewService(snapName, ServiceName, conf, snap.Command, snapChannel, "", backgroundServices, prerequisites)
if err != nil {
return errors.Trace(err)
}

return nil
return service.Install()
}

// DbDir returns the dir where mongo storage is.
func DbDir(dataDir string) string {
return filepath.Join(dataDir, "db")
}

// providesMongoAsSnap returns true if a mongo dependency provides mongo
// as a snap for the specified OS series.
func providesMongoAsSnap(mongoDep packaging.Dependency, series string) bool {
pkgList, _ := mongoDep.PackageList(series)
for _, pkg := range pkgList {
if pkg.PackageManager == packaging.SnapPackageManager && pkg.Name == JujuDbSnap {
return true
}
}
return false
}
8 changes: 4 additions & 4 deletions mongo/mongodfinder.go
Original file line number Diff line number Diff line change
Expand Up @@ -10,9 +10,6 @@ import (
"strconv"

"github.com/juju/errors"
"github.com/juju/utils/featureflag"

"github.com/juju/juju/feature"
)

// SearchTools represents the OS functionality we need to find the correct MongoDB executable.
Expand Down Expand Up @@ -40,11 +37,14 @@ func NewMongodFinder() *MongodFinder {

// FindBest tries to find the mongo version that best fits what we want to use.
func (m *MongodFinder) FindBest() (string, Version, error) {
if featureflag.Enabled(feature.MongoDbSnap) {
// Look for the snap version; focal and beyond OR if the relevant snap
// feature flag is enabled.
if m.search.Exists(JujuDbSnapMongodPath) {
v, err := m.findVersion(JujuDbSnapMongodPath)
if err != nil {
return "", Version{}, errors.NotFoundf("%s snap not installed correctly. Executable %s", JujuDbSnap, JujuDbSnapMongodPath)
}

return JujuDbSnapMongodPath, v, nil
}

Expand Down
38 changes: 38 additions & 0 deletions mongo/mongodfinder_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,7 @@ func (s *MongodFinderSuite) TestFindSystemMongo36(c *gc.C) {
defer s.ctrl.Finish()
exp := s.search.EXPECT()
gomock.InOrder(
exp.Exists("/snap/bin/juju-db.mongod").Return(false),
exp.Exists("/usr/bin/mongod").Return(true),
exp.GetCommandOutput("/usr/bin/mongod", "--version").Return(mongodb36Version, nil),
)
Expand All @@ -61,6 +62,7 @@ func (s *MongodFinderSuite) TestFindSystemMongo34(c *gc.C) {
defer s.ctrl.Finish()
exp := s.search.EXPECT()
gomock.InOrder(
exp.Exists("/snap/bin/juju-db.mongod").Return(false),
exp.Exists("/usr/bin/mongod").Return(true),
exp.GetCommandOutput("/usr/bin/mongod", "--version").Return(mongodb34Version, nil),
)
Expand All @@ -80,6 +82,7 @@ func (s *MongodFinderSuite) TestFindJujuMongodb(c *gc.C) {
defer s.ctrl.Finish()
exp := s.search.EXPECT()
gomock.InOrder(
exp.Exists("/snap/bin/juju-db.mongod").Return(false),
exp.Exists("/usr/bin/mongod").Return(false),
exp.Exists("/usr/lib/juju/mongo3.2/bin/mongod").Return(false),
exp.Exists("/usr/lib/juju/bin/mongod").Return(true),
Expand All @@ -103,6 +106,7 @@ func (s *MongodFinderSuite) TestFindJujuMongodbIgnoringSystemMongodb(c *gc.C) {
// However, we don't use the system mongod. It might not have --ssl, an it probably also has the Javascript engine
exp := s.search.EXPECT()
gomock.InOrder(
exp.Exists("/snap/bin/juju-db.mongod").Return(false),
exp.Exists("/usr/bin/mongod").Return(true),
exp.GetCommandOutput("/usr/bin/mongod", "--version").Return(mongodb24Version, nil),
exp.Exists("/usr/lib/juju/mongo3.2/bin/mongod").Return(false),
Expand All @@ -125,6 +129,7 @@ func (s *MongodFinderSuite) TestFindJujuMongodb32(c *gc.C) {
defer s.ctrl.Finish()
exp := s.search.EXPECT()
gomock.InOrder(
exp.Exists("/snap/bin/juju-db.mongod").Return(false),
exp.Exists("/usr/bin/mongod").Return(false),
exp.Exists("/usr/lib/juju/mongo3.2/bin/mongod").Return(true),
exp.GetCommandOutput("/usr/lib/juju/mongo3.2/bin/mongod", "--version").Return(mongodb32Version, nil),
Expand All @@ -145,6 +150,7 @@ func (s *MongodFinderSuite) TestFindJujuMongodb32IgnoringFailedVersion(c *gc.C)
defer s.ctrl.Finish()
exp := s.search.EXPECT()
gomock.InOrder(
exp.Exists("/snap/bin/juju-db.mongod").Return(false),
exp.Exists("/usr/bin/mongod").Return(false),
exp.Exists("/usr/lib/juju/mongo3.2/bin/mongod").Return(true),
exp.GetCommandOutput("/usr/lib/juju/mongo3.2/bin/mongod", "--version").Return("bad version string", nil),
Expand All @@ -164,6 +170,7 @@ func (s *MongodFinderSuite) TestFindJujuMongodb32IgnoringSystemMongo(c *gc.C) {
defer s.ctrl.Finish()
exp := s.search.EXPECT()
gomock.InOrder(
exp.Exists("/snap/bin/juju-db.mongod").Return(false),
exp.Exists("/usr/bin/mongod").Return(true),
exp.GetCommandOutput("/usr/bin/mongod", "--version").Return(mongodb26Version, nil),
exp.Exists("/usr/lib/juju/mongo3.2/bin/mongod").Return(true),
Expand All @@ -185,6 +192,7 @@ func (s *MongodFinderSuite) TestStatButNoExecSystemMongo(c *gc.C) {
defer s.ctrl.Finish()
exp := s.search.EXPECT()
gomock.InOrder(
exp.Exists("/snap/bin/juju-db.mongod").Return(false),
exp.Exists("/usr/bin/mongod").Return(true),
exp.GetCommandOutput("/usr/bin/mongod", "--version").Return(
"bad result", errors.Errorf("unknown error"), // would be an exec.ExitError
Expand All @@ -198,6 +206,25 @@ func (s *MongodFinderSuite) TestStatButNoExecSystemMongo(c *gc.C) {
c.Check(version, gc.Equals, mongo.Version{})
}

func (s *MongodFinderSuite) TestFindJujuMongodbFromSnap(c *gc.C) {
s.setUpMock(c)
defer s.ctrl.Finish()
exp := s.search.EXPECT()
gomock.InOrder(
exp.Exists("/snap/bin/juju-db.mongod").Return(true),
exp.GetCommandOutput("/snap/bin/juju-db.mongod", "--version").Return(mongodb409Version, nil),
)
path, version, err := s.finder.FindBest()
c.Assert(err, jc.ErrorIsNil)
c.Check(path, gc.Equals, "/snap/bin/juju-db.mongod")
c.Check(version, gc.Equals, mongo.Version{
Major: 4,
Minor: 0,
Point: 9,
StorageEngine: mongo.WiredTiger,
})
}

func (s *MongodFinderSuite) TestParseMongoVersion(c *gc.C) {
assertVersion := func(major, minor, point int, patch string, content string) {
v, err := mongo.ParseMongoVersion(content)
Expand Down Expand Up @@ -271,6 +298,17 @@ build environment:
target_arch: x86_64
`

// mongodb409Version is the output of 'mongodb --version' from the 4.0/stable snap.
const mongodb409Version = `db version v4.0.9
git version: fc525e2d9b0e4bceff5c2201457e564362909765
OpenSSL version: OpenSSL 1.1.0g 2 Nov 2017
allocator: tcmalloc
modules: none
build environment:
distarch: x86_64
target_arch: x86_64
`

type OSSearchToolsSuite struct {
testing.IsolationSuite
}
Expand Down
Loading

0 comments on commit c18a5bd

Please sign in to comment.