Skip to content

Commit

Permalink
Add support for installing MongoDB as a snap
Browse files Browse the repository at this point in the history
  • Loading branch information
Tim McNamara committed Feb 12, 2019
1 parent df8fba6 commit 403f706
Show file tree
Hide file tree
Showing 18 changed files with 1,459 additions and 409 deletions.
4 changes: 4 additions & 0 deletions feature/flags.go
Original file line number Diff line number Diff line change
Expand Up @@ -49,3 +49,7 @@ const LegacyLeases = "legacy-leases"

// Generations will allow for model generation functionality to be used.
const Generations = "generations"

// MongoDbSnap tells Juju to install MongoDB as a snap, rather than installing
// it from APT.
const MongoDbSnap = "mongodb-snap"
3 changes: 2 additions & 1 deletion mongo/export_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,8 @@ var (
SharedSecretPath = sharedSecretPath
SSLKeyPath = sslKeyPath

NewConf = newConf
NewConf = newConf
GenerateConf = generateConfig

HostWordSize = &hostWordSize
RuntimeGOOS = &runtimeGOOS
Expand Down
143 changes: 90 additions & 53 deletions mongo/mongo.go
Original file line number Diff line number Diff line change
Expand Up @@ -22,11 +22,15 @@ import (
"github.com/juju/packaging/manager"
"github.com/juju/replicaset"
"github.com/juju/utils"
"github.com/juju/utils/featureflag"
"gopkg.in/mgo.v2"

"github.com/juju/juju/controller"
"github.com/juju/juju/feature"
"github.com/juju/juju/network"
"github.com/juju/juju/service"
"github.com/juju/juju/service/common"
"github.com/juju/juju/service/snap"
)

var (
Expand Down Expand Up @@ -58,6 +62,13 @@ const (
// installing mongo.
JujuMongoPackage = "juju-mongodb3.2"

// JujuDbSnap is the snap of MongoDB that Juju uses.
JujuDbSnap = "juju-db"

// JujuDbSnapMongodPath is the path that the juju-db snap
// makes mongod available at
JujuDbSnapMongodPath = "/snap/bin/juju-db.mongod"

// JujuMongoToolsPackage is the mongo package Juju uses when
// installing mongo tools to get mongodump etc.
JujuMongoToolsPackage = "juju-mongo-tools3.2"
Expand Down Expand Up @@ -433,26 +444,46 @@ 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")
}

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)

if featureflag.Enabled(feature.MongoDbSnap) {
// TODO(tsm): push this to earlier in the bootstrapping process
if args.DataDir != dataPathForJujuDbSnap {
logger.Warningf("overwriting args.dataDir (set to %v) to %v", args.DataDir, dataPathForJujuDbSnap)
args.DataDir = dataPathForJujuDbSnap
}
}

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

dbDir := filepath.Join(args.DataDir, "db")
if err := os.MkdirAll(dbDir, 0700); err != nil {
return zeroVersion, errors.Errorf("cannot create mongo database directory: %v", err)
}

oplogSizeMB := args.OplogSize
if oplogSizeMB == 0 {
var err error
if oplogSizeMB, err = defaultOplogSize(dbDir); err != nil {
return zeroVersion, errors.Trace(err)
}
}
setupDataDirectory(args)

if err := installMongod(series.MustHostSeries(), args.SetNUMAControlPolicy); err != nil {
// This isn't treated as fatal because the Juju MongoDB
Expand All @@ -469,13 +500,12 @@ func ensureServer(args EnsureServerParams, mongoKernelTweaks map[string]string)
}
logVersion(mongoPath)

if err := UpdateSSLKey(args.DataDir, args.Cert, args.PrivateKey); err != nil {
return zeroVersion, errors.Trace(err)
}

err = utils.AtomicWriteFile(sharedSecretPath(args.DataDir), []byte(args.SharedSecret), 0600)
if err != nil {
return zeroVersion, errors.Errorf("cannot write mongod shared secret: %v", err)
oplogSizeMB := args.OplogSize
if oplogSizeMB == 0 {
oplogSizeMB, err = defaultOplogSize(DbDir(args.DataDir))
if err != nil {
return zeroVersion, errors.Trace(err)
}
}

// Disable the default mongodb installed by the mongodb-server package.
Expand All @@ -492,53 +522,50 @@ func ensureServer(args EnsureServerParams, mongoKernelTweaks map[string]string)
}
}

svcConf := newConf(ConfigArgs{
DataDir: args.DataDir,
DBDir: dbDir,
MongoPath: mongoPath,
Port: args.StatePort,
OplogSizeMB: oplogSizeMB,
WantNUMACtl: args.SetNUMAControlPolicy,
Version: mongodVersion,
Auth: true,
IPv6: network.SupportsIPv6(),
MemoryProfile: args.MemoryProfile,
})
svc, err := newService(ServiceName, svcConf)
if err != nil {
return zeroVersion, errors.Trace(err)
}
installed, err := svc.Installed()
mongoArgs := generateConfig(mongoPath, args.DataDir, args.StatePort, oplogSizeMB, args.SetNUMAControlPolicy, mongodVersion, args.MemoryProfile)
logger.Debugf("creating mongo service configuration for mongo version: %d.%d.%s at %q",
mongoArgs.Version.Major, mongoArgs.Version.Minor, mongoArgs.Version.Patch, mongoArgs.MongoPath)

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

// TODO(tsm): refactor out to service.Configure
if featureflag.Enabled(feature.MongoDbSnap) {
err = mongoArgs.writeConfig(configPath(args.DataDir))
if err != nil {
return zeroVersion, errors.Trace(err)
}
if exists {
logger.Debugf("mongo exists as expected")
running, err := svc.Running()
if err != nil {
return zeroVersion, errors.Trace(err)
}

if !running {
return mongodVersion, errors.Trace(svc.Start())
}
return mongodVersion, nil
err := snap.SetSnapConfig(ServiceName, "configpath", configPath(args.DataDir))
if err != nil {
return zeroVersion, errors.Trace(err)
}

err = service.ManuallyRestart(svc)
if err != nil {
logger.Criticalf("unable to (re)start mongod service: %v", err)
return zeroVersion, errors.Trace(err)
}

return mongodVersion, nil
}

if err := svc.Stop(); err != nil {
return zeroVersion, errors.Annotatef(err, "failed to stop mongo")
running, err := svc.Running()
if err != nil {
return zeroVersion, errors.Trace(err)
}
if running {
return mongodVersion, nil
}

dbDir := DbDir(args.DataDir)
if err := makeJournalDirs(dbDir); err != nil {
return zeroVersion, fmt.Errorf("error creating journal directories: %v", err)
return zeroVersion, errors.Errorf("error creating journal directories: %v", err)
}
if err := preallocOplog(dbDir, oplogSizeMB); err != nil {
return zeroVersion, fmt.Errorf("error creating oplog files: %v", err)
return zeroVersion, errors.Errorf("error creating oplog files: %v", err)
}

if err := service.InstallAndStart(svc); err != nil {
Expand Down Expand Up @@ -610,6 +637,17 @@ func installPackage(pkg string, pacconfer config.PackagingConfigurer, pacman man
}

func installMongod(operatingsystem string, numaCtl bool) error {
if featureflag.Enabled(feature.MongoDbSnap) {
prerequisites := []snap.App{snap.NewApp("core")}
backgroundServices := []snap.BackgroundService{{"daemon", true}}
conf := common.Conf{Desc: ServiceName + " snap"}
service, err := snap.NewService(ServiceName, conf, snap.Command, "edge", "jailmode", backgroundServices, prerequisites)
if err != nil {
return errors.Trace(err)
}
return service.Install()
}

// fetch the packaging configuration manager for the current operating system.
pacconfer, err := config.NewPackagingConfigurer(operatingsystem)
if err != nil {
Expand Down Expand Up @@ -678,7 +716,6 @@ func installMongod(operatingsystem string, numaCtl bool) error {
}
}
}

return nil
}

Expand Down
47 changes: 35 additions & 12 deletions mongo/mongo_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -117,7 +117,6 @@ func (s *MongoSuite) makeConfigArgs(dataDir string) mongo.ConfigArgs {
OplogSizeMB: 1024,
WantNUMACtl: false,
Version: s.mongodVersion,
Auth: true,
IPv6: true,
}
}
Expand Down Expand Up @@ -256,7 +255,7 @@ func (s *MongoSuite) TestEnsureServerServerExistsAndRunning(c *gc.C) {
s.assertMongoConfigFile(c)

c.Check(s.data.Installed(), gc.HasLen, 0)
s.data.CheckCallNames(c, "Installed", "Exists", "Running")
s.data.CheckCallNames(c, "Running")
}

func (s *MongoSuite) TestEnsureServerSetsSysctlValues(c *gc.C) {
Expand Down Expand Up @@ -305,7 +304,7 @@ func (s *MongoSuite) TestEnsureServerServerExistsNotRunningIsStarted(c *gc.C) {
s.assertMongoConfigFile(c)

c.Check(s.data.Installed(), gc.HasLen, 0)
s.data.CheckCallNames(c, "Installed", "Exists", "Running", "Start")
s.data.CheckCallNames(c, "Running", "Install", "Stop", "Start")
}

func (s *MongoSuite) TestEnsureServerServerExistsNotRunningStartError(c *gc.C) {
Expand All @@ -317,13 +316,35 @@ func (s *MongoSuite) TestEnsureServerServerExistsNotRunningStartError(c *gc.C) {

s.data.SetStatus(mongo.ServiceName, "installed")
failure := errors.New("won't start")
s.data.SetErrors(nil, nil, nil, failure) // Installed, Exists, Running, Running, Start
s.data.SetErrors(
nil, // Running
nil, // Install
nil, // Stop
failure, // Start
nil, // Stop
failure, // Start
nil, // Stop
failure, // Start
nil, // Stop
failure, // Start
)

_, err = mongo.EnsureServer(makeEnsureServerParams(dataDir))

c.Check(errors.Cause(err), gc.Equals, failure)
c.Check(s.data.Installed(), gc.HasLen, 0)
s.data.CheckCallNames(c, "Installed", "Exists", "Running", "Start")
s.data.CheckCallNames(c,
"Running",
"Install",
"Stop",
"Start",
"Stop",
"Start",
"Stop",
"Start",
"Stop",
"Start",
)
}

func (s *MongoSuite) TestEnsureServerNUMACtl(c *gc.C) {
Expand Down Expand Up @@ -596,7 +617,7 @@ func (s *MongoSuite) assertTestMongoGetFails(c *gc.C, series string, packageMana

// Verify that EnsureServer continued and started the mongodb service.
c.Check(s.data.Installed(), gc.HasLen, 0)
s.data.CheckCallNames(c, "Installed", "Exists", "Running", "Start")
s.data.CheckCallNames(c, "Running", "Install", "Stop", "Start")
}

func (s *MongoSuite) TestInstallMongodServiceExists(c *gc.C) {
Expand All @@ -617,38 +638,40 @@ func (s *MongoSuite) TestInstallMongodServiceExists(c *gc.C) {
c.Assert(err, jc.ErrorIsNil)

c.Check(s.data.Installed(), gc.HasLen, 0)
s.data.CheckCallNames(c, "Installed", "Exists", "Running")
s.data.CheckCallNames(c, "Running")
}

func (s *MongoSuite) TestNewServiceWithReplSet(c *gc.C) {
conf := mongo.NewConf(s.makeConfigArgs(c.MkDir()))
confArgs := s.makeConfigArgs(c.MkDir())
conf := mongo.NewConf(&confArgs)
c.Assert(strings.Contains(conf.ExecStart, "--replSet"), jc.IsTrue)
}

func (s *MongoSuite) TestNewServiceWithNumCtl(c *gc.C) {
args := s.makeConfigArgs(c.MkDir())
args.WantNUMACtl = true
conf := mongo.NewConf(args)
conf := mongo.NewConf(&args)
c.Assert(conf.ExtraScript, gc.Not(gc.Matches), "")
}

func (s *MongoSuite) TestNewServiceWithIPv6(c *gc.C) {
args := s.makeConfigArgs(c.MkDir())
args.IPv6 = true
conf := mongo.NewConf(args)
conf := mongo.NewConf(&args)
c.Assert(strings.Contains(conf.ExecStart, "--ipv6"), jc.IsTrue)
}

func (s *MongoSuite) TestNewServiceWithoutIPv6(c *gc.C) {
args := s.makeConfigArgs(c.MkDir())
args.IPv6 = false
conf := mongo.NewConf(args)
conf := mongo.NewConf(&args)
c.Assert(strings.Contains(conf.ExecStart, "--ipv6"), jc.IsFalse)
}

func (s *MongoSuite) TestNewServiceWithJournal(c *gc.C) {
args := s.makeConfigArgs(c.MkDir())
conf := mongo.NewConf(args)
args.Journal = true
conf := mongo.NewConf(&args)
c.Assert(conf.ExecStart, gc.Matches, `.* --journal.*`)
}

Expand Down
11 changes: 11 additions & 0 deletions mongo/mongodfinder.go
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,9 @@ 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 @@ -37,6 +40,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) {
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
}

// In Bionic and beyond (and early trusty) we just use the system mongo.
// We only use the system mongo if it is at least Mongo 3.4
if m.search.Exists(MongodSystemPath) {
Expand Down
1 change: 0 additions & 1 deletion mongo/prealloc.go
Original file line number Diff line number Diff line change
Expand Up @@ -53,7 +53,6 @@ func preallocOplog(dir string, oplogSizeMB int) error {
// in opLogSize requires mongo restart we are not using the default
// MongoDB formula but simply using 512MB for small disks and 1GB
// for larger ones.

func defaultOplogSize(dir string) (int, error) {
if hostWordSize == 32 {
// "For 32-bit systems, MongoDB allocates about 48 megabytes
Expand Down
Loading

0 comments on commit 403f706

Please sign in to comment.