github.com/makyo/juju@v0.0.0-20160425123129-2608902037e9/service/discovery.go (about)

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