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