github.com/makyo/juju@v0.0.0-20160425123129-2608902037e9/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/ssh" 20 21 "github.com/juju/juju/agent" 22 "github.com/juju/juju/cloudconfig/instancecfg" 23 "github.com/juju/juju/constraints" 24 "github.com/juju/juju/environs" 25 "github.com/juju/juju/environs/config" 26 "github.com/juju/juju/environs/manual" 27 "github.com/juju/juju/instance" 28 "github.com/juju/juju/juju/names" 29 "github.com/juju/juju/mongo" 30 "github.com/juju/juju/network" 31 "github.com/juju/juju/provider/common" 32 "github.com/juju/juju/worker/terminationworker" 33 ) 34 35 const ( 36 // BootstrapInstanceId is the instance ID used 37 // for the manual provider's bootstrap instance. 38 BootstrapInstanceId instance.Id = "manual:" 39 ) 40 41 var ( 42 logger = loggo.GetLogger("juju.provider.manual") 43 manualCheckProvisioned = manual.CheckProvisioned 44 manualDetectSeriesAndHardwareCharacteristics = manual.DetectSeriesAndHardwareCharacteristics 45 ) 46 47 type manualEnviron struct { 48 common.SupportsUnitPlacementPolicy 49 50 cfg *environConfig 51 cfgmutex sync.Mutex 52 ubuntuUserInited bool 53 ubuntuUserInitMutex sync.Mutex 54 } 55 56 var errNoStartInstance = errors.New("manual provider cannot start instances") 57 var errNoStopInstance = errors.New("manual provider cannot stop instances") 58 59 // MaintainInstance is specified in the InstanceBroker interface. 60 func (*manualEnviron) MaintainInstance(args environs.StartInstanceParams) error { 61 return nil 62 } 63 64 func (*manualEnviron) StartInstance(args environs.StartInstanceParams) (*environs.StartInstanceResult, error) { 65 return nil, errNoStartInstance 66 } 67 68 func (*manualEnviron) StopInstances(...instance.Id) error { 69 return errNoStopInstance 70 } 71 72 func (e *manualEnviron) AllInstances() ([]instance.Instance, error) { 73 return e.Instances([]instance.Id{BootstrapInstanceId}) 74 } 75 76 func (e *manualEnviron) envConfig() (cfg *environConfig) { 77 e.cfgmutex.Lock() 78 cfg = e.cfg 79 e.cfgmutex.Unlock() 80 return cfg 81 } 82 83 func (e *manualEnviron) Config() *config.Config { 84 return e.envConfig().Config 85 } 86 87 // SupportedArchitectures is specified on the EnvironCapability interface. 88 func (e *manualEnviron) SupportedArchitectures() ([]string, error) { 89 return arch.AllSupportedArches, nil 90 } 91 92 // Bootstrap is specified on the Environ interface. 93 func (e *manualEnviron) Bootstrap(ctx environs.BootstrapContext, args environs.BootstrapParams) (*environs.BootstrapResult, error) { 94 envConfig := e.envConfig() 95 host := envConfig.bootstrapHost() 96 provisioned, err := manualCheckProvisioned(host) 97 if err != nil { 98 return nil, errors.Annotate(err, "failed to check provisioned status") 99 } 100 if provisioned { 101 return nil, manual.ErrProvisioned 102 } 103 hc, series, err := manualDetectSeriesAndHardwareCharacteristics(host) 104 if err != nil { 105 return nil, err 106 } 107 finalize := func(ctx environs.BootstrapContext, icfg *instancecfg.InstanceConfig) error { 108 icfg.InstanceId = BootstrapInstanceId 109 icfg.HardwareCharacteristics = &hc 110 if err := instancecfg.FinishInstanceConfig(icfg, e.Config()); err != nil { 111 return err 112 } 113 return common.ConfigureMachine(ctx, ssh.DefaultClient, host, icfg) 114 } 115 116 result := &environs.BootstrapResult{ 117 Arch: *hc.Arch, 118 Series: series, 119 Finalize: finalize, 120 } 121 return result, nil 122 } 123 124 // ControllerInstances is specified in the Environ interface. 125 func (e *manualEnviron) ControllerInstances() ([]instance.Id, error) { 126 arg0 := filepath.Base(os.Args[0]) 127 if arg0 != names.Jujud { 128 // Not running inside the controller, so we must 129 // verify the host. 130 if err := e.verifyBootstrapHost(); err != nil { 131 return nil, err 132 } 133 } 134 return []instance.Id{BootstrapInstanceId}, nil 135 } 136 137 func (e *manualEnviron) verifyBootstrapHost() error { 138 // First verify that the environment is bootstrapped by checking 139 // if the agents directory exists. Note that we cannot test the 140 // root data directory, as that is created in the process of 141 // initialising sshstorage. 142 agentsDir := path.Join(agent.DefaultPaths.DataDir, "agents") 143 const noAgentDir = "no-agent-dir" 144 stdin := fmt.Sprintf( 145 "test -d %s || echo %s", 146 utils.ShQuote(agentsDir), 147 noAgentDir, 148 ) 149 out, err := runSSHCommand( 150 "ubuntu@"+e.cfg.bootstrapHost(), 151 []string{"/bin/bash"}, 152 stdin, 153 ) 154 if err != nil { 155 return err 156 } 157 if out = strings.TrimSpace(out); len(out) > 0 { 158 if out == noAgentDir { 159 return environs.ErrNotBootstrapped 160 } 161 err := errors.Errorf("unexpected output: %q", out) 162 logger.Infof(err.Error()) 163 return err 164 } 165 return nil 166 } 167 168 func (e *manualEnviron) SetConfig(cfg *config.Config) error { 169 e.cfgmutex.Lock() 170 defer e.cfgmutex.Unlock() 171 _, err := manualProvider{}.validate(cfg, e.cfg.Config) 172 if err != nil { 173 return err 174 } 175 e.cfg = newModelConfig(cfg, cfg.UnknownAttrs()) 176 return nil 177 } 178 179 // Implements environs.Environ. 180 // 181 // This method will only ever return an Instance for the Id 182 // BootstrapInstanceId. If any others are specified, then 183 // ErrPartialInstances or ErrNoInstances will result. 184 func (e *manualEnviron) Instances(ids []instance.Id) (instances []instance.Instance, err error) { 185 instances = make([]instance.Instance, len(ids)) 186 var found bool 187 for i, id := range ids { 188 if id == BootstrapInstanceId { 189 instances[i] = manualBootstrapInstance{e.envConfig().bootstrapHost()} 190 found = true 191 } else { 192 err = environs.ErrPartialInstances 193 } 194 } 195 if !found { 196 err = environs.ErrNoInstances 197 } 198 return instances, err 199 } 200 201 var runSSHCommand = func(host string, command []string, stdin string) (stdout string, err error) { 202 cmd := ssh.Command(host, command, nil) 203 cmd.Stdin = strings.NewReader(stdin) 204 var stdoutBuf, stderrBuf bytes.Buffer 205 cmd.Stdout = &stdoutBuf 206 cmd.Stderr = &stderrBuf 207 if err := cmd.Run(); err != nil { 208 if stderr := strings.TrimSpace(stderrBuf.String()); len(stderr) > 0 { 209 err = errors.Annotate(err, stderr) 210 } 211 return "", err 212 } 213 return stdoutBuf.String(), nil 214 } 215 216 func (e *manualEnviron) Destroy() error { 217 script := ` 218 set -x 219 touch %s 220 pkill -%d jujud && exit 221 stop %s 222 rm -f /etc/init/juju* 223 rm -fr %s %s 224 exit 0 225 ` 226 script = fmt.Sprintf( 227 script, 228 // WARNING: this is linked with the use of uninstallFile in 229 // the agent package. Don't change it without extreme care, 230 // and handling for mismatches with already-deployed agents. 231 utils.ShQuote(path.Join( 232 agent.DefaultPaths.DataDir, 233 agent.UninstallFile, 234 )), 235 terminationworker.TerminationSignal, 236 mongo.ServiceName, 237 utils.ShQuote(agent.DefaultPaths.DataDir), 238 utils.ShQuote(agent.DefaultPaths.LogDir), 239 ) 240 _, err := runSSHCommand( 241 "ubuntu@"+e.envConfig().bootstrapHost(), 242 []string{"sudo", "/bin/bash"}, script, 243 ) 244 return err 245 } 246 247 func (*manualEnviron) PrecheckInstance(series string, _ constraints.Value, placement string) error { 248 return errors.New(`use "juju add-machine ssh:[user@]<host>" to provision machines`) 249 } 250 251 var unsupportedConstraints = []string{ 252 constraints.CpuPower, 253 constraints.InstanceType, 254 constraints.Tags, 255 constraints.VirtType, 256 } 257 258 // ConstraintsValidator is defined on the Environs interface. 259 func (e *manualEnviron) ConstraintsValidator() (constraints.Validator, error) { 260 validator := constraints.NewValidator() 261 validator.RegisterUnsupported(unsupportedConstraints) 262 return validator, nil 263 } 264 265 func (e *manualEnviron) OpenPorts(ports []network.PortRange) error { 266 return nil 267 } 268 269 func (e *manualEnviron) ClosePorts(ports []network.PortRange) error { 270 return nil 271 } 272 273 func (e *manualEnviron) Ports() ([]network.PortRange, error) { 274 return nil, nil 275 } 276 277 func (*manualEnviron) Provider() environs.EnvironProvider { 278 return manualProvider{} 279 }