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