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