github.com/hugh712/snapd@v0.0.0-20200910133618-1a99902bd583/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 "time" 26 27 "github.com/snapcore/snapd/client" 28 "github.com/snapcore/snapd/dirs" 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 // Control creates a taskset for starting/stopping/restarting services via systemctl. 48 // The appInfos and inst define the services and the command to execute. 49 // Context is used to determine change conflicts - we will not conflict with 50 // tasks from same change as that of context's. 51 func Control(st *state.State, appInfos []*snap.AppInfo, inst *Instruction, context *hookstate.Context) ([]*state.TaskSet, error) { 52 var tts []*state.TaskSet 53 54 var ctlcmds []string 55 switch { 56 case inst.Action == "start": 57 if inst.Enable { 58 ctlcmds = []string{"enable"} 59 } 60 ctlcmds = append(ctlcmds, "start") 61 case inst.Action == "stop": 62 if inst.Disable { 63 ctlcmds = []string{"disable"} 64 } 65 ctlcmds = append(ctlcmds, "stop") 66 case inst.Action == "restart": 67 if inst.Reload { 68 ctlcmds = []string{"reload-or-restart"} 69 } else { 70 ctlcmds = []string{"restart"} 71 } 72 default: 73 return nil, fmt.Errorf("unknown action %q", inst.Action) 74 } 75 76 st.Lock() 77 defer st.Unlock() 78 79 svcs := make([]string, 0, len(appInfos)) 80 snapNames := make([]string, 0, len(appInfos)) 81 lastName := "" 82 names := make([]string, len(appInfos)) 83 for i, svc := range appInfos { 84 svcs = append(svcs, svc.ServiceName()) 85 snapName := svc.Snap.InstanceName() 86 names[i] = snapName + "." + svc.Name 87 if snapName != lastName { 88 snapNames = append(snapNames, snapName) 89 lastName = snapName 90 } 91 } 92 93 var ignoreChangeID string 94 if context != nil && !context.IsEphemeral() { 95 if task, ok := context.Task(); ok { 96 if chg := task.Change(); chg != nil { 97 ignoreChangeID = chg.ID() 98 } 99 } 100 } 101 102 if err := snapstate.CheckChangeConflictMany(st, snapNames, ignoreChangeID); err != nil { 103 return nil, &ServiceActionConflictError{err} 104 } 105 106 for _, cmd := range ctlcmds { 107 argv := append([]string{"systemctl", cmd}, svcs...) 108 desc := fmt.Sprintf("%s of %v", cmd, names) 109 // Give the systemctl a maximum time of 61 for now. 110 // 111 // Longer term we need to refactor this code and 112 // reuse the snapd/systemd and snapd/wrapper packages 113 // to control the timeout in a single place. 114 ts := cmdstate.ExecWithTimeout(st, desc, argv, 61*time.Second) 115 tts = append(tts, ts) 116 } 117 118 // make a taskset wait for its predecessor 119 for i := 1; i < len(tts); i++ { 120 tts[i].WaitAll(tts[i-1]) 121 } 122 123 return tts, nil 124 } 125 126 // StatusDecorator supports decorating client.AppInfos with service status. 127 type StatusDecorator struct { 128 sysd systemd.Systemd 129 } 130 131 // NewStatusDecorator returns a new StatusDecorator. 132 func NewStatusDecorator(rep interface { 133 Notify(string) 134 }) *StatusDecorator { 135 return &StatusDecorator{ 136 sysd: systemd.New(dirs.GlobalRootDir, systemd.SystemMode, rep), 137 } 138 } 139 140 // DecorateWithStatus adds service status information to the given 141 // client.AppInfo associated with the given snap.AppInfo. 142 // If the snap is inactive or the app is not service it does nothing. 143 func (sd *StatusDecorator) DecorateWithStatus(appInfo *client.AppInfo, snapApp *snap.AppInfo) error { 144 if appInfo.Snap != snapApp.Snap.InstanceName() || appInfo.Name != snapApp.Name { 145 return fmt.Errorf("internal error: misassociated app info %v and client app info %s.%s", snapApp, appInfo.Snap, appInfo.Name) 146 } 147 if !snapApp.Snap.IsActive() || !snapApp.IsService() { 148 // nothing to do 149 return nil 150 } 151 152 // collect all services for a single call to systemctl 153 extra := len(snapApp.Sockets) 154 if snapApp.Timer != nil { 155 extra++ 156 } 157 serviceNames := make([]string, 0, 1+extra) 158 serviceNames = append(serviceNames, snapApp.ServiceName()) 159 160 sockSvcFileToName := make(map[string]string, len(snapApp.Sockets)) 161 for _, sock := range snapApp.Sockets { 162 sockUnit := filepath.Base(sock.File()) 163 sockSvcFileToName[sockUnit] = sock.Name 164 serviceNames = append(serviceNames, sockUnit) 165 } 166 if snapApp.Timer != nil { 167 timerUnit := filepath.Base(snapApp.Timer.File()) 168 serviceNames = append(serviceNames, timerUnit) 169 } 170 171 // sysd.Status() makes sure that we get only the units we asked 172 // for and raises an error otherwise 173 sts, err := sd.sysd.Status(serviceNames...) 174 if err != nil { 175 return fmt.Errorf("cannot get status of services of app %q: %v", appInfo.Name, err) 176 } 177 if len(sts) != len(serviceNames) { 178 return fmt.Errorf("cannot get status of services of app %q: expected %d results, got %d", appInfo.Name, len(serviceNames), len(sts)) 179 } 180 for _, st := range sts { 181 switch filepath.Ext(st.UnitName) { 182 case ".service": 183 appInfo.Enabled = st.Enabled 184 appInfo.Active = st.Active 185 case ".timer": 186 appInfo.Activators = append(appInfo.Activators, client.AppActivator{ 187 Name: snapApp.Name, 188 Enabled: st.Enabled, 189 Active: st.Active, 190 Type: "timer", 191 }) 192 case ".socket": 193 appInfo.Activators = append(appInfo.Activators, client.AppActivator{ 194 Name: sockSvcFileToName[st.UnitName], 195 Enabled: st.Enabled, 196 Active: st.Active, 197 Type: "socket", 198 }) 199 } 200 } 201 202 return nil 203 }