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