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