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