github.com/mhilton/juju-juju@v0.0.0-20150901100907-a94dd2c73455/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 // when the environment is bootstrapped. 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 "fmt" 27 "net" 28 "net/http" 29 "os" 30 "strconv" 31 "strings" 32 "sync" 33 "time" 34 35 "github.com/juju/errors" 36 "github.com/juju/loggo" 37 "github.com/juju/names" 38 "github.com/juju/schema" 39 gitjujutesting "github.com/juju/testing" 40 "gopkg.in/juju/environschema.v1" 41 42 "github.com/juju/juju/agent" 43 "github.com/juju/juju/api" 44 "github.com/juju/juju/apiserver" 45 "github.com/juju/juju/cloudconfig/instancecfg" 46 "github.com/juju/juju/constraints" 47 "github.com/juju/juju/environs" 48 "github.com/juju/juju/environs/config" 49 "github.com/juju/juju/instance" 50 "github.com/juju/juju/juju/arch" 51 "github.com/juju/juju/mongo" 52 "github.com/juju/juju/network" 53 "github.com/juju/juju/provider/common" 54 "github.com/juju/juju/state" 55 "github.com/juju/juju/state/multiwatcher" 56 "github.com/juju/juju/storage" 57 "github.com/juju/juju/testing" 58 coretools "github.com/juju/juju/tools" 59 ) 60 61 var logger = loggo.GetLogger("juju.provider.dummy") 62 63 var transientErrorInjection chan error 64 65 const ( 66 BootstrapInstanceId = instance.Id("localhost") 67 ) 68 69 var ( 70 ErrNotPrepared = errors.New("environment is not prepared") 71 ErrDestroyed = errors.New("environment has been destroyed") 72 ) 73 74 // SampleConfig() returns an environment configuration with all required 75 // attributes set. 76 func SampleConfig() testing.Attrs { 77 return testing.Attrs{ 78 "type": "dummy", 79 "name": "only", 80 "uuid": testing.EnvironmentTag.Id(), 81 "authorized-keys": testing.FakeAuthKeys, 82 "firewall-mode": config.FwInstance, 83 "admin-secret": testing.DefaultMongoPassword, 84 "ca-cert": testing.CACert, 85 "ca-private-key": testing.CAKey, 86 "ssl-hostname-verification": true, 87 "development": false, 88 "state-port": 1234, 89 "api-port": 4321, 90 "syslog-port": 2345, 91 "default-series": config.LatestLtsSeries(), 92 93 "secret": "pork", 94 "state-server": true, 95 "prefer-ipv6": true, 96 } 97 } 98 99 // PatchTransientErrorInjectionChannel sets the transientInjectionError 100 // channel which can be used to inject errors into StartInstance for 101 // testing purposes 102 // The injected errors will use the string received on the channel 103 // and the instance's state will eventually go to error, while the 104 // received string will appear in the info field of the machine's status 105 func PatchTransientErrorInjectionChannel(c chan error) func() { 106 return gitjujutesting.PatchValue(&transientErrorInjection, c) 107 } 108 109 // AdminUserTag returns the user tag used to bootstrap the dummy environment. 110 // The dummy bootstrapping is handled slightly differently, and the user is 111 // created as part of the bootstrap process. This method is used to provide 112 // tests a way to get to the user name that was used to initialise the 113 // database, and as such, is the owner of the initial environment. 114 func AdminUserTag() names.UserTag { 115 return names.NewLocalUserTag("dummy-admin") 116 } 117 118 // stateInfo returns a *state.Info which allows clients to connect to the 119 // shared dummy state, if it exists. If preferIPv6 is true, an IPv6 endpoint 120 // will be added as primary. 121 func stateInfo(preferIPv6 bool) *mongo.MongoInfo { 122 if gitjujutesting.MgoServer.Addr() == "" { 123 panic("dummy environ state tests must be run with MgoTestPackage") 124 } 125 mongoPort := strconv.Itoa(gitjujutesting.MgoServer.Port()) 126 var addrs []string 127 if preferIPv6 { 128 addrs = []string{ 129 net.JoinHostPort("::1", mongoPort), 130 net.JoinHostPort("localhost", mongoPort), 131 } 132 } else { 133 addrs = []string{net.JoinHostPort("localhost", mongoPort)} 134 } 135 return &mongo.MongoInfo{ 136 Info: mongo.Info{ 137 Addrs: addrs, 138 CACert: testing.CACert, 139 }, 140 } 141 } 142 143 // Operation represents an action on the dummy provider. 144 type Operation interface{} 145 146 type OpBootstrap struct { 147 Context environs.BootstrapContext 148 Env string 149 Args environs.BootstrapParams 150 } 151 152 type OpFinalizeBootstrap struct { 153 Context environs.BootstrapContext 154 Env string 155 InstanceConfig *instancecfg.InstanceConfig 156 } 157 158 type OpDestroy struct { 159 Env string 160 Error error 161 } 162 163 type OpAllocateAddress struct { 164 Env string 165 InstanceId instance.Id 166 SubnetId network.Id 167 Address network.Address 168 HostName string 169 MACAddress string 170 } 171 172 type OpReleaseAddress struct { 173 Env string 174 InstanceId instance.Id 175 SubnetId network.Id 176 Address network.Address 177 MACAddress string 178 } 179 180 type OpNetworkInterfaces struct { 181 Env string 182 InstanceId instance.Id 183 Info []network.InterfaceInfo 184 } 185 186 type OpSubnets struct { 187 Env string 188 InstanceId instance.Id 189 SubnetIds []network.Id 190 Info []network.SubnetInfo 191 } 192 193 type OpStartInstance struct { 194 Env string 195 MachineId string 196 MachineNonce string 197 PossibleTools coretools.List 198 Instance instance.Instance 199 Constraints constraints.Value 200 SubnetsToZones map[network.Id][]string 201 Networks []string 202 NetworkInfo []network.InterfaceInfo 203 Volumes []storage.Volume 204 Info *mongo.MongoInfo 205 Jobs []multiwatcher.MachineJob 206 APIInfo *api.Info 207 Secret string 208 AgentEnvironment map[string]string 209 } 210 211 type OpStopInstances struct { 212 Env string 213 Ids []instance.Id 214 } 215 216 type OpOpenPorts struct { 217 Env string 218 MachineId string 219 InstanceId instance.Id 220 Ports []network.PortRange 221 } 222 223 type OpClosePorts struct { 224 Env string 225 MachineId string 226 InstanceId instance.Id 227 Ports []network.PortRange 228 } 229 230 type OpPutFile struct { 231 Env string 232 FileName string 233 } 234 235 // environProvider represents the dummy provider. There is only ever one 236 // instance of this type (providerInstance) 237 type environProvider struct { 238 mu sync.Mutex 239 ops chan<- Operation 240 statePolicy state.Policy 241 // We have one state for each environment name 242 state map[int]*environState 243 maxStateId int 244 } 245 246 var providerInstance environProvider 247 248 const noStateId = 0 249 250 // environState represents the state of an environment. 251 // It can be shared between several environ values, 252 // so that a given environment can be opened several times. 253 type environState struct { 254 id int 255 name string 256 ops chan<- Operation 257 statePolicy state.Policy 258 mu sync.Mutex 259 maxId int // maximum instance id allocated so far. 260 maxAddr int // maximum allocated address last byte 261 insts map[instance.Id]*dummyInstance 262 globalPorts map[network.PortRange]bool 263 bootstrapped bool 264 storageDelay time.Duration 265 storage *storageServer 266 apiListener net.Listener 267 httpListener net.Listener 268 apiServer *apiserver.Server 269 apiState *state.State 270 preferIPv6 bool 271 } 272 273 // environ represents a client's connection to a given environment's 274 // state. 275 type environ struct { 276 common.SupportsUnitPlacementPolicy 277 278 name string 279 ecfgMutex sync.Mutex 280 ecfgUnlocked *environConfig 281 } 282 283 var _ environs.Environ = (*environ)(nil) 284 285 // discardOperations discards all Operations written to it. 286 var discardOperations chan<- Operation 287 288 func init() { 289 environs.RegisterProvider("dummy", &providerInstance) 290 291 // Prime the first ops channel, so that naive clients can use 292 // the testing environment by simply importing it. 293 c := make(chan Operation) 294 go func() { 295 for _ = range c { 296 } 297 }() 298 discardOperations = c 299 Reset() 300 301 // parse errors are ignored 302 providerDelay, _ = time.ParseDuration(os.Getenv("JUJU_DUMMY_DELAY")) 303 } 304 305 // Reset resets the entire dummy environment and forgets any registered 306 // operation listener. All opened environments after Reset will share 307 // the same underlying state. 308 func Reset() { 309 logger.Infof("reset environment") 310 p := &providerInstance 311 p.mu.Lock() 312 defer p.mu.Unlock() 313 providerInstance.ops = discardOperations 314 for _, s := range p.state { 315 s.httpListener.Close() 316 if s.apiListener != nil { 317 s.apiListener.Close() 318 } 319 s.destroy() 320 } 321 providerInstance.state = make(map[int]*environState) 322 if mongoAlive() { 323 gitjujutesting.MgoServer.Reset() 324 } 325 providerInstance.statePolicy = environs.NewStatePolicy() 326 } 327 328 func (state *environState) destroy() { 329 state.storage.files = make(map[string][]byte) 330 if !state.bootstrapped { 331 return 332 } 333 if state.apiServer != nil { 334 if err := state.apiServer.Stop(); err != nil && mongoAlive() { 335 panic(err) 336 } 337 state.apiServer = nil 338 if err := state.apiState.Close(); err != nil && mongoAlive() { 339 panic(err) 340 } 341 state.apiState = nil 342 } 343 if mongoAlive() { 344 gitjujutesting.MgoServer.Reset() 345 } 346 state.bootstrapped = false 347 } 348 349 // mongoAlive reports whether the mongo server is 350 // still alive (i.e. has not been deliberately destroyed). 351 // If it has been deliberately destroyed, we will 352 // expect some errors when closing things down. 353 func mongoAlive() bool { 354 return gitjujutesting.MgoServer.Addr() != "" 355 } 356 357 // GetStateInAPIServer returns the state connection used by the API server 358 // This is so code in the test suite can trigger Syncs, etc that the API server 359 // will see, which will then trigger API watchers, etc. 360 func (e *environ) GetStateInAPIServer() *state.State { 361 st, err := e.state() 362 if err != nil { 363 panic(err) 364 } 365 return st.apiState 366 } 367 368 // newState creates the state for a new environment with the 369 // given name and starts an http server listening for 370 // storage requests. 371 func newState(name string, ops chan<- Operation, policy state.Policy) *environState { 372 s := &environState{ 373 name: name, 374 ops: ops, 375 statePolicy: policy, 376 insts: make(map[instance.Id]*dummyInstance), 377 globalPorts: make(map[network.PortRange]bool), 378 } 379 s.storage = newStorageServer(s, "/"+name+"/private") 380 s.listenStorage() 381 return s 382 } 383 384 // listenStorage starts a network listener listening for http 385 // requests to retrieve files in the state's storage. 386 func (s *environState) listenStorage() { 387 l, err := net.Listen("tcp", ":0") 388 if err != nil { 389 panic(fmt.Errorf("cannot start listener: %v", err)) 390 } 391 s.httpListener = l 392 mux := http.NewServeMux() 393 mux.Handle(s.storage.path+"/", http.StripPrefix(s.storage.path+"/", s.storage)) 394 go http.Serve(l, mux) 395 } 396 397 // listenAPI starts a network listener listening for API 398 // connections and proxies them to the API server port. 399 func (s *environState) listenAPI() int { 400 l, err := net.Listen("tcp", ":0") 401 if err != nil { 402 panic(fmt.Errorf("cannot start listener: %v", err)) 403 } 404 s.apiListener = l 405 return l.Addr().(*net.TCPAddr).Port 406 } 407 408 // SetStatePolicy sets the state.Policy to use when a 409 // state server is initialised by dummy. 410 func SetStatePolicy(policy state.Policy) { 411 p := &providerInstance 412 p.mu.Lock() 413 defer p.mu.Unlock() 414 p.statePolicy = policy 415 } 416 417 // Listen closes the previously registered listener (if any). 418 // Subsequent operations on any dummy environment can be received on c 419 // (if not nil). 420 func Listen(c chan<- Operation) { 421 p := &providerInstance 422 p.mu.Lock() 423 defer p.mu.Unlock() 424 if c == nil { 425 c = discardOperations 426 } 427 if p.ops != discardOperations { 428 close(p.ops) 429 } 430 p.ops = c 431 for _, st := range p.state { 432 st.mu.Lock() 433 st.ops = c 434 st.mu.Unlock() 435 } 436 } 437 438 // SetStorageDelay causes any storage download operation in any current 439 // environment to be delayed for the given duration. 440 func SetStorageDelay(d time.Duration) { 441 p := &providerInstance 442 p.mu.Lock() 443 defer p.mu.Unlock() 444 for _, st := range p.state { 445 st.mu.Lock() 446 st.storageDelay = d 447 st.mu.Unlock() 448 } 449 } 450 451 var configSchema = environschema.Fields{ 452 "state-server": { 453 Description: "Whether the environment should start a state server", 454 Type: environschema.Tbool, 455 }, 456 "broken": { 457 Description: "Whitespace-separated Environ methods that should return an error when called", 458 Type: environschema.Tstring, 459 }, 460 "secret": { 461 Description: "A secret", 462 Type: environschema.Tstring, 463 }, 464 "state-id": { 465 Description: "Id of state server", 466 Type: environschema.Tstring, 467 Group: environschema.JujuGroup, 468 }, 469 } 470 471 var configFields = func() schema.Fields { 472 fs, _, err := configSchema.ValidationSchema() 473 if err != nil { 474 panic(err) 475 } 476 return fs 477 }() 478 479 var configDefaults = schema.Defaults{ 480 "broken": "", 481 "secret": "pork", 482 "state-id": schema.Omit, 483 } 484 485 type environConfig struct { 486 *config.Config 487 attrs map[string]interface{} 488 } 489 490 func (c *environConfig) stateServer() bool { 491 return c.attrs["state-server"].(bool) 492 } 493 494 func (c *environConfig) broken() string { 495 return c.attrs["broken"].(string) 496 } 497 498 func (c *environConfig) secret() string { 499 return c.attrs["secret"].(string) 500 } 501 502 func (c *environConfig) stateId() int { 503 idStr, ok := c.attrs["state-id"].(string) 504 if !ok { 505 return noStateId 506 } 507 id, err := strconv.Atoi(idStr) 508 if err != nil { 509 panic(fmt.Errorf("unexpected state-id %q (should have pre-checked)", idStr)) 510 } 511 return id 512 } 513 514 func (p *environProvider) newConfig(cfg *config.Config) (*environConfig, error) { 515 valid, err := p.Validate(cfg, nil) 516 if err != nil { 517 return nil, err 518 } 519 return &environConfig{valid, valid.UnknownAttrs()}, nil 520 } 521 522 func (p *environProvider) Schema() environschema.Fields { 523 fields, err := config.Schema(configSchema) 524 if err != nil { 525 panic(err) 526 } 527 return fields 528 } 529 530 func (p *environProvider) Validate(cfg, old *config.Config) (valid *config.Config, err error) { 531 // Check for valid changes for the base config values. 532 if err := config.Validate(cfg, old); err != nil { 533 return nil, err 534 } 535 validated, err := cfg.ValidateUnknownAttrs(configFields, configDefaults) 536 if err != nil { 537 return nil, err 538 } 539 if idStr, ok := validated["state-id"].(string); ok { 540 if _, err := strconv.Atoi(idStr); err != nil { 541 return nil, fmt.Errorf("invalid state-id %q", idStr) 542 } 543 } 544 // Apply the coerced unknown values back into the config. 545 return cfg.Apply(validated) 546 } 547 548 func (e *environ) state() (*environState, error) { 549 stateId := e.ecfg().stateId() 550 if stateId == noStateId { 551 return nil, ErrNotPrepared 552 } 553 p := &providerInstance 554 p.mu.Lock() 555 defer p.mu.Unlock() 556 if state := p.state[stateId]; state != nil { 557 return state, nil 558 } 559 return nil, ErrDestroyed 560 } 561 562 func (p *environProvider) Open(cfg *config.Config) (environs.Environ, error) { 563 p.mu.Lock() 564 defer p.mu.Unlock() 565 ecfg, err := p.newConfig(cfg) 566 if err != nil { 567 return nil, err 568 } 569 if ecfg.stateId() == noStateId { 570 return nil, ErrNotPrepared 571 } 572 env := &environ{ 573 name: ecfg.Name(), 574 ecfgUnlocked: ecfg, 575 } 576 if err := env.checkBroken("Open"); err != nil { 577 return nil, err 578 } 579 return env, nil 580 } 581 582 // RestrictedConfigAttributes is specified in the EnvironProvider interface. 583 func (p *environProvider) RestrictedConfigAttributes() []string { 584 return nil 585 } 586 587 // PrepareForCreateEnvironment is specified in the EnvironProvider interface. 588 func (p *environProvider) PrepareForCreateEnvironment(cfg *config.Config) (*config.Config, error) { 589 return cfg, nil 590 } 591 592 func (p *environProvider) PrepareForBootstrap(ctx environs.BootstrapContext, cfg *config.Config) (environs.Environ, error) { 593 cfg, err := p.prepare(cfg) 594 if err != nil { 595 return nil, err 596 } 597 return p.Open(cfg) 598 } 599 600 // prepare is the internal version of Prepare - it prepares the 601 // environment but does not open it. 602 func (p *environProvider) prepare(cfg *config.Config) (*config.Config, error) { 603 ecfg, err := p.newConfig(cfg) 604 if err != nil { 605 return nil, err 606 } 607 p.mu.Lock() 608 defer p.mu.Unlock() 609 name := cfg.Name() 610 if ecfg.stateId() != noStateId { 611 return cfg, nil 612 } 613 if ecfg.stateServer() && len(p.state) != 0 { 614 for _, old := range p.state { 615 panic(fmt.Errorf("cannot share a state between two dummy environs; old %q; new %q", old.name, name)) 616 } 617 } 618 // The environment has not been prepared, 619 // so create it and set its state identifier accordingly. 620 state := newState(name, p.ops, p.statePolicy) 621 p.maxStateId++ 622 state.id = p.maxStateId 623 p.state[state.id] = state 624 625 attrs := map[string]interface{}{"state-id": fmt.Sprint(state.id)} 626 if ecfg.stateServer() { 627 attrs["api-port"] = state.listenAPI() 628 } 629 return cfg.Apply(attrs) 630 } 631 632 func (*environProvider) SecretAttrs(cfg *config.Config) (map[string]string, error) { 633 ecfg, err := providerInstance.newConfig(cfg) 634 if err != nil { 635 return nil, err 636 } 637 return map[string]string{ 638 "secret": ecfg.secret(), 639 }, nil 640 } 641 642 func (*environProvider) BoilerplateConfig() string { 643 return ` 644 # Fake configuration for dummy provider. 645 dummy: 646 type: dummy 647 648 `[1:] 649 } 650 651 var errBroken = errors.New("broken environment") 652 653 // Override for testing - the data directory with which the state api server is initialised. 654 var DataDir = "" 655 var LogDir = "" 656 657 func (e *environ) ecfg() *environConfig { 658 e.ecfgMutex.Lock() 659 ecfg := e.ecfgUnlocked 660 e.ecfgMutex.Unlock() 661 return ecfg 662 } 663 664 func (e *environ) checkBroken(method string) error { 665 for _, m := range strings.Fields(e.ecfg().broken()) { 666 if m == method { 667 return fmt.Errorf("dummy.%s is broken", method) 668 } 669 } 670 return nil 671 } 672 673 // SupportedArchitectures is specified on the EnvironCapability interface. 674 func (*environ) SupportedArchitectures() ([]string, error) { 675 return []string{arch.AMD64, arch.I386, arch.PPC64EL, arch.ARM64}, nil 676 } 677 678 // PrecheckInstance is specified in the state.Prechecker interface. 679 func (*environ) PrecheckInstance(series string, cons constraints.Value, placement string) error { 680 if placement != "" && placement != "valid" { 681 return fmt.Errorf("%s placement is invalid", placement) 682 } 683 return nil 684 } 685 686 func (e *environ) Bootstrap(ctx environs.BootstrapContext, args environs.BootstrapParams) (arch, series string, _ environs.BootstrapFinalizer, _ error) { 687 series = config.PreferredSeries(e.Config()) 688 availableTools, err := args.AvailableTools.Match(coretools.Filter{Series: series}) 689 if err != nil { 690 return "", "", nil, err 691 } 692 arch = availableTools.Arches()[0] 693 694 defer delay() 695 if err := e.checkBroken("Bootstrap"); err != nil { 696 return "", "", nil, err 697 } 698 network.InitializeFromConfig(e.Config()) 699 password := e.Config().AdminSecret() 700 if password == "" { 701 return "", "", nil, fmt.Errorf("admin-secret is required for bootstrap") 702 } 703 if _, ok := e.Config().CACert(); !ok { 704 return "", "", nil, fmt.Errorf("no CA certificate in environment configuration") 705 } 706 707 logger.Infof("would pick tools from %s", availableTools) 708 cfg, err := environs.BootstrapConfig(e.Config()) 709 if err != nil { 710 return "", "", nil, fmt.Errorf("cannot make bootstrap config: %v", err) 711 } 712 713 estate, err := e.state() 714 if err != nil { 715 return "", "", nil, err 716 } 717 estate.mu.Lock() 718 defer estate.mu.Unlock() 719 if estate.bootstrapped { 720 return "", "", nil, fmt.Errorf("environment is already bootstrapped") 721 } 722 estate.preferIPv6 = e.Config().PreferIPv6() 723 724 // Create an instance for the bootstrap node. 725 logger.Infof("creating bootstrap instance") 726 i := &dummyInstance{ 727 id: BootstrapInstanceId, 728 addresses: network.NewAddresses("localhost"), 729 ports: make(map[network.PortRange]bool), 730 machineId: agent.BootstrapMachineId, 731 series: series, 732 firewallMode: e.Config().FirewallMode(), 733 state: estate, 734 stateServer: true, 735 } 736 estate.insts[i.id] = i 737 738 if e.ecfg().stateServer() { 739 // TODO(rog) factor out relevant code from cmd/jujud/bootstrap.go 740 // so that we can call it here. 741 742 info := stateInfo(estate.preferIPv6) 743 // Since the admin user isn't setup until after here, 744 // the password in the info structure is empty, so the admin 745 // user is constructed with an empty password here. 746 // It is set just below. 747 st, err := state.Initialize( 748 AdminUserTag(), info, cfg, 749 mongo.DefaultDialOpts(), estate.statePolicy) 750 if err != nil { 751 panic(err) 752 } 753 if err := st.SetEnvironConstraints(args.Constraints); err != nil { 754 panic(err) 755 } 756 if err := st.SetAdminMongoPassword(password); err != nil { 757 panic(err) 758 } 759 if err := st.MongoSession().DB("admin").Login("admin", password); err != nil { 760 panic(err) 761 } 762 env, err := st.Environment() 763 if err != nil { 764 panic(err) 765 } 766 owner, err := st.User(env.Owner()) 767 if err != nil { 768 panic(err) 769 } 770 // We log this out for test purposes only. No one in real life can use 771 // a dummy provider for anything other than testing, so logging the password 772 // here is fine. 773 logger.Debugf("setting password for %q to %q", owner.Name(), password) 774 owner.SetPassword(password) 775 776 estate.apiServer, err = apiserver.NewServer(st, estate.apiListener, apiserver.ServerConfig{ 777 Cert: []byte(testing.ServerCert), 778 Key: []byte(testing.ServerKey), 779 Tag: names.NewMachineTag("0"), 780 DataDir: DataDir, 781 LogDir: LogDir, 782 }) 783 if err != nil { 784 panic(err) 785 } 786 estate.apiState = st 787 } 788 estate.bootstrapped = true 789 estate.ops <- OpBootstrap{Context: ctx, Env: e.name, Args: args} 790 finalize := func(ctx environs.BootstrapContext, icfg *instancecfg.InstanceConfig) error { 791 estate.ops <- OpFinalizeBootstrap{Context: ctx, Env: e.name, InstanceConfig: icfg} 792 return nil 793 } 794 return arch, series, finalize, nil 795 } 796 797 func (e *environ) StateServerInstances() ([]instance.Id, error) { 798 estate, err := e.state() 799 if err != nil { 800 return nil, err 801 } 802 estate.mu.Lock() 803 defer estate.mu.Unlock() 804 if err := e.checkBroken("StateServerInstances"); err != nil { 805 return nil, err 806 } 807 if !estate.bootstrapped { 808 return nil, environs.ErrNotBootstrapped 809 } 810 var stateServerInstances []instance.Id 811 for _, v := range estate.insts { 812 if v.stateServer { 813 stateServerInstances = append(stateServerInstances, v.Id()) 814 } 815 } 816 return stateServerInstances, nil 817 } 818 819 func (e *environ) Config() *config.Config { 820 return e.ecfg().Config 821 } 822 823 func (e *environ) SetConfig(cfg *config.Config) error { 824 if err := e.checkBroken("SetConfig"); err != nil { 825 return err 826 } 827 ecfg, err := providerInstance.newConfig(cfg) 828 if err != nil { 829 return err 830 } 831 e.ecfgMutex.Lock() 832 e.ecfgUnlocked = ecfg 833 e.ecfgMutex.Unlock() 834 return nil 835 } 836 837 func (e *environ) Destroy() (res error) { 838 defer delay() 839 estate, err := e.state() 840 if err != nil { 841 if err == ErrDestroyed { 842 return nil 843 } 844 return err 845 } 846 defer func() { estate.ops <- OpDestroy{Env: estate.name, Error: res} }() 847 if err := e.checkBroken("Destroy"); err != nil { 848 return err 849 } 850 p := &providerInstance 851 p.mu.Lock() 852 delete(p.state, estate.id) 853 p.mu.Unlock() 854 855 estate.mu.Lock() 856 defer estate.mu.Unlock() 857 estate.destroy() 858 return nil 859 } 860 861 // ConstraintsValidator is defined on the Environs interface. 862 func (e *environ) ConstraintsValidator() (constraints.Validator, error) { 863 validator := constraints.NewValidator() 864 validator.RegisterUnsupported([]string{constraints.CpuPower}) 865 validator.RegisterConflicts([]string{constraints.InstanceType}, []string{constraints.Mem}) 866 return validator, nil 867 } 868 869 // MaintainInstance is specified in the InstanceBroker interface. 870 func (*environ) MaintainInstance(args environs.StartInstanceParams) error { 871 return nil 872 } 873 874 // StartInstance is specified in the InstanceBroker interface. 875 func (e *environ) StartInstance(args environs.StartInstanceParams) (*environs.StartInstanceResult, error) { 876 877 defer delay() 878 machineId := args.InstanceConfig.MachineId 879 logger.Infof("dummy startinstance, machine %s", machineId) 880 if err := e.checkBroken("StartInstance"); err != nil { 881 return nil, err 882 } 883 estate, err := e.state() 884 if err != nil { 885 return nil, err 886 } 887 estate.mu.Lock() 888 defer estate.mu.Unlock() 889 890 // check if an error has been injected on the transientErrorInjection channel (testing purposes) 891 select { 892 case injectedError := <-transientErrorInjection: 893 return nil, injectedError 894 default: 895 } 896 897 if args.InstanceConfig.MachineNonce == "" { 898 return nil, errors.New("cannot start instance: missing machine nonce") 899 } 900 if _, ok := e.Config().CACert(); !ok { 901 return nil, errors.New("no CA certificate in environment configuration") 902 } 903 if args.InstanceConfig.MongoInfo.Tag != names.NewMachineTag(machineId) { 904 return nil, errors.New("entity tag must match started machine") 905 } 906 if args.InstanceConfig.APIInfo.Tag != names.NewMachineTag(machineId) { 907 return nil, errors.New("entity tag must match started machine") 908 } 909 logger.Infof("would pick tools from %s", args.Tools) 910 series := args.Tools.OneSeries() 911 912 idString := fmt.Sprintf("%s-%d", e.name, estate.maxId) 913 addrs := network.NewAddresses(idString+".dns", "127.0.0.1") 914 if estate.preferIPv6 { 915 addrs = append(addrs, network.NewAddress(fmt.Sprintf("fc00::%x", estate.maxId+1))) 916 } 917 logger.Debugf("StartInstance addresses: %v", addrs) 918 i := &dummyInstance{ 919 id: instance.Id(idString), 920 addresses: addrs, 921 ports: make(map[network.PortRange]bool), 922 machineId: machineId, 923 series: series, 924 firewallMode: e.Config().FirewallMode(), 925 state: estate, 926 } 927 928 var hc *instance.HardwareCharacteristics 929 // To match current system capability, only provide hardware characteristics for 930 // environ machines, not containers. 931 if state.ParentId(machineId) == "" { 932 // We will just assume the instance hardware characteristics exactly matches 933 // the supplied constraints (if specified). 934 hc = &instance.HardwareCharacteristics{ 935 Arch: args.Constraints.Arch, 936 Mem: args.Constraints.Mem, 937 RootDisk: args.Constraints.RootDisk, 938 CpuCores: args.Constraints.CpuCores, 939 CpuPower: args.Constraints.CpuPower, 940 Tags: args.Constraints.Tags, 941 } 942 // Fill in some expected instance hardware characteristics if constraints not specified. 943 if hc.Arch == nil { 944 arch := "amd64" 945 hc.Arch = &arch 946 } 947 if hc.Mem == nil { 948 mem := uint64(1024) 949 hc.Mem = &mem 950 } 951 if hc.RootDisk == nil { 952 disk := uint64(8192) 953 hc.RootDisk = &disk 954 } 955 if hc.CpuCores == nil { 956 cores := uint64(1) 957 hc.CpuCores = &cores 958 } 959 } 960 // Simulate subnetsToZones gets populated when spaces given in constraints. 961 spaces := args.Constraints.IncludeSpaces() 962 var subnetsToZones map[network.Id][]string 963 for isp := range spaces { 964 // Simulate 2 subnets per space. 965 if subnetsToZones == nil { 966 subnetsToZones = make(map[network.Id][]string) 967 } 968 for isn := 0; isn < 2; isn++ { 969 providerId := fmt.Sprintf("subnet-%d", isp+isn) 970 zone := fmt.Sprintf("zone%d", isp+isn) 971 subnetsToZones[network.Id(providerId)] = []string{zone} 972 } 973 } 974 // Simulate creating volumes when requested. 975 volumes := make([]storage.Volume, len(args.Volumes)) 976 for iv, v := range args.Volumes { 977 persistent, _ := v.Attributes[storage.Persistent].(bool) 978 volumes[iv] = storage.Volume{ 979 Tag: names.NewVolumeTag(strconv.Itoa(iv + 1)), 980 VolumeInfo: storage.VolumeInfo{ 981 Size: v.Size, 982 Persistent: persistent, 983 }, 984 } 985 } 986 estate.insts[i.id] = i 987 estate.maxId++ 988 estate.ops <- OpStartInstance{ 989 Env: e.name, 990 MachineId: machineId, 991 MachineNonce: args.InstanceConfig.MachineNonce, 992 PossibleTools: args.Tools, 993 Constraints: args.Constraints, 994 SubnetsToZones: subnetsToZones, 995 Volumes: volumes, 996 Instance: i, 997 Jobs: args.InstanceConfig.Jobs, 998 Info: args.InstanceConfig.MongoInfo, 999 APIInfo: args.InstanceConfig.APIInfo, 1000 AgentEnvironment: args.InstanceConfig.AgentEnvironment, 1001 Secret: e.ecfg().secret(), 1002 } 1003 return &environs.StartInstanceResult{ 1004 Instance: i, 1005 Hardware: hc, 1006 }, nil 1007 } 1008 1009 func (e *environ) StopInstances(ids ...instance.Id) error { 1010 defer delay() 1011 if err := e.checkBroken("StopInstance"); err != nil { 1012 return err 1013 } 1014 estate, err := e.state() 1015 if err != nil { 1016 return err 1017 } 1018 estate.mu.Lock() 1019 defer estate.mu.Unlock() 1020 for _, id := range ids { 1021 delete(estate.insts, id) 1022 } 1023 estate.ops <- OpStopInstances{ 1024 Env: e.name, 1025 Ids: ids, 1026 } 1027 return nil 1028 } 1029 1030 func (e *environ) Instances(ids []instance.Id) (insts []instance.Instance, err error) { 1031 defer delay() 1032 if err := e.checkBroken("Instances"); err != nil { 1033 return nil, err 1034 } 1035 if len(ids) == 0 { 1036 return nil, nil 1037 } 1038 estate, err := e.state() 1039 if err != nil { 1040 return nil, err 1041 } 1042 estate.mu.Lock() 1043 defer estate.mu.Unlock() 1044 notFound := 0 1045 for _, id := range ids { 1046 inst := estate.insts[id] 1047 if inst == nil { 1048 err = environs.ErrPartialInstances 1049 notFound++ 1050 insts = append(insts, nil) 1051 } else { 1052 insts = append(insts, inst) 1053 } 1054 } 1055 if notFound == len(ids) { 1056 return nil, environs.ErrNoInstances 1057 } 1058 return 1059 } 1060 1061 // SupportsAddressAllocation is specified on environs.Networking. 1062 func (env *environ) SupportsAddressAllocation(subnetId network.Id) (bool, error) { 1063 if !environs.AddressAllocationEnabled() { 1064 return false, errors.NotSupportedf("address allocation") 1065 } 1066 1067 if err := env.checkBroken("SupportsAddressAllocation"); err != nil { 1068 return false, err 1069 } 1070 // Any subnetId starting with "noalloc-" will cause this to return 1071 // false, so it can be used in tests. 1072 if strings.HasPrefix(string(subnetId), "noalloc-") { 1073 return false, nil 1074 } 1075 return true, nil 1076 } 1077 1078 // AllocateAddress requests an address to be allocated for the 1079 // given instance on the given subnet. 1080 func (env *environ) AllocateAddress(instId instance.Id, subnetId network.Id, addr network.Address, macAddress, hostname string) error { 1081 if !environs.AddressAllocationEnabled() { 1082 return errors.NotSupportedf("address allocation") 1083 } 1084 1085 if err := env.checkBroken("AllocateAddress"); err != nil { 1086 return err 1087 } 1088 1089 estate, err := env.state() 1090 if err != nil { 1091 return err 1092 } 1093 estate.mu.Lock() 1094 defer estate.mu.Unlock() 1095 estate.maxAddr++ 1096 estate.ops <- OpAllocateAddress{ 1097 Env: env.name, 1098 InstanceId: instId, 1099 SubnetId: subnetId, 1100 Address: addr, 1101 MACAddress: macAddress, 1102 HostName: hostname, 1103 } 1104 return nil 1105 } 1106 1107 // ReleaseAddress releases a specific address previously allocated with 1108 // AllocateAddress. 1109 func (env *environ) ReleaseAddress(instId instance.Id, subnetId network.Id, addr network.Address, macAddress string) error { 1110 if !environs.AddressAllocationEnabled() { 1111 return errors.NotSupportedf("address allocation") 1112 } 1113 1114 if err := env.checkBroken("ReleaseAddress"); err != nil { 1115 return err 1116 } 1117 estate, err := env.state() 1118 if err != nil { 1119 return err 1120 } 1121 estate.mu.Lock() 1122 defer estate.mu.Unlock() 1123 estate.maxAddr-- 1124 estate.ops <- OpReleaseAddress{ 1125 Env: env.name, 1126 InstanceId: instId, 1127 SubnetId: subnetId, 1128 Address: addr, 1129 MACAddress: macAddress, 1130 } 1131 return nil 1132 } 1133 1134 // NetworkInterfaces implements Environ.NetworkInterfaces(). 1135 func (env *environ) NetworkInterfaces(instId instance.Id) ([]network.InterfaceInfo, error) { 1136 if err := env.checkBroken("NetworkInterfaces"); err != nil { 1137 return nil, err 1138 } 1139 1140 estate, err := env.state() 1141 if err != nil { 1142 return nil, err 1143 } 1144 estate.mu.Lock() 1145 defer estate.mu.Unlock() 1146 1147 // Simulate 3 NICs - primary and secondary enabled plus a disabled NIC. 1148 // all configured using DHCP and having fake DNS servers and gateway. 1149 info := make([]network.InterfaceInfo, 3) 1150 for i, netName := range []string{"private", "public", "disabled"} { 1151 info[i] = network.InterfaceInfo{ 1152 DeviceIndex: i, 1153 ProviderId: network.Id(fmt.Sprintf("dummy-eth%d", i)), 1154 ProviderSubnetId: network.Id("dummy-" + netName), 1155 NetworkName: "juju-" + netName, 1156 CIDR: fmt.Sprintf("0.%d.0.0/24", (i+1)*10), 1157 InterfaceName: fmt.Sprintf("eth%d", i), 1158 VLANTag: i, 1159 MACAddress: fmt.Sprintf("aa:bb:cc:dd:ee:f%d", i), 1160 Disabled: i == 2, 1161 NoAutoStart: i%2 != 0, 1162 ConfigType: network.ConfigDHCP, 1163 Address: network.NewAddress( 1164 fmt.Sprintf("0.%d.0.%d", (i+1)*10, estate.maxAddr+2), 1165 ), 1166 DNSServers: network.NewAddresses("ns1.dummy", "ns2.dummy"), 1167 GatewayAddress: network.NewAddress( 1168 fmt.Sprintf("0.%d.0.1", (i+1)*10), 1169 ), 1170 } 1171 } 1172 1173 if strings.HasPrefix(string(instId), "i-no-nics-") { 1174 // Simulate no NICs on instances with id prefix "i-no-nics-". 1175 info = info[:0] 1176 } else if strings.HasPrefix(string(instId), "i-nic-no-subnet-") { 1177 // Simulate a nic with no subnet on instances with id prefix 1178 // "i-nic-no-subnet-" 1179 info = []network.InterfaceInfo{{ 1180 DeviceIndex: 0, 1181 ProviderId: network.Id("dummy-eth0"), 1182 NetworkName: "juju-public", 1183 InterfaceName: "eth0", 1184 MACAddress: "aa:bb:cc:dd:ee:f0", 1185 Disabled: false, 1186 NoAutoStart: false, 1187 ConfigType: network.ConfigDHCP, 1188 }} 1189 } else if strings.HasPrefix(string(instId), "i-disabled-nic-") { 1190 info = info[2:] 1191 } 1192 1193 estate.ops <- OpNetworkInterfaces{ 1194 Env: env.name, 1195 InstanceId: instId, 1196 Info: info, 1197 } 1198 1199 return info, nil 1200 } 1201 1202 type azShim struct { 1203 name string 1204 available bool 1205 } 1206 1207 func (az azShim) Name() string { 1208 return az.name 1209 } 1210 1211 func (az azShim) Available() bool { 1212 return az.available 1213 } 1214 1215 // AvailabilityZones implements environs.ZonedEnviron. 1216 func (env *environ) AvailabilityZones() ([]common.AvailabilityZone, error) { 1217 // TODO(dimitern): Fix this properly. 1218 return []common.AvailabilityZone{ 1219 azShim{"zone1", true}, 1220 azShim{"zone2", false}, 1221 }, nil 1222 } 1223 1224 // InstanceAvailabilityZoneNames implements environs.ZonedEnviron. 1225 func (env *environ) InstanceAvailabilityZoneNames(ids []instance.Id) ([]string, error) { 1226 // TODO(dimitern): Fix this properly. 1227 if err := env.checkBroken("InstanceAvailabilityZoneNames"); err != nil { 1228 return nil, errors.NotSupportedf("instance availability zones") 1229 } 1230 return []string{"zone1"}, nil 1231 } 1232 1233 // Subnets implements environs.Environ.Subnets. 1234 func (env *environ) Subnets(instId instance.Id, subnetIds []network.Id) ([]network.SubnetInfo, error) { 1235 if err := env.checkBroken("Subnets"); err != nil { 1236 return nil, err 1237 } 1238 1239 estate, err := env.state() 1240 if err != nil { 1241 return nil, err 1242 } 1243 estate.mu.Lock() 1244 defer estate.mu.Unlock() 1245 1246 allSubnets := []network.SubnetInfo{{ 1247 CIDR: "0.10.0.0/24", 1248 ProviderId: "dummy-private", 1249 AllocatableIPLow: net.ParseIP("0.10.0.0"), 1250 AllocatableIPHigh: net.ParseIP("0.10.0.255"), 1251 AvailabilityZones: []string{"zone1", "zone2"}, 1252 }, { 1253 CIDR: "0.20.0.0/24", 1254 ProviderId: "dummy-public", 1255 AllocatableIPLow: net.ParseIP("0.20.0.0"), 1256 AllocatableIPHigh: net.ParseIP("0.20.0.255"), 1257 }} 1258 1259 // Filter result by ids, if given. 1260 var result []network.SubnetInfo 1261 for _, subId := range subnetIds { 1262 switch subId { 1263 case "dummy-private": 1264 result = append(result, allSubnets[0]) 1265 case "dummy-public": 1266 result = append(result, allSubnets[1]) 1267 } 1268 } 1269 if len(subnetIds) == 0 { 1270 result = append([]network.SubnetInfo{}, allSubnets...) 1271 } 1272 1273 noSubnets := strings.HasPrefix(string(instId), "i-no-subnets") 1274 noNICSubnets := strings.HasPrefix(string(instId), "i-nic-no-subnet-") 1275 if noSubnets || noNICSubnets { 1276 // Simulate no subnets available if the instance id has prefix 1277 // "i-no-subnets-". 1278 result = result[:0] 1279 } 1280 if len(result) == 0 { 1281 // No results, so just return them now. 1282 estate.ops <- OpSubnets{ 1283 Env: env.name, 1284 InstanceId: instId, 1285 SubnetIds: subnetIds, 1286 Info: result, 1287 } 1288 return result, nil 1289 } 1290 1291 makeNoAlloc := func(info network.SubnetInfo) network.SubnetInfo { 1292 // Remove the allocatable range and change the provider id 1293 // prefix. 1294 pid := string(info.ProviderId) 1295 pid = strings.TrimPrefix(pid, "dummy-") 1296 info.ProviderId = network.Id("noalloc-" + pid) 1297 info.AllocatableIPLow = nil 1298 info.AllocatableIPHigh = nil 1299 return info 1300 } 1301 if strings.HasPrefix(string(instId), "i-no-alloc-") { 1302 iid := strings.TrimPrefix(string(instId), "i-no-alloc-") 1303 lastIdx := len(result) - 1 1304 if strings.HasPrefix(iid, "all") { 1305 // Simulate all subnets have no allocatable ranges set. 1306 for i := range result { 1307 result[i] = makeNoAlloc(result[i]) 1308 } 1309 } else if idx, err := strconv.Atoi(iid); err == nil { 1310 // Simulate only the subnet with index idx in the result 1311 // have no allocatable range set. 1312 if idx < 0 || idx > lastIdx { 1313 err = errors.Errorf("index %d out of range; expected 0..%d", idx, lastIdx) 1314 return nil, err 1315 } 1316 result[idx] = makeNoAlloc(result[idx]) 1317 } else { 1318 err = errors.Errorf("invalid index %q; expected int", iid) 1319 return nil, err 1320 } 1321 } 1322 1323 estate.ops <- OpSubnets{ 1324 Env: env.name, 1325 InstanceId: instId, 1326 SubnetIds: subnetIds, 1327 Info: result, 1328 } 1329 return result, nil 1330 } 1331 1332 func (e *environ) AllInstances() ([]instance.Instance, error) { 1333 defer delay() 1334 if err := e.checkBroken("AllInstances"); err != nil { 1335 return nil, err 1336 } 1337 var insts []instance.Instance 1338 estate, err := e.state() 1339 if err != nil { 1340 return nil, err 1341 } 1342 estate.mu.Lock() 1343 defer estate.mu.Unlock() 1344 for _, v := range estate.insts { 1345 insts = append(insts, v) 1346 } 1347 return insts, nil 1348 } 1349 1350 func (e *environ) OpenPorts(ports []network.PortRange) error { 1351 if mode := e.ecfg().FirewallMode(); mode != config.FwGlobal { 1352 return fmt.Errorf("invalid firewall mode %q for opening ports on environment", mode) 1353 } 1354 estate, err := e.state() 1355 if err != nil { 1356 return err 1357 } 1358 estate.mu.Lock() 1359 defer estate.mu.Unlock() 1360 for _, p := range ports { 1361 estate.globalPorts[p] = true 1362 } 1363 return nil 1364 } 1365 1366 func (e *environ) ClosePorts(ports []network.PortRange) error { 1367 if mode := e.ecfg().FirewallMode(); mode != config.FwGlobal { 1368 return fmt.Errorf("invalid firewall mode %q for closing ports on environment", mode) 1369 } 1370 estate, err := e.state() 1371 if err != nil { 1372 return err 1373 } 1374 estate.mu.Lock() 1375 defer estate.mu.Unlock() 1376 for _, p := range ports { 1377 delete(estate.globalPorts, p) 1378 } 1379 return nil 1380 } 1381 1382 func (e *environ) Ports() (ports []network.PortRange, err error) { 1383 if mode := e.ecfg().FirewallMode(); mode != config.FwGlobal { 1384 return nil, fmt.Errorf("invalid firewall mode %q for retrieving ports from environment", mode) 1385 } 1386 estate, err := e.state() 1387 if err != nil { 1388 return nil, err 1389 } 1390 estate.mu.Lock() 1391 defer estate.mu.Unlock() 1392 for p := range estate.globalPorts { 1393 ports = append(ports, p) 1394 } 1395 network.SortPortRanges(ports) 1396 return 1397 } 1398 1399 func (*environ) Provider() environs.EnvironProvider { 1400 return &providerInstance 1401 } 1402 1403 type dummyInstance struct { 1404 state *environState 1405 ports map[network.PortRange]bool 1406 id instance.Id 1407 status string 1408 machineId string 1409 series string 1410 firewallMode string 1411 stateServer bool 1412 1413 mu sync.Mutex 1414 addresses []network.Address 1415 } 1416 1417 func (inst *dummyInstance) Id() instance.Id { 1418 return inst.id 1419 } 1420 1421 func (inst *dummyInstance) Status() string { 1422 return inst.status 1423 } 1424 1425 // SetInstanceAddresses sets the addresses associated with the given 1426 // dummy instance. 1427 func SetInstanceAddresses(inst instance.Instance, addrs []network.Address) { 1428 inst0 := inst.(*dummyInstance) 1429 inst0.mu.Lock() 1430 inst0.addresses = append(inst0.addresses[:0], addrs...) 1431 logger.Debugf("setting instance %q addresses to %v", inst0.Id(), addrs) 1432 inst0.mu.Unlock() 1433 } 1434 1435 // SetInstanceStatus sets the status associated with the given 1436 // dummy instance. 1437 func SetInstanceStatus(inst instance.Instance, status string) { 1438 inst0 := inst.(*dummyInstance) 1439 inst0.mu.Lock() 1440 inst0.status = status 1441 inst0.mu.Unlock() 1442 } 1443 1444 func (*dummyInstance) Refresh() error { 1445 return nil 1446 } 1447 1448 func (inst *dummyInstance) Addresses() ([]network.Address, error) { 1449 inst.mu.Lock() 1450 defer inst.mu.Unlock() 1451 return append([]network.Address{}, inst.addresses...), nil 1452 } 1453 1454 func (inst *dummyInstance) OpenPorts(machineId string, ports []network.PortRange) error { 1455 defer delay() 1456 logger.Infof("openPorts %s, %#v", machineId, ports) 1457 if inst.firewallMode != config.FwInstance { 1458 return fmt.Errorf("invalid firewall mode %q for opening ports on instance", 1459 inst.firewallMode) 1460 } 1461 if inst.machineId != machineId { 1462 panic(fmt.Errorf("OpenPorts with mismatched machine id, expected %q got %q", inst.machineId, machineId)) 1463 } 1464 inst.state.mu.Lock() 1465 defer inst.state.mu.Unlock() 1466 inst.state.ops <- OpOpenPorts{ 1467 Env: inst.state.name, 1468 MachineId: machineId, 1469 InstanceId: inst.Id(), 1470 Ports: ports, 1471 } 1472 for _, p := range ports { 1473 inst.ports[p] = true 1474 } 1475 return nil 1476 } 1477 1478 func (inst *dummyInstance) ClosePorts(machineId string, ports []network.PortRange) error { 1479 defer delay() 1480 if inst.firewallMode != config.FwInstance { 1481 return fmt.Errorf("invalid firewall mode %q for closing ports on instance", 1482 inst.firewallMode) 1483 } 1484 if inst.machineId != machineId { 1485 panic(fmt.Errorf("ClosePorts with mismatched machine id, expected %s got %s", inst.machineId, machineId)) 1486 } 1487 inst.state.mu.Lock() 1488 defer inst.state.mu.Unlock() 1489 inst.state.ops <- OpClosePorts{ 1490 Env: inst.state.name, 1491 MachineId: machineId, 1492 InstanceId: inst.Id(), 1493 Ports: ports, 1494 } 1495 for _, p := range ports { 1496 delete(inst.ports, p) 1497 } 1498 return nil 1499 } 1500 1501 func (inst *dummyInstance) Ports(machineId string) (ports []network.PortRange, err error) { 1502 defer delay() 1503 if inst.firewallMode != config.FwInstance { 1504 return nil, fmt.Errorf("invalid firewall mode %q for retrieving ports from instance", 1505 inst.firewallMode) 1506 } 1507 if inst.machineId != machineId { 1508 panic(fmt.Errorf("Ports with mismatched machine id, expected %q got %q", inst.machineId, machineId)) 1509 } 1510 inst.state.mu.Lock() 1511 defer inst.state.mu.Unlock() 1512 for p := range inst.ports { 1513 ports = append(ports, p) 1514 } 1515 network.SortPortRanges(ports) 1516 return 1517 } 1518 1519 // providerDelay controls the delay before dummy responds. 1520 // non empty values in JUJU_DUMMY_DELAY will be parsed as 1521 // time.Durations into this value. 1522 var providerDelay time.Duration 1523 1524 // pause execution to simulate the latency of a real provider 1525 func delay() { 1526 if providerDelay > 0 { 1527 logger.Infof("pausing for %v", providerDelay) 1528 <-time.After(providerDelay) 1529 } 1530 }