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