github.com/mhilton/juju-juju@v0.0.0-20150901100907-a94dd2c73455/environs/configstore/disk.go (about) 1 // Copyright 2013-2015 Canonical Ltd. 2 // Licensed under the AGPLv3, see LICENCE file for details. 3 4 package configstore 5 6 import ( 7 "fmt" 8 "io/ioutil" 9 "os" 10 "path/filepath" 11 "sync" 12 "time" 13 14 "github.com/juju/errors" 15 "github.com/juju/loggo" 16 "github.com/juju/utils/featureflag" 17 "github.com/juju/utils/fslock" 18 "github.com/juju/utils/set" 19 goyaml "gopkg.in/yaml.v1" 20 21 "github.com/juju/juju/feature" 22 "github.com/juju/juju/juju/osenv" 23 ) 24 25 var logger = loggo.GetLogger("juju.environs.configstore") 26 27 type configSource string 28 29 const ( 30 lockName = "env.lock" 31 32 sourceCreated configSource = "created" 33 sourceJenv configSource = "jenv" 34 sourceCache configSource = "cache" 35 sourceMem configSource = "mem" 36 ) 37 38 // A second should be way more than enough to write or read any files. 39 var lockTimeout = time.Second 40 41 // Default returns disk-based environment config storage 42 // rooted at JujuHome. 43 var Default = func() (Storage, error) { 44 return NewDisk(osenv.JujuHome()) 45 } 46 47 type diskStore struct { 48 dir string 49 } 50 51 // EnvironInfoData is the serialisation structure for the original JENV file. 52 type EnvironInfoData struct { 53 User string 54 Password string 55 EnvironUUID string `json:"environ-uuid,omitempty" yaml:"environ-uuid,omitempty"` 56 ServerUUID string `json:"server-uuid,omitempty" yaml:"server-uuid,omitempty"` 57 StateServers []string `json:"state-servers" yaml:"state-servers"` 58 ServerHostnames []string `json:"server-hostnames,omitempty" yaml:"server-hostnames,omitempty"` 59 CACert string `json:"ca-cert" yaml:"ca-cert"` 60 Config map[string]interface{} `json:"bootstrap-config,omitempty" yaml:"bootstrap-config,omitempty"` 61 } 62 63 type environInfo struct { 64 mu sync.Mutex 65 66 // environmentDir is the directory where the files are written. 67 environmentDir string 68 69 // path is the location of the file that we read to load the info. 70 path string 71 72 // source identifies how this instance was created 73 source configSource 74 75 name string 76 user string 77 credentials string 78 environmentUUID string 79 serverUUID string 80 apiEndpoints []string 81 apiHostnames []string 82 caCert string 83 bootstrapConfig map[string]interface{} 84 } 85 86 // NewDisk returns a ConfigStorage implementation that stores configuration in 87 // the given directory. The parent of the directory must already exist; the 88 // directory itself is created if it doesn't already exist. 89 func NewDisk(dir string) (Storage, error) { 90 if _, err := os.Stat(dir); err != nil { 91 return nil, err 92 } 93 if err := os.MkdirAll(dir, 0755); err != nil { 94 return nil, err 95 } 96 d := &diskStore{ 97 dir: filepath.Join(dir, "environments"), 98 } 99 if err := d.mkEnvironmentsDir(); err != nil { 100 return nil, err 101 } 102 return d, nil 103 } 104 105 func (d *diskStore) mkEnvironmentsDir() error { 106 err := os.Mkdir(d.dir, 0700) 107 if os.IsExist(err) { 108 return nil 109 } 110 logger.Debugf("Made dir %v", d.dir) 111 return err 112 } 113 114 // CreateInfo implements Storage.CreateInfo. 115 func (d *diskStore) CreateInfo(envName string) EnvironInfo { 116 return &environInfo{ 117 environmentDir: d.dir, 118 source: sourceCreated, 119 name: envName, 120 } 121 } 122 123 // List implements Storage.List 124 func (d *diskStore) List() ([]string, error) { 125 var envs []string 126 127 // Awkward - list both jenv files and cache entries. 128 cache, err := readCacheFile(cacheFilename(d.dir)) 129 if err != nil { 130 return nil, errors.Trace(err) 131 } 132 for name := range cache.Environment { 133 envs = append(envs, name) 134 } 135 136 files, err := filepath.Glob(d.dir + "/*" + jenvExtension) 137 if err != nil { 138 return nil, err 139 } 140 for _, file := range files { 141 fName := filepath.Base(file) 142 name := fName[:len(fName)-len(jenvExtension)] 143 envs = append(envs, name) 144 } 145 return envs, nil 146 } 147 148 // ListSystems implements Storage.ListSystems 149 func (d *diskStore) ListSystems() ([]string, error) { 150 // List both jenv files and cache entries. Record 151 // results in a set to avoid repeat entries. 152 servers := set.NewStrings() 153 cache, err := readCacheFile(cacheFilename(d.dir)) 154 if err != nil { 155 return nil, errors.Trace(err) 156 } 157 for name := range cache.Server { 158 servers.Add(name) 159 } 160 161 files, err := filepath.Glob(d.dir + "/*" + jenvExtension) 162 if err != nil { 163 return nil, err 164 } 165 for _, file := range files { 166 fName := filepath.Base(file) 167 name := fName[:len(fName)-len(jenvExtension)] 168 env, err := d.ReadInfo(name) 169 if err != nil { 170 return nil, err 171 } 172 173 // If ServerUUID is not set, it is an old env and is a 174 // server by default. Otherwise, if the server and env 175 // UUIDs match, it is a server. 176 api := env.APIEndpoint() 177 if api.ServerUUID == "" || api.ServerUUID == api.EnvironUUID { 178 servers.Add(name) 179 } 180 } 181 return servers.SortedValues(), nil 182 } 183 184 // ReadInfo implements Storage.ReadInfo. 185 func (d *diskStore) ReadInfo(envName string) (EnvironInfo, error) { 186 // NOTE: any reading or writing from the directory should be done with a 187 // fslock to make sure we have a consistent read or write. Also worth 188 // noting, we should use a very short timeout. 189 lock, err := acquireEnvironmentLock(d.dir, "reading") 190 if err != nil { 191 return nil, errors.Annotatef(err, "cannot read info") 192 } 193 defer lock.Unlock() 194 195 info, err := d.readCacheFile(envName) 196 if err != nil { 197 if errors.IsNotFound(err) { 198 info, err = d.readJENVFile(envName) 199 } 200 } 201 if err != nil { 202 return nil, errors.Trace(err) 203 } 204 info.environmentDir = d.dir 205 return info, nil 206 } 207 208 func cacheFilename(dir string) string { 209 return filepath.Join(dir, "cache.yaml") 210 } 211 212 func (d *diskStore) readCacheFile(envName string) (*environInfo, error) { 213 cache, err := readCacheFile(cacheFilename(d.dir)) 214 if err != nil { 215 return nil, errors.Trace(err) 216 } 217 info, err := cache.readInfo(envName) 218 if err != nil { 219 return nil, errors.Trace(err) 220 } 221 return info, nil 222 } 223 224 // Initialized implements EnvironInfo.Initialized. 225 func (info *environInfo) Initialized() bool { 226 info.mu.Lock() 227 defer info.mu.Unlock() 228 return info.initialized() 229 } 230 231 func (info *environInfo) initialized() bool { 232 return info.source != sourceCreated 233 } 234 235 // BootstrapConfig implements EnvironInfo.BootstrapConfig. 236 func (info *environInfo) BootstrapConfig() map[string]interface{} { 237 info.mu.Lock() 238 defer info.mu.Unlock() 239 return info.bootstrapConfig 240 } 241 242 // APICredentials implements EnvironInfo.APICredentials. 243 func (info *environInfo) APICredentials() APICredentials { 244 info.mu.Lock() 245 defer info.mu.Unlock() 246 return APICredentials{ 247 User: info.user, 248 Password: info.credentials, 249 } 250 } 251 252 // APIEndpoint implements EnvironInfo.APIEndpoint. 253 func (info *environInfo) APIEndpoint() APIEndpoint { 254 info.mu.Lock() 255 defer info.mu.Unlock() 256 return APIEndpoint{ 257 Addresses: info.apiEndpoints, 258 Hostnames: info.apiHostnames, 259 CACert: info.caCert, 260 EnvironUUID: info.environmentUUID, 261 ServerUUID: info.serverUUID, 262 } 263 } 264 265 // SetBootstrapConfig implements EnvironInfo.SetBootstrapConfig. 266 func (info *environInfo) SetBootstrapConfig(attrs map[string]interface{}) { 267 info.mu.Lock() 268 defer info.mu.Unlock() 269 if info.source != sourceCreated { 270 panic("bootstrap config set on environment info that has not just been created") 271 } 272 info.bootstrapConfig = attrs 273 } 274 275 // SetAPIEndpoint implements EnvironInfo.SetAPIEndpoint. 276 func (info *environInfo) SetAPIEndpoint(endpoint APIEndpoint) { 277 info.mu.Lock() 278 defer info.mu.Unlock() 279 info.apiEndpoints = endpoint.Addresses 280 info.apiHostnames = endpoint.Hostnames 281 info.caCert = endpoint.CACert 282 info.environmentUUID = endpoint.EnvironUUID 283 info.serverUUID = endpoint.ServerUUID 284 } 285 286 // SetAPICredentials implements EnvironInfo.SetAPICredentials. 287 func (info *environInfo) SetAPICredentials(creds APICredentials) { 288 info.mu.Lock() 289 defer info.mu.Unlock() 290 info.user = creds.User 291 info.credentials = creds.Password 292 } 293 294 // Location returns the location of the environInfo in human readable format. 295 func (info *environInfo) Location() string { 296 info.mu.Lock() 297 defer info.mu.Unlock() 298 return fmt.Sprintf("file %q", info.path) 299 } 300 301 // Write implements EnvironInfo.Write. 302 func (info *environInfo) Write() error { 303 info.mu.Lock() 304 defer info.mu.Unlock() 305 lock, err := acquireEnvironmentLock(info.environmentDir, "writing") 306 if err != nil { 307 return errors.Annotatef(err, "cannot write info") 308 } 309 defer lock.Unlock() 310 311 // In order to write out the environment info to the cache 312 // file we need to make sure the server UUID is set. Sufficiently 313 // up to date servers will write the server UUID to the JENV 314 // file as connections are made to the API server. It is possible 315 // that for an old JENV file, the first update (on API connection) 316 // may write a JENV file, and the subsequent update will create the 317 // entry in the cache file. 318 // If the source was the cache file, then always write there to 319 // avoid stale data in the cache file. 320 if info.source == sourceCache || 321 (featureflag.Enabled(feature.JES) && info.serverUUID != "") { 322 if err := info.ensureNoJENV(); info.source == sourceCreated && err != nil { 323 return errors.Trace(err) 324 } 325 logger.Debugf("writing cache file") 326 filename := cacheFilename(info.environmentDir) 327 cache, err := readCacheFile(filename) 328 if err != nil { 329 return errors.Trace(err) 330 } 331 if err := cache.updateInfo(info); err != nil { 332 return errors.Trace(err) 333 } 334 if err := writeCacheFile(filename, cache); err != nil { 335 return errors.Trace(err) 336 } 337 oldPath := info.path 338 info.path = filename 339 // If source was jenv file, delete the jenv. 340 if info.source == sourceJenv { 341 err := os.Remove(oldPath) 342 if err != nil { 343 return errors.Trace(err) 344 } 345 } 346 info.source = sourceCache 347 } else { 348 logger.Debugf("writing jenv file") 349 if err := info.writeJENVFile(); err != nil { 350 return errors.Trace(err) 351 } 352 info.source = sourceJenv 353 } 354 return nil 355 } 356 357 // Destroy implements EnvironInfo.Destroy. 358 func (info *environInfo) Destroy() error { 359 info.mu.Lock() 360 defer info.mu.Unlock() 361 lock, err := acquireEnvironmentLock(info.environmentDir, "destroying") 362 if err != nil { 363 return errors.Annotatef(err, "cannot destroy environment info") 364 } 365 defer lock.Unlock() 366 367 if info.initialized() { 368 if info.source == sourceJenv { 369 err := os.Remove(info.path) 370 if os.IsNotExist(err) { 371 return errors.New("environment info has already been removed") 372 } 373 return err 374 } 375 if info.source == sourceCache { 376 filename := cacheFilename(info.environmentDir) 377 cache, err := readCacheFile(filename) 378 if err != nil { 379 return errors.Trace(err) 380 } 381 if err := cache.removeInfo(info); err != nil { 382 return errors.Trace(err) 383 } 384 if err := writeCacheFile(filename, cache); err != nil { 385 return errors.Trace(err) 386 } 387 return nil 388 } 389 return errors.Errorf("unknown source %q for environment info", info.source) 390 } 391 return nil 392 } 393 394 const jenvExtension = ".jenv" 395 396 func jenvFilename(basedir, envName string) string { 397 return filepath.Join(basedir, envName+jenvExtension) 398 } 399 400 func (d *diskStore) readJENVFile(envName string) (*environInfo, error) { 401 path := jenvFilename(d.dir, envName) 402 data, err := ioutil.ReadFile(path) 403 if err != nil { 404 if os.IsNotExist(err) { 405 return nil, errors.NotFoundf("environment %q", envName) 406 } 407 return nil, err 408 } 409 var info environInfo 410 info.path = path 411 if len(data) == 0 { 412 return &info, nil 413 } 414 var values EnvironInfoData 415 if err := goyaml.Unmarshal(data, &values); err != nil { 416 return nil, errors.Annotatef(err, "error unmarshalling %q", path) 417 } 418 info.name = envName 419 info.user = values.User 420 info.credentials = values.Password 421 info.environmentUUID = values.EnvironUUID 422 info.serverUUID = values.ServerUUID 423 info.caCert = values.CACert 424 info.apiEndpoints = values.StateServers 425 info.apiHostnames = values.ServerHostnames 426 info.bootstrapConfig = values.Config 427 428 info.source = sourceJenv 429 return &info, nil 430 } 431 432 func (info *environInfo) ensureNoJENV() error { 433 path := jenvFilename(info.environmentDir, info.name) 434 _, err := os.Stat(path) 435 if os.IsNotExist(err) { 436 return nil 437 } 438 if err == nil { 439 return ErrEnvironInfoAlreadyExists 440 } 441 return err 442 } 443 444 // Kept primarily for testing purposes now. 445 func (info *environInfo) writeJENVFile() error { 446 447 infoData := EnvironInfoData{ 448 User: info.user, 449 Password: info.credentials, 450 EnvironUUID: info.environmentUUID, 451 ServerUUID: info.serverUUID, 452 StateServers: info.apiEndpoints, 453 ServerHostnames: info.apiHostnames, 454 CACert: info.caCert, 455 Config: info.bootstrapConfig, 456 } 457 458 data, err := goyaml.Marshal(infoData) 459 if err != nil { 460 return errors.Annotate(err, "cannot marshal environment info") 461 } 462 // We now use a fslock to sync reads and writes across the environment, 463 // so we don't need to use a temporary file any more. 464 465 flags := os.O_WRONLY 466 if info.initialized() { 467 flags |= os.O_TRUNC 468 } else { 469 flags |= os.O_CREATE | os.O_EXCL 470 } 471 path := jenvFilename(info.environmentDir, info.name) 472 logger.Debugf("writing jenv file to %s", path) 473 file, err := os.OpenFile(path, flags, 0600) 474 if os.IsExist(err) { 475 return ErrEnvironInfoAlreadyExists 476 } 477 478 _, err = file.Write(data) 479 file.Close() 480 info.path = path 481 return errors.Annotate(err, "cannot write file") 482 } 483 484 func acquireEnvironmentLock(dir, operation string) (*fslock.Lock, error) { 485 lock, err := fslock.NewLock(dir, lockName) 486 if err != nil { 487 return nil, errors.Trace(err) 488 } 489 message := fmt.Sprintf("pid: %d, operation: %s", os.Getpid(), operation) 490 err = lock.LockWithTimeout(lockTimeout, message) 491 if err != nil { 492 logger.Warningf("configstore lock held, lock dir: %s", filepath.Join(dir, lockName)) 493 logger.Warningf(" lock holder message: %s", lock.Message()) 494 return nil, errors.Trace(err) 495 } 496 return lock, nil 497 }