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