github.com/cloudbase/juju-core@v0.0.0-20140504232958-a7271ac7912f/provider/dummy/environs.go (about) 1 // Copyright 2012, 2013 Canonical Ltd. 2 // Licensed under the AGPLv3, see LICENCE file for details. 3 4 // The dummy provider implements an environment provider for testing 5 // purposes, registered with environs under the name "dummy". 6 // 7 // The configuration YAML for the testing environment 8 // must specify a "state-server" property with a boolean 9 // value. If this is true, a state server will be started 10 // the first time StateInfo is called on a newly reset environment. 11 // 12 // The configuration data also accepts a "broken" property 13 // of type boolean. If this is non-empty, any operation 14 // after the environment has been opened will return 15 // the error "broken environment", and will also log that. 16 // 17 // The DNS name of instances is the same as the Id, 18 // with ".dns" appended. 19 // 20 // To avoid enumerating all possible series and architectures, 21 // any series or architecture with the prefix "unknown" is 22 // treated as bad when starting a new instance. 23 package dummy 24 25 import ( 26 "errors" 27 "fmt" 28 "net" 29 "net/http" 30 "os" 31 "strconv" 32 "strings" 33 "sync" 34 "time" 35 36 "github.com/juju/loggo" 37 38 "launchpad.net/juju-core/constraints" 39 "launchpad.net/juju-core/environs" 40 "launchpad.net/juju-core/environs/bootstrap" 41 "launchpad.net/juju-core/environs/cloudinit" 42 "launchpad.net/juju-core/environs/config" 43 "launchpad.net/juju-core/environs/imagemetadata" 44 "launchpad.net/juju-core/environs/simplestreams" 45 "launchpad.net/juju-core/environs/storage" 46 "launchpad.net/juju-core/environs/tools" 47 "launchpad.net/juju-core/instance" 48 "launchpad.net/juju-core/names" 49 "launchpad.net/juju-core/provider" 50 "launchpad.net/juju-core/provider/common" 51 "launchpad.net/juju-core/schema" 52 "launchpad.net/juju-core/state" 53 "launchpad.net/juju-core/state/api" 54 "launchpad.net/juju-core/state/apiserver" 55 "launchpad.net/juju-core/testing" 56 coretools "launchpad.net/juju-core/tools" 57 "launchpad.net/juju-core/utils" 58 ) 59 60 var logger = loggo.GetLogger("juju.provider.dummy") 61 62 // SampleConfig() returns an environment configuration with all required 63 // attributes set. 64 func SampleConfig() testing.Attrs { 65 return testing.Attrs{ 66 "type": "dummy", 67 "name": "only", 68 "authorized-keys": testing.FakeAuthKeys, 69 "firewall-mode": config.FwInstance, 70 "admin-secret": testing.DefaultMongoPassword, 71 "ca-cert": testing.CACert, 72 "ca-private-key": testing.CAKey, 73 "ssl-hostname-verification": true, 74 "development": false, 75 "state-port": 1234, 76 "api-port": 4321, 77 "syslog-port": 2345, 78 "default-series": "precise", 79 80 "secret": "pork", 81 "state-server": true, 82 } 83 } 84 85 // stateInfo returns a *state.Info which allows clients to connect to the 86 // shared dummy state, if it exists. 87 func stateInfo() *state.Info { 88 if testing.MgoServer.Addr() == "" { 89 panic("dummy environ state tests must be run with MgoTestPackage") 90 } 91 return &state.Info{ 92 Addrs: []string{testing.MgoServer.Addr()}, 93 CACert: []byte(testing.CACert), 94 } 95 } 96 97 // Operation represents an action on the dummy provider. 98 type Operation interface{} 99 100 type OpBootstrap struct { 101 Context environs.BootstrapContext 102 Env string 103 Constraints constraints.Value 104 } 105 106 type OpDestroy struct { 107 Env string 108 Error error 109 } 110 111 type OpStartInstance struct { 112 Env string 113 MachineId string 114 MachineNonce string 115 Instance instance.Instance 116 Constraints constraints.Value 117 Info *state.Info 118 APIInfo *api.Info 119 Secret string 120 } 121 122 type OpStopInstances struct { 123 Env string 124 Instances []instance.Instance 125 } 126 127 type OpOpenPorts struct { 128 Env string 129 MachineId string 130 InstanceId instance.Id 131 Ports []instance.Port 132 } 133 134 type OpClosePorts struct { 135 Env string 136 MachineId string 137 InstanceId instance.Id 138 Ports []instance.Port 139 } 140 141 type OpPutFile struct { 142 Env string 143 FileName string 144 } 145 146 // environProvider represents the dummy provider. There is only ever one 147 // instance of this type (providerInstance) 148 type environProvider struct { 149 mu sync.Mutex 150 ops chan<- Operation 151 statePolicy state.Policy 152 // We have one state for each environment name 153 state map[int]*environState 154 maxStateId int 155 } 156 157 var providerInstance environProvider 158 159 const noStateId = 0 160 161 // environState represents the state of an environment. 162 // It can be shared between several environ values, 163 // so that a given environment can be opened several times. 164 type environState struct { 165 id int 166 name string 167 ops chan<- Operation 168 statePolicy state.Policy 169 mu sync.Mutex 170 maxId int // maximum instance id allocated so far. 171 insts map[instance.Id]*dummyInstance 172 globalPorts map[instance.Port]bool 173 bootstrapped bool 174 storageDelay time.Duration 175 storage *storageServer 176 httpListener net.Listener 177 apiServer *apiserver.Server 178 apiState *state.State 179 } 180 181 // environ represents a client's connection to a given environment's 182 // state. 183 type environ struct { 184 name string 185 ecfgMutex sync.Mutex 186 ecfgUnlocked *environConfig 187 } 188 189 var _ imagemetadata.SupportsCustomSources = (*environ)(nil) 190 var _ tools.SupportsCustomSources = (*environ)(nil) 191 var _ environs.Environ = (*environ)(nil) 192 193 // discardOperations discards all Operations written to it. 194 var discardOperations chan<- Operation 195 196 func init() { 197 environs.RegisterProvider("dummy", &providerInstance) 198 199 // Prime the first ops channel, so that naive clients can use 200 // the testing environment by simply importing it. 201 c := make(chan Operation) 202 go func() { 203 for _ = range c { 204 } 205 }() 206 discardOperations = c 207 Reset() 208 209 // parse errors are ignored 210 providerDelay, _ = time.ParseDuration(os.Getenv("JUJU_DUMMY_DELAY")) 211 } 212 213 // Reset resets the entire dummy environment and forgets any registered 214 // operation listener. All opened environments after Reset will share 215 // the same underlying state. 216 func Reset() { 217 logger.Infof("reset environment") 218 p := &providerInstance 219 p.mu.Lock() 220 defer p.mu.Unlock() 221 providerInstance.ops = discardOperations 222 for _, s := range p.state { 223 s.httpListener.Close() 224 s.destroy() 225 } 226 providerInstance.state = make(map[int]*environState) 227 if testing.MgoServer.Addr() != "" { 228 testing.MgoServer.Reset() 229 } 230 providerInstance.statePolicy = environs.NewStatePolicy() 231 } 232 233 func (state *environState) destroy() { 234 state.storage.files = make(map[string][]byte) 235 if !state.bootstrapped { 236 return 237 } 238 if state.apiServer != nil { 239 if err := state.apiServer.Stop(); err != nil { 240 panic(err) 241 } 242 state.apiServer = nil 243 if err := state.apiState.Close(); err != nil { 244 panic(err) 245 } 246 state.apiState = nil 247 } 248 if testing.MgoServer.Addr() != "" { 249 testing.MgoServer.Reset() 250 } 251 state.bootstrapped = false 252 } 253 254 // GetStateInAPIServer returns the state connection used by the API server 255 // This is so code in the test suite can trigger Syncs, etc that the API server 256 // will see, which will then trigger API watchers, etc. 257 func (e *environ) GetStateInAPIServer() *state.State { 258 st, err := e.state() 259 if err != nil { 260 panic(err) 261 } 262 return st.apiState 263 } 264 265 // newState creates the state for a new environment with the 266 // given name and starts an http server listening for 267 // storage requests. 268 func newState(name string, ops chan<- Operation, policy state.Policy) *environState { 269 s := &environState{ 270 name: name, 271 ops: ops, 272 statePolicy: policy, 273 insts: make(map[instance.Id]*dummyInstance), 274 globalPorts: make(map[instance.Port]bool), 275 } 276 s.storage = newStorageServer(s, "/"+name+"/private") 277 s.listen() 278 return s 279 } 280 281 // listen starts a network listener listening for http 282 // requests to retrieve files in the state's storage. 283 func (s *environState) listen() { 284 l, err := net.Listen("tcp", "127.0.0.1:0") 285 if err != nil { 286 panic(fmt.Errorf("cannot start listener: %v", err)) 287 } 288 s.httpListener = l 289 mux := http.NewServeMux() 290 mux.Handle(s.storage.path+"/", http.StripPrefix(s.storage.path+"/", s.storage)) 291 go http.Serve(l, mux) 292 } 293 294 // SetStatePolicy sets the state.Policy to use when a 295 // state server is initialised by dummy. 296 func SetStatePolicy(policy state.Policy) { 297 p := &providerInstance 298 p.mu.Lock() 299 defer p.mu.Unlock() 300 p.statePolicy = policy 301 } 302 303 // Listen closes the previously registered listener (if any). 304 // Subsequent operations on any dummy environment can be received on c 305 // (if not nil). 306 func Listen(c chan<- Operation) { 307 p := &providerInstance 308 p.mu.Lock() 309 defer p.mu.Unlock() 310 if c == nil { 311 c = discardOperations 312 } 313 if p.ops != discardOperations { 314 close(p.ops) 315 } 316 p.ops = c 317 for _, st := range p.state { 318 st.mu.Lock() 319 st.ops = c 320 st.mu.Unlock() 321 } 322 } 323 324 // SetStorageDelay causes any storage download operation in any current 325 // environment to be delayed for the given duration. 326 func SetStorageDelay(d time.Duration) { 327 p := &providerInstance 328 p.mu.Lock() 329 defer p.mu.Unlock() 330 for _, st := range p.state { 331 st.mu.Lock() 332 st.storageDelay = d 333 st.mu.Unlock() 334 } 335 } 336 337 var configFields = schema.Fields{ 338 "state-server": schema.Bool(), 339 "broken": schema.String(), 340 "secret": schema.String(), 341 "state-id": schema.String(), 342 } 343 var configDefaults = schema.Defaults{ 344 "broken": "", 345 "secret": "pork", 346 "state-id": schema.Omit, 347 } 348 349 type environConfig struct { 350 *config.Config 351 attrs map[string]interface{} 352 } 353 354 func (c *environConfig) stateServer() bool { 355 return c.attrs["state-server"].(bool) 356 } 357 358 func (c *environConfig) broken() string { 359 return c.attrs["broken"].(string) 360 } 361 362 func (c *environConfig) secret() string { 363 return c.attrs["secret"].(string) 364 } 365 366 func (c *environConfig) stateId() int { 367 idStr, ok := c.attrs["state-id"].(string) 368 if !ok { 369 return noStateId 370 } 371 id, err := strconv.Atoi(idStr) 372 if err != nil { 373 panic(fmt.Errorf("unexpected state-id %q (should have pre-checked)", idStr)) 374 } 375 return id 376 } 377 378 func (p *environProvider) newConfig(cfg *config.Config) (*environConfig, error) { 379 valid, err := p.Validate(cfg, nil) 380 if err != nil { 381 return nil, err 382 } 383 return &environConfig{valid, valid.UnknownAttrs()}, nil 384 } 385 386 func (p *environProvider) Validate(cfg, old *config.Config) (valid *config.Config, err error) { 387 // Check for valid changes for the base config values. 388 if err := config.Validate(cfg, old); err != nil { 389 return nil, err 390 } 391 validated, err := cfg.ValidateUnknownAttrs(configFields, configDefaults) 392 if err != nil { 393 return nil, err 394 } 395 if idStr, ok := validated["state-id"].(string); ok { 396 if _, err := strconv.Atoi(idStr); err != nil { 397 return nil, fmt.Errorf("invalid state-id %q", idStr) 398 } 399 } 400 // Apply the coerced unknown values back into the config. 401 return cfg.Apply(validated) 402 } 403 404 func (e *environ) state() (*environState, error) { 405 stateId := e.ecfg().stateId() 406 if stateId == noStateId { 407 return nil, provider.ErrNotPrepared 408 } 409 p := &providerInstance 410 p.mu.Lock() 411 defer p.mu.Unlock() 412 if state := p.state[stateId]; state != nil { 413 return state, nil 414 } 415 return nil, provider.ErrDestroyed 416 } 417 418 func (p *environProvider) Open(cfg *config.Config) (environs.Environ, error) { 419 p.mu.Lock() 420 defer p.mu.Unlock() 421 ecfg, err := p.newConfig(cfg) 422 if err != nil { 423 return nil, err 424 } 425 if ecfg.stateId() == noStateId { 426 return nil, provider.ErrNotPrepared 427 } 428 env := &environ{ 429 name: ecfg.Name(), 430 ecfgUnlocked: ecfg, 431 } 432 if err := env.checkBroken("Open"); err != nil { 433 return nil, err 434 } 435 return env, nil 436 } 437 438 func (p *environProvider) Prepare(ctx environs.BootstrapContext, cfg *config.Config) (environs.Environ, error) { 439 cfg, err := p.prepare(cfg) 440 if err != nil { 441 return nil, err 442 } 443 return p.Open(cfg) 444 } 445 446 // prepare is the internal version of Prepare - it prepares the 447 // environment but does not open it. 448 func (p *environProvider) prepare(cfg *config.Config) (*config.Config, error) { 449 ecfg, err := p.newConfig(cfg) 450 if err != nil { 451 return nil, err 452 } 453 p.mu.Lock() 454 defer p.mu.Unlock() 455 name := cfg.Name() 456 if ecfg.stateId() != noStateId { 457 return cfg, nil 458 } 459 // The environment has not been prepared, 460 // so create it and set its state identifier accordingly. 461 if ecfg.stateServer() && len(p.state) != 0 { 462 for _, old := range p.state { 463 panic(fmt.Errorf("cannot share a state between two dummy environs; old %q; new %q", old.name, name)) 464 } 465 } 466 state := newState(name, p.ops, p.statePolicy) 467 p.maxStateId++ 468 state.id = p.maxStateId 469 p.state[state.id] = state 470 // Add the state id to the configuration we use to 471 // in the returned environment. 472 return cfg.Apply(map[string]interface{}{ 473 "state-id": fmt.Sprint(state.id), 474 }) 475 } 476 477 func (*environProvider) SecretAttrs(cfg *config.Config) (map[string]string, error) { 478 ecfg, err := providerInstance.newConfig(cfg) 479 if err != nil { 480 return nil, err 481 } 482 return map[string]string{ 483 "secret": ecfg.secret(), 484 }, nil 485 } 486 487 func (*environProvider) PublicAddress() (string, error) { 488 return "public.dummy.address.example.com", nil 489 } 490 491 func (*environProvider) PrivateAddress() (string, error) { 492 return "private.dummy.address.example.com", nil 493 } 494 495 func (*environProvider) BoilerplateConfig() string { 496 return ` 497 # Fake configuration for dummy provider. 498 dummy: 499 type: dummy 500 501 `[1:] 502 } 503 504 var errBroken = errors.New("broken environment") 505 506 // Override for testing - the data directory with which the state api server is initialised. 507 var DataDir = "" 508 509 func (e *environ) ecfg() *environConfig { 510 e.ecfgMutex.Lock() 511 ecfg := e.ecfgUnlocked 512 e.ecfgMutex.Unlock() 513 return ecfg 514 } 515 516 func (e *environ) checkBroken(method string) error { 517 for _, m := range strings.Fields(e.ecfg().broken()) { 518 if m == method { 519 return fmt.Errorf("dummy.%s is broken", method) 520 } 521 } 522 return nil 523 } 524 525 func (e *environ) Name() string { 526 return e.name 527 } 528 529 // GetImageSources returns a list of sources which are used to search for simplestreams image metadata. 530 func (e *environ) GetImageSources() ([]simplestreams.DataSource, error) { 531 return []simplestreams.DataSource{ 532 storage.NewStorageSimpleStreamsDataSource("cloud storage", e.Storage(), storage.BaseImagesPath)}, nil 533 } 534 535 // GetToolsSources returns a list of sources which are used to search for simplestreams tools metadata. 536 func (e *environ) GetToolsSources() ([]simplestreams.DataSource, error) { 537 return []simplestreams.DataSource{ 538 storage.NewStorageSimpleStreamsDataSource("cloud storage", e.Storage(), storage.BaseToolsPath)}, nil 539 } 540 541 func (e *environ) Bootstrap(ctx environs.BootstrapContext, cons constraints.Value) error { 542 selectedTools, err := common.EnsureBootstrapTools(e, e.Config().DefaultSeries(), cons.Arch) 543 if err != nil { 544 return err 545 } 546 547 defer delay() 548 if err := e.checkBroken("Bootstrap"); err != nil { 549 return err 550 } 551 password := e.Config().AdminSecret() 552 if password == "" { 553 return fmt.Errorf("admin-secret is required for bootstrap") 554 } 555 if _, ok := e.Config().CACert(); !ok { 556 return fmt.Errorf("no CA certificate in environment configuration") 557 } 558 559 logger.Infof("would pick tools from %s", selectedTools) 560 cfg, err := environs.BootstrapConfig(e.Config()) 561 if err != nil { 562 return fmt.Errorf("cannot make bootstrap config: %v", err) 563 } 564 565 estate, err := e.state() 566 if err != nil { 567 return err 568 } 569 estate.mu.Lock() 570 defer estate.mu.Unlock() 571 if estate.bootstrapped { 572 return fmt.Errorf("environment is already bootstrapped") 573 } 574 // Write the bootstrap file just like a normal provider. However 575 // we need to release the mutex for the save state to work, so regain 576 // it after the call. 577 estate.mu.Unlock() 578 if err := bootstrap.SaveState(e.Storage(), &bootstrap.BootstrapState{StateInstances: []instance.Id{"localhost"}}); err != nil { 579 logger.Errorf("failed to save state instances: %v", err) 580 estate.mu.Lock() // otherwise defered unlock will fail 581 return err 582 } 583 estate.mu.Lock() // back at it 584 585 if e.ecfg().stateServer() { 586 // TODO(rog) factor out relevant code from cmd/jujud/bootstrap.go 587 // so that we can call it here. 588 589 info := stateInfo() 590 st, err := state.Initialize(info, cfg, state.DefaultDialOpts(), estate.statePolicy) 591 if err != nil { 592 panic(err) 593 } 594 if err := st.SetEnvironConstraints(cons); err != nil { 595 panic(err) 596 } 597 if err := st.SetAdminMongoPassword(utils.UserPasswordHash(password, utils.CompatSalt)); err != nil { 598 panic(err) 599 } 600 _, err = st.AddUser("admin", password) 601 if err != nil { 602 panic(err) 603 } 604 estate.apiServer, err = apiserver.NewServer(st, "localhost:0", []byte(testing.ServerCert), []byte(testing.ServerKey), DataDir) 605 if err != nil { 606 panic(err) 607 } 608 estate.apiState = st 609 } 610 estate.bootstrapped = true 611 estate.ops <- OpBootstrap{Context: ctx, Env: e.name, Constraints: cons} 612 return nil 613 } 614 615 func (e *environ) StateInfo() (*state.Info, *api.Info, error) { 616 estate, err := e.state() 617 if err != nil { 618 return nil, nil, err 619 } 620 estate.mu.Lock() 621 defer estate.mu.Unlock() 622 if err := e.checkBroken("StateInfo"); err != nil { 623 return nil, nil, err 624 } 625 if !e.ecfg().stateServer() { 626 return nil, nil, errors.New("dummy environment has no state configured") 627 } 628 if !estate.bootstrapped { 629 return nil, nil, environs.ErrNotBootstrapped 630 } 631 return stateInfo(), &api.Info{ 632 Addrs: []string{estate.apiServer.Addr()}, 633 CACert: []byte(testing.CACert), 634 }, nil 635 } 636 637 func (e *environ) Config() *config.Config { 638 return e.ecfg().Config 639 } 640 641 func (e *environ) SetConfig(cfg *config.Config) error { 642 if err := e.checkBroken("SetConfig"); err != nil { 643 return err 644 } 645 ecfg, err := providerInstance.newConfig(cfg) 646 if err != nil { 647 return err 648 } 649 e.ecfgMutex.Lock() 650 e.ecfgUnlocked = ecfg 651 e.ecfgMutex.Unlock() 652 return nil 653 } 654 655 func (e *environ) Destroy() (res error) { 656 defer delay() 657 estate, err := e.state() 658 if err != nil { 659 if err == provider.ErrDestroyed { 660 return nil 661 } 662 return err 663 } 664 defer func() { estate.ops <- OpDestroy{Env: estate.name, Error: res} }() 665 if err := e.checkBroken("Destroy"); err != nil { 666 return err 667 } 668 p := &providerInstance 669 p.mu.Lock() 670 delete(p.state, estate.id) 671 p.mu.Unlock() 672 673 estate.mu.Lock() 674 defer estate.mu.Unlock() 675 estate.destroy() 676 return nil 677 } 678 679 // StartInstance is specified in the InstanceBroker interface. 680 func (e *environ) StartInstance(cons constraints.Value, possibleTools coretools.List, 681 machineConfig *cloudinit.MachineConfig) (instance.Instance, *instance.HardwareCharacteristics, error) { 682 683 defer delay() 684 machineId := machineConfig.MachineId 685 logger.Infof("dummy startinstance, machine %s", machineId) 686 if err := e.checkBroken("StartInstance"); err != nil { 687 return nil, nil, err 688 } 689 estate, err := e.state() 690 if err != nil { 691 return nil, nil, err 692 } 693 estate.mu.Lock() 694 defer estate.mu.Unlock() 695 if machineConfig.MachineNonce == "" { 696 return nil, nil, fmt.Errorf("cannot start instance: missing machine nonce") 697 } 698 if _, ok := e.Config().CACert(); !ok { 699 return nil, nil, fmt.Errorf("no CA certificate in environment configuration") 700 } 701 if machineConfig.StateInfo.Tag != names.MachineTag(machineId) { 702 return nil, nil, fmt.Errorf("entity tag must match started machine") 703 } 704 if machineConfig.APIInfo.Tag != names.MachineTag(machineId) { 705 return nil, nil, fmt.Errorf("entity tag must match started machine") 706 } 707 logger.Infof("would pick tools from %s", possibleTools) 708 series := possibleTools.OneSeries() 709 i := &dummyInstance{ 710 id: instance.Id(fmt.Sprintf("%s-%d", e.name, estate.maxId)), 711 ports: make(map[instance.Port]bool), 712 machineId: machineId, 713 series: series, 714 firewallMode: e.Config().FirewallMode(), 715 state: estate, 716 } 717 var hc *instance.HardwareCharacteristics 718 // To match current system capability, only provide hardware characteristics for 719 // environ machines, not containers. 720 if state.ParentId(machineId) == "" { 721 // We will just assume the instance hardware characteristics exactly matches 722 // the supplied constraints (if specified). 723 hc = &instance.HardwareCharacteristics{ 724 Arch: cons.Arch, 725 Mem: cons.Mem, 726 RootDisk: cons.RootDisk, 727 CpuCores: cons.CpuCores, 728 CpuPower: cons.CpuPower, 729 Tags: cons.Tags, 730 } 731 // Fill in some expected instance hardware characteristics if constraints not specified. 732 if hc.Arch == nil { 733 arch := "amd64" 734 hc.Arch = &arch 735 } 736 if hc.Mem == nil { 737 mem := uint64(1024) 738 hc.Mem = &mem 739 } 740 if hc.RootDisk == nil { 741 disk := uint64(8192) 742 hc.RootDisk = &disk 743 } 744 if hc.CpuCores == nil { 745 cores := uint64(1) 746 hc.CpuCores = &cores 747 } 748 } 749 estate.insts[i.id] = i 750 estate.maxId++ 751 estate.ops <- OpStartInstance{ 752 Env: e.name, 753 MachineId: machineId, 754 MachineNonce: machineConfig.MachineNonce, 755 Constraints: cons, 756 Instance: i, 757 Info: machineConfig.StateInfo, 758 APIInfo: machineConfig.APIInfo, 759 Secret: e.ecfg().secret(), 760 } 761 return i, hc, nil 762 } 763 764 func (e *environ) StopInstances(is []instance.Instance) error { 765 defer delay() 766 if err := e.checkBroken("StopInstance"); err != nil { 767 return err 768 } 769 estate, err := e.state() 770 if err != nil { 771 return err 772 } 773 estate.mu.Lock() 774 defer estate.mu.Unlock() 775 for _, i := range is { 776 delete(estate.insts, i.(*dummyInstance).id) 777 } 778 estate.ops <- OpStopInstances{ 779 Env: e.name, 780 Instances: is, 781 } 782 return nil 783 } 784 785 func (e *environ) Instances(ids []instance.Id) (insts []instance.Instance, err error) { 786 defer delay() 787 if err := e.checkBroken("Instances"); err != nil { 788 return nil, err 789 } 790 if len(ids) == 0 { 791 return nil, nil 792 } 793 estate, err := e.state() 794 if err != nil { 795 return nil, err 796 } 797 estate.mu.Lock() 798 defer estate.mu.Unlock() 799 notFound := 0 800 for _, id := range ids { 801 inst := estate.insts[id] 802 if inst == nil { 803 err = environs.ErrPartialInstances 804 notFound++ 805 insts = append(insts, nil) 806 } else { 807 insts = append(insts, inst) 808 } 809 } 810 if notFound == len(ids) { 811 return nil, environs.ErrNoInstances 812 } 813 return 814 } 815 816 func (e *environ) AllInstances() ([]instance.Instance, error) { 817 defer delay() 818 if err := e.checkBroken("AllInstances"); err != nil { 819 return nil, err 820 } 821 var insts []instance.Instance 822 estate, err := e.state() 823 if err != nil { 824 return nil, err 825 } 826 estate.mu.Lock() 827 defer estate.mu.Unlock() 828 for _, v := range estate.insts { 829 insts = append(insts, v) 830 } 831 return insts, nil 832 } 833 834 func (e *environ) OpenPorts(ports []instance.Port) error { 835 if mode := e.ecfg().FirewallMode(); mode != config.FwGlobal { 836 return fmt.Errorf("invalid firewall mode %q for opening ports on environment", mode) 837 } 838 estate, err := e.state() 839 if err != nil { 840 return err 841 } 842 estate.mu.Lock() 843 defer estate.mu.Unlock() 844 for _, p := range ports { 845 estate.globalPorts[p] = true 846 } 847 return nil 848 } 849 850 func (e *environ) ClosePorts(ports []instance.Port) error { 851 if mode := e.ecfg().FirewallMode(); mode != config.FwGlobal { 852 return fmt.Errorf("invalid firewall mode %q for closing ports on environment", mode) 853 } 854 estate, err := e.state() 855 if err != nil { 856 return err 857 } 858 estate.mu.Lock() 859 defer estate.mu.Unlock() 860 for _, p := range ports { 861 delete(estate.globalPorts, p) 862 } 863 return nil 864 } 865 866 func (e *environ) Ports() (ports []instance.Port, err error) { 867 if mode := e.ecfg().FirewallMode(); mode != config.FwGlobal { 868 return nil, fmt.Errorf("invalid firewall mode %q for retrieving ports from environment", mode) 869 } 870 estate, err := e.state() 871 if err != nil { 872 return nil, err 873 } 874 estate.mu.Lock() 875 defer estate.mu.Unlock() 876 for p := range estate.globalPorts { 877 ports = append(ports, p) 878 } 879 instance.SortPorts(ports) 880 return 881 } 882 883 func (*environ) Provider() environs.EnvironProvider { 884 return &providerInstance 885 } 886 887 type dummyInstance struct { 888 state *environState 889 ports map[instance.Port]bool 890 id instance.Id 891 status string 892 machineId string 893 series string 894 firewallMode string 895 896 mu sync.Mutex 897 addresses []instance.Address 898 } 899 900 func (inst *dummyInstance) Id() instance.Id { 901 return inst.id 902 } 903 904 func (inst *dummyInstance) Status() string { 905 return inst.status 906 } 907 908 // SetInstanceAddresses sets the addresses associated with the given 909 // dummy instance. 910 func SetInstanceAddresses(inst instance.Instance, addrs []instance.Address) { 911 inst0 := inst.(*dummyInstance) 912 inst0.mu.Lock() 913 inst0.addresses = append(inst0.addresses[:0], addrs...) 914 inst0.mu.Unlock() 915 } 916 917 // SetInstanceStatus sets the status associated with the given 918 // dummy instance. 919 func SetInstanceStatus(inst instance.Instance, status string) { 920 inst0 := inst.(*dummyInstance) 921 inst0.mu.Lock() 922 inst0.status = status 923 inst0.mu.Unlock() 924 } 925 926 func (inst *dummyInstance) DNSName() (string, error) { 927 defer delay() 928 return string(inst.id) + ".dns", nil 929 } 930 931 func (*dummyInstance) Refresh() error { 932 return nil 933 } 934 935 func (inst *dummyInstance) Addresses() ([]instance.Address, error) { 936 inst.mu.Lock() 937 defer inst.mu.Unlock() 938 return append([]instance.Address{}, inst.addresses...), nil 939 } 940 941 func (inst *dummyInstance) WaitDNSName() (string, error) { 942 return common.WaitDNSName(inst) 943 } 944 945 func (inst *dummyInstance) OpenPorts(machineId string, ports []instance.Port) error { 946 defer delay() 947 logger.Infof("openPorts %s, %#v", machineId, ports) 948 if inst.firewallMode != config.FwInstance { 949 return fmt.Errorf("invalid firewall mode %q for opening ports on instance", 950 inst.firewallMode) 951 } 952 if inst.machineId != machineId { 953 panic(fmt.Errorf("OpenPorts with mismatched machine id, expected %q got %q", inst.machineId, machineId)) 954 } 955 inst.state.mu.Lock() 956 defer inst.state.mu.Unlock() 957 inst.state.ops <- OpOpenPorts{ 958 Env: inst.state.name, 959 MachineId: machineId, 960 InstanceId: inst.Id(), 961 Ports: ports, 962 } 963 for _, p := range ports { 964 inst.ports[p] = true 965 } 966 return nil 967 } 968 969 func (inst *dummyInstance) ClosePorts(machineId string, ports []instance.Port) error { 970 defer delay() 971 if inst.firewallMode != config.FwInstance { 972 return fmt.Errorf("invalid firewall mode %q for closing ports on instance", 973 inst.firewallMode) 974 } 975 if inst.machineId != machineId { 976 panic(fmt.Errorf("ClosePorts with mismatched machine id, expected %s got %s", inst.machineId, machineId)) 977 } 978 inst.state.mu.Lock() 979 defer inst.state.mu.Unlock() 980 inst.state.ops <- OpClosePorts{ 981 Env: inst.state.name, 982 MachineId: machineId, 983 InstanceId: inst.Id(), 984 Ports: ports, 985 } 986 for _, p := range ports { 987 delete(inst.ports, p) 988 } 989 return nil 990 } 991 992 func (inst *dummyInstance) Ports(machineId string) (ports []instance.Port, err error) { 993 defer delay() 994 if inst.firewallMode != config.FwInstance { 995 return nil, fmt.Errorf("invalid firewall mode %q for retrieving ports from instance", 996 inst.firewallMode) 997 } 998 if inst.machineId != machineId { 999 panic(fmt.Errorf("Ports with mismatched machine id, expected %q got %q", inst.machineId, machineId)) 1000 } 1001 inst.state.mu.Lock() 1002 defer inst.state.mu.Unlock() 1003 for p := range inst.ports { 1004 ports = append(ports, p) 1005 } 1006 instance.SortPorts(ports) 1007 return 1008 } 1009 1010 // providerDelay controls the delay before dummy responds. 1011 // non empty values in JUJU_DUMMY_DELAY will be parsed as 1012 // time.Durations into this value. 1013 var providerDelay time.Duration 1014 1015 // pause execution to simulate the latency of a real provider 1016 func delay() { 1017 if providerDelay > 0 { 1018 logger.Infof("pausing for %v", providerDelay) 1019 <-time.After(providerDelay) 1020 } 1021 }