github.com/axw/juju@v0.0.0-20161005053422-4bd6544d08d4/service/discovery.go (about)

     1  // Copyright 2015 Canonical Ltd.
     2  // Licensed under the AGPLv3, see LICENCE file for details.
     3  
     4  package service
     5  
     6  import (
     7  	"fmt"
     8  	"strings"
     9  
    10  	"github.com/juju/errors"
    11  	"github.com/juju/utils/featureflag"
    12  	"github.com/juju/utils/os"
    13  	"github.com/juju/utils/series"
    14  	"github.com/juju/utils/shell"
    15  
    16  	"github.com/juju/juju/feature"
    17  	"github.com/juju/juju/service/common"
    18  	"github.com/juju/juju/service/systemd"
    19  	"github.com/juju/juju/service/upstart"
    20  	"github.com/juju/juju/service/windows"
    21  )
    22  
    23  // DiscoverService returns an interface to a service appropriate
    24  // for the current system
    25  func DiscoverService(name string, conf common.Conf) (Service, error) {
    26  	initName, err := discoverInitSystem()
    27  	if err != nil {
    28  		return nil, errors.Trace(err)
    29  	}
    30  
    31  	service, err := newService(name, conf, initName, series.HostSeries())
    32  	if err != nil {
    33  		return nil, errors.Trace(err)
    34  	}
    35  	return service, nil
    36  }
    37  
    38  func discoverInitSystem() (string, error) {
    39  	initName, err := discoverLocalInitSystem()
    40  	if errors.IsNotFound(err) {
    41  		// Fall back to checking the juju version.
    42  		versionInitName, err2 := VersionInitSystem(series.HostSeries())
    43  		if err2 != nil {
    44  			// The key error is the one from discoverLocalInitSystem so
    45  			// that is what we return.
    46  			return "", errors.Wrap(err2, err)
    47  		}
    48  		initName = versionInitName
    49  	} else if err != nil {
    50  		return "", errors.Trace(err)
    51  	}
    52  	return initName, nil
    53  }
    54  
    55  // VersionInitSystem returns an init system name based on the provided
    56  // series. If one cannot be identified a NotFound error is returned.
    57  func VersionInitSystem(series string) (string, error) {
    58  	initName, err := versionInitSystem(series)
    59  	if err != nil {
    60  		return "", errors.Trace(err)
    61  	}
    62  	logger.Debugf("discovered init system %q from series %q", initName, series)
    63  	return initName, nil
    64  }
    65  
    66  func versionInitSystem(ser string) (string, error) {
    67  	seriesos, err := series.GetOSFromSeries(ser)
    68  	if err != nil {
    69  		notFound := errors.NotFoundf("init system for series %q", ser)
    70  		return "", errors.Wrap(err, notFound)
    71  	}
    72  
    73  	switch seriesos {
    74  	case os.Windows:
    75  		return InitSystemWindows, nil
    76  	case os.Ubuntu:
    77  		switch ser {
    78  		case "precise", "quantal", "raring", "saucy", "trusty", "utopic":
    79  			return InitSystemUpstart, nil
    80  		default:
    81  			// vivid and later
    82  			if featureflag.Enabled(feature.LegacyUpstart) {
    83  				return InitSystemUpstart, nil
    84  			}
    85  			return InitSystemSystemd, nil
    86  		}
    87  	case os.CentOS:
    88  		return InitSystemSystemd, nil
    89  	}
    90  	return "", errors.NotFoundf("unknown os %q (from series %q), init system", seriesos, ser)
    91  }
    92  
    93  type discoveryCheck struct {
    94  	name      string
    95  	isRunning func() (bool, error)
    96  }
    97  
    98  var discoveryFuncs = []discoveryCheck{
    99  	{InitSystemUpstart, upstart.IsRunning},
   100  	{InitSystemSystemd, systemd.IsRunning},
   101  	{InitSystemWindows, windows.IsRunning},
   102  }
   103  
   104  func discoverLocalInitSystem() (string, error) {
   105  	for _, check := range discoveryFuncs {
   106  		local, err := check.isRunning()
   107  		if err != nil {
   108  			logger.Debugf("failed to find init system %q: %v", check.name, err)
   109  		}
   110  		// We expect that in error cases "local" will be false.
   111  		if local {
   112  			logger.Debugf("discovered init system %q from local host", check.name)
   113  			return check.name, nil
   114  		}
   115  	}
   116  	return "", errors.NotFoundf("init system (based on local host)")
   117  }
   118  
   119  const discoverInitSystemScript = `
   120  # Use guaranteed discovery mechanisms for known init systems.
   121  if [ -d /run/systemd/system ]; then
   122      echo -n systemd
   123      exit 0
   124  elif [ -f /sbin/initctl ] && /sbin/initctl --system list 2>&1 > /dev/null; then
   125      echo -n upstart
   126      exit 0
   127  fi
   128  
   129  # uh-oh
   130  exit 1
   131  `
   132  
   133  // DiscoverInitSystemScript returns the shell script to use when
   134  // discovering the local init system. The script is quite specific to
   135  // bash, so it includes an explicit bash shbang.
   136  func DiscoverInitSystemScript() string {
   137  	renderer := shell.BashRenderer{}
   138  	data := renderer.RenderScript([]string{discoverInitSystemScript})
   139  	return string(data)
   140  }
   141  
   142  // shellCase is the template for a bash case statement, for use in
   143  // newShellSelectCommand.
   144  const shellCase = `
   145  case "$%s" in
   146  %s
   147  *)
   148      %s
   149      ;;
   150  esac`
   151  
   152  // newShellSelectCommand creates a bash case statement with clause for
   153  // each of the linux init systems. The body of each clause comes from
   154  // calling the provided handler with the init system name. If the
   155  // handler does not support the args then it returns a false "ok" value.
   156  func newShellSelectCommand(envVarName, dflt string, handler func(string) (string, bool)) string {
   157  	var cases []string
   158  	for _, initSystem := range linuxInitSystems {
   159  		cmd, ok := handler(initSystem)
   160  		if !ok {
   161  			continue
   162  		}
   163  		cases = append(cases, initSystem+")", "    "+cmd, "    ;;")
   164  	}
   165  	if len(cases) == 0 {
   166  		return ""
   167  	}
   168  
   169  	return fmt.Sprintf(shellCase[1:], envVarName, strings.Join(cases, "\n"), dflt)
   170  }