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