github.com/niedbalski/juju@v0.0.0-20190215020005-8ff100488e47/cloudconfig/podcfg/podcfg.go (about) 1 // Copyright 2018 Canonical Ltd. 2 // Licensed under the AGPLv3, see LICENCE file for details. 3 4 package podcfg 5 6 import ( 7 "net" 8 "path" 9 "strconv" 10 11 "github.com/juju/errors" 12 "github.com/juju/loggo" 13 "github.com/juju/version" 14 "gopkg.in/juju/names.v2" 15 16 "github.com/juju/juju/agent" 17 "github.com/juju/juju/api" 18 "github.com/juju/juju/cloudconfig/instancecfg" 19 "github.com/juju/juju/controller" 20 "github.com/juju/juju/environs/config" 21 "github.com/juju/juju/environs/tags" 22 "github.com/juju/juju/juju/paths" 23 "github.com/juju/juju/state/multiwatcher" 24 ) 25 26 var logger = loggo.GetLogger("juju.cloudconfig.podcfg") 27 28 // ControllerPodConfig represents initialization information for a new juju caas controller pod. 29 type ControllerPodConfig struct { 30 // Tags is a set of tags/labels to set on the Pod, if supported. This 31 // should be populated using the PodLabels method in this package. 32 Tags map[string]string 33 34 // Bootstrap contains bootstrap-specific configuration. If this is set, 35 // Controller must also be set. 36 Bootstrap *BootstrapConfig 37 38 // Controller contains controller-specific configuration. If this is 39 // set, then the instance will be configured as a controller pod. 40 Controller *ControllerConfig 41 42 // APIInfo holds the means for the new pod to communicate with the 43 // juju state API. Unless the new pod is running a controller (Controller is 44 // set), there must be at least one controller address supplied. 45 // The entity name must match that of the pod being started, 46 // or be empty when starting a controller. 47 APIInfo *api.Info 48 49 // ControllerTag identifies the controller. 50 ControllerTag names.ControllerTag 51 52 // JujuVersion is the juju version. 53 JujuVersion version.Number 54 55 // DataDir holds the directory that juju state will be put in the new 56 // instance. 57 DataDir string 58 59 // LogDir holds the directory that juju logs will be written to. 60 LogDir string 61 62 // MetricsSpoolDir represents the spool directory path, where all 63 // metrics are stored. 64 MetricsSpoolDir string 65 66 // Jobs holds what machine jobs to run. 67 Jobs []multiwatcher.MachineJob 68 69 // MachineId identifies the new machine. 70 MachineId string // TODO(caas): change it to PodId once we introduced the new tag for pod. 71 72 // AgentEnvironment defines additional configuration variables to set in 73 // the pod agent config. 74 AgentEnvironment map[string]string 75 } 76 77 // BootstrapConfig represents bootstrap-specific initialization information 78 // for a new juju caas pod. This is only relevant for the bootstrap pod. 79 type BootstrapConfig struct { 80 instancecfg.BootstrapConfig 81 } 82 83 // ControllerConfig represents controller-specific initialization information 84 // for a new juju caas pod. This is only relevant for controller pod. 85 type ControllerConfig struct { 86 instancecfg.ControllerConfig 87 } 88 89 // AgentConfig returns an agent config. 90 func (cfg *ControllerPodConfig) AgentConfig(tag names.Tag) (agent.ConfigSetterWriter, error) { 91 var password, cacert string 92 if cfg.Controller == nil { 93 password = cfg.APIInfo.Password 94 cacert = cfg.APIInfo.CACert 95 } else { 96 password = cfg.Controller.MongoInfo.Password 97 cacert = cfg.Controller.MongoInfo.CACert 98 } 99 configParams := agent.AgentConfigParams{ 100 Paths: agent.Paths{ 101 DataDir: cfg.DataDir, 102 LogDir: cfg.LogDir, 103 MetricsSpoolDir: cfg.MetricsSpoolDir, 104 }, 105 Jobs: cfg.Jobs, 106 Tag: tag, 107 UpgradedToVersion: cfg.JujuVersion, 108 Password: password, 109 APIAddresses: cfg.APIHostAddrs(), 110 CACert: cacert, 111 Values: cfg.AgentEnvironment, 112 Controller: cfg.ControllerTag, 113 Model: cfg.APIInfo.ModelTag, 114 } 115 return agent.NewStateMachineConfig(configParams, cfg.Bootstrap.StateServingInfo) 116 } 117 118 // APIHostAddrs returns a list of api server addresses. 119 func (cfg *ControllerPodConfig) APIHostAddrs() []string { 120 var hosts []string 121 if cfg.Bootstrap != nil { 122 hosts = append(hosts, net.JoinHostPort( 123 "localhost", strconv.Itoa(cfg.Bootstrap.StateServingInfo.APIPort)), 124 ) 125 } 126 if cfg.APIInfo != nil { 127 hosts = append(hosts, cfg.APIInfo.Addrs...) 128 } 129 return hosts 130 } 131 132 // APIHosts returns api a list of server addresses. 133 func (cfg *ControllerPodConfig) APIHosts() []string { 134 var hosts []string 135 if cfg.Bootstrap != nil { 136 hosts = append(hosts, "localhost") 137 } 138 if cfg.APIInfo != nil { 139 for _, addr := range cfg.APIInfo.Addrs { 140 host, _, err := net.SplitHostPort(addr) 141 if err != nil { 142 logger.Errorf("Can't split API address %q to host:port - %q", host, err) 143 continue 144 } 145 hosts = append(hosts, host) 146 } 147 } 148 return hosts 149 } 150 151 // VerifyConfig verifies that the ControllerPodConfig is valid. 152 func (cfg *ControllerPodConfig) VerifyConfig() (err error) { 153 defer errors.DeferredAnnotatef(&err, "invalid machine configuration") 154 if !names.IsValidMachine(cfg.MachineId) { 155 return errors.New("invalid machine id") 156 } 157 if cfg.DataDir == "" { 158 return errors.New("missing var directory") 159 } 160 if cfg.LogDir == "" { 161 return errors.New("missing log directory") 162 } 163 if cfg.MetricsSpoolDir == "" { 164 return errors.New("missing metrics spool directory") 165 } 166 if len(cfg.Jobs) == 0 { 167 return errors.New("missing machine jobs") 168 } 169 if cfg.JujuVersion == version.Zero { 170 return errors.New("missing juju version") 171 } 172 if cfg.APIInfo == nil { 173 return errors.New("missing API info") 174 } 175 if cfg.APIInfo.ModelTag.Id() == "" { 176 return errors.New("missing model tag") 177 } 178 if len(cfg.APIInfo.CACert) == 0 { 179 return errors.New("missing API CA certificate") 180 } 181 if cfg.Controller != nil { 182 if err := cfg.verifyControllerConfig(); err != nil { 183 return errors.Trace(err) 184 } 185 } 186 if cfg.Bootstrap != nil { 187 if err := cfg.verifyBootstrapConfig(); err != nil { 188 return errors.Trace(err) 189 } 190 } else { 191 if cfg.APIInfo.Tag != names.NewMachineTag(cfg.MachineId) { 192 return errors.New("API entity tag must match started machine") 193 } 194 if len(cfg.APIInfo.Addrs) == 0 { 195 return errors.New("missing API hosts") 196 } 197 } 198 return nil 199 } 200 201 func (cfg *ControllerPodConfig) verifyBootstrapConfig() (err error) { 202 defer errors.DeferredAnnotatef(&err, "invalid bootstrap configuration") 203 if cfg.Controller == nil { 204 return errors.New("bootstrap config supplied without controller config") 205 } 206 if err := cfg.Bootstrap.VerifyConfig(); err != nil { 207 return errors.Trace(err) 208 } 209 if cfg.APIInfo.Tag != nil || cfg.Controller.MongoInfo.Tag != nil { 210 return errors.New("entity tag must be nil when bootstrapping") 211 } 212 return nil 213 } 214 215 func (cfg *ControllerPodConfig) verifyControllerConfig() (err error) { 216 defer errors.DeferredAnnotatef(&err, "invalid controller configuration") 217 if err := cfg.Controller.VerifyConfig(); err != nil { 218 return errors.Trace(err) 219 } 220 if cfg.Bootstrap == nil { 221 if len(cfg.Controller.MongoInfo.Addrs) == 0 { 222 return errors.New("missing state hosts") 223 } 224 if cfg.Controller.MongoInfo.Tag != names.NewMachineTag(cfg.MachineId) { 225 return errors.New("entity tag must match started machine") 226 } 227 } 228 return nil 229 } 230 231 // VerifyConfig verifies that the BootstrapConfig is valid. 232 func (cfg *BootstrapConfig) VerifyConfig() (err error) { 233 if cfg.ControllerModelConfig == nil { 234 return errors.New("missing model configuration") 235 } 236 if len(cfg.StateServingInfo.Cert) == 0 { 237 return errors.New("missing controller certificate") 238 } 239 if len(cfg.StateServingInfo.PrivateKey) == 0 { 240 return errors.New("missing controller private key") 241 } 242 if len(cfg.StateServingInfo.CAPrivateKey) == 0 { 243 return errors.New("missing ca cert private key") 244 } 245 if cfg.StateServingInfo.StatePort == 0 { 246 return errors.New("missing state port") 247 } 248 if cfg.StateServingInfo.APIPort == 0 { 249 return errors.New("missing API port") 250 } 251 return nil 252 } 253 254 // VerifyConfig verifies that the ControllerConfig is valid. 255 func (cfg *ControllerConfig) VerifyConfig() error { 256 if cfg.MongoInfo == nil { 257 return errors.New("missing state info") 258 } 259 if len(cfg.MongoInfo.CACert) == 0 { 260 return errors.New("missing CA certificate") 261 } 262 return nil 263 } 264 265 // NewControllerPodConfig sets up a basic pod configuration. You'll still need to supply more information, 266 // but this takes care of the fixed entries and the ones that are 267 // always needed. 268 func NewControllerPodConfig( 269 controllerTag names.ControllerTag, 270 machineID, series string, 271 apiInfo *api.Info, 272 ) (*ControllerPodConfig, error) { 273 dataDir, err := paths.DataDir(series) 274 if err != nil { 275 return nil, err 276 } 277 logDir, err := paths.LogDir(series) 278 if err != nil { 279 return nil, err 280 } 281 metricsSpoolDir, err := paths.MetricsSpoolDir(series) 282 if err != nil { 283 return nil, err 284 } 285 pcfg := &ControllerPodConfig{ 286 // Fixed entries. 287 DataDir: dataDir, 288 LogDir: path.Join(logDir, "juju"), 289 MetricsSpoolDir: metricsSpoolDir, 290 // CAAS only has JobManageModel. 291 Jobs: []multiwatcher.MachineJob{ 292 multiwatcher.JobManageModel, 293 }, 294 Tags: map[string]string{}, 295 // Parameter entries. 296 ControllerTag: controllerTag, 297 MachineId: machineID, 298 APIInfo: apiInfo, 299 } 300 return pcfg, nil 301 } 302 303 // NewBootstrapControllerPodConfig sets up a basic pod configuration for a 304 // bootstrap pod. You'll still need to supply more information, but this 305 // takes care of the fixed entries and the ones that are always needed. 306 func NewBootstrapControllerPodConfig(config controller.Config, series string) (*ControllerPodConfig, error) { 307 // For a bootstrap pod, the caller must provide the state.Info 308 // and the api.Info. The machine id must *always* be "0". 309 pcfg, err := NewControllerPodConfig(names.NewControllerTag(config.ControllerUUID()), "0", series, nil) 310 if err != nil { 311 return nil, err 312 } 313 pcfg.Controller = &ControllerConfig{} 314 pcfg.Controller.Config = make(map[string]interface{}) 315 for k, v := range config { 316 pcfg.Controller.Config[k] = v 317 } 318 pcfg.Bootstrap = &BootstrapConfig{ 319 instancecfg.BootstrapConfig{ 320 StateInitializationParams: instancecfg.StateInitializationParams{}, 321 }, 322 } 323 pcfg.Jobs = []multiwatcher.MachineJob{ 324 multiwatcher.JobManageModel, 325 } 326 return pcfg, nil 327 } 328 329 // PopulateControllerPodConfig is called both from the FinishControllerPodConfig below, 330 // which does have access to the environment config, and from the container 331 // provisioners, which don't have access to the environment config. Everything 332 // that is needed to provision a container needs to be returned to the 333 // provisioner in the ContainerConfig structure. Those values are then used to 334 // call this function. 335 func PopulateControllerPodConfig(pcfg *ControllerPodConfig, providerType string) error { 336 if pcfg.AgentEnvironment == nil { 337 pcfg.AgentEnvironment = make(map[string]string) 338 } 339 pcfg.AgentEnvironment[agent.ProviderType] = providerType 340 return nil 341 } 342 343 // FinishControllerPodConfig sets fields on a ControllerPodConfig that can be determined by 344 // inspecting a plain config.Config and the pod constraints at the last 345 // moment before creating podspec. It assumes that the supplied Config comes 346 // from an environment that has passed through all the validation checks in the 347 // Bootstrap func, and that has set an agent-version (via finding the tools to, 348 // use for bootstrap, or otherwise). 349 func FinishControllerPodConfig(pcfg *ControllerPodConfig, cfg *config.Config) (err error) { 350 defer errors.DeferredAnnotatef(&err, "cannot complete pod configuration") 351 if err := PopulateControllerPodConfig(pcfg, cfg.Type()); err != nil { 352 return errors.Trace(err) 353 } 354 return nil 355 } 356 357 // PodLabels returns the minimum set of tags that should be set on a 358 // pod, if the provider supports them. 359 func PodLabels(modelUUID, controllerUUID string, tagger tags.ResourceTagger, jobs []multiwatcher.MachineJob) map[string]string { 360 podLabels := tags.ResourceTags( 361 names.NewModelTag(modelUUID), 362 names.NewControllerTag(controllerUUID), 363 tagger, 364 ) 365 // always be a controller. 366 podLabels[tags.JujuIsController] = "true" 367 return podLabels 368 }