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