github.com/axw/juju@v0.0.0-20161005053422-4bd6544d08d4/provider/manual/environ.go (about) 1 // Copyright 2013 Canonical Ltd. 2 // Licensed under the AGPLv3, see LICENCE file for details. 3 4 package manual 5 6 import ( 7 "bytes" 8 "fmt" 9 "os" 10 "path" 11 "path/filepath" 12 "strings" 13 "sync" 14 15 "github.com/juju/errors" 16 "github.com/juju/loggo" 17 "github.com/juju/utils" 18 "github.com/juju/utils/arch" 19 "github.com/juju/utils/featureflag" 20 "github.com/juju/utils/ssh" 21 22 "github.com/juju/juju/agent" 23 "github.com/juju/juju/cloudconfig/instancecfg" 24 "github.com/juju/juju/constraints" 25 "github.com/juju/juju/environs" 26 "github.com/juju/juju/environs/config" 27 "github.com/juju/juju/environs/manual" 28 "github.com/juju/juju/feature" 29 "github.com/juju/juju/instance" 30 "github.com/juju/juju/juju/names" 31 "github.com/juju/juju/mongo" 32 "github.com/juju/juju/network" 33 "github.com/juju/juju/provider/common" 34 "github.com/juju/juju/worker/terminationworker" 35 ) 36 37 const ( 38 // BootstrapInstanceId is the instance ID used 39 // for the manual provider's bootstrap instance. 40 BootstrapInstanceId instance.Id = "manual:" 41 ) 42 43 var ( 44 logger = loggo.GetLogger("juju.provider.manual") 45 manualCheckProvisioned = manual.CheckProvisioned 46 ) 47 48 type manualEnviron struct { 49 host string 50 user string 51 mu sync.Mutex 52 cfg *environConfig 53 // hw and series are detected by running a script on the 54 // target machine. We cache these, as they should not change. 55 hw *instance.HardwareCharacteristics 56 series string 57 } 58 59 var errNoStartInstance = errors.New("manual provider cannot start instances") 60 var errNoStopInstance = errors.New("manual provider cannot stop instances") 61 62 // MaintainInstance is specified in the InstanceBroker interface. 63 func (*manualEnviron) MaintainInstance(args environs.StartInstanceParams) error { 64 return nil 65 } 66 67 func (*manualEnviron) StartInstance(args environs.StartInstanceParams) (*environs.StartInstanceResult, error) { 68 return nil, errNoStartInstance 69 } 70 71 func (*manualEnviron) StopInstances(...instance.Id) error { 72 return errNoStopInstance 73 } 74 75 func (e *manualEnviron) AllInstances() ([]instance.Instance, error) { 76 return e.Instances([]instance.Id{BootstrapInstanceId}) 77 } 78 79 func (e *manualEnviron) envConfig() (cfg *environConfig) { 80 e.mu.Lock() 81 cfg = e.cfg 82 e.mu.Unlock() 83 return cfg 84 } 85 86 func (e *manualEnviron) Config() *config.Config { 87 return e.envConfig().Config 88 } 89 90 // PrepareForBootstrap is part of the Environ interface. 91 func (e *manualEnviron) PrepareForBootstrap(ctx environs.BootstrapContext) error { 92 if err := ensureBootstrapUbuntuUser(ctx, e.host, e.user, e.envConfig()); err != nil { 93 return err 94 } 95 return nil 96 } 97 98 // Create is part of the Environ interface. 99 func (e *manualEnviron) Create(environs.CreateParams) error { 100 return nil 101 } 102 103 // Bootstrap is part of the Environ interface. 104 func (e *manualEnviron) Bootstrap(ctx environs.BootstrapContext, args environs.BootstrapParams) (*environs.BootstrapResult, error) { 105 provisioned, err := manualCheckProvisioned(e.host) 106 if err != nil { 107 return nil, errors.Annotate(err, "failed to check provisioned status") 108 } 109 if provisioned { 110 return nil, manual.ErrProvisioned 111 } 112 hw, series, err := e.seriesAndHardwareCharacteristics() 113 if err != nil { 114 return nil, err 115 } 116 finalize := func(ctx environs.BootstrapContext, icfg *instancecfg.InstanceConfig, _ environs.BootstrapDialOpts) error { 117 icfg.Bootstrap.BootstrapMachineInstanceId = BootstrapInstanceId 118 icfg.Bootstrap.BootstrapMachineHardwareCharacteristics = hw 119 if err := instancecfg.FinishInstanceConfig(icfg, e.Config()); err != nil { 120 return err 121 } 122 return common.ConfigureMachine(ctx, ssh.DefaultClient, e.host, icfg) 123 } 124 125 result := &environs.BootstrapResult{ 126 Arch: *hw.Arch, 127 Series: series, 128 Finalize: finalize, 129 } 130 return result, nil 131 } 132 133 // ControllerInstances is specified in the Environ interface. 134 func (e *manualEnviron) ControllerInstances(controllerUUID string) ([]instance.Id, error) { 135 if !isRunningController() { 136 // Not running inside the controller, so we must 137 // verify the host. 138 if err := e.verifyBootstrapHost(); err != nil { 139 return nil, err 140 } 141 } 142 return []instance.Id{BootstrapInstanceId}, nil 143 } 144 145 func (e *manualEnviron) verifyBootstrapHost() error { 146 // First verify that the environment is bootstrapped by checking 147 // if the agents directory exists. Note that we cannot test the 148 // root data directory, as that is created in the process of 149 // initialising sshstorage. 150 agentsDir := path.Join(agent.DefaultPaths.DataDir, "agents") 151 const noAgentDir = "no-agent-dir" 152 stdin := fmt.Sprintf( 153 "test -d %s || echo %s", 154 utils.ShQuote(agentsDir), 155 noAgentDir, 156 ) 157 out, _, err := runSSHCommand( 158 "ubuntu@"+e.host, 159 []string{"/bin/bash"}, 160 stdin, 161 ) 162 if err != nil { 163 return err 164 } 165 if out = strings.TrimSpace(out); len(out) > 0 { 166 if out == noAgentDir { 167 return environs.ErrNotBootstrapped 168 } 169 err := errors.Errorf("unexpected output: %q", out) 170 logger.Infof(err.Error()) 171 return err 172 } 173 return nil 174 } 175 176 func (e *manualEnviron) SetConfig(cfg *config.Config) error { 177 e.mu.Lock() 178 defer e.mu.Unlock() 179 _, err := manualProvider{}.validate(cfg, e.cfg.Config) 180 if err != nil { 181 return err 182 } 183 e.cfg = newModelConfig(cfg, cfg.UnknownAttrs()) 184 return nil 185 } 186 187 // Implements environs.Environ. 188 // 189 // This method will only ever return an Instance for the Id 190 // BootstrapInstanceId. If any others are specified, then 191 // ErrPartialInstances or ErrNoInstances will result. 192 func (e *manualEnviron) Instances(ids []instance.Id) (instances []instance.Instance, err error) { 193 instances = make([]instance.Instance, len(ids)) 194 var found bool 195 for i, id := range ids { 196 if id == BootstrapInstanceId { 197 instances[i] = manualBootstrapInstance{e.host} 198 found = true 199 } else { 200 err = environs.ErrPartialInstances 201 } 202 } 203 if !found { 204 err = environs.ErrNoInstances 205 } 206 return instances, err 207 } 208 209 var runSSHCommand = func(host string, command []string, stdin string) (stdout, stderr string, err error) { 210 cmd := ssh.Command(host, command, nil) 211 cmd.Stdin = strings.NewReader(stdin) 212 var stdoutBuf, stderrBuf bytes.Buffer 213 cmd.Stdout = &stdoutBuf 214 cmd.Stderr = &stderrBuf 215 if err := cmd.Run(); err != nil { 216 if stderr := strings.TrimSpace(stderrBuf.String()); len(stderr) > 0 { 217 err = errors.Annotate(err, stderr) 218 } 219 return "", "", err 220 } 221 return stdoutBuf.String(), stderrBuf.String(), nil 222 } 223 224 // Destroy implements the Environ interface. 225 func (e *manualEnviron) Destroy() error { 226 // There is nothing we can do for manual environments, 227 // except when destroying the controller as a whole 228 // (see DestroyController below). 229 return nil 230 } 231 232 // DestroyController implements the Environ interface. 233 func (e *manualEnviron) DestroyController(controllerUUID string) error { 234 script := ` 235 set -x 236 touch %s 237 # If jujud is running, we then wait for a while for it to stop. 238 stopped=0 239 if pkill -%d jujud; then 240 for i in ` + "`seq 1 30`" + `; do 241 if pgrep jujud > /dev/null ; then 242 sleep 1 243 else 244 echo "jujud stopped" 245 stopped=1 246 break 247 fi 248 done 249 fi 250 if [ $stopped -ne 1 ]; then 251 # If jujud didn't stop nicely, we kill it hard here. 252 %spkill -9 jujud 253 service %s stop 254 fi 255 rm -f /etc/init/juju* 256 rm -f /etc/systemd/system/juju* 257 rm -fr %s %s 258 exit 0 259 ` 260 var diagnostics string 261 if featureflag.Enabled(feature.DeveloperMode) { 262 diagnostics = ` 263 echo "Dump engine report and goroutines for stuck jujud" 264 source /etc/profile.d/juju-introspection.sh 265 juju-engine-report 266 juju-goroutines 267 ` 268 } 269 script = fmt.Sprintf( 270 script, 271 // WARNING: this is linked with the use of uninstallFile in 272 // the agent package. Don't change it without extreme care, 273 // and handling for mismatches with already-deployed agents. 274 utils.ShQuote(path.Join( 275 agent.DefaultPaths.DataDir, 276 agent.UninstallFile, 277 )), 278 terminationworker.TerminationSignal, 279 diagnostics, 280 mongo.ServiceName, 281 utils.ShQuote(agent.DefaultPaths.DataDir), 282 utils.ShQuote(agent.DefaultPaths.LogDir), 283 ) 284 logger.Tracef("destroy controller script: %s", script) 285 stdout, stderr, err := runSSHCommand( 286 "ubuntu@"+e.host, 287 []string{"sudo", "/bin/bash"}, script, 288 ) 289 logger.Debugf("script stdout: \n%s", stdout) 290 logger.Debugf("script stderr: \n%s", stderr) 291 return err 292 } 293 294 func (*manualEnviron) PrecheckInstance(series string, _ constraints.Value, placement string) error { 295 return errors.New(`use "juju add-machine ssh:[user@]<host>" to provision machines`) 296 } 297 298 var unsupportedConstraints = []string{ 299 constraints.CpuPower, 300 constraints.InstanceType, 301 constraints.Tags, 302 constraints.VirtType, 303 } 304 305 // ConstraintsValidator is defined on the Environs interface. 306 func (e *manualEnviron) ConstraintsValidator() (constraints.Validator, error) { 307 validator := constraints.NewValidator() 308 validator.RegisterUnsupported(unsupportedConstraints) 309 if isRunningController() { 310 validator.UpdateVocabulary(constraints.Arch, []string{arch.HostArch()}) 311 } else { 312 // We're running outside of the Juju controller, so we must 313 // SSH to the machine and detect its architecture. 314 hw, _, err := e.seriesAndHardwareCharacteristics() 315 if err != nil { 316 return nil, errors.Trace(err) 317 } 318 validator.UpdateVocabulary(constraints.Arch, []string{*hw.Arch}) 319 } 320 return validator, nil 321 } 322 323 func (e *manualEnviron) seriesAndHardwareCharacteristics() (_ *instance.HardwareCharacteristics, series string, _ error) { 324 e.mu.Lock() 325 defer e.mu.Unlock() 326 if e.hw != nil { 327 return e.hw, e.series, nil 328 } 329 hw, series, err := manual.DetectSeriesAndHardwareCharacteristics(e.host) 330 if err != nil { 331 return nil, "", errors.Trace(err) 332 } 333 e.hw, e.series = &hw, series 334 return e.hw, e.series, nil 335 } 336 337 func (e *manualEnviron) OpenPorts(ports []network.PortRange) error { 338 return nil 339 } 340 341 func (e *manualEnviron) ClosePorts(ports []network.PortRange) error { 342 return nil 343 } 344 345 func (e *manualEnviron) Ports() ([]network.PortRange, error) { 346 return nil, nil 347 } 348 349 func (*manualEnviron) Provider() environs.EnvironProvider { 350 return manualProvider{} 351 } 352 353 func isRunningController() bool { 354 return filepath.Base(os.Args[0]) == names.Jujud 355 }