github.com/bugraaydogar/snapd@v0.0.0-20210315170335-8c70bb858939/overlord/servicestate/servicestate.go (about) 1 // -*- Mode: Go; indent-tabs-mode: t -*- 2 3 /* 4 * Copyright (C) 2015-2020 Canonical Ltd 5 * 6 * This program is free software: you can redistribute it and/or modify 7 * it under the terms of the GNU General Public License version 3 as 8 * published by the Free Software Foundation. 9 * 10 * This program is distributed in the hope that it will be useful, 11 * but WITHOUT ANY WARRANTY; without even the implied warranty of 12 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 * GNU General Public License for more details. 14 * 15 * You should have received a copy of the GNU General Public License 16 * along with this program. If not, see <http://www.gnu.org/licenses/>. 17 * 18 */ 19 20 package servicestate 21 22 import ( 23 "fmt" 24 "path/filepath" 25 "sort" 26 "time" 27 28 "github.com/snapcore/snapd/client" 29 "github.com/snapcore/snapd/overlord/cmdstate" 30 "github.com/snapcore/snapd/overlord/hookstate" 31 "github.com/snapcore/snapd/overlord/snapstate" 32 "github.com/snapcore/snapd/overlord/state" 33 "github.com/snapcore/snapd/snap" 34 "github.com/snapcore/snapd/systemd" 35 ) 36 37 type Instruction struct { 38 Action string `json:"action"` 39 Names []string `json:"names"` 40 client.StartOptions 41 client.StopOptions 42 client.RestartOptions 43 } 44 45 type ServiceActionConflictError struct{ error } 46 47 // serviceControlTs creates "service-control" task for every snap derived from appInfos. 48 func serviceControlTs(st *state.State, appInfos []*snap.AppInfo, inst *Instruction) (*state.TaskSet, error) { 49 servicesBySnap := make(map[string][]string, len(appInfos)) 50 sortedNames := make([]string, 0, len(appInfos)) 51 52 // group services by snap, we need to create one task for every affected snap 53 for _, app := range appInfos { 54 snapName := app.Snap.InstanceName() 55 if _, ok := servicesBySnap[snapName]; !ok { 56 sortedNames = append(sortedNames, snapName) 57 } 58 servicesBySnap[snapName] = append(servicesBySnap[snapName], app.Name) 59 } 60 sort.Strings(sortedNames) 61 62 ts := state.NewTaskSet() 63 var prev *state.Task 64 for _, snapName := range sortedNames { 65 var snapst snapstate.SnapState 66 if err := snapstate.Get(st, snapName, &snapst); err != nil { 67 if err == state.ErrNoState { 68 return nil, fmt.Errorf("snap not found: %s", snapName) 69 } 70 return nil, err 71 } 72 73 cmd := &ServiceAction{SnapName: snapName} 74 switch { 75 case inst.Action == "start": 76 cmd.Action = "start" 77 if inst.Enable { 78 cmd.ActionModifier = "enable" 79 } 80 case inst.Action == "stop": 81 cmd.Action = "stop" 82 if inst.Disable { 83 cmd.ActionModifier = "disable" 84 } 85 case inst.Action == "restart": 86 if inst.Reload { 87 cmd.Action = "reload-or-restart" 88 } else { 89 cmd.Action = "restart" 90 } 91 default: 92 return nil, fmt.Errorf("unknown action %q", inst.Action) 93 } 94 95 svcs := servicesBySnap[snapName] 96 sort.Strings(svcs) 97 cmd.Services = svcs 98 99 summary := fmt.Sprintf("Run service command %q for services %q of snap %q", cmd.Action, svcs, cmd.SnapName) 100 task := st.NewTask("service-control", summary) 101 task.Set("service-action", cmd) 102 if prev != nil { 103 task.WaitFor(prev) 104 } 105 prev = task 106 ts.AddTask(task) 107 } 108 return ts, nil 109 } 110 111 // Flags carries extra flags for Control 112 type Flags struct { 113 // CreateExecCommandTasks tells Control method to create exec-command tasks 114 // (alongside service-control tasks) for compatibility with old snapd. 115 CreateExecCommandTasks bool 116 } 117 118 // Control creates a taskset for starting/stopping/restarting services via systemctl. 119 // The appInfos and inst define the services and the command to execute. 120 // Context is used to determine change conflicts - we will not conflict with 121 // tasks from same change as that of context's. 122 func Control(st *state.State, appInfos []*snap.AppInfo, inst *Instruction, flags *Flags, context *hookstate.Context) ([]*state.TaskSet, error) { 123 var tts []*state.TaskSet 124 var ctlcmds []string 125 126 // create exec-command tasks for compatibility with old snapd 127 if flags != nil && flags.CreateExecCommandTasks { 128 switch { 129 case inst.Action == "start": 130 if inst.Enable { 131 ctlcmds = []string{"enable"} 132 } 133 ctlcmds = append(ctlcmds, "start") 134 case inst.Action == "stop": 135 if inst.Disable { 136 ctlcmds = []string{"disable"} 137 } 138 ctlcmds = append(ctlcmds, "stop") 139 case inst.Action == "restart": 140 if inst.Reload { 141 ctlcmds = []string{"reload-or-restart"} 142 } else { 143 ctlcmds = []string{"restart"} 144 } 145 default: 146 return nil, fmt.Errorf("unknown action %q", inst.Action) 147 } 148 } 149 150 svcs := make([]string, 0, len(appInfos)) 151 snapNames := make([]string, 0, len(appInfos)) 152 lastName := "" 153 names := make([]string, len(appInfos)) 154 for i, svc := range appInfos { 155 svcs = append(svcs, svc.ServiceName()) 156 snapName := svc.Snap.InstanceName() 157 names[i] = snapName + "." + svc.Name 158 if snapName != lastName { 159 snapNames = append(snapNames, snapName) 160 lastName = snapName 161 } 162 } 163 164 var ignoreChangeID string 165 if context != nil { 166 ignoreChangeID = context.ChangeID() 167 } 168 if err := snapstate.CheckChangeConflictMany(st, snapNames, ignoreChangeID); err != nil { 169 return nil, &ServiceActionConflictError{err} 170 } 171 172 for _, cmd := range ctlcmds { 173 argv := append([]string{"systemctl", cmd}, svcs...) 174 desc := fmt.Sprintf("%s of %v", cmd, names) 175 // Give the systemctl a maximum time of 61 for now. 176 // 177 // Longer term we need to refactor this code and 178 // reuse the snapd/systemd and snapd/wrapper packages 179 // to control the timeout in a single place. 180 ts := cmdstate.ExecWithTimeout(st, desc, argv, 61*time.Second) 181 182 // set ignore flag on the tasks, new snapd uses service-control tasks. 183 ignore := true 184 for _, t := range ts.Tasks() { 185 t.Set("ignore", ignore) 186 } 187 tts = append(tts, ts) 188 } 189 190 // XXX: serviceControlTs could be merged with above logic at the cost of 191 // slightly more complicated logic. 192 ts, err := serviceControlTs(st, appInfos, inst) 193 if err != nil { 194 return nil, err 195 } 196 tts = append(tts, ts) 197 198 // make a taskset wait for its predecessor 199 for i := 1; i < len(tts); i++ { 200 tts[i].WaitAll(tts[i-1]) 201 } 202 203 return tts, nil 204 } 205 206 // StatusDecorator supports decorating client.AppInfos with service status. 207 type StatusDecorator struct { 208 sysd systemd.Systemd 209 globalUserSysd systemd.Systemd 210 } 211 212 // NewStatusDecorator returns a new StatusDecorator. 213 func NewStatusDecorator(rep interface { 214 Notify(string) 215 }) *StatusDecorator { 216 return &StatusDecorator{ 217 sysd: systemd.New(systemd.SystemMode, rep), 218 globalUserSysd: systemd.New(systemd.GlobalUserMode, rep), 219 } 220 } 221 222 // DecorateWithStatus adds service status information to the given 223 // client.AppInfo associated with the given snap.AppInfo. 224 // If the snap is inactive or the app is not service it does nothing. 225 func (sd *StatusDecorator) DecorateWithStatus(appInfo *client.AppInfo, snapApp *snap.AppInfo) error { 226 if appInfo.Snap != snapApp.Snap.InstanceName() || appInfo.Name != snapApp.Name { 227 return fmt.Errorf("internal error: misassociated app info %v and client app info %s.%s", snapApp, appInfo.Snap, appInfo.Name) 228 } 229 if !snapApp.Snap.IsActive() || !snapApp.IsService() { 230 // nothing to do 231 return nil 232 } 233 var sysd systemd.Systemd 234 switch snapApp.DaemonScope { 235 case snap.SystemDaemon: 236 sysd = sd.sysd 237 case snap.UserDaemon: 238 sysd = sd.globalUserSysd 239 default: 240 return fmt.Errorf("internal error: unknown daemon-scope %q", snapApp.DaemonScope) 241 } 242 243 // collect all services for a single call to systemctl 244 extra := len(snapApp.Sockets) 245 if snapApp.Timer != nil { 246 extra++ 247 } 248 serviceNames := make([]string, 0, 1+extra) 249 serviceNames = append(serviceNames, snapApp.ServiceName()) 250 251 sockSvcFileToName := make(map[string]string, len(snapApp.Sockets)) 252 for _, sock := range snapApp.Sockets { 253 sockUnit := filepath.Base(sock.File()) 254 sockSvcFileToName[sockUnit] = sock.Name 255 serviceNames = append(serviceNames, sockUnit) 256 } 257 if snapApp.Timer != nil { 258 timerUnit := filepath.Base(snapApp.Timer.File()) 259 serviceNames = append(serviceNames, timerUnit) 260 } 261 262 // sysd.Status() makes sure that we get only the units we asked 263 // for and raises an error otherwise 264 sts, err := sysd.Status(serviceNames...) 265 if err != nil { 266 return fmt.Errorf("cannot get status of services of app %q: %v", appInfo.Name, err) 267 } 268 if len(sts) != len(serviceNames) { 269 return fmt.Errorf("cannot get status of services of app %q: expected %d results, got %d", appInfo.Name, len(serviceNames), len(sts)) 270 } 271 for _, st := range sts { 272 switch filepath.Ext(st.UnitName) { 273 case ".service": 274 appInfo.Enabled = st.Enabled 275 appInfo.Active = st.Active 276 case ".timer": 277 appInfo.Activators = append(appInfo.Activators, client.AppActivator{ 278 Name: snapApp.Name, 279 Enabled: st.Enabled, 280 Active: st.Active, 281 Type: "timer", 282 }) 283 case ".socket": 284 appInfo.Activators = append(appInfo.Activators, client.AppActivator{ 285 Name: sockSvcFileToName[st.UnitName], 286 Enabled: st.Enabled, 287 Active: st.Active, 288 Type: "socket", 289 }) 290 } 291 } 292 // Decorate with D-Bus names that activate this service 293 for _, slot := range snapApp.ActivatesOn { 294 var busName string 295 if err := slot.Attr("name", &busName); err != nil { 296 return fmt.Errorf("cannot get D-Bus bus name of slot %q: %v", slot.Name, err) 297 } 298 // D-Bus activators do not correspond to systemd 299 // units, so don't have the concept of being disabled 300 // or deactivated. As the service activation file is 301 // created when the snap is installed, report as 302 // enabled/active. 303 appInfo.Activators = append(appInfo.Activators, client.AppActivator{ 304 Name: busName, 305 Enabled: true, 306 Active: true, 307 Type: "dbus", 308 }) 309 } 310 311 return nil 312 }