github.com/rigado/snapd@v2.42.5-go-mod+incompatible/overlord/servicestate/servicestate.go (about) 1 // -*- Mode: Go; indent-tabs-mode: t -*- 2 3 /* 4 * Copyright (C) 2015-2016 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 "time" 25 26 "github.com/snapcore/snapd/client" 27 "github.com/snapcore/snapd/overlord/cmdstate" 28 "github.com/snapcore/snapd/overlord/hookstate" 29 "github.com/snapcore/snapd/overlord/snapstate" 30 "github.com/snapcore/snapd/overlord/state" 31 "github.com/snapcore/snapd/snap" 32 ) 33 34 type Instruction struct { 35 Action string `json:"action"` 36 Names []string `json:"names"` 37 client.StartOptions 38 client.StopOptions 39 client.RestartOptions 40 } 41 42 type ServiceActionConflictError struct{ error } 43 44 // Control creates a taskset for starting/stopping/restarting services via systemctl. 45 // The appInfos and inst define the services and the command to execute. 46 // Context is used to determine change conflicts - we will not conflict with 47 // tasks from same change as that of context's. 48 func Control(st *state.State, appInfos []*snap.AppInfo, inst *Instruction, context *hookstate.Context) ([]*state.TaskSet, error) { 49 var tts []*state.TaskSet 50 51 var ctlcmds []string 52 switch { 53 case inst.Action == "start": 54 if inst.Enable { 55 ctlcmds = []string{"enable"} 56 } 57 ctlcmds = append(ctlcmds, "start") 58 case inst.Action == "stop": 59 if inst.Disable { 60 ctlcmds = []string{"disable"} 61 } 62 ctlcmds = append(ctlcmds, "stop") 63 case inst.Action == "restart": 64 if inst.Reload { 65 ctlcmds = []string{"reload-or-restart"} 66 } else { 67 ctlcmds = []string{"restart"} 68 } 69 default: 70 return nil, fmt.Errorf("unknown action %q", inst.Action) 71 } 72 73 st.Lock() 74 defer st.Unlock() 75 76 svcs := make([]string, 0, len(appInfos)) 77 snapNames := make([]string, 0, len(appInfos)) 78 lastName := "" 79 names := make([]string, len(appInfos)) 80 for i, svc := range appInfos { 81 svcs = append(svcs, svc.ServiceName()) 82 snapName := svc.Snap.InstanceName() 83 names[i] = snapName + "." + svc.Name 84 if snapName != lastName { 85 snapNames = append(snapNames, snapName) 86 lastName = snapName 87 } 88 } 89 90 var ignoreChangeID string 91 if context != nil && !context.IsEphemeral() { 92 if task, ok := context.Task(); ok { 93 if chg := task.Change(); chg != nil { 94 ignoreChangeID = chg.ID() 95 } 96 } 97 } 98 99 if err := snapstate.CheckChangeConflictMany(st, snapNames, ignoreChangeID); err != nil { 100 return nil, &ServiceActionConflictError{err} 101 } 102 103 for _, cmd := range ctlcmds { 104 argv := append([]string{"systemctl", cmd}, svcs...) 105 desc := fmt.Sprintf("%s of %v", cmd, names) 106 // Give the systemctl a maximum time of 61 for now. 107 // 108 // Longer term we need to refactor this code and 109 // reuse the snapd/systemd and snapd/wrapper packages 110 // to control the timeout in a single place. 111 ts := cmdstate.ExecWithTimeout(st, desc, argv, 61*time.Second) 112 tts = append(tts, ts) 113 } 114 115 // make a taskset wait for its predecessor 116 for i := 1; i < len(tts); i++ { 117 tts[i].WaitAll(tts[i-1]) 118 } 119 120 return tts, nil 121 }