github.com/niedbalski/juju@v0.0.0-20190215020005-8ff100488e47/service/agentconf.go (about)

     1  // Copyright 2018 Canonical Ltd.
     2  // Licensed under the AGPLv3, see LICENCE file for details.
     3  
     4  // This file has routines which can be used for agent specific functionality
     5  // related to service files:
     6  //	- finding all agents in the machine
     7  //	- create conf file using the machine details
     8  // 	- write systemd service file and setting links
     9  // 	- copy all tools and related to agents and setup the links
    10  // 	- start all the agents
    11  // These routines can be used by any tools/cmds trying to implement the above
    12  // functionality as part of the process, eg. upgrade-series.
    13  
    14  // TODO (manadart 2018-07-31) This module is specific to systemd and should
    15  // reside in the service/systemd package.
    16  
    17  package service
    18  
    19  import (
    20  	"fmt"
    21  	"os"
    22  	"path"
    23  	"path/filepath"
    24  	"strings"
    25  
    26  	"github.com/juju/errors"
    27  	"github.com/juju/utils/arch"
    28  	"github.com/juju/utils/fs"
    29  	"github.com/juju/utils/shell"
    30  	"github.com/juju/utils/symlink"
    31  	"github.com/juju/version"
    32  	"gopkg.in/juju/names.v2"
    33  
    34  	"github.com/juju/juju/agent/tools"
    35  	"github.com/juju/juju/juju/paths"
    36  	"github.com/juju/juju/service/common"
    37  	"github.com/juju/juju/service/systemd"
    38  )
    39  
    40  type SystemdServiceManager interface {
    41  	// FindAgents finds all the agents available in the machine.
    42  	FindAgents(dataDir string) (string, []string, []string, error)
    43  
    44  	// WriteSystemdAgents creates systemd files and create symlinks for the
    45  	// list of machine and units passed in the standard filepath.
    46  	WriteSystemdAgents(
    47  		machineAgent string, unitAgents []string, dataDir, symLinkSystemdDir, symLinkSystemdMultiUserDir string,
    48  	) ([]string, []string, []string, error)
    49  
    50  	//CreateAgentConf creates the configfile for specified agent running on a
    51  	// host with specified series.
    52  	CreateAgentConf(agentName string, dataDir string) (common.Conf, error)
    53  
    54  	// CopyAgentBinary copies all the tools into the path specified for each agent.
    55  	CopyAgentBinary(
    56  		machineAgent string, unitAgents []string, dataDir, toSeries, fromSeries string, jujuVersion version.Number) error
    57  
    58  	// StartAllAgents starts all the agents in the machine with specified series.
    59  	StartAllAgents(machineAgent string, unitAgents []string, dataDir string) (string, []string, error)
    60  
    61  	// WriteServiceFiles writes the service files for machine and unit agents
    62  	// in the '/lib/systemd/system' path.
    63  	WriteServiceFiles() error
    64  }
    65  
    66  type systemdServiceManager struct {
    67  	isRunning  func() bool
    68  	newService func(string, common.Conf) (Service, error)
    69  }
    70  
    71  // NewServiceManagerWithDefaults returns a SystemdServiceManager created with
    72  // sensible defaults.
    73  func NewServiceManagerWithDefaults() SystemdServiceManager {
    74  	return NewServiceManager(
    75  		systemd.IsRunning,
    76  		func(name string, conf common.Conf) (Service, error) {
    77  			return systemd.NewServiceWithDefaults(name, conf)
    78  		},
    79  	)
    80  }
    81  
    82  // NewServiceManager allows creation of a new SystemdServiceManager from
    83  // custom dependencies.
    84  func NewServiceManager(
    85  	isRunning func() bool,
    86  	newService func(string, common.Conf) (Service, error),
    87  ) SystemdServiceManager {
    88  	return &systemdServiceManager{
    89  		isRunning:  isRunning,
    90  		newService: newService,
    91  	}
    92  }
    93  
    94  // WriteServiceFiles writes service files to the standard
    95  // "/lib/systemd/system" path.
    96  func (s *systemdServiceManager) WriteServiceFiles() error {
    97  	machineAgent, unitAgents, _, err := s.FindAgents(paths.NixDataDir)
    98  	if err != nil {
    99  		return errors.Trace(err)
   100  	}
   101  
   102  	serviceNames, linkNames, failed, err := s.WriteSystemdAgents(
   103  		machineAgent,
   104  		unitAgents,
   105  		paths.NixDataDir,
   106  		systemd.EtcSystemdDir,
   107  		systemd.EtcSystemdMultiUserDir,
   108  	)
   109  	if err != nil {
   110  		for _, agent := range failed {
   111  			logger.Errorf("failed to write service for %s: %s", agent, err)
   112  		}
   113  		logger.Errorf("%s", err)
   114  		return errors.Trace(err)
   115  	}
   116  	for _, s := range serviceNames {
   117  		logger.Infof("wrote %s agent, enabled and linked by systemd", s)
   118  	}
   119  	for _, s := range linkNames {
   120  		logger.Infof("wrote %s agent, enabled and linked by symlink", s)
   121  	}
   122  
   123  	return errors.Trace(systemd.SysdReload())
   124  }
   125  
   126  // FindAgents finds all the agents available on the machine.
   127  func (s *systemdServiceManager) FindAgents(dataDir string) (string, []string, []string, error) {
   128  	var (
   129  		machineAgent  string
   130  		unitAgents    []string
   131  		errAgentNames []string
   132  	)
   133  
   134  	agentDir := filepath.Join(dataDir, "agents")
   135  	dir, err := os.Open(agentDir)
   136  	if err != nil {
   137  		return "", nil, nil, errors.Annotate(err, "opening agents dir")
   138  	}
   139  	defer dir.Close()
   140  
   141  	entries, err := dir.Readdir(-1)
   142  	if err != nil {
   143  		return "", nil, nil, errors.Annotate(err, "reading agents dir")
   144  	}
   145  	for _, info := range entries {
   146  		name := info.Name()
   147  		tag, err := names.ParseTag(name)
   148  		if err != nil {
   149  			continue
   150  		}
   151  		switch tag.Kind() {
   152  		case names.MachineTagKind:
   153  			machineAgent = name
   154  		case names.UnitTagKind:
   155  			unitAgents = append(unitAgents, name)
   156  		default:
   157  			errAgentNames = append(errAgentNames, name)
   158  			logger.Infof("%s is not of type Machine nor Unit, ignoring", name)
   159  		}
   160  	}
   161  	return machineAgent, unitAgents, errAgentNames, nil
   162  }
   163  
   164  // WriteSystemdAgents creates systemd files and symlinks for the input machine
   165  // and unit agents, in the standard filepath '/var/lib/juju'.
   166  func (s *systemdServiceManager) WriteSystemdAgents(
   167  	machineAgent string, unitAgents []string, dataDir, symLinkSystemdDir, symLinkSystemdMultiUserDir string,
   168  ) ([]string, []string, []string, error) {
   169  	var (
   170  		startedSysServiceNames []string
   171  		startedSymServiceNames []string
   172  		errAgentNames          []string
   173  		lastError              error
   174  	)
   175  
   176  	for _, agentName := range append(unitAgents, machineAgent) {
   177  		conf, err := s.CreateAgentConf(agentName, dataDir)
   178  		if err != nil {
   179  			logger.Infof("%s", err)
   180  			lastError = err
   181  			continue
   182  		}
   183  
   184  		svcName := serviceName(agentName)
   185  		svc, err := s.newService(svcName, conf)
   186  		if err != nil {
   187  			logger.Infof("Failed to create new service %s: ", err)
   188  			continue
   189  		}
   190  
   191  		uSvc, ok := svc.(UpgradableService)
   192  		if !ok {
   193  			return nil, nil, nil, errors.Errorf("%s service not of type UpgradableService", svcName)
   194  		}
   195  
   196  		dbusMethodFound := true
   197  		if err = uSvc.WriteService(); err != nil {
   198  			// Note that this error is already logged by the systemd package.
   199  
   200  			// This is not ideal, but it is possible on an Upstart-based OS
   201  			// (such as Trusty) for run/systemd/system to exist, which is used
   202  			// for detection of systemd as the running init system.
   203  			// If this happens, then D-Bus will error with the message below.
   204  			// We need to detect this condition and fall through to linking the
   205  			// service files manually.
   206  			if strings.Contains(strings.ToLower(err.Error()), "no such method") {
   207  				dbusMethodFound = false
   208  				logger.Infof("attempting to manually link service file for %s", agentName)
   209  			} else {
   210  				errAgentNames = append(errAgentNames, agentName)
   211  				lastError = err
   212  				continue
   213  			}
   214  		} else {
   215  			logger.Infof("successfully wrote service for %s:", agentName)
   216  		}
   217  
   218  		// If systemd is the running init system on this host, *and* if the
   219  		// call to DBusAPI.LinkUnitFiles in WriteService above returned no
   220  		// error, it will have resulted in updated sym-links for the file.
   221  		// We are done.
   222  		if s.isRunning() && dbusMethodFound {
   223  			startedSysServiceNames = append(startedSysServiceNames, svcName)
   224  			logger.Infof("wrote %s agent, enabled and linked by systemd", svcName)
   225  			continue
   226  		}
   227  
   228  		// Otherwise we need to manually ensure the service unit links.
   229  		svcFileName := svcName + ".service"
   230  		if err = os.Symlink(path.Join(systemd.LibSystemdDir, svcName, svcFileName),
   231  			path.Join(symLinkSystemdDir, svcFileName)); err != nil && !os.IsExist(err) {
   232  			return nil, nil, nil, errors.Errorf(
   233  				"failed to link service file (%s) in systemd dir: %s\n", svcFileName, err)
   234  		}
   235  
   236  		if err = os.Symlink(path.Join(systemd.LibSystemdDir, svcName, svcFileName),
   237  			path.Join(symLinkSystemdMultiUserDir, svcFileName)); err != nil && !os.IsExist(err) {
   238  			return nil, nil, nil, errors.Errorf(
   239  				"failed to link service file (%s) in multi-user.target.wants dir: %s\n", svcFileName, err)
   240  		}
   241  
   242  		startedSymServiceNames = append(startedSymServiceNames, svcName)
   243  		logger.Infof("wrote %s agent, enabled and linked by symlink", svcName)
   244  	}
   245  	return startedSysServiceNames, startedSymServiceNames, errAgentNames, lastError
   246  }
   247  
   248  // CreateAgentConf creates the configfile for specified agent running on a host with specified series.
   249  func (s *systemdServiceManager) CreateAgentConf(name string, dataDir string) (_ common.Conf, err error) {
   250  	defer func() {
   251  		if err != nil {
   252  			logger.Infof("failed create agent conf for %s: %s", name, err)
   253  		}
   254  	}()
   255  
   256  	renderer, err := shell.NewRenderer("")
   257  	if err != nil {
   258  		return common.Conf{}, err
   259  	}
   260  
   261  	tag, err := names.ParseTag(name)
   262  	if err != nil {
   263  		return common.Conf{}, err
   264  	}
   265  
   266  	var kind AgentKind
   267  	switch tag.Kind() {
   268  	case names.MachineTagKind:
   269  		kind = AgentKindMachine
   270  	case names.UnitTagKind:
   271  		kind = AgentKindUnit
   272  	default:
   273  		return common.Conf{}, errors.NewNotValid(nil, fmt.Sprintf("agent %q is neither a machine nor a unit", name))
   274  	}
   275  
   276  	srvPath := path.Join(paths.NixLogDir, "juju")
   277  	info := NewAgentInfo(
   278  		kind,
   279  		tag.Id(),
   280  		dataDir,
   281  		srvPath)
   282  	return AgentConf(info, renderer), nil
   283  }
   284  
   285  // CopyAgentBinary copies all the tools into the path specified for each agent.
   286  func (s *systemdServiceManager) CopyAgentBinary(
   287  	machineAgent string,
   288  	unitAgents []string,
   289  	dataDir, toSeries, fromSeries string,
   290  	jujuVersion version.Number,
   291  ) (err error) {
   292  	defer func() {
   293  		if err != nil {
   294  			err = errors.Annotate(err, "failed to copy tools")
   295  		}
   296  	}()
   297  
   298  	// Setup new and old version.Binary instances with different series.
   299  	fromVers := version.Binary{
   300  		Number: jujuVersion,
   301  		Arch:   arch.HostArch(),
   302  		Series: fromSeries,
   303  	}
   304  	toVers := version.Binary{
   305  		Number: jujuVersion,
   306  		Arch:   arch.HostArch(),
   307  		Series: toSeries,
   308  	}
   309  
   310  	// If tools with the new series don't already exist, copy
   311  	// current tools to new directory with correct series.
   312  	if _, err = os.Stat(tools.SharedToolsDir(dataDir, toVers)); err != nil {
   313  		// Copy tools to new directory with correct series.
   314  		if err = fs.Copy(tools.SharedToolsDir(dataDir, fromVers), tools.SharedToolsDir(dataDir, toVers)); err != nil {
   315  			return err
   316  		}
   317  	}
   318  
   319  	// Write tools metadata with new version, however don't change
   320  	// the URL, so we know where it came from.
   321  	jujuTools, err := tools.ReadTools(dataDir, toVers)
   322  	if err != nil {
   323  		return errors.Trace(err)
   324  	}
   325  
   326  	// Only write once
   327  	if jujuTools.Version != toVers {
   328  		jujuTools.Version = toVers
   329  		if err = tools.WriteToolsMetadataData(tools.ToolsDir(dataDir, toVers.String()), jujuTools); err != nil {
   330  			return err
   331  		}
   332  	}
   333  
   334  	// Update Agent Tool links
   335  	var lastError error
   336  	for _, agentName := range append(unitAgents, machineAgent) {
   337  		toolPath := tools.ToolsDir(dataDir, toVers.String())
   338  		toolsDir := tools.ToolsDir(dataDir, agentName)
   339  
   340  		err = symlink.Replace(toolsDir, toolPath)
   341  		if err != nil {
   342  			lastError = err
   343  		}
   344  	}
   345  
   346  	return lastError
   347  }
   348  
   349  // StartAllAgents starts all of the input agents.
   350  func (s *systemdServiceManager) StartAllAgents(
   351  	machineAgent string, unitAgents []string, dataDir string,
   352  ) (string, []string, error) {
   353  	if !s.isRunning() {
   354  		return "", nil, errors.Errorf("cannot interact with systemd; reboot to start agents")
   355  	}
   356  
   357  	var startedUnits []string
   358  	for _, unit := range unitAgents {
   359  		if err := s.startAgent(unit, AgentKindUnit, dataDir); err != nil {
   360  			return "", startedUnits, errors.Annotatef(err, "failed to start %s service", serviceName(unit))
   361  		}
   362  		startedUnits = append(startedUnits, serviceName(unit))
   363  		logger.Infof("started %s service", serviceName(unit))
   364  	}
   365  
   366  	machineService := serviceName(machineAgent)
   367  	err := s.startAgent(machineAgent, AgentKindMachine, dataDir)
   368  	if err == nil {
   369  		logger.Infof("started %s service", machineService)
   370  		return machineService, startedUnits, nil
   371  	}
   372  
   373  	return "", startedUnits, errors.Annotatef(err, "failed to start %s service", machineService)
   374  }
   375  
   376  func (s *systemdServiceManager) startAgent(name string, kind AgentKind, dataDir string) (err error) {
   377  	renderer, err := shell.NewRenderer("")
   378  	if err != nil {
   379  		return errors.Trace(err)
   380  	}
   381  
   382  	srvPath := path.Join(paths.NixLogDir, "juju")
   383  	info := NewAgentInfo(kind, name, dataDir, srvPath)
   384  	conf := AgentConf(info, renderer)
   385  
   386  	svc, err := s.newService(serviceName(name), conf)
   387  	if err != nil {
   388  		return errors.Trace(err)
   389  	}
   390  
   391  	return errors.Trace(svc.Start())
   392  }
   393  
   394  func serviceName(agent string) string {
   395  	return "jujud-" + agent
   396  }