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 }