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