github.com/juju/juju@v0.0.0-20240430160146-1752b71fcf00/state/controller.go (about) 1 // Copyright 2016 Canonical Ltd. 2 // Licensed under the AGPLv3, see LICENCE file for details. 3 4 package state 5 6 import ( 7 "fmt" 8 "strings" 9 10 "github.com/juju/errors" 11 "github.com/juju/mgo/v3" 12 "github.com/juju/mgo/v3/bson" 13 "github.com/juju/mgo/v3/txn" 14 "github.com/juju/names/v5" 15 16 jujucontroller "github.com/juju/juju/controller" 17 ) 18 19 const ( 20 // ControllerSettingsGlobalKey is the key for the controller and its settings. 21 ControllerSettingsGlobalKey = "controllerSettings" 22 23 // controllerGlobalKey is the key for controller. 24 controllerGlobalKey = "c" 25 ) 26 27 // controllerKey will return the key for a given controller using the 28 // controller uuid and the controllerGlobalKey. 29 func controllerKey(controllerUUID string) string { 30 return fmt.Sprintf("%s#%s", controllerGlobalKey, controllerUUID) 31 } 32 33 // Controller encapsulates state for the Juju controller as a whole, 34 // as opposed to model specific functionality. 35 // 36 // This type is primarily used in the state.Initialize function, and 37 // in the yet to be hooked up controller worker. 38 type Controller struct { 39 pool *StatePool 40 ownsPool bool 41 } 42 43 // NewController returns a controller object that doesn't own 44 // the state pool it has been given. This is for convenience 45 // at this time to get access to controller methods. 46 func NewController(pool *StatePool) *Controller { 47 return &Controller{pool: pool} 48 } 49 50 // StatePool provides access to the state pool of the controller. 51 func (ctlr *Controller) StatePool() *StatePool { 52 return ctlr.pool 53 } 54 55 // SystemState returns the State object for the controller model. 56 func (ctlr *Controller) SystemState() (*State, error) { 57 return ctlr.pool.SystemState() 58 } 59 60 // Close the connection to the database. 61 func (ctlr *Controller) Close() error { 62 if ctlr.ownsPool { 63 ctlr.pool.Close() 64 } 65 return nil 66 } 67 68 // GetState returns a new State instance for the specified model. The 69 // connection uses the same credentials and policy as the Controller. 70 func (ctlr *Controller) GetState(modelTag names.ModelTag) (*PooledState, error) { 71 return ctlr.pool.Get(modelTag.Id()) 72 } 73 74 // Ping probes the Controllers's database connection to ensure that it 75 // is still alive. 76 func (ctlr *Controller) Ping() error { 77 systemState, err := ctlr.pool.SystemState() 78 if err != nil { 79 return errors.Trace(err) 80 } 81 return systemState.Ping() 82 } 83 84 // ControllerConfig returns the config values for the controller. 85 func (st *State) ControllerConfig() (jujucontroller.Config, error) { 86 settings, err := readSettings(st.db(), controllersC, ControllerSettingsGlobalKey) 87 if err != nil { 88 return nil, errors.Annotatef(err, "controller %q", st.ControllerUUID()) 89 } 90 return settings.Map(), nil 91 } 92 93 // UpdateControllerConfig allows changing some of the configuration 94 // for the controller. Changes passed in updateAttrs will be applied 95 // to the current config, and keys in removeAttrs will be unset (and 96 // so revert to their defaults). Only a subset of keys can be changed 97 // after bootstrapping. 98 func (st *State) UpdateControllerConfig(updateAttrs map[string]interface{}, removeAttrs []string) error { 99 fields, _, err := jujucontroller.ConfigSchema.ValidationSchema() 100 if err != nil { 101 return errors.Trace(err) 102 } 103 for k := range updateAttrs { 104 if field, ok := fields[k]; ok { 105 v, err := field.Coerce(updateAttrs[k], []string{k}) 106 if err != nil { 107 return err 108 } 109 updateAttrs[k] = v 110 } 111 } 112 113 if err := st.checkValidControllerConfig(updateAttrs, removeAttrs); err != nil { 114 return errors.Trace(err) 115 } 116 117 settings, err := readSettings(st.db(), controllersC, ControllerSettingsGlobalKey) 118 if err != nil { 119 return errors.Annotatef(err, "controller %q", st.ControllerUUID()) 120 } 121 for _, r := range removeAttrs { 122 settings.Delete(r) 123 } 124 settings.Update(updateAttrs) 125 126 // Ensure the resulting config is still valid. 127 newValues := settings.Map() 128 _, err = jujucontroller.NewConfig( 129 newValues[jujucontroller.ControllerUUIDKey].(string), 130 newValues[jujucontroller.CACertKey].(string), 131 newValues, 132 ) 133 if err != nil { 134 return errors.Trace(err) 135 } 136 137 _, ops := settings.settingsUpdateOps() 138 return errors.Trace(settings.write(ops)) 139 } 140 141 func (st *State) checkValidControllerConfig(updateAttrs map[string]interface{}, removeAttrs []string) error { 142 for k := range updateAttrs { 143 if err := checkUpdateControllerConfig(k); err != nil { 144 return errors.Trace(err) 145 } 146 147 if k == jujucontroller.JujuHASpace || k == jujucontroller.JujuManagementSpace { 148 cVal := updateAttrs[k].(string) 149 if err := st.checkSpaceIsAvailableToAllControllers(cVal); err != nil { 150 return errors.Annotatef(err, "invalid config %q=%q", k, cVal) 151 } 152 } 153 } 154 for _, r := range removeAttrs { 155 if err := checkUpdateControllerConfig(r); err != nil { 156 return errors.Trace(err) 157 } 158 } 159 return nil 160 } 161 162 func checkUpdateControllerConfig(name string) error { 163 if !jujucontroller.ControllerOnlyAttribute(name) { 164 return errors.Errorf("unknown controller config setting %q", name) 165 } 166 if !jujucontroller.AllowedUpdateConfigAttributes.Contains(name) { 167 return errors.Errorf("can't change %q after bootstrap", name) 168 } 169 return nil 170 } 171 172 // checkSpaceIsAvailableToAllControllers checks if each controller machine has 173 // at least one address in the input space. If not, an error is returned. 174 func (st *State) checkSpaceIsAvailableToAllControllers(spaceName string) error { 175 controllerIds, err := st.ControllerIds() 176 if err != nil { 177 return errors.Annotate(err, "cannot get controller info") 178 } 179 180 space, err := st.SpaceByName(spaceName) 181 if err != nil { 182 return errors.Trace(err) 183 } 184 netSpace, err := space.NetworkSpace() 185 if err != nil { 186 return errors.Annotate(err, "getting network space") 187 } 188 189 var missing []string 190 for _, id := range controllerIds { 191 m, err := st.Machine(id) 192 if err != nil { 193 return errors.Annotate(err, "cannot get machine") 194 } 195 if _, ok := m.Addresses().InSpaces(netSpace); !ok { 196 missing = append(missing, id) 197 } 198 } 199 200 if len(missing) > 0 { 201 return errors.Errorf("machines with no addresses in this space: %s", strings.Join(missing, ", ")) 202 } 203 return nil 204 } 205 206 type controllersDoc struct { 207 Id string `bson:"_id"` 208 CloudName string `bson:"cloud"` 209 ModelUUID string `bson:"model-uuid"` 210 ControllerIds []string `bson:"controller-ids"` 211 } 212 213 // ControllerInfo holds information about currently 214 // configured controller machines. 215 type ControllerInfo struct { 216 // CloudName is the name of the cloud to which this controller is deployed. 217 CloudName string 218 219 // ModelTag identifies the initial model. Only the initial 220 // model is able to have machines that manage state. The initial 221 // model is the model that is created when bootstrapping. 222 ModelTag names.ModelTag 223 224 // ControllerIds holds the ids of all the controller nodes. 225 // It's main purpose is to allow assertions tha the set of 226 // controllers hasn't changed when adding/removing controller nodes. 227 ControllerIds []string 228 } 229 230 // ControllerInfo returns information about 231 // the currently configured controller machines. 232 func (st *State) ControllerInfo() (*ControllerInfo, error) { 233 session := st.session.Copy() 234 defer session.Close() 235 return readRawControllerInfo(session) 236 } 237 238 // readRawControllerInfo reads ControllerInfo direct from the supplied session, 239 // falling back to the bootstrap model document to extract the UUID when 240 // required. 241 func readRawControllerInfo(session *mgo.Session) (*ControllerInfo, error) { 242 db := session.DB(jujuDB) 243 controllers := db.C(controllersC) 244 245 var doc controllersDoc 246 err := controllers.Find(bson.D{{"_id", modelGlobalKey}}).One(&doc) 247 if err == mgo.ErrNotFound { 248 return nil, errors.NotFoundf("controllers document") 249 } 250 if err != nil { 251 return nil, errors.Annotatef(err, "cannot get controllers document") 252 } 253 return &ControllerInfo{ 254 CloudName: doc.CloudName, 255 ModelTag: names.NewModelTag(doc.ModelUUID), 256 ControllerIds: doc.ControllerIds, 257 }, nil 258 } 259 260 const stateServingInfoKey = "stateServingInfo" 261 262 type stateServingInfo struct { 263 APIPort int `bson:"apiport"` 264 StatePort int `bson:"stateport"` 265 Cert string `bson:"cert"` 266 PrivateKey string `bson:"privatekey"` 267 CAPrivateKey string `bson:"caprivatekey"` 268 // this will be passed as the KeyFile argument to MongoDB 269 SharedSecret string `bson:"sharedsecret"` 270 SystemIdentity string `bson:"systemidentity"` 271 } 272 273 // StateServingInfo returns information for running a controller machine 274 func (st *State) StateServingInfo() (jujucontroller.StateServingInfo, error) { 275 controllers, closer := st.db().GetCollection(controllersC) 276 defer closer() 277 278 var info stateServingInfo 279 err := controllers.Find(bson.D{{"_id", stateServingInfoKey}}).One(&info) 280 if err != nil { 281 return jujucontroller.StateServingInfo{}, errors.Trace(err) 282 } 283 if info.StatePort == 0 { 284 return jujucontroller.StateServingInfo{}, errors.NotFoundf("state serving info") 285 } 286 return jujucontroller.StateServingInfo{ 287 APIPort: info.APIPort, 288 StatePort: info.StatePort, 289 Cert: info.Cert, 290 PrivateKey: info.PrivateKey, 291 CAPrivateKey: info.CAPrivateKey, 292 SharedSecret: info.SharedSecret, 293 SystemIdentity: info.SystemIdentity, 294 }, nil 295 } 296 297 // SetStateServingInfo stores information needed for running a controller 298 func (st *State) SetStateServingInfo(info jujucontroller.StateServingInfo) error { 299 if info.StatePort == 0 || info.APIPort == 0 || 300 info.Cert == "" || info.PrivateKey == "" { 301 return errors.Errorf("incomplete state serving info set in state") 302 } 303 if info.CAPrivateKey == "" { 304 // No CA certificate key means we can't generate new controller 305 // certificates when needed to add to the certificate SANs. 306 // Older Juju deployments discard the key because no one realised 307 // the certificate was flawed, so at best we can log a warning 308 // until an upgrade process is written. 309 logger.Warningf("state serving info has no CA certificate key") 310 } 311 ops := []txn.Op{{ 312 C: controllersC, 313 Id: stateServingInfoKey, 314 Update: bson.D{{"$set", stateServingInfo{ 315 APIPort: info.APIPort, 316 StatePort: info.StatePort, 317 Cert: info.Cert, 318 PrivateKey: info.PrivateKey, 319 CAPrivateKey: info.CAPrivateKey, 320 SharedSecret: info.SharedSecret, 321 SystemIdentity: info.SystemIdentity, 322 }}}, 323 }} 324 if err := st.db().RunTransaction(ops); err != nil { 325 return errors.Annotatef(err, "cannot set state serving info") 326 } 327 return nil 328 }