github.com/juju/juju@v0.0.0-20240430160146-1752b71fcf00/mongo/mongo_test.go (about)

     1  // Copyright 2014 Canonical Ltd.
     2  // Licensed under the AGPLv3, see LICENCE file for details.
     3  
     4  package mongo_test
     5  
     6  import (
     7  	"context"
     8  	"encoding/base64"
     9  	"fmt"
    10  	"os"
    11  	"os/exec"
    12  	"path/filepath"
    13  	"time"
    14  
    15  	"github.com/juju/clock"
    16  	"github.com/juju/clock/testclock"
    17  	"github.com/juju/errors"
    18  	"github.com/juju/testing"
    19  	jc "github.com/juju/testing/checkers"
    20  	"go.uber.org/mock/gomock"
    21  	gc "gopkg.in/check.v1"
    22  
    23  	"github.com/juju/juju/core/base"
    24  	"github.com/juju/juju/core/network"
    25  	"github.com/juju/juju/mongo"
    26  	"github.com/juju/juju/mongo/mongotest"
    27  	"github.com/juju/juju/packaging"
    28  	"github.com/juju/juju/service/common"
    29  	"github.com/juju/juju/service/snap"
    30  	coretesting "github.com/juju/juju/testing"
    31  )
    32  
    33  type MongoSuite struct {
    34  	coretesting.BaseSuite
    35  
    36  	clock            clock.Clock
    37  	mongodConfigPath string
    38  
    39  	mongoSnapService *mongotest.MockMongoSnapService
    40  }
    41  
    42  var _ = gc.Suite(&MongoSuite{})
    43  
    44  var testInfo = struct {
    45  	StatePort    int
    46  	Cert         string
    47  	PrivateKey   string
    48  	SharedSecret string
    49  }{
    50  	StatePort:    25252,
    51  	Cert:         "foobar-cert",
    52  	PrivateKey:   "foobar-privkey",
    53  	SharedSecret: "foobar-sharedsecret",
    54  }
    55  
    56  func makeEnsureServerParams(dataDir, configDir string) mongo.EnsureServerParams {
    57  	return mongo.EnsureServerParams{
    58  		StatePort:    testInfo.StatePort,
    59  		Cert:         testInfo.Cert,
    60  		PrivateKey:   testInfo.PrivateKey,
    61  		SharedSecret: testInfo.SharedSecret,
    62  
    63  		DataDir:           dataDir,
    64  		ConfigDir:         configDir,
    65  		JujuDBSnapChannel: "latest",
    66  
    67  		OplogSize: 1,
    68  	}
    69  }
    70  
    71  func (s *MongoSuite) SetUpTest(c *gc.C) {
    72  	s.BaseSuite.SetUpTest(c)
    73  
    74  	testing.PatchExecutable(c, s, "juju-db.mongod", "#!/bin/bash\n\nprintf %s 'db version v6.6.6'\n")
    75  	jujuMongodPath, err := exec.LookPath("juju-db.mongod")
    76  	c.Assert(err, jc.ErrorIsNil)
    77  	s.PatchValue(&mongo.JujuDbSnapMongodPath, jujuMongodPath)
    78  
    79  	// Patch "df" such that it always reports there's 1MB free.
    80  	s.PatchValue(mongo.AvailSpace, func(dir string) (float64, error) {
    81  		info, err := os.Stat(dir)
    82  		if err != nil {
    83  			return 0, err
    84  		}
    85  		if info.IsDir() {
    86  			return 1, nil
    87  
    88  		}
    89  		return 0, fmt.Errorf("not a directory")
    90  	})
    91  	s.PatchValue(mongo.SmallOplogSizeMB, 1)
    92  
    93  	s.clock = testclock.NewClock(time.Now())
    94  }
    95  
    96  func (s *MongoSuite) setupMocks(c *gc.C) *gomock.Controller {
    97  	ctrl := gomock.NewController(c)
    98  
    99  	s.mongoSnapService = mongotest.NewMockMongoSnapService(ctrl)
   100  
   101  	return ctrl
   102  }
   103  
   104  func (s *MongoSuite) expectInstallMongoSnap() {
   105  	mExp := s.mongoSnapService.EXPECT()
   106  	mExp.Name().Return("not-juju-db")
   107  	mExp.Install().Return(nil)
   108  	mExp.ConfigOverride().Return(nil)
   109  	mExp.Start().Return(nil).AnyTimes()
   110  	mExp.Running().Return(true, nil).AnyTimes()
   111  
   112  	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) {
   113  		return s.mongoSnapService, nil
   114  	})
   115  }
   116  
   117  func (s *MongoSuite) assertTLSKeyFile(c *gc.C, dataDir string) {
   118  	contents, err := os.ReadFile(mongo.SSLKeyPath(dataDir))
   119  	c.Assert(err, jc.ErrorIsNil)
   120  	c.Assert(string(contents), gc.Equals, testInfo.Cert+"\n"+testInfo.PrivateKey)
   121  }
   122  
   123  func (s *MongoSuite) assertSharedSecretFile(c *gc.C, dataDir string) {
   124  	contents, err := os.ReadFile(mongo.SharedSecretPath(dataDir))
   125  	c.Assert(err, jc.ErrorIsNil)
   126  	c.Assert(string(contents), gc.Equals, testInfo.SharedSecret)
   127  }
   128  
   129  func (s *MongoSuite) assertMongoConfigFile(c *gc.C, dataDir string, ipV6 bool) {
   130  	contents, err := os.ReadFile(s.mongodConfigPath)
   131  	c.Assert(err, jc.ErrorIsNil)
   132  	part1 := fmt.Sprintf(`
   133  # WARNING
   134  # autogenerated by juju on .*
   135  # manual changes to this file are likely to be overwritten
   136  auth = true
   137  bind_ip_all = true
   138  dbpath = %s/db`[1:], dataDir)
   139  	if ipV6 {
   140  		part1 += "\nipv6 = true"
   141  	}
   142  
   143  	part2 := fmt.Sprintf(`
   144  journal = true
   145  keyFile = %s/shared-secret
   146  logpath = %s/logs/mongodb.log
   147  oplogSize = 1
   148  port = 25252
   149  quiet = true
   150  replSet = juju
   151  slowms = 1000
   152  storageEngine = wiredTiger
   153  tlsCertificateKeyFile = %s/server.pem
   154  tlsCertificateKeyFilePassword=ignored
   155  tlsMode = requireTLS`, dataDir, dataDir, dataDir)
   156  
   157  	c.Assert(string(contents), gc.Matches, part1+part2)
   158  }
   159  
   160  func (s *MongoSuite) TestEnsureServerInstalled(c *gc.C) {
   161  	defer s.setupMocks(c).Finish()
   162  	s.expectInstallMongoSnap()
   163  
   164  	dataDir := s.assertEnsureServerIPv6(c, true)
   165  
   166  	s.assertTLSKeyFile(c, dataDir)
   167  	s.assertSharedSecretFile(c, dataDir)
   168  	s.assertMongoConfigFile(c, dataDir, true)
   169  
   170  	// make sure that we log the version of mongodb as we get ready to
   171  	// start it
   172  	tlog := c.GetTestLog()
   173  	anyExp := `(.|\n)*`
   174  	start := "^" + anyExp
   175  	tail := anyExp + "$"
   176  	c.Assert(tlog, gc.Matches, start+`using mongod: .*mongod --version:\sdb version v\d\.\d\.\d`+tail)
   177  }
   178  
   179  func (s *MongoSuite) TestEnsureServerInstalledNoIPv6(c *gc.C) {
   180  	defer s.setupMocks(c).Finish()
   181  	s.expectInstallMongoSnap()
   182  
   183  	dataDir := s.assertEnsureServerIPv6(c, false)
   184  
   185  	s.assertTLSKeyFile(c, dataDir)
   186  	s.assertSharedSecretFile(c, dataDir)
   187  	s.assertMongoConfigFile(c, dataDir, false)
   188  }
   189  
   190  func (s *MongoSuite) TestEnsureServerInstalledSetsSysctlValues(c *gc.C) {
   191  	defer s.setupMocks(c).Finish()
   192  	s.expectInstallMongoSnap()
   193  
   194  	dataDir := c.MkDir()
   195  	dataFilePath := filepath.Join(dataDir, "mongoKernelTweaks")
   196  	dataFile, err := os.Create(dataFilePath)
   197  	c.Assert(err, jc.ErrorIsNil)
   198  	_, err = dataFile.WriteString("original value")
   199  	c.Assert(err, jc.ErrorIsNil)
   200  	_ = dataFile.Close()
   201  
   202  	testing.PatchExecutableAsEchoArgs(c, s, "snap")
   203  
   204  	contents, err := os.ReadFile(dataFilePath)
   205  	c.Assert(err, jc.ErrorIsNil)
   206  	c.Assert(string(contents), gc.Equals, "original value")
   207  
   208  	configDir := c.MkDir()
   209  	err = mongo.SysctlEditableEnsureServer(
   210  		context.Background(),
   211  		makeEnsureServerParams(dataDir, configDir),
   212  		map[string]string{dataFilePath: "new value"},
   213  	)
   214  	c.Assert(err, jc.ErrorIsNil)
   215  
   216  	contents, err = os.ReadFile(dataFilePath)
   217  	c.Assert(err, jc.ErrorIsNil)
   218  	c.Assert(string(contents), gc.Equals, "new value")
   219  }
   220  
   221  func (s *MongoSuite) TestEnsureServerInstalledError(c *gc.C) {
   222  	defer s.setupMocks(c).Finish()
   223  
   224  	dataDir := c.MkDir()
   225  	configDir := c.MkDir()
   226  
   227  	testing.PatchExecutableAsEchoArgs(c, s, "snap")
   228  
   229  	failure := errors.New("boom")
   230  	s.PatchValue(mongo.InstallMongo, func(dep packaging.Dependency, b base.Base) error {
   231  		return failure
   232  	})
   233  
   234  	err := mongo.EnsureServerInstalled(context.Background(), makeEnsureServerParams(dataDir, configDir))
   235  	c.Assert(errors.Cause(err), gc.Equals, failure)
   236  }
   237  
   238  func (s *MongoSuite) assertEnsureServerIPv6(c *gc.C, ipv6 bool) string {
   239  	dataDir := c.MkDir()
   240  	configDir := c.MkDir()
   241  	s.mongodConfigPath = filepath.Join(dataDir, "juju-db.config")
   242  
   243  	testing.PatchExecutableAsEchoArgs(c, s, "snap")
   244  
   245  	s.PatchValue(mongo.SupportsIPv6, func() bool {
   246  		return ipv6
   247  	})
   248  	testParams := makeEnsureServerParams(dataDir, configDir)
   249  	err := mongo.EnsureServerInstalled(context.Background(), testParams)
   250  	c.Assert(err, jc.ErrorIsNil)
   251  	return dataDir
   252  }
   253  
   254  func (s *MongoSuite) TestNoMongoDir(c *gc.C) {
   255  	defer s.setupMocks(c).Finish()
   256  	s.expectInstallMongoSnap()
   257  
   258  	// Make a non-existent directory that can nonetheless be
   259  	// created.
   260  	testing.PatchExecutableAsEchoArgs(c, s, "snap")
   261  
   262  	dataDir := filepath.Join(c.MkDir(), "dir", "data")
   263  	configDir := c.MkDir()
   264  	err := mongo.EnsureServerInstalled(context.Background(), makeEnsureServerParams(dataDir, configDir))
   265  	c.Check(err, jc.ErrorIsNil)
   266  
   267  	_, err = os.Stat(filepath.Join(dataDir, "db"))
   268  	c.Assert(err, jc.ErrorIsNil)
   269  }
   270  
   271  func (s *MongoSuite) TestSelectPeerAddress(c *gc.C) {
   272  	addresses := network.ProviderAddresses{
   273  		network.NewMachineAddress("126.0.0.1", network.WithScope(network.ScopeMachineLocal)).AsProviderAddress(),
   274  		network.NewMachineAddress("10.0.0.1", network.WithScope(network.ScopeCloudLocal)).AsProviderAddress(),
   275  		network.NewMachineAddress("8.8.8.8", network.WithScope(network.ScopePublic)).AsProviderAddress(),
   276  	}
   277  
   278  	address := mongo.SelectPeerAddress(addresses)
   279  	c.Assert(address, gc.Equals, "10.0.0.1")
   280  }
   281  
   282  func (s *MongoSuite) TestGenerateSharedSecret(c *gc.C) {
   283  	secret, err := mongo.GenerateSharedSecret()
   284  	c.Assert(err, jc.ErrorIsNil)
   285  	c.Assert(secret, gc.HasLen, 1024)
   286  	_, err = base64.StdEncoding.DecodeString(secret)
   287  	c.Assert(err, jc.ErrorIsNil)
   288  }