github.com/mattyw/juju@v0.0.0-20140610034352-732aecd63861/worker/deployer/simple.go (about) 1 // Copyright 2012, 2013 Canonical Ltd. 2 // Licensed under the AGPLv3, see LICENCE file for details. 3 4 package deployer 5 6 import ( 7 "fmt" 8 "io/ioutil" 9 "os" 10 "path" 11 "regexp" 12 "strings" 13 14 "github.com/juju/names" 15 16 "github.com/juju/juju/agent" 17 "github.com/juju/juju/agent/tools" 18 "github.com/juju/juju/juju/osenv" 19 "github.com/juju/juju/state/api/params" 20 "github.com/juju/juju/upstart" 21 "github.com/juju/juju/version" 22 ) 23 24 // InitDir is the default upstart init directory. 25 // This is a var so it can be overridden by tests. 26 var InitDir = "/etc/init" 27 28 // APICalls defines the interface to the API that the simple context needs. 29 type APICalls interface { 30 ConnectionInfo() (params.DeployerConnectionValues, error) 31 } 32 33 // SimpleContext is a Context that manages unit deployments via upstart 34 // jobs on the local system. 35 type SimpleContext struct { 36 37 // api is used to get the current state server addresses at the time the 38 // given unit is deployed. 39 api APICalls 40 41 // agentConfig returns the agent config for the machine agent that is 42 // running the deployer. 43 agentConfig agent.Config 44 45 // initDir specifies the directory used by upstart on the local system. 46 // It is typically set to "/etc/init". 47 initDir string 48 } 49 50 var _ Context = (*SimpleContext)(nil) 51 52 // NewSimpleContext returns a new SimpleContext, acting on behalf of 53 // the specified deployer, that deploys unit agents as upstart jobs in 54 // "/etc/init". Paths to which agents and tools are installed are 55 // relative to dataDir. 56 func NewSimpleContext(agentConfig agent.Config, api APICalls) *SimpleContext { 57 return &SimpleContext{ 58 api: api, 59 agentConfig: agentConfig, 60 initDir: InitDir, 61 } 62 } 63 64 func (ctx *SimpleContext) AgentConfig() agent.Config { 65 return ctx.agentConfig 66 } 67 68 func (ctx *SimpleContext) DeployUnit(unitName, initialPassword string) (err error) { 69 // Check sanity. 70 svc := ctx.upstartService(unitName) 71 if svc.Installed() { 72 return fmt.Errorf("unit %q is already deployed", unitName) 73 } 74 75 // Link the current tools for use by the new agent. 76 tag := names.UnitTag(unitName) 77 dataDir := ctx.agentConfig.DataDir() 78 logDir := ctx.agentConfig.LogDir() 79 _, err = tools.ChangeAgentTools(dataDir, tag, version.Current) 80 toolsDir := tools.ToolsDir(dataDir, tag) 81 defer removeOnErr(&err, toolsDir) 82 83 result, err := ctx.api.ConnectionInfo() 84 if err != nil { 85 return err 86 } 87 logger.Debugf("state addresses: %q", result.StateAddresses) 88 logger.Debugf("API addresses: %q", result.APIAddresses) 89 containerType := ctx.agentConfig.Value(agent.ContainerType) 90 namespace := ctx.agentConfig.Value(agent.Namespace) 91 conf, err := agent.NewAgentConfig( 92 agent.AgentConfigParams{ 93 DataDir: dataDir, 94 LogDir: logDir, 95 UpgradedToVersion: version.Current.Number, 96 Tag: tag, 97 Password: initialPassword, 98 Nonce: "unused", 99 // TODO: remove the state addresses here and test when api only. 100 StateAddresses: result.StateAddresses, 101 APIAddresses: result.APIAddresses, 102 CACert: ctx.agentConfig.CACert(), 103 Values: map[string]string{ 104 agent.ContainerType: containerType, 105 agent.Namespace: namespace, 106 }, 107 }) 108 if err != nil { 109 return err 110 } 111 if err := conf.Write(); err != nil { 112 return err 113 } 114 defer removeOnErr(&err, conf.Dir()) 115 116 // Install an upstart job that runs the unit agent. 117 logPath := path.Join(logDir, tag+".log") 118 cmd := strings.Join([]string{ 119 path.Join(toolsDir, "jujud"), "unit", 120 "--data-dir", dataDir, 121 "--unit-name", unitName, 122 "--debug", // TODO: propagate debug state sensibly 123 }, " ") 124 // TODO(thumper): 2013-09-02 bug 1219630 125 // As much as I'd like to remove JujuContainerType now, it is still 126 // needed as MAAS still needs it at this stage, and we can't fix 127 // everything at once. 128 uconf := &upstart.Conf{ 129 Service: *svc, 130 Desc: "juju unit agent for " + unitName, 131 Cmd: cmd, 132 Out: logPath, 133 Env: map[string]string{ 134 osenv.JujuContainerTypeEnvKey: containerType, 135 }, 136 } 137 return uconf.Install() 138 } 139 140 // findUpstartJob tries to find an upstart job matching the 141 // given unit name in one of these formats: 142 // jujud-<deployer-tag>:<unit-tag>.conf (for compatibility) 143 // jujud-<unit-tag>.conf (default) 144 func (ctx *SimpleContext) findUpstartJob(unitName string) *upstart.Service { 145 unitsAndJobs, err := ctx.deployedUnitsUpstartJobs() 146 if err != nil { 147 return nil 148 } 149 if job, ok := unitsAndJobs[unitName]; ok { 150 svc := upstart.NewService(job) 151 svc.InitDir = ctx.initDir 152 return svc 153 } 154 return nil 155 } 156 157 func (ctx *SimpleContext) RecallUnit(unitName string) error { 158 svc := ctx.findUpstartJob(unitName) 159 if svc == nil || !svc.Installed() { 160 return fmt.Errorf("unit %q is not deployed", unitName) 161 } 162 if err := svc.StopAndRemove(); err != nil { 163 return err 164 } 165 tag := names.UnitTag(unitName) 166 dataDir := ctx.agentConfig.DataDir() 167 agentDir := agent.Dir(dataDir, tag) 168 if err := os.RemoveAll(agentDir); err != nil { 169 return err 170 } 171 toolsDir := tools.ToolsDir(dataDir, tag) 172 return os.Remove(toolsDir) 173 } 174 175 var deployedRe = regexp.MustCompile("^(jujud-.*unit-([a-z0-9-]+)-([0-9]+))\\.conf$") 176 177 func (ctx *SimpleContext) deployedUnitsUpstartJobs() (map[string]string, error) { 178 fis, err := ioutil.ReadDir(ctx.initDir) 179 if err != nil { 180 return nil, err 181 } 182 installed := make(map[string]string) 183 for _, fi := range fis { 184 if groups := deployedRe.FindStringSubmatch(fi.Name()); len(groups) == 4 { 185 unitName := groups[2] + "/" + groups[3] 186 if !names.IsUnit(unitName) { 187 continue 188 } 189 installed[unitName] = groups[1] 190 } 191 } 192 return installed, nil 193 } 194 195 func (ctx *SimpleContext) DeployedUnits() ([]string, error) { 196 unitsAndJobs, err := ctx.deployedUnitsUpstartJobs() 197 if err != nil { 198 return nil, err 199 } 200 var installed []string 201 for unitName := range unitsAndJobs { 202 installed = append(installed, unitName) 203 } 204 return installed, nil 205 } 206 207 // upstartService returns an upstart.Service corresponding to the specified 208 // unit. 209 func (ctx *SimpleContext) upstartService(unitName string) *upstart.Service { 210 tag := names.UnitTag(unitName) 211 svcName := "jujud-" + tag 212 svc := upstart.NewService(svcName) 213 svc.InitDir = ctx.initDir 214 return svc 215 } 216 217 func removeOnErr(err *error, path string) { 218 if *err != nil { 219 if err := os.Remove(path); err != nil { 220 logger.Warningf("installer: cannot remove %q: %v", path, err) 221 } 222 } 223 }