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  }