github.com/altoros/juju-vmware@v0.0.0-20150312064031-f19ae857ccca/environs/cloudinit.go (about) 1 // Copyright 2013 Canonical Ltd. 2 // Licensed under the AGPLv3, see LICENCE file for details. 3 4 package environs 5 6 import ( 7 "fmt" 8 "path" 9 "strconv" 10 11 "github.com/juju/errors" 12 "github.com/juju/names" 13 "github.com/juju/utils" 14 "github.com/juju/utils/proxy" 15 16 "github.com/juju/juju/agent" 17 "github.com/juju/juju/api" 18 "github.com/juju/juju/apiserver/params" 19 coreCloudinit "github.com/juju/juju/cloudinit" 20 "github.com/juju/juju/constraints" 21 "github.com/juju/juju/environs/cloudinit" 22 "github.com/juju/juju/environs/config" 23 "github.com/juju/juju/juju/paths" 24 "github.com/juju/juju/mongo" 25 "github.com/juju/juju/state/multiwatcher" 26 "github.com/juju/juju/version" 27 ) 28 29 // DataDir is the default data directory. Tests can override this 30 // where needed, so they don't need to mess with global system state. 31 var DataDir = agent.DefaultDataDir 32 33 // logDir returns a filesystem path to the location where applications 34 // may create a folder containing logs 35 var logDir = paths.MustSucceed(paths.LogDir(version.Current.Series)) 36 37 // DefaultBridgeName is the network bridge device name used for LXC 38 // and KVM containers. 39 const DefaultBridgeName = "juju-br0" 40 41 // NewMachineConfig sets up a basic machine configuration, for a 42 // non-bootstrap node. You'll still need to supply more information, 43 // but this takes care of the fixed entries and the ones that are 44 // always needed. 45 func NewMachineConfig( 46 machineID, 47 machineNonce, 48 imageStream, 49 series string, 50 secureServerConnections bool, 51 networks []string, 52 mongoInfo *mongo.MongoInfo, 53 apiInfo *api.Info, 54 ) (*cloudinit.MachineConfig, error) { 55 dataDir, err := paths.DataDir(series) 56 if err != nil { 57 return nil, err 58 } 59 logDir, err := paths.LogDir(series) 60 if err != nil { 61 return nil, err 62 } 63 cloudInitOutputLog := path.Join(logDir, "cloud-init-output.log") 64 mcfg := &cloudinit.MachineConfig{ 65 // Fixed entries. 66 DataDir: dataDir, 67 LogDir: path.Join(logDir, "juju"), 68 Jobs: []multiwatcher.MachineJob{multiwatcher.JobHostUnits}, 69 CloudInitOutputLog: cloudInitOutputLog, 70 MachineAgentServiceName: "jujud-" + names.NewMachineTag(machineID).String(), 71 Series: series, 72 73 // Parameter entries. 74 MachineId: machineID, 75 MachineNonce: machineNonce, 76 Networks: networks, 77 MongoInfo: mongoInfo, 78 APIInfo: apiInfo, 79 ImageStream: imageStream, 80 AgentEnvironment: map[string]string{ 81 agent.AllowsSecureConnection: strconv.FormatBool(secureServerConnections), 82 }, 83 } 84 return mcfg, nil 85 } 86 87 // NewBootstrapMachineConfig sets up a basic machine configuration for a 88 // bootstrap node. You'll still need to supply more information, but this 89 // takes care of the fixed entries and the ones that are always needed. 90 func NewBootstrapMachineConfig(cons constraints.Value, series string) (*cloudinit.MachineConfig, error) { 91 // For a bootstrap instance, FinishMachineConfig will provide the 92 // state.Info and the api.Info. The machine id must *always* be "0". 93 mcfg, err := NewMachineConfig("0", agent.BootstrapNonce, "", series, true, nil, nil, nil) 94 if err != nil { 95 return nil, err 96 } 97 mcfg.Bootstrap = true 98 mcfg.Jobs = []multiwatcher.MachineJob{ 99 multiwatcher.JobManageEnviron, 100 multiwatcher.JobHostUnits, 101 } 102 mcfg.Constraints = cons 103 return mcfg, nil 104 } 105 106 // PopulateMachineConfig is called both from the FinishMachineConfig below, 107 // which does have access to the environment config, and from the container 108 // provisioners, which don't have access to the environment config. Everything 109 // that is needed to provision a container needs to be returned to the 110 // provisioner in the ContainerConfig structure. Those values are then used to 111 // call this function. 112 func PopulateMachineConfig(mcfg *cloudinit.MachineConfig, 113 providerType, authorizedKeys string, 114 sslHostnameVerification bool, 115 proxySettings, aptProxySettings proxy.Settings, 116 aptMirror string, 117 preferIPv6 bool, 118 enableOSRefreshUpdates bool, 119 enableOSUpgrade bool, 120 ) error { 121 if authorizedKeys == "" { 122 return fmt.Errorf("environment configuration has no authorized-keys") 123 } 124 mcfg.AuthorizedKeys = authorizedKeys 125 if mcfg.AgentEnvironment == nil { 126 mcfg.AgentEnvironment = make(map[string]string) 127 } 128 mcfg.AgentEnvironment[agent.ProviderType] = providerType 129 mcfg.AgentEnvironment[agent.ContainerType] = string(mcfg.MachineContainerType) 130 mcfg.DisableSSLHostnameVerification = !sslHostnameVerification 131 mcfg.ProxySettings = proxySettings 132 mcfg.AptProxySettings = aptProxySettings 133 mcfg.AptMirror = aptMirror 134 mcfg.PreferIPv6 = preferIPv6 135 mcfg.EnableOSRefreshUpdate = enableOSRefreshUpdates 136 mcfg.EnableOSUpgrade = enableOSUpgrade 137 return nil 138 } 139 140 // FinishMachineConfig sets fields on a MachineConfig that can be determined by 141 // inspecting a plain config.Config and the machine constraints at the last 142 // moment before bootstrapping. It assumes that the supplied Config comes from 143 // an environment that has passed through all the validation checks in the 144 // Bootstrap func, and that has set an agent-version (via finding the tools to, 145 // use for bootstrap, or otherwise). 146 // TODO(fwereade) This function is not meant to be "good" in any serious way: 147 // it is better that this functionality be collected in one place here than 148 // that it be spread out across 3 or 4 providers, but this is its only 149 // redeeming feature. 150 func FinishMachineConfig(mcfg *cloudinit.MachineConfig, cfg *config.Config) (err error) { 151 defer errors.DeferredAnnotatef(&err, "cannot complete machine configuration") 152 153 if err := PopulateMachineConfig( 154 mcfg, 155 cfg.Type(), 156 cfg.AuthorizedKeys(), 157 cfg.SSLHostnameVerification(), 158 cfg.ProxySettings(), 159 cfg.AptProxySettings(), 160 cfg.AptMirror(), 161 cfg.PreferIPv6(), 162 cfg.EnableOSRefreshUpdate(), 163 cfg.EnableOSUpgrade(), 164 ); err != nil { 165 return errors.Trace(err) 166 } 167 168 if isStateMachineConfig(mcfg) { 169 // Add NUMACTL preference. Needed to work for both bootstrap and high availability 170 // Only makes sense for state server 171 logger.Debugf("Setting numa ctl preference to %v", cfg.NumaCtlPreference()) 172 // Unfortunately, AgentEnvironment can only take strings as values 173 mcfg.AgentEnvironment[agent.NumaCtlPreference] = fmt.Sprintf("%v", cfg.NumaCtlPreference()) 174 } 175 // The following settings are only appropriate at bootstrap time. At the 176 // moment, the only state server is the bootstrap node, but this 177 // will probably change. 178 if !mcfg.Bootstrap { 179 return nil 180 } 181 if mcfg.APIInfo != nil || mcfg.MongoInfo != nil { 182 return errors.New("machine configuration already has api/state info") 183 } 184 caCert, hasCACert := cfg.CACert() 185 if !hasCACert { 186 return errors.New("environment configuration has no ca-cert") 187 } 188 password := cfg.AdminSecret() 189 if password == "" { 190 return errors.New("environment configuration has no admin-secret") 191 } 192 passwordHash := utils.UserPasswordHash(password, utils.CompatSalt) 193 envUUID, uuidSet := cfg.UUID() 194 if !uuidSet { 195 return errors.New("config missing environment uuid") 196 } 197 mcfg.APIInfo = &api.Info{ 198 Password: passwordHash, 199 CACert: caCert, 200 EnvironTag: names.NewEnvironTag(envUUID), 201 } 202 mcfg.MongoInfo = &mongo.MongoInfo{Password: passwordHash, Info: mongo.Info{CACert: caCert}} 203 204 // These really are directly relevant to running a state server. 205 // Initially, generate a state server certificate with no host IP 206 // addresses in the SAN field. Once the state server is up and the 207 // NIC addresses become known, the certificate can be regenerated. 208 cert, key, err := cfg.GenerateStateServerCertAndKey(nil) 209 if err != nil { 210 return errors.Annotate(err, "cannot generate state server certificate") 211 } 212 caPrivateKey, hasCAPrivateKey := cfg.CAPrivateKey() 213 if !hasCAPrivateKey { 214 return errors.New("environment configuration has no ca-private-key") 215 } 216 srvInfo := params.StateServingInfo{ 217 StatePort: cfg.StatePort(), 218 APIPort: cfg.APIPort(), 219 Cert: string(cert), 220 PrivateKey: string(key), 221 CAPrivateKey: caPrivateKey, 222 } 223 mcfg.StateServingInfo = &srvInfo 224 if mcfg.Config, err = BootstrapConfig(cfg); err != nil { 225 return errors.Trace(err) 226 } 227 228 return nil 229 } 230 231 // isStateMachineConfig determines if given machine configuration 232 // is for State Server by iterating over machine's jobs. 233 // If JobManageEnviron is present, this is a state server. 234 func isStateMachineConfig(mcfg *cloudinit.MachineConfig) bool { 235 for _, aJob := range mcfg.Jobs { 236 if aJob == multiwatcher.JobManageEnviron { 237 return true 238 } 239 } 240 return false 241 } 242 243 func configureCloudinit(mcfg *cloudinit.MachineConfig, cloudcfg *coreCloudinit.Config) (cloudinit.UserdataConfig, error) { 244 // When bootstrapping, we only want to apt-get update/upgrade 245 // and setup the SSH keys. The rest we leave to cloudinit/sshinit. 246 udata, err := cloudinit.NewUserdataConfig(mcfg, cloudcfg) 247 if err != nil { 248 return nil, err 249 } 250 if mcfg.Bootstrap { 251 err = udata.ConfigureBasic() 252 if err != nil { 253 return nil, err 254 } 255 return udata, nil 256 } 257 err = udata.Configure() 258 if err != nil { 259 return nil, err 260 } 261 return udata, nil 262 } 263 264 // ComposeUserData fills out the provided cloudinit configuration structure 265 // so it is suitable for initialising a machine with the given configuration, 266 // and then renders it and returns it as a binary (gzipped) blob of user data. 267 // 268 // If the provided cloudcfg is nil, a new one will be created internally. 269 func ComposeUserData(mcfg *cloudinit.MachineConfig, cloudcfg *coreCloudinit.Config) ([]byte, error) { 270 if cloudcfg == nil { 271 cloudcfg = coreCloudinit.New() 272 } 273 udata, err := configureCloudinit(mcfg, cloudcfg) 274 if err != nil { 275 return nil, err 276 } 277 data, err := udata.Render() 278 logger.Tracef("Generated cloud init:\n%s", string(data)) 279 if err != nil { 280 return nil, err 281 } 282 return utils.Gzip(data), nil 283 }