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 }