github.com/niedbalski/juju@v0.0.0-20190215020005-8ff100488e47/mongo/mongo.go (about)

     1  // Copyright 2014 Canonical Ltd.
     2  // Licensed under the AGPLv3, see LICENCE file for details.
     3  
     4  package mongo
     5  
     6  import (
     7  	"crypto/rand"
     8  	"encoding/base64"
     9  	"fmt"
    10  	"net"
    11  	"os"
    12  	"os/exec"
    13  	"path"
    14  	"path/filepath"
    15  	"strconv"
    16  	"strings"
    17  
    18  	"github.com/juju/errors"
    19  	"github.com/juju/loggo"
    20  	"github.com/juju/os/series"
    21  	"github.com/juju/packaging/config"
    22  	"github.com/juju/packaging/manager"
    23  	"github.com/juju/replicaset"
    24  	"github.com/juju/utils"
    25  	"github.com/juju/utils/featureflag"
    26  	"gopkg.in/mgo.v2"
    27  
    28  	"github.com/juju/juju/controller"
    29  	"github.com/juju/juju/feature"
    30  	"github.com/juju/juju/network"
    31  	"github.com/juju/juju/service"
    32  	"github.com/juju/juju/service/common"
    33  	"github.com/juju/juju/service/snap"
    34  )
    35  
    36  var (
    37  	logger          = loggo.GetLogger("juju.mongo")
    38  	mongoConfigPath = "/etc/default/mongodb"
    39  
    40  	// JujuMongod24Path holds the default path to the legacy Juju
    41  	// mongod.
    42  	JujuMongod24Path = "/usr/lib/juju/bin/mongod"
    43  
    44  	// JujuMongod32Path holds the default path to juju-mongodb3.2
    45  	JujuMongod32Path = "/usr/lib/juju/mongo3.2/bin/mongod"
    46  
    47  	// MongodSystemPath is actually just the system path
    48  	MongodSystemPath = "/usr/bin/mongod"
    49  
    50  	// This is NUMACTL package name for apt-get
    51  	numaCtlPkg = "numactl"
    52  
    53  	// mininmumSystemMongoVersion is the minimum version we would allow to be used from /usr/bin/mongod.
    54  	minimumSystemMongoVersion = Version{Major: 3, Minor: 4}
    55  )
    56  
    57  // StorageEngine represents the storage used by mongo.
    58  type StorageEngine string
    59  
    60  const (
    61  	// JujuMongoPackage is the mongo package Juju uses when
    62  	// installing mongo.
    63  	JujuMongoPackage = "juju-mongodb3.2"
    64  
    65  	// JujuDbSnap is the snap of MongoDB that Juju uses.
    66  	JujuDbSnap = "juju-db"
    67  
    68  	// JujuDbSnapMongodPath is the path that the juju-db snap
    69  	// makes mongod available at
    70  	JujuDbSnapMongodPath = "/snap/bin/juju-db.mongod"
    71  
    72  	// JujuMongoToolsPackage is the mongo package Juju uses when
    73  	// installing mongo tools to get mongodump etc.
    74  	JujuMongoToolsPackage = "juju-mongo-tools3.2"
    75  
    76  	// MMAPV1 is the default storage engine in mongo db up to 3.x
    77  	MMAPV1 StorageEngine = "mmapv1"
    78  
    79  	// WiredTiger is a storage type introduced in 3
    80  	WiredTiger StorageEngine = "wiredTiger"
    81  
    82  	// Upgrading is a special case where mongo is being upgraded.
    83  	Upgrading StorageEngine = "Upgrading"
    84  )
    85  
    86  // Version represents the major.minor version of the running mongo.
    87  type Version struct {
    88  	Major         int
    89  	Minor         int
    90  	Patch         string // supports variants like 1-alpha
    91  	StorageEngine StorageEngine
    92  }
    93  
    94  // NewerThan will return 1 if the passed version is older than
    95  // v, 0 if they are equal (or ver is a special case such as
    96  // Upgrading and -1 if ver is newer.
    97  func (v Version) NewerThan(ver Version) int {
    98  	if v == MongoUpgrade || ver == MongoUpgrade {
    99  		return 0
   100  	}
   101  	if v.Major > ver.Major {
   102  		return 1
   103  	}
   104  	if v.Major < ver.Major {
   105  		return -1
   106  	}
   107  	if v.Minor > ver.Minor {
   108  		return 1
   109  	}
   110  	if v.Minor < ver.Minor {
   111  		return -1
   112  	}
   113  	return 0
   114  }
   115  
   116  // NewVersion returns a mongo Version parsing the passed version string
   117  // or error if not possible.
   118  // A valid version string is of the form:
   119  // 1.2.patch/storage
   120  // major and minor are positive integers, patch is a string containing
   121  // any ascii character except / and storage is one of the above defined
   122  // StorageEngine. Only major is mandatory.
   123  // An alternative valid string is 0.0/Upgrading which represents that
   124  // mongo is being upgraded.
   125  func NewVersion(v string) (Version, error) {
   126  	version := Version{}
   127  	if v == "" {
   128  		return Mongo24, nil
   129  	}
   130  
   131  	parts := strings.SplitN(v, "/", 2)
   132  	switch len(parts) {
   133  	case 0:
   134  		return Version{}, errors.New("invalid version string")
   135  	case 1:
   136  		version.StorageEngine = MMAPV1
   137  	case 2:
   138  		switch StorageEngine(parts[1]) {
   139  		case MMAPV1:
   140  			version.StorageEngine = MMAPV1
   141  		case WiredTiger:
   142  			version.StorageEngine = WiredTiger
   143  		case Upgrading:
   144  			version.StorageEngine = Upgrading
   145  		}
   146  	}
   147  	vParts := strings.SplitN(parts[0], ".", 3)
   148  
   149  	if len(vParts) >= 1 {
   150  		i, err := strconv.Atoi(vParts[0])
   151  		if err != nil {
   152  			return Version{}, errors.Annotate(err, "Invalid version string, major is not an int")
   153  		}
   154  		version.Major = i
   155  	}
   156  	if len(vParts) >= 2 {
   157  		i, err := strconv.Atoi(vParts[1])
   158  		if err != nil {
   159  			return Version{}, errors.Annotate(err, "Invalid version string, minor is not an int")
   160  		}
   161  		version.Minor = i
   162  	}
   163  	if len(vParts) == 3 {
   164  		version.Patch = vParts[2]
   165  	}
   166  
   167  	if version.Major == 2 && version.StorageEngine == WiredTiger {
   168  		return Version{}, errors.Errorf("Version 2.x does not support Wired Tiger storage engine")
   169  	}
   170  
   171  	// This deserialises the special "Mongo Upgrading" version
   172  	if version.Major == 0 && version.Minor == 0 {
   173  		return Version{StorageEngine: Upgrading}, nil
   174  	}
   175  
   176  	return version, nil
   177  }
   178  
   179  // String serializes the version into a string.
   180  func (v Version) String() string {
   181  	s := fmt.Sprintf("%d.%d", v.Major, v.Minor)
   182  	if v.Patch != "" {
   183  		s = fmt.Sprintf("%s.%s", s, v.Patch)
   184  	}
   185  	if v.StorageEngine != "" {
   186  		s = fmt.Sprintf("%s/%s", s, v.StorageEngine)
   187  	}
   188  	return s
   189  }
   190  
   191  // JujuMongodPath returns the path for the mongod binary
   192  // with the specified version.
   193  func JujuMongodPath(v Version) string {
   194  	return fmt.Sprintf("/usr/lib/juju/mongo%d.%d/bin/mongod", v.Major, v.Minor)
   195  }
   196  
   197  var (
   198  	// Mongo24 represents juju-mongodb 2.4.x
   199  	Mongo24 = Version{Major: 2,
   200  		Minor:         4,
   201  		Patch:         "",
   202  		StorageEngine: MMAPV1,
   203  	}
   204  	// Mongo26 represents juju-mongodb26 2.6.x
   205  	Mongo26 = Version{Major: 2,
   206  		Minor:         6,
   207  		Patch:         "",
   208  		StorageEngine: MMAPV1,
   209  	}
   210  	// Mongo32wt represents juju-mongodb3 3.2.x with wiredTiger storage.
   211  	Mongo32wt = Version{Major: 3,
   212  		Minor:         2,
   213  		Patch:         "",
   214  		StorageEngine: WiredTiger,
   215  	}
   216  	// Mongo34wt represents 'mongodb-server-core' at version 3.4.x with WiredTiger
   217  	Mongo34wt = Version{Major: 3,
   218  		Minor:         4,
   219  		Patch:         "",
   220  		StorageEngine: WiredTiger,
   221  	}
   222  	// Mongo36wt represents 'mongodb-server-core' at version 3.6.x with WiredTiger
   223  	Mongo36wt = Version{Major: 3,
   224  		Minor:         6,
   225  		Patch:         "",
   226  		StorageEngine: WiredTiger,
   227  	}
   228  	// MongoUpgrade represents a sepacial case where an upgrade is in
   229  	// progress.
   230  	MongoUpgrade = Version{Major: 0,
   231  		Minor:         0,
   232  		Patch:         "Upgrading",
   233  		StorageEngine: Upgrading,
   234  	}
   235  )
   236  
   237  // WithAddresses represents an entity that has a set of
   238  // addresses. e.g. a state Machine object
   239  type WithAddresses interface {
   240  	Addresses() []network.Address
   241  }
   242  
   243  // IsMaster returns a boolean that represents whether the given
   244  // machine's peer address is the primary mongo host for the replicaset
   245  func IsMaster(session *mgo.Session, obj WithAddresses) (bool, error) {
   246  	addrs := obj.Addresses()
   247  
   248  	masterHostPort, err := replicaset.MasterHostPort(session)
   249  
   250  	// If the replica set has not been configured, then we
   251  	// can have only one master and the caller must
   252  	// be that master.
   253  	if err == replicaset.ErrMasterNotConfigured {
   254  		return true, nil
   255  	}
   256  	if err != nil {
   257  		return false, err
   258  	}
   259  
   260  	masterAddr, _, err := net.SplitHostPort(masterHostPort)
   261  	if err != nil {
   262  		return false, err
   263  	}
   264  
   265  	for _, addr := range addrs {
   266  		if addr.Value == masterAddr {
   267  			return true, nil
   268  		}
   269  	}
   270  	return false, nil
   271  }
   272  
   273  // SelectPeerAddress returns the address to use as the mongo replica set peer
   274  // address by selecting it from the given addresses. If no addresses are
   275  // available an empty string is returned.
   276  func SelectPeerAddress(addrs []network.Address) string {
   277  	// ScopeMachineLocal addresses are never suitable for mongo peers,
   278  	// as each controller runs on a separate machine.
   279  	const allowMachineLocal = false
   280  
   281  	// The second bool result is ignored intentionally (we return an empty
   282  	// string if no suitable address is available.)
   283  	addr, _ := network.SelectControllerAddress(addrs, allowMachineLocal)
   284  	return addr.Value
   285  }
   286  
   287  // GenerateSharedSecret generates a pseudo-random shared secret (keyfile)
   288  // for use with Mongo replica sets.
   289  func GenerateSharedSecret() (string, error) {
   290  	// "A key’s length must be between 6 and 1024 characters and may
   291  	// only contain characters in the base64 set."
   292  	//   -- http://docs.mongodb.org/manual/tutorial/generate-key-file/
   293  	buf := make([]byte, base64.StdEncoding.DecodedLen(1024))
   294  	if _, err := rand.Read(buf); err != nil {
   295  		return "", fmt.Errorf("cannot read random secret: %v", err)
   296  	}
   297  	return base64.StdEncoding.EncodeToString(buf), nil
   298  }
   299  
   300  // Path returns the executable path to be used to run mongod on this
   301  // machine. If the juju-bundled version of mongo exists, it will return that
   302  // path, otherwise it will return the command to run mongod from the path.
   303  func Path(version Version) (string, error) {
   304  	return mongoPath(version, os.Stat, exec.LookPath)
   305  }
   306  
   307  func mongoPath(
   308  	version Version,
   309  	stat func(string) (os.FileInfo, error),
   310  	lookPath func(string) (string, error),
   311  ) (string, error) {
   312  	// we don't want to match on patch so we remove it.
   313  	if version.Major == 2 && version.Minor == 4 {
   314  		if _, err := stat(JujuMongod24Path); err == nil {
   315  			return JujuMongod24Path, nil
   316  		}
   317  		path, err := lookPath("mongod")
   318  		if err != nil {
   319  			logger.Infof("could not find %v or mongod in $PATH", JujuMongod24Path)
   320  			return "", err
   321  		}
   322  		return path, nil
   323  	}
   324  	if version.Major == 3 && version.Minor == 6 {
   325  		if _, err := stat(MongodSystemPath); err == nil {
   326  			return MongodSystemPath, nil
   327  		} else {
   328  			return "", err
   329  		}
   330  	}
   331  	path := JujuMongodPath(version)
   332  	var err error
   333  	if _, err = stat(path); err == nil {
   334  		return path, nil
   335  	}
   336  	logger.Infof("could not find a suitable binary for %q", version)
   337  	errMsg := fmt.Sprintf("no suitable binary for %q", version)
   338  	return "", errors.New(errMsg)
   339  }
   340  
   341  /*
   342  Values set as per bug:
   343  https://bugs.launchpad.net/juju/+bug/1656430
   344  net.ipv4.tcp_max_syn_backlog = 4096
   345  net.core.somaxconn = 16384
   346  net.core.netdev_max_backlog = 1000
   347  net.ipv4.tcp_fin_timeout = 30
   348  
   349  Values set as per mongod recommendation (see syslog on default mongod run)
   350  /sys/kernel/mm/transparent_hugepage/enabled 'always' > 'never'
   351  /sys/kernel/mm/transparent_hugepage/defrag 'always' > 'never'
   352  */
   353  var mongoKernelTweaks = map[string]string{
   354  	"/sys/kernel/mm/transparent_hugepage/enabled": "never",
   355  	"/sys/kernel/mm/transparent_hugepage/defrag":  "never",
   356  	"/proc/sys/net/ipv4/tcp_max_syn_backlog":      "4096",
   357  	"/proc/sys/net/core/somaxconn":                "16384",
   358  	"/proc/sys/net/core/netdev_max_backlog":       "1000",
   359  	"/proc/sys/net/ipv4/tcp_fin_timeout":          "30",
   360  }
   361  
   362  // NewMemoryProfile returns a Memory Profile from the passed value.
   363  func NewMemoryProfile(m string) (MemoryProfile, error) {
   364  	mp := MemoryProfile(m)
   365  	if err := mp.Validate(); err != nil {
   366  		return MemoryProfile(""), err
   367  	}
   368  	return mp, nil
   369  }
   370  
   371  // MemoryProfile represents a type of meory configuration for Mongo.
   372  type MemoryProfile string
   373  
   374  // String returns a string representation of this profile value.
   375  func (m MemoryProfile) String() string {
   376  	return string(m)
   377  }
   378  
   379  func (m MemoryProfile) Validate() error {
   380  	if m != MemoryProfileLow && m != MemoryProfileDefault {
   381  		return errors.NotValidf("memory profile %q", m)
   382  	}
   383  	return nil
   384  }
   385  
   386  const (
   387  	// MemoryProfileLow will use as little memory as possible in mongo.
   388  	MemoryProfileLow MemoryProfile = "low"
   389  	// MemoryProfileDefault will use mongo config ootb.
   390  	MemoryProfileDefault = "default"
   391  )
   392  
   393  // EnsureServerParams is a parameter struct for EnsureServer.
   394  type EnsureServerParams struct {
   395  	// APIPort is the port to connect to the api server.
   396  	APIPort int
   397  
   398  	// StatePort is the port to connect to the mongo server.
   399  	StatePort int
   400  
   401  	// Cert is the certificate.
   402  	Cert string
   403  
   404  	// PrivateKey is the certificate's private key.
   405  	PrivateKey string
   406  
   407  	// CAPrivateKey is the CA certificate's private key.
   408  	CAPrivateKey string
   409  
   410  	// SharedSecret is a secret shared between mongo servers.
   411  	SharedSecret string
   412  
   413  	// SystemIdentity is the identity of the system.
   414  	SystemIdentity string
   415  
   416  	// DataDir is the machine agent data directory.
   417  	DataDir string
   418  
   419  	// Namespace is the machine agent's namespace, which is used to
   420  	// generate a unique service name for Mongo.
   421  	Namespace string
   422  
   423  	// OplogSize is the size of the Mongo oplog.
   424  	// If this is zero, then EnsureServer will
   425  	// calculate a default size according to the
   426  	// algorithm defined in Mongo.
   427  	OplogSize int
   428  
   429  	// SetNUMAControlPolicy preference - whether the user
   430  	// wants to set the numa control policy when starting mongo.
   431  	SetNUMAControlPolicy bool
   432  
   433  	// MemoryProfile determines which value is going to be used by
   434  	// the cache and future memory tweaks.
   435  	MemoryProfile MemoryProfile
   436  }
   437  
   438  // EnsureServer ensures that the MongoDB server is installed,
   439  // configured, and ready to run.
   440  //
   441  // This method will remove old versions of the mongo init service as necessary
   442  // before installing the new version.
   443  func EnsureServer(args EnsureServerParams) (Version, error) {
   444  	return ensureServer(args, mongoKernelTweaks)
   445  }
   446  
   447  func setupDataDirectory(args EnsureServerParams) error {
   448  	dbDir := DbDir(args.DataDir)
   449  	if err := os.MkdirAll(dbDir, 0700); err != nil {
   450  		return errors.Annotate(err, "cannot create mongo database directory")
   451  	}
   452  
   453  	if err := UpdateSSLKey(args.DataDir, args.Cert, args.PrivateKey); err != nil {
   454  		return errors.Trace(err)
   455  	}
   456  
   457  	err := utils.AtomicWriteFile(sharedSecretPath(args.DataDir), []byte(args.SharedSecret), 0600)
   458  	if err != nil {
   459  		return errors.Annotatef(err, "cannot write mongod shared secret to %v", sharedSecretPath(args.DataDir))
   460  	}
   461  
   462  	if err := os.MkdirAll(logPath(dbDir), 0644); err != nil {
   463  		return errors.Annotate(err, "cannot create mongodb logging directory")
   464  	}
   465  
   466  	return nil
   467  }
   468  
   469  func ensureServer(args EnsureServerParams, mongoKernelTweaks map[string]string) (Version, error) {
   470  	var zeroVersion Version
   471  	tweakSysctlForMongo(mongoKernelTweaks)
   472  
   473  	if featureflag.Enabled(feature.MongoDbSnap) {
   474  		// TODO(tsm): push this to earlier in the bootstrapping process
   475  		if args.DataDir != dataPathForJujuDbSnap {
   476  			logger.Warningf("overwriting args.dataDir (set to %v) to %v", args.DataDir, dataPathForJujuDbSnap)
   477  			args.DataDir = dataPathForJujuDbSnap
   478  		}
   479  	}
   480  
   481  	logger.Infof(
   482  		"Ensuring mongo server is running; data directory %s; port %d",
   483  		args.DataDir, args.StatePort,
   484  	)
   485  
   486  	setupDataDirectory(args)
   487  
   488  	if err := installMongod(series.MustHostSeries(), args.SetNUMAControlPolicy); err != nil {
   489  		// This isn't treated as fatal because the Juju MongoDB
   490  		// package is likely to be already installed anyway. There
   491  		// could just be a temporary issue with apt-get/yum/whatever
   492  		// and we don't want this to stop jujud from starting.
   493  		// (LP #1441904)
   494  		logger.Errorf("cannot install/upgrade mongod (will proceed anyway): %v", err)
   495  	}
   496  	finder := NewMongodFinder()
   497  	mongoPath, mongodVersion, err := finder.FindBest()
   498  	if err != nil {
   499  		return zeroVersion, errors.Trace(err)
   500  	}
   501  	logVersion(mongoPath)
   502  
   503  	oplogSizeMB := args.OplogSize
   504  	if oplogSizeMB == 0 {
   505  		oplogSizeMB, err = defaultOplogSize(DbDir(args.DataDir))
   506  		if err != nil {
   507  			return zeroVersion, errors.Trace(err)
   508  		}
   509  	}
   510  
   511  	// Disable the default mongodb installed by the mongodb-server package.
   512  	// Only do this if the file doesn't exist already, so users can run
   513  	// their own mongodb server if they wish to.
   514  	if _, err := os.Stat(mongoConfigPath); os.IsNotExist(err) {
   515  		err = utils.AtomicWriteFile(
   516  			mongoConfigPath,
   517  			[]byte("ENABLE_MONGODB=no"),
   518  			0644,
   519  		)
   520  		if err != nil {
   521  			return zeroVersion, errors.Trace(err)
   522  		}
   523  	}
   524  
   525  	mongoArgs := generateConfig(mongoPath, args.DataDir, args.StatePort, oplogSizeMB, args.SetNUMAControlPolicy, mongodVersion, args.MemoryProfile)
   526  	logger.Debugf("creating mongo service configuration for mongo version: %d.%d.%s at %q",
   527  		mongoArgs.Version.Major, mongoArgs.Version.Minor, mongoArgs.Version.Patch, mongoArgs.MongoPath)
   528  
   529  	svc, err := mongoArgs.asService()
   530  	if err != nil {
   531  		return zeroVersion, errors.Trace(err)
   532  	}
   533  
   534  	// TODO(tsm): refactor out to service.Configure
   535  	if featureflag.Enabled(feature.MongoDbSnap) {
   536  		err = mongoArgs.writeConfig(configPath(args.DataDir))
   537  		if err != nil {
   538  			return zeroVersion, errors.Trace(err)
   539  		}
   540  
   541  		err := snap.SetSnapConfig(ServiceName, "configpath", configPath(args.DataDir))
   542  		if err != nil {
   543  			return zeroVersion, errors.Trace(err)
   544  		}
   545  
   546  		err = service.ManuallyRestart(svc)
   547  		if err != nil {
   548  			logger.Criticalf("unable to (re)start mongod service: %v", err)
   549  			return zeroVersion, errors.Trace(err)
   550  		}
   551  
   552  		return mongodVersion, nil
   553  	}
   554  
   555  	running, err := svc.Running()
   556  	if err != nil {
   557  		return zeroVersion, errors.Trace(err)
   558  	}
   559  	if running {
   560  		return mongodVersion, nil
   561  	}
   562  
   563  	dbDir := DbDir(args.DataDir)
   564  	if err := makeJournalDirs(dbDir); err != nil {
   565  		return zeroVersion, errors.Errorf("error creating journal directories: %v", err)
   566  	}
   567  	if err := preallocOplog(dbDir, oplogSizeMB); err != nil {
   568  		return zeroVersion, errors.Errorf("error creating oplog files: %v", err)
   569  	}
   570  
   571  	if err := service.InstallAndStart(svc); err != nil {
   572  		return zeroVersion, errors.Trace(err)
   573  	}
   574  	return mongodVersion, nil
   575  }
   576  
   577  func truncateAndWriteIfExists(procFile, value string) error {
   578  	if _, err := os.Stat(procFile); os.IsNotExist(err) {
   579  		logger.Debugf("%q does not exist, will not set %q", procFile, value)
   580  		return errors.Errorf("%q does not exist, will not set %q", procFile, value)
   581  	}
   582  	f, err := os.OpenFile(procFile, os.O_WRONLY|os.O_TRUNC, 0600)
   583  	if err != nil {
   584  		return errors.Trace(err)
   585  	}
   586  	defer f.Close()
   587  	_, err = f.WriteString(value)
   588  	return errors.Trace(err)
   589  }
   590  
   591  func tweakSysctlForMongo(editables map[string]string) {
   592  	for editableFile, value := range editables {
   593  		if err := truncateAndWriteIfExists(editableFile, value); err != nil {
   594  			logger.Errorf("could not set the value of %q to %q because of: %v\n", editableFile, value, err)
   595  		}
   596  	}
   597  }
   598  
   599  // UpdateSSLKey writes a new SSL key used by mongo to validate connections from Juju controller(s)
   600  func UpdateSSLKey(dataDir, cert, privateKey string) error {
   601  	certKey := cert + "\n" + privateKey
   602  	err := utils.AtomicWriteFile(sslKeyPath(dataDir), []byte(certKey), 0600)
   603  	return errors.Annotate(err, "cannot write SSL key")
   604  }
   605  
   606  func makeJournalDirs(dataDir string) error {
   607  	journalDir := path.Join(dataDir, "journal")
   608  	if err := os.MkdirAll(journalDir, 0700); err != nil {
   609  		logger.Errorf("failed to make mongo journal dir %s: %v", journalDir, err)
   610  		return err
   611  	}
   612  
   613  	// Manually create the prealloc files, since otherwise they get
   614  	// created as 100M files. We create three files of 1MB each.
   615  	prefix := filepath.Join(journalDir, "prealloc.")
   616  	preallocSize := 1024 * 1024
   617  	return preallocFiles(prefix, preallocSize, preallocSize, preallocSize)
   618  }
   619  
   620  func logVersion(mongoPath string) {
   621  	cmd := exec.Command(mongoPath, "--version")
   622  	output, err := cmd.CombinedOutput()
   623  	if err != nil {
   624  		logger.Infof("failed to read the output from %s --version: %v", mongoPath, err)
   625  		return
   626  	}
   627  	logger.Debugf("using mongod: %s --version: %q", mongoPath, output)
   628  }
   629  
   630  func installPackage(pkg string, pacconfer config.PackagingConfigurer, pacman manager.PackageManager) error {
   631  	// apply release targeting if needed.
   632  	if pacconfer.IsCloudArchivePackage(pkg) {
   633  		pkg = strings.Join(pacconfer.ApplyCloudArchiveTarget(pkg), " ")
   634  	}
   635  
   636  	return pacman.Install(pkg)
   637  }
   638  
   639  func installMongod(operatingsystem string, numaCtl bool) error {
   640  	if featureflag.Enabled(feature.MongoDbSnap) {
   641  		prerequisites := []snap.App{snap.NewApp("core")}
   642  		backgroundServices := []snap.BackgroundService{{"daemon", true}}
   643  		conf := common.Conf{Desc: ServiceName + " snap"}
   644  		service, err := snap.NewService(ServiceName, conf, snap.Command, "edge", "jailmode", backgroundServices, prerequisites)
   645  		if err != nil {
   646  			return errors.Trace(err)
   647  		}
   648  		return service.Install()
   649  	}
   650  
   651  	// fetch the packaging configuration manager for the current operating system.
   652  	pacconfer, err := config.NewPackagingConfigurer(operatingsystem)
   653  	if err != nil {
   654  		return err
   655  	}
   656  
   657  	// fetch the package manager implementation for the current operating system.
   658  	pacman, err := manager.NewPackageManager(operatingsystem)
   659  	if err != nil {
   660  		return err
   661  	}
   662  
   663  	// CentOS requires "epel-release" for the epel repo mongodb-server is in.
   664  	if operatingsystem == "centos7" {
   665  		// install epel-release
   666  		if err := pacman.Install("epel-release"); err != nil {
   667  			return err
   668  		}
   669  	}
   670  	mongoPkgs, fallbackPkgs := packagesForSeries(operatingsystem)
   671  
   672  	if numaCtl {
   673  		logger.Infof("installing %v and %s", mongoPkgs, numaCtlPkg)
   674  		if err = installPackage(numaCtlPkg, pacconfer, pacman); err != nil {
   675  			return errors.Trace(err)
   676  		}
   677  	} else {
   678  		logger.Infof("installing %v", mongoPkgs)
   679  	}
   680  
   681  	for i := range mongoPkgs {
   682  		if err = installPackage(mongoPkgs[i], pacconfer, pacman); err != nil {
   683  			break
   684  		}
   685  	}
   686  	if err != nil && len(fallbackPkgs) == 0 {
   687  		return errors.Trace(err)
   688  	}
   689  	if err != nil {
   690  		logger.Errorf("installing mongo failed: %v", err)
   691  		logger.Infof("will try fallback packages %v", fallbackPkgs)
   692  		for i := range fallbackPkgs {
   693  			if err = installPackage(fallbackPkgs[i], pacconfer, pacman); err != nil {
   694  				return errors.Trace(err)
   695  			}
   696  		}
   697  	}
   698  
   699  	// Work around SELinux on centos7
   700  	if operatingsystem == "centos7" {
   701  		cmd := []string{"chcon", "-R", "-v", "-t", "mongod_var_lib_t", "/var/lib/juju/"}
   702  		logger.Infof("running %s %v", cmd[0], cmd[1:])
   703  		_, err = utils.RunCommand(cmd[0], cmd[1:]...)
   704  		if err != nil {
   705  			logger.Errorf("chcon failed to change file security context error %s", err)
   706  			return err
   707  		}
   708  
   709  		cmd = []string{"semanage", "port", "-a", "-t", "mongod_port_t", "-p", "tcp", strconv.Itoa(controller.DefaultStatePort)}
   710  		logger.Infof("running %s %v", cmd[0], cmd[1:])
   711  		_, err = utils.RunCommand(cmd[0], cmd[1:]...)
   712  		if err != nil {
   713  			if !strings.Contains(err.Error(), "exit status 1") {
   714  				logger.Errorf("semanage failed to provide access on port %d error %s", controller.DefaultStatePort, err)
   715  				return err
   716  			}
   717  		}
   718  	}
   719  	return nil
   720  }
   721  
   722  // packagesForSeries returns the name of the mongo package for the series
   723  // of the machine that it is going to be running on plus a fallback for
   724  // options where the package is going to be ready eventually but might not
   725  // yet be.
   726  func packagesForSeries(series string) ([]string, []string) {
   727  	switch series {
   728  	case "precise", "centos7":
   729  		return []string{"mongodb-server"}, []string{}
   730  	case "trusty":
   731  		return []string{"juju-mongodb"}, []string{}
   732  	case "xenial", "artful":
   733  		return []string{JujuMongoPackage, JujuMongoToolsPackage}, []string{}
   734  	default:
   735  		// Bionic and newer
   736  		return []string{"mongodb-server-core", "mongodb-clients"}, []string{}
   737  	}
   738  }
   739  
   740  // DbDir returns the dir where mongo storage is.
   741  func DbDir(dataDir string) string {
   742  	return filepath.Join(dataDir, "db")
   743  }