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