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