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