github.com/cloud-green/juju@v0.0.0-20151002100041-a00291338d3d/provider/local/environprovider.go (about)

     1  // Copyright 2013 Canonical Ltd.
     2  // Licensed under the AGPLv3, see LICENCE file for details.
     3  
     4  package local
     5  
     6  import (
     7  	"fmt"
     8  	"net"
     9  	"net/url"
    10  	"os"
    11  	"os/user"
    12  	"regexp"
    13  	"strconv"
    14  	"strings"
    15  	"syscall"
    16  
    17  	"github.com/juju/errors"
    18  	"github.com/juju/loggo"
    19  	"github.com/juju/utils"
    20  	"github.com/juju/utils/proxy"
    21  	"gopkg.in/juju/environschema.v1"
    22  
    23  	"github.com/juju/juju/environs"
    24  	"github.com/juju/juju/environs/config"
    25  	"github.com/juju/juju/instance"
    26  	"github.com/juju/juju/juju/osenv"
    27  	"github.com/juju/juju/version"
    28  	jujuos "github.com/juju/utils/os"
    29  )
    30  
    31  var logger = loggo.GetLogger("juju.provider.local")
    32  
    33  var _ environs.EnvironProvider = (*environProvider)(nil)
    34  
    35  type environProvider struct{}
    36  
    37  var providerInstance = &environProvider{}
    38  
    39  var userCurrent = user.Current
    40  
    41  // Open implements environs.EnvironProvider.Open.
    42  func (environProvider) Open(cfg *config.Config) (environs.Environ, error) {
    43  	logger.Infof("opening environment %q", cfg.Name())
    44  	// Do the initial validation on the config.
    45  	localConfig, err := providerInstance.newConfig(cfg)
    46  	if err != nil {
    47  		return nil, errors.Trace(err)
    48  	}
    49  	if err := VerifyPrerequisites(localConfig.container()); err != nil {
    50  		return nil, errors.Annotate(err, "failed verification of local provider prerequisites")
    51  	}
    52  	if cfg, err = providerInstance.correctLocalhostURLs(cfg, localConfig); err != nil {
    53  		return nil, errors.Annotate(err, "failed to replace localhost references in loopback URLs specified in proxy config settings")
    54  	}
    55  	environ := &localEnviron{name: cfg.Name()}
    56  	if err := environ.SetConfig(cfg); err != nil {
    57  		return nil, errors.Annotate(err, "failure setting config")
    58  	}
    59  	return environ, nil
    60  }
    61  
    62  // Schema returns the configuration schema for an environment.
    63  func (environProvider) Schema() environschema.Fields {
    64  	fields, err := config.Schema(configSchema)
    65  	if err != nil {
    66  		panic(err)
    67  	}
    68  	return fields
    69  }
    70  
    71  // correctLocalhostURLs exams proxy attributes and changes URL values pointing to localhost to use bridge IP.
    72  func (p environProvider) correctLocalhostURLs(cfg *config.Config, providerCfg *environConfig) (*config.Config, error) {
    73  	attrs := cfg.AllAttrs()
    74  	updatedAttrs := make(map[string]interface{})
    75  	for _, key := range config.ProxyAttributes {
    76  		anAttr := attrs[key]
    77  		if anAttr == nil {
    78  			continue
    79  		}
    80  		var attrStr string
    81  		var isString bool
    82  		if attrStr, isString = anAttr.(string); !isString || attrStr == "" {
    83  			continue
    84  		}
    85  		newValue, err := p.swapLocalhostForBridgeIP(attrStr, providerCfg)
    86  		if err != nil {
    87  			return nil, errors.Trace(err)
    88  		}
    89  		updatedAttrs[key] = newValue
    90  		logger.Infof("\nAttribute %q is set to (%v)\n", key, newValue)
    91  	}
    92  	// Update desired attributes on current configuration
    93  	return cfg.Apply(updatedAttrs)
    94  }
    95  
    96  // RestrictedConfigAttributes is specified in the EnvironProvider interface.
    97  func (p environProvider) RestrictedConfigAttributes() []string {
    98  	return []string{ContainerKey, NetworkBridgeKey, RootDirKey, "proxy-ssh"}
    99  }
   100  
   101  // PrepareForCreateEnvironment is specified in the EnvironProvider interface.
   102  func (p environProvider) PrepareForCreateEnvironment(cfg *config.Config) (*config.Config, error) {
   103  	return cfg, nil
   104  }
   105  
   106  // PrepareForBootstrap implements environs.EnvironProvider.PrepareForBootstrap.
   107  func (p environProvider) PrepareForBootstrap(ctx environs.BootstrapContext, cfg *config.Config) (environs.Environ, error) {
   108  	attrs := map[string]interface{}{
   109  		// We must not proxy SSH through the API server in a
   110  		// local provider environment. Besides not being useful,
   111  		// it may not work; there is no requirement for sshd to
   112  		// be available on machine-0.
   113  		"proxy-ssh": false,
   114  	}
   115  	if _, ok := cfg.AgentVersion(); !ok {
   116  		attrs["agent-version"] = version.Current.Number.String()
   117  	}
   118  	if namespace, _ := cfg.UnknownAttrs()["namespace"].(string); namespace == "" {
   119  		username := os.Getenv("USER")
   120  		if username == "" {
   121  			u, err := userCurrent()
   122  			if err != nil {
   123  				return nil, errors.Annotate(err, "failed to determine username for namespace")
   124  			}
   125  			username = u.Username
   126  		}
   127  		attrs["namespace"] = fmt.Sprintf("%s-%s", username, cfg.Name())
   128  	}
   129  
   130  	setIfNotBlank := func(key, value string) {
   131  		if value != "" {
   132  			attrs[key] = value
   133  		}
   134  	}
   135  	// If the user has specified no values for any of the four normal
   136  	// proxies, then look in the environment and set them.
   137  	logger.Tracef("Look for proxies?")
   138  	if cfg.HttpProxy() == "" &&
   139  		cfg.HttpsProxy() == "" &&
   140  		cfg.FtpProxy() == "" &&
   141  		cfg.NoProxy() == "" {
   142  		proxySettings := proxy.DetectProxies()
   143  		logger.Tracef("Proxies detected %#v", proxySettings)
   144  		setIfNotBlank(config.HttpProxyKey, proxySettings.Http)
   145  		setIfNotBlank(config.HttpsProxyKey, proxySettings.Https)
   146  		setIfNotBlank(config.FtpProxyKey, proxySettings.Ftp)
   147  		setIfNotBlank(config.NoProxyKey, proxySettings.NoProxy)
   148  	}
   149  	if jujuos.HostOS() == jujuos.Ubuntu {
   150  		if cfg.AptHttpProxy() == "" &&
   151  			cfg.AptHttpsProxy() == "" &&
   152  			cfg.AptFtpProxy() == "" {
   153  			proxySettings, err := detectPackageProxies()
   154  			if err != nil {
   155  				return nil, errors.Trace(err)
   156  			}
   157  			setIfNotBlank(config.AptHttpProxyKey, proxySettings.Http)
   158  			setIfNotBlank(config.AptHttpsProxyKey, proxySettings.Https)
   159  			setIfNotBlank(config.AptFtpProxyKey, proxySettings.Ftp)
   160  		}
   161  	}
   162  
   163  	cfg, err := cfg.Apply(attrs)
   164  	if err != nil {
   165  		return nil, errors.Trace(err)
   166  	}
   167  	// Make sure everything is valid.
   168  	cfg, err = p.Validate(cfg, nil)
   169  	if err != nil {
   170  		return nil, errors.Trace(err)
   171  	}
   172  
   173  	// The user must not set bootstrap-ip; this is determined by the provider,
   174  	// and its presence used to determine whether the environment has yet been
   175  	// bootstrapped.
   176  	if _, ok := cfg.UnknownAttrs()["bootstrap-ip"]; ok {
   177  		return nil, errors.Errorf("bootstrap-ip must not be specified")
   178  	}
   179  	err = checkLocalPort(cfg.StatePort(), "state port")
   180  	if err != nil {
   181  		return nil, errors.Trace(err)
   182  	}
   183  	err = checkLocalPort(cfg.APIPort(), "API port")
   184  	if err != nil {
   185  		return nil, errors.Trace(err)
   186  	}
   187  
   188  	return p.Open(cfg)
   189  }
   190  
   191  // swapLocalhostForBridgeIP substitutes bridge ip for localhost. Non-localhost values are not modified.
   192  func (p environProvider) swapLocalhostForBridgeIP(originalURL string, providerConfig *environConfig) (string, error) {
   193  	// TODO(anastasia) 2014-10-31 Bug#1385277 Parse method does not cater for malformed URL, eg. localhost:8080
   194  	parsedUrl, err := url.Parse(originalURL)
   195  	if err != nil {
   196  		return "", errors.Trace(err)
   197  	}
   198  
   199  	isLoopback, _, port := isLoopback(parsedUrl.Host)
   200  	if !isLoopback {
   201  		// If not loopback host address, return current attribute value
   202  		return originalURL, nil
   203  	}
   204  	//If localhost is specified, use its network bridge ip
   205  	bridgeAddress, nwerr := getAddressForInterface(providerConfig.networkBridge())
   206  	if nwerr != nil {
   207  		return "", errors.Trace(nwerr)
   208  	}
   209  	parsedUrl.Host = bridgeAddress + port
   210  	return parsedUrl.String(), nil
   211  }
   212  
   213  // isLoopback returns whether given url is a loopback url.
   214  // The argument to the method is expected to be in the form of
   215  // host:port where host and port are also returned as distinct values.
   216  func isLoopback(hostAndPort string) (isLoopback bool, host, port string) {
   217  	host, port = getHostAndPort(hostAndPort)
   218  	isLoopback = strings.ToLower(host) == "localhost"
   219  	if !isLoopback {
   220  		ip := net.ParseIP(host)
   221  		isLoopback = ip != nil && ip.IsLoopback()
   222  	}
   223  	return
   224  }
   225  
   226  // getHostAndPort expects argument in the form host:port and
   227  // returns host and port as distinctive strings.
   228  func getHostAndPort(original string) (host, port string) {
   229  	// Host and post specification is host:port
   230  	hostAndPortRegexp := regexp.MustCompile(`(?P<host>\[?[::]*[^:]+)(?P<port>$|:[^:]+$)`)
   231  
   232  	matched := hostAndPortRegexp.FindStringSubmatch(original)
   233  	if len(matched) == 0 {
   234  		// Passed in parameter is not in the form host:port.
   235  		// Let's not mess with it.
   236  		return original, ""
   237  	}
   238  
   239  	// For the string in the form host:port, FindStringSubmatch above
   240  	// will return {host:port, host, :port}
   241  	host = matched[1]
   242  	port = matched[2]
   243  
   244  	// For hosts like [::1], remove brackets
   245  	if strings.Contains(host, "[") {
   246  		host = host[1 : len(host)-1]
   247  	}
   248  
   249  	// For hosts like ::1, substring :1 is not a port!
   250  	if strings.Contains(host, port) {
   251  		port = ""
   252  	}
   253  	return
   254  }
   255  
   256  // checkLocalPort checks that the passed port is not used so far.
   257  var checkLocalPort = func(port int, description string) error {
   258  	logger.Infof("checking %s", description)
   259  	// Try to connect the port on localhost.
   260  	address := net.JoinHostPort("localhost", strconv.Itoa(port))
   261  	// TODO(mue) Add a timeout?
   262  	conn, err := net.Dial("tcp", address)
   263  	if err != nil {
   264  		if isConnectionRefused(err) {
   265  			// we're expecting to get conn refused
   266  			return nil
   267  		}
   268  		// some other error happened
   269  		return errors.Trace(err)
   270  	}
   271  	// Connected, so port is in use.
   272  	err = conn.Close()
   273  	if err != nil {
   274  		return err
   275  	}
   276  	return errors.Errorf("cannot use %d as %s, already in use", port, description)
   277  }
   278  
   279  // isConnectionRefused indicates if the err was caused by a refused connection.
   280  func isConnectionRefused(err error) bool {
   281  	if err, ok := err.(*net.OpError); ok {
   282  		// go 1.4 and earlier
   283  		if err.Err == syscall.ECONNREFUSED {
   284  			return true
   285  		}
   286  		// go 1.5 and later
   287  		if err, ok := err.Err.(*os.SyscallError); ok {
   288  			return err.Err == syscall.ECONNREFUSED
   289  		}
   290  	}
   291  	return false
   292  }
   293  
   294  // Validate implements environs.EnvironProvider.Validate.
   295  func (provider environProvider) Validate(cfg, old *config.Config) (valid *config.Config, err error) {
   296  	// Check for valid changes for the base config values.
   297  	if err := config.Validate(cfg, old); err != nil {
   298  		return nil, err
   299  	}
   300  	validated, err := cfg.ValidateUnknownAttrs(configFields, configDefaults)
   301  	if err != nil {
   302  		return nil, errors.Annotatef(err, "failed to validate unknown attrs")
   303  	}
   304  	localConfig := newEnvironConfig(cfg, validated)
   305  	// Set correct default network bridge if needed
   306  	// fix for http://pad.lv/1394450
   307  	localConfig.setDefaultNetworkBridge()
   308  	// Before potentially creating directories, make sure that the
   309  	// root directory has not changed.
   310  	if localConfig.namespace() == "" {
   311  		return nil, errors.New("missing namespace, config not prepared")
   312  	}
   313  	containerType := localConfig.container()
   314  	if old != nil {
   315  		oldLocalConfig, err := provider.newConfig(old)
   316  		if err != nil {
   317  			return nil, errors.Annotatef(err, "old config is not a valid local config: %v", old)
   318  		}
   319  		if containerType != oldLocalConfig.container() {
   320  			return nil, errors.Errorf("cannot change container from %q to %q",
   321  				oldLocalConfig.container(), containerType)
   322  		}
   323  		if localConfig.rootDir() != oldLocalConfig.rootDir() {
   324  			return nil, errors.Errorf("cannot change root-dir from %q to %q",
   325  				oldLocalConfig.rootDir(),
   326  				localConfig.rootDir())
   327  		}
   328  		if localConfig.networkBridge() != oldLocalConfig.networkBridge() {
   329  			return nil, errors.Errorf("cannot change network-bridge from %q to %q",
   330  				oldLocalConfig.rootDir(),
   331  				localConfig.rootDir())
   332  		}
   333  		if localConfig.storagePort() != oldLocalConfig.storagePort() {
   334  			return nil, errors.Errorf("cannot change storage-port from %v to %v",
   335  				oldLocalConfig.storagePort(),
   336  				localConfig.storagePort())
   337  		}
   338  		if localConfig.namespace() != oldLocalConfig.namespace() {
   339  			return nil, errors.Errorf("cannot change namespace from %v to %v",
   340  				oldLocalConfig.namespace(),
   341  				localConfig.namespace())
   342  		}
   343  	}
   344  	// Currently only supported containers are "lxc" and "kvm".
   345  	if containerType != instance.LXC && containerType != instance.KVM {
   346  		return nil, errors.Errorf("unsupported container type: %q", containerType)
   347  	}
   348  	dir, err := utils.NormalizePath(localConfig.rootDir())
   349  	if err != nil {
   350  		return nil, err
   351  	}
   352  	if dir == "." {
   353  		dir = osenv.JujuHomePath(cfg.Name())
   354  	}
   355  	// Always assign the normalized path.
   356  	localConfig.attrs["root-dir"] = dir
   357  
   358  	// If the user hasn't already specified a value, set it to the
   359  	// given value.
   360  	defineIfNot := func(keyName string, value interface{}) {
   361  		if _, defined := cfg.AllAttrs()[keyName]; !defined {
   362  			logger.Infof("lxc-clone is enabled. Switching %s to %v", keyName, value)
   363  			localConfig.attrs[keyName] = value
   364  		}
   365  	}
   366  
   367  	// If we're cloning, and the user hasn't specified otherwise,
   368  	// prefer to skip update logic.
   369  	if useClone, _ := localConfig.LXCUseClone(); useClone && containerType == instance.LXC {
   370  		defineIfNot("enable-os-refresh-update", true)
   371  		defineIfNot("enable-os-upgrade", false)
   372  	}
   373  
   374  	// Apply the coerced unknown values back into the config.
   375  	return cfg.Apply(localConfig.attrs)
   376  }
   377  
   378  // BoilerplateConfig implements environs.EnvironProvider.BoilerplateConfig.
   379  func (environProvider) BoilerplateConfig() string {
   380  	return `
   381  # https://juju.ubuntu.com/docs/config-local.html
   382  local:
   383      type: local
   384  
   385      # root-dir holds the directory that is used for the storage files and
   386      # database. The default location is $JUJU_HOME/<env-name>.
   387      # $JUJU_HOME defaults to ~/.juju. Override if needed.
   388      #
   389      # root-dir: ~/.juju/local
   390  
   391      # storage-port holds the port where the local provider starts the
   392      # HTTP file server. Override the value if you have multiple local
   393      # providers, or if the default port is used by another program.
   394      #
   395      # storage-port: 8040
   396  
   397      # network-bridge holds the name of the LXC network bridge to use.
   398      # Override if the default LXC network bridge is different.
   399      #
   400      #
   401      # network-bridge: lxcbr0
   402  
   403      # The default series to deploy the state-server and charms on.
   404      # Make sure to uncomment the following option and set the value to
   405      # precise or trusty as desired.
   406      #
   407      # default-series: trusty
   408  
   409      # Whether or not to refresh the list of available updates for an
   410      # OS. The default option of true is recommended for use in
   411      # production systems, but disabling this can speed up local
   412      # deployments for development or testing.
   413      #
   414      # enable-os-refresh-update: true
   415  
   416      # Whether or not to perform OS upgrades when machines are
   417      # provisioned. The default option of true is recommended for use
   418      # in production systems, but disabling this can speed up local
   419      # deployments for development or testing.
   420      #
   421      # enable-os-upgrade: true
   422  
   423  `[1:]
   424  }
   425  
   426  // SecretAttrs implements environs.EnvironProvider.SecretAttrs.
   427  func (environProvider) SecretAttrs(cfg *config.Config) (map[string]string, error) {
   428  	// don't have any secret attrs
   429  	return nil, nil
   430  }
   431  
   432  func (p environProvider) newConfig(cfg *config.Config) (*environConfig, error) {
   433  	valid, err := p.Validate(cfg, nil)
   434  	if err != nil {
   435  		return nil, err
   436  	}
   437  	return newEnvironConfig(valid, valid.UnknownAttrs()), nil
   438  }