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