github.com/juju/juju@v0.0.0-20240430160146-1752b71fcf00/mongo/service.go (about) 1 // Copyright 2015 Canonical Ltd. 2 // Licensed under the AGPLv3, see LICENCE file for details. 3 4 package mongo 5 6 import ( 7 "fmt" 8 "path/filepath" 9 "sort" 10 "strconv" 11 "strings" 12 "time" 13 14 "github.com/juju/clock" 15 "github.com/juju/collections/set" 16 "github.com/juju/errors" 17 "github.com/juju/utils/v3" 18 19 "github.com/juju/juju/network" 20 ) 21 22 const ( 23 // ServiceName is the name of the service that Juju's mongod instance 24 // will be named. 25 ServiceName = "juju-db" 26 27 // SharedSecretFile is the name of the Mongo shared secret file 28 // located within the Juju data directory. 29 SharedSecretFile = "shared-secret" 30 31 // ReplicaSetName is the name of the replica set that juju uses for its 32 // controllers. 33 ReplicaSetName = "juju" 34 35 // LowCacheSize expressed in GB sets the max value Mongo WiredTiger cache can 36 // reach, down to 256MB. 37 LowCacheSize = 0.25 38 39 // flagMarker is an in-line comment for bash. If it somehow makes its way onto 40 // the command line, it will be ignored. See https://stackoverflow.com/a/1456019/395287 41 flagMarker = "`#flag: true` \\" 42 43 dataPathForJujuDbSnap = "/var/snap/juju-db/common" 44 45 // FileNameDBSSLKey is the file name of db ssl key file name. 46 FileNameDBSSLKey = "server.pem" 47 ) 48 49 // See https://docs.mongodb.com/manual/reference/ulimit/. 50 var mongoULimits = map[string]string{ 51 "fsize": "unlimited", // file size 52 "cpu": "unlimited", // cpu time 53 "as": "unlimited", // virtual memory size 54 "memlock": "unlimited", // locked-in-memory size 55 "nofile": "64000", // open files 56 "nproc": "64000", // processes/threads 57 } 58 59 func sslKeyPath(dataDir string) string { 60 return filepath.Join(dataDir, FileNameDBSSLKey) 61 } 62 63 func sharedSecretPath(dataDir string) string { 64 return filepath.Join(dataDir, SharedSecretFile) 65 } 66 67 func logPath(dataDir string) string { 68 return filepath.Join(dataDir, "logs", "mongodb.log") 69 } 70 71 func configPath(dataDir string) string { 72 return filepath.Join(dataDir, "juju-db.config") 73 } 74 75 // ConfigArgs holds the attributes of a service configuration for mongo. 76 type ConfigArgs struct { 77 Clock clock.Clock 78 79 DataDir string 80 DBDir string 81 ReplicaSet string 82 83 // connection params 84 BindIP string 85 BindToAllIP bool 86 Port int 87 OplogSizeMB int 88 89 // auth 90 AuthKeyFile string 91 PEMKeyFile string 92 PEMKeyPassword string 93 94 // network params 95 IPv6 bool 96 TLSOnNormalPorts bool 97 TLSMode string 98 99 // Logging. Syslog cannot be true with LogPath set to a non-empty string. 100 // SlowMS is the threshold time in milliseconds that Mongo will consider an 101 // operation to be slow, causing it to be written to the log. 102 Syslog bool 103 LogPath string 104 SlowMS int 105 106 // db kernel 107 MemoryProfile MemoryProfile 108 WiredTigerCacheSizeGB float32 109 110 // misc 111 Quiet bool 112 } 113 114 type configArgsConverter map[string]string 115 116 func (conf configArgsConverter) asMongoDbConfigurationFileFormat() string { 117 pathArgs := set.NewStrings("dbpath", "logpath", "tlsCertificateKeyFile", "keyFile") 118 command := make([]string, 0, len(conf)) 119 var keys []string 120 for k := range conf { 121 keys = append(keys, k) 122 } 123 sort.Strings(keys) 124 for _, key := range keys { 125 value := conf[key] 126 if len(key) == 0 { 127 continue 128 } 129 if pathArgs.Contains(key) { 130 value = strings.Trim(value, " '") 131 } 132 if value == flagMarker { 133 value = "true" 134 } 135 line := fmt.Sprintf("%s = %s", key, value) 136 if strings.HasPrefix(key, "tlsCertificateKeyFilePassword") { 137 line = key 138 } 139 command = append(command, line) 140 } 141 142 return strings.Join(command, "\n") 143 } 144 145 func (mongoArgs *ConfigArgs) asMap() configArgsConverter { 146 result := configArgsConverter{} 147 result["replSet"] = mongoArgs.ReplicaSet 148 result["dbpath"] = utils.ShQuote(mongoArgs.DBDir) 149 150 if mongoArgs.LogPath != "" { 151 result["logpath"] = utils.ShQuote(mongoArgs.LogPath) 152 } 153 154 if mongoArgs.BindIP != "" { 155 result["bind_ip"] = mongoArgs.BindIP 156 } 157 if mongoArgs.Port != 0 { 158 result["port"] = strconv.Itoa(mongoArgs.Port) 159 } 160 if mongoArgs.IPv6 { 161 result["ipv6"] = flagMarker 162 } 163 if mongoArgs.BindToAllIP { 164 result["bind_ip_all"] = flagMarker 165 } 166 if mongoArgs.TLSMode != "" { 167 result["tlsMode"] = mongoArgs.TLSMode 168 } 169 if mongoArgs.TLSOnNormalPorts { 170 result["tlsOnNormalPorts"] = flagMarker 171 } 172 173 // authn 174 if mongoArgs.PEMKeyFile != "" { 175 result["tlsCertificateKeyFile"] = utils.ShQuote(mongoArgs.PEMKeyFile) 176 //--tlsCertificateKeyFilePassword must be concatenated to the equals sign (lp:1581284) 177 pemPassword := mongoArgs.PEMKeyPassword 178 if pemPassword == "" { 179 pemPassword = "ignored" 180 } 181 result["tlsCertificateKeyFilePassword="+pemPassword] = flagMarker 182 } 183 184 if mongoArgs.AuthKeyFile != "" { 185 result["auth"] = flagMarker 186 result["keyFile"] = utils.ShQuote(mongoArgs.AuthKeyFile) 187 } else { 188 logger.Warningf("configuring mongod with --noauth flag enabled") 189 result["noauth"] = flagMarker 190 } 191 192 // ops config 193 result["journal"] = flagMarker 194 if mongoArgs.OplogSizeMB != 0 { 195 result["oplogSize"] = strconv.Itoa(mongoArgs.OplogSizeMB) 196 } 197 198 result["storageEngine"] = string(WiredTiger) 199 if mongoArgs.WiredTigerCacheSizeGB > 0.0 { 200 result["wiredTigerCacheSizeGB"] = fmt.Sprint(mongoArgs.WiredTigerCacheSizeGB) 201 } 202 203 // Logging 204 if mongoArgs.Syslog { 205 result["syslog"] = flagMarker 206 } 207 if mongoArgs.SlowMS != 0 { 208 result["slowms"] = strconv.Itoa(mongoArgs.SlowMS) 209 } 210 211 // misc 212 if mongoArgs.Quiet { 213 result["quiet"] = flagMarker 214 } 215 216 return result 217 } 218 219 func (mongoArgs *ConfigArgs) writeConfig(path string) error { 220 generatedAt := mongoArgs.Clock.Now().UTC().Format(time.RFC822) 221 configPrologue := fmt.Sprintf(` 222 # WARNING 223 # autogenerated by juju on %v 224 # manual changes to this file are likely to be overwritten 225 `[1:], generatedAt) 226 configBody := mongoArgs.asMap().asMongoDbConfigurationFileFormat() 227 config := []byte(configPrologue + configBody) 228 229 err := utils.AtomicWriteFile(path, config, 0644) 230 if err != nil { 231 return errors.Annotate(err, fmt.Sprintf("writingconfig to %s", path)) 232 } 233 234 return nil 235 } 236 237 // Override for testing. 238 var supportsIPv6 = network.SupportsIPv6 239 240 // newMongoDBArgsWithDefaults returns *mongoDbConfigArgs 241 // under the assumption that MongoDB 3.4 or later is running. 242 func generateConfig(oplogSizeMB int, args EnsureServerParams) *ConfigArgs { 243 useLowMemory := args.MemoryProfile == MemoryProfileLow 244 245 mongoArgs := &ConfigArgs{ 246 Clock: clock.WallClock, 247 DataDir: args.DataDir, 248 DBDir: dbDir(args.DataDir), 249 LogPath: logPath(args.DataDir), 250 Port: args.StatePort, 251 OplogSizeMB: oplogSizeMB, 252 IPv6: supportsIPv6(), 253 MemoryProfile: args.MemoryProfile, 254 // Switch from syslog to appending to dataDir, because snaps don't 255 // have the same permissions. 256 Syslog: false, 257 // SlowMS defaults to 100. This appears to log excessively. 258 SlowMS: 1000, 259 Quiet: true, 260 ReplicaSet: ReplicaSetName, 261 AuthKeyFile: sharedSecretPath(args.DataDir), 262 PEMKeyFile: sslKeyPath(args.DataDir), 263 PEMKeyPassword: "ignored", // used as boilerplate later 264 TLSOnNormalPorts: false, 265 TLSMode: "requireTLS", 266 BindToAllIP: true, // TODO(tsm): disable when not needed 267 //BindIP: "127.0.0.1", // TODO(tsm): use machine's actual IP address via dialInfo 268 } 269 270 if useLowMemory { 271 mongoArgs.WiredTigerCacheSizeGB = LowCacheSize 272 } 273 return mongoArgs 274 }