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 }