github.com/cloud-green/juju@v0.0.0-20151002100041-a00291338d3d/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/filepath"
    10  	"regexp"
    11  
    12  	"github.com/juju/errors"
    13  	"github.com/juju/names"
    14  	"github.com/juju/utils/shell"
    15  
    16  	"github.com/juju/juju/agent"
    17  	"github.com/juju/juju/agent/tools"
    18  	"github.com/juju/juju/apiserver/params"
    19  	"github.com/juju/juju/service"
    20  	"github.com/juju/juju/service/common"
    21  	"github.com/juju/juju/version"
    22  )
    23  
    24  // TODO(ericsnow) Use errors.Trace, etc. in this file.
    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 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  	// discoverService is a surrogate for service.DiscoverService.
    43  	discoverService func(string, common.Conf) (deployerService, error)
    44  
    45  	// listServices is a surrogate for service.ListServices.
    46  	listServices func() ([]string, error)
    47  }
    48  
    49  var _ Context = (*SimpleContext)(nil)
    50  
    51  // recursiveChmod will change the permissions on all files and
    52  // folders inside path
    53  func recursiveChmod(path string, mode os.FileMode) error {
    54  	walker := func(p string, fi os.FileInfo, err error) error {
    55  		if _, err := os.Stat(p); err == nil {
    56  			errPerm := os.Chmod(p, mode)
    57  			if errPerm != nil {
    58  				return errPerm
    59  			}
    60  		}
    61  		return nil
    62  	}
    63  	if err := filepath.Walk(path, walker); err != nil {
    64  		return err
    65  	}
    66  	return nil
    67  }
    68  
    69  // NewSimpleContext returns a new SimpleContext, acting on behalf of
    70  // the specified deployer, that deploys unit agents.
    71  // Paths to which agents and tools are installed are relative to dataDir.
    72  func NewSimpleContext(agentConfig agent.Config, api APICalls) *SimpleContext {
    73  	return &SimpleContext{
    74  		api:         api,
    75  		agentConfig: agentConfig,
    76  		discoverService: func(name string, conf common.Conf) (deployerService, error) {
    77  			return service.DiscoverService(name, conf)
    78  		},
    79  		listServices: func() ([]string, error) {
    80  			return service.ListServices()
    81  		},
    82  	}
    83  }
    84  
    85  func (ctx *SimpleContext) AgentConfig() agent.Config {
    86  	return ctx.agentConfig
    87  }
    88  
    89  func (ctx *SimpleContext) DeployUnit(unitName, initialPassword string) (err error) {
    90  	// Check sanity.
    91  	renderer, err := shell.NewRenderer("")
    92  	if err != nil {
    93  		return errors.Trace(err)
    94  	}
    95  	svc, err := ctx.service(unitName, renderer)
    96  	if err != nil {
    97  		return errors.Trace(err)
    98  	}
    99  	installed, err := svc.Installed()
   100  	if err != nil {
   101  		return errors.Trace(err)
   102  	}
   103  	if installed {
   104  		return fmt.Errorf("unit %q is already deployed", unitName)
   105  	}
   106  
   107  	// Link the current tools for use by the new agent.
   108  	tag := names.NewUnitTag(unitName)
   109  	dataDir := ctx.agentConfig.DataDir()
   110  	logDir := ctx.agentConfig.LogDir()
   111  	_, err = tools.ChangeAgentTools(dataDir, tag.String(), version.Current)
   112  	toolsDir := tools.ToolsDir(dataDir, tag.String())
   113  	defer removeOnErr(&err, toolsDir)
   114  
   115  	result, err := ctx.api.ConnectionInfo()
   116  	if err != nil {
   117  		return err
   118  	}
   119  	logger.Debugf("state addresses: %q", result.StateAddresses)
   120  	logger.Debugf("API addresses: %q", result.APIAddresses)
   121  	containerType := ctx.agentConfig.Value(agent.ContainerType)
   122  	namespace := ctx.agentConfig.Value(agent.Namespace)
   123  	conf, err := agent.NewAgentConfig(
   124  		agent.AgentConfigParams{
   125  			Paths: agent.Paths{
   126  				DataDir:         dataDir,
   127  				LogDir:          logDir,
   128  				MetricsSpoolDir: agent.DefaultPaths.MetricsSpoolDir,
   129  			},
   130  			UpgradedToVersion: version.Current.Number,
   131  			Tag:               tag,
   132  			Password:          initialPassword,
   133  			Nonce:             "unused",
   134  			Environment:       ctx.agentConfig.Environment(),
   135  			// TODO: remove the state addresses here and test when api only.
   136  			StateAddresses: result.StateAddresses,
   137  			APIAddresses:   result.APIAddresses,
   138  			CACert:         ctx.agentConfig.CACert(),
   139  			Values: map[string]string{
   140  				agent.ContainerType: containerType,
   141  				agent.Namespace:     namespace,
   142  			},
   143  		})
   144  	if err != nil {
   145  		return err
   146  	}
   147  	if err := conf.Write(); err != nil {
   148  		return err
   149  	}
   150  	defer removeOnErr(&err, conf.Dir())
   151  
   152  	// Install an init service that runs the unit agent.
   153  	if err := service.InstallAndStart(svc); err != nil {
   154  		return errors.Trace(err)
   155  	}
   156  	return nil
   157  }
   158  
   159  type deployerService interface {
   160  	Installed() (bool, error)
   161  	Install() error
   162  	Remove() error
   163  	Start() error
   164  	Stop() error
   165  }
   166  
   167  // findUpstartJob tries to find an init system job matching the
   168  // given unit name in one of these formats:
   169  //   jujud-<deployer-tag>:<unit-tag>.conf (for compatibility)
   170  //   jujud-<unit-tag>.conf (default)
   171  func (ctx *SimpleContext) findInitSystemJob(unitName string) (deployerService, error) {
   172  	unitsAndJobs, err := ctx.deployedUnitsInitSystemJobs()
   173  	if err != nil {
   174  		return nil, errors.Trace(err)
   175  	}
   176  	if job, ok := unitsAndJobs[unitName]; ok {
   177  		return ctx.discoverService(job, common.Conf{})
   178  	}
   179  	return nil, errors.Errorf("unit %q is not deployed", unitName)
   180  }
   181  
   182  func (ctx *SimpleContext) RecallUnit(unitName string) error {
   183  	svc, err := ctx.findInitSystemJob(unitName)
   184  	if err != nil {
   185  		return errors.Trace(err)
   186  	}
   187  	installed, err := svc.Installed()
   188  	if err != nil {
   189  		return errors.Trace(err)
   190  	}
   191  	if !installed {
   192  		return errors.Errorf("unit %q is not deployed", unitName)
   193  	}
   194  	if err := svc.Stop(); err != nil {
   195  		return err
   196  	}
   197  	if err := svc.Remove(); err != nil {
   198  		return err
   199  	}
   200  	tag := names.NewUnitTag(unitName)
   201  	dataDir := ctx.agentConfig.DataDir()
   202  	agentDir := agent.Dir(dataDir, tag)
   203  	// Recursivley change mode to 777 on windows to avoid
   204  	// Operation not permitted errors when deleting the agentDir
   205  	err = recursiveChmod(agentDir, os.FileMode(0777))
   206  	if err != nil {
   207  		return err
   208  	}
   209  	if err := os.RemoveAll(agentDir); err != nil {
   210  		return err
   211  	}
   212  	// TODO(dfc) should take a Tag
   213  	toolsDir := tools.ToolsDir(dataDir, tag.String())
   214  	return os.Remove(toolsDir)
   215  }
   216  
   217  var deployedRe = regexp.MustCompile("^(jujud-.*unit-([a-z0-9-]+)-([0-9]+))$")
   218  
   219  func (ctx *SimpleContext) deployedUnitsInitSystemJobs() (map[string]string, error) {
   220  	fis, err := ctx.listServices()
   221  	if err != nil {
   222  		return nil, err
   223  	}
   224  	if err != nil {
   225  		return nil, err
   226  	}
   227  	installed := make(map[string]string)
   228  	for _, fi := range fis {
   229  		if groups := deployedRe.FindStringSubmatch(fi); len(groups) > 0 {
   230  			unitName := groups[2] + "/" + groups[3]
   231  			if !names.IsValidUnit(unitName) {
   232  				continue
   233  			}
   234  			installed[unitName] = groups[1]
   235  		}
   236  	}
   237  	return installed, nil
   238  }
   239  
   240  func (ctx *SimpleContext) DeployedUnits() ([]string, error) {
   241  	unitsAndJobs, err := ctx.deployedUnitsInitSystemJobs()
   242  	if err != nil {
   243  		return nil, err
   244  	}
   245  	var installed []string
   246  	for unitName := range unitsAndJobs {
   247  		installed = append(installed, unitName)
   248  	}
   249  	return installed, nil
   250  }
   251  
   252  // service returns a service.Service corresponding to the specified
   253  // unit.
   254  func (ctx *SimpleContext) service(unitName string, renderer shell.Renderer) (deployerService, error) {
   255  	tag := names.NewUnitTag(unitName).String()
   256  	svcName := "jujud-" + tag
   257  
   258  	info := service.NewAgentInfo(
   259  		service.AgentKindUnit,
   260  		unitName,
   261  		ctx.agentConfig.DataDir(),
   262  		ctx.agentConfig.LogDir(),
   263  	)
   264  
   265  	// TODO(thumper): 2013-09-02 bug 1219630
   266  	// As much as I'd like to remove JujuContainerType now, it is still
   267  	// needed as MAAS still needs it at this stage, and we can't fix
   268  	// everything at once.
   269  	containerType := ctx.agentConfig.Value(agent.ContainerType)
   270  
   271  	conf := service.ContainerAgentConf(info, renderer, containerType)
   272  	return ctx.discoverService(svcName, conf)
   273  }
   274  
   275  func removeOnErr(err *error, path string) {
   276  	if *err != nil {
   277  		if err := os.RemoveAll(path); err != nil {
   278  			logger.Warningf("installer: cannot remove %q: %v", path, err)
   279  		}
   280  	}
   281  }