github.com/wallyworld/juju@v0.0.0-20161013125918-6cf1bc9d917a/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/utils/arch"
    14  	"github.com/juju/utils/series"
    15  	"github.com/juju/utils/shell"
    16  	"github.com/juju/version"
    17  	"gopkg.in/juju/names.v2"
    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  			Controller:        ctx.agentConfig.Controller(),
   146  			Model:             ctx.agentConfig.Model(),
   147  			// TODO: remove the state addresses here and test when api only.
   148  			StateAddresses: result.StateAddresses,
   149  			APIAddresses:   result.APIAddresses,
   150  			CACert:         ctx.agentConfig.CACert(),
   151  			Values: map[string]string{
   152  				agent.ContainerType: containerType,
   153  				agent.Namespace:     namespace,
   154  			},
   155  		})
   156  	if err != nil {
   157  		return errors.Trace(err)
   158  	}
   159  	if err := conf.Write(); err != nil {
   160  		return err
   161  	}
   162  	defer removeOnErr(&err, conf.Dir())
   163  
   164  	// Install an init service that runs the unit agent.
   165  	if err := service.InstallAndStart(svc); err != nil {
   166  		return errors.Trace(err)
   167  	}
   168  	return nil
   169  }
   170  
   171  type deployerService interface {
   172  	Installed() (bool, error)
   173  	Install() error
   174  	Remove() error
   175  	Start() error
   176  	Stop() error
   177  }
   178  
   179  // findUpstartJob tries to find an init system job matching the
   180  // given unit name in one of these formats:
   181  //   jujud-<deployer-tag>:<unit-tag>.conf (for compatibility)
   182  //   jujud-<unit-tag>.conf (default)
   183  func (ctx *SimpleContext) findInitSystemJob(unitName string) (deployerService, error) {
   184  	unitsAndJobs, err := ctx.deployedUnitsInitSystemJobs()
   185  	if err != nil {
   186  		return nil, errors.Trace(err)
   187  	}
   188  	if job, ok := unitsAndJobs[unitName]; ok {
   189  		return ctx.discoverService(job, common.Conf{})
   190  	}
   191  	return nil, errors.Errorf("unit %q is not deployed", unitName)
   192  }
   193  
   194  func (ctx *SimpleContext) RecallUnit(unitName string) error {
   195  	svc, err := ctx.findInitSystemJob(unitName)
   196  	if err != nil {
   197  		return errors.Trace(err)
   198  	}
   199  	installed, err := svc.Installed()
   200  	if err != nil {
   201  		return errors.Trace(err)
   202  	}
   203  	if !installed {
   204  		return errors.Errorf("unit %q is not deployed", unitName)
   205  	}
   206  	if err := svc.Stop(); err != nil {
   207  		return err
   208  	}
   209  	if err := svc.Remove(); err != nil {
   210  		return err
   211  	}
   212  	tag := names.NewUnitTag(unitName)
   213  	dataDir := ctx.agentConfig.DataDir()
   214  	agentDir := agent.Dir(dataDir, tag)
   215  	// Recursivley change mode to 777 on windows to avoid
   216  	// Operation not permitted errors when deleting the agentDir
   217  	err = recursiveChmod(agentDir, os.FileMode(0777))
   218  	if err != nil {
   219  		return err
   220  	}
   221  	if err := os.RemoveAll(agentDir); err != nil {
   222  		return err
   223  	}
   224  	// TODO(dfc) should take a Tag
   225  	toolsDir := tools.ToolsDir(dataDir, tag.String())
   226  	return os.Remove(toolsDir)
   227  }
   228  
   229  var deployedRe = regexp.MustCompile("^(jujud-.*unit-([a-z0-9-]+)-([0-9]+))$")
   230  
   231  func (ctx *SimpleContext) deployedUnitsInitSystemJobs() (map[string]string, error) {
   232  	fis, err := ctx.listServices()
   233  	if err != nil {
   234  		return nil, err
   235  	}
   236  	if err != nil {
   237  		return nil, err
   238  	}
   239  	installed := make(map[string]string)
   240  	for _, fi := range fis {
   241  		if groups := deployedRe.FindStringSubmatch(fi); len(groups) > 0 {
   242  			unitName := groups[2] + "/" + groups[3]
   243  			if !names.IsValidUnit(unitName) {
   244  				continue
   245  			}
   246  			installed[unitName] = groups[1]
   247  		}
   248  	}
   249  	return installed, nil
   250  }
   251  
   252  func (ctx *SimpleContext) DeployedUnits() ([]string, error) {
   253  	unitsAndJobs, err := ctx.deployedUnitsInitSystemJobs()
   254  	if err != nil {
   255  		return nil, err
   256  	}
   257  	var installed []string
   258  	for unitName := range unitsAndJobs {
   259  		installed = append(installed, unitName)
   260  	}
   261  	return installed, nil
   262  }
   263  
   264  // service returns a service.Service corresponding to the specified
   265  // unit.
   266  func (ctx *SimpleContext) service(unitName string, renderer shell.Renderer) (deployerService, error) {
   267  	tag := names.NewUnitTag(unitName).String()
   268  	svcName := "jujud-" + tag
   269  
   270  	info := service.NewAgentInfo(
   271  		service.AgentKindUnit,
   272  		unitName,
   273  		ctx.agentConfig.DataDir(),
   274  		ctx.agentConfig.LogDir(),
   275  	)
   276  
   277  	// TODO(thumper): 2013-09-02 bug 1219630
   278  	// As much as I'd like to remove JujuContainerType now, it is still
   279  	// needed as MAAS still needs it at this stage, and we can't fix
   280  	// everything at once.
   281  	containerType := ctx.agentConfig.Value(agent.ContainerType)
   282  
   283  	conf := service.ContainerAgentConf(info, renderer, containerType)
   284  	return ctx.discoverService(svcName, conf)
   285  }
   286  
   287  func removeOnErr(err *error, path string) {
   288  	if *err != nil {
   289  		if err := os.RemoveAll(path); err != nil {
   290  			logger.Errorf("installer: cannot remove %q: %v", path, err)
   291  		}
   292  	}
   293  }