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