github.com/freetocompute/snapd@v0.0.0-20210618182524-2fb355d72fd9/overlord/servicestate/service_control.go (about) 1 // -*- Mode: Go; indent-tabs-mode: t -*- 2 3 /* 4 * Copyright (C) 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 25 tomb "gopkg.in/tomb.v2" 26 27 "github.com/snapcore/snapd/overlord/snapstate" 28 "github.com/snapcore/snapd/overlord/state" 29 "github.com/snapcore/snapd/snap" 30 "github.com/snapcore/snapd/wrappers" 31 ) 32 33 // ServiceAction encapsulates a single service-related action (such as starting, 34 // stopping or restarting) run against services of a given snap. The action is 35 // run for services listed in services attribute, or for all services of the 36 // snap if services list is empty. 37 // The names of services are app names (as defined in snap yaml). 38 type ServiceAction struct { 39 SnapName string `json:"snap-name"` 40 Action string `json:"action"` 41 ActionModifier string `json:"action-modifier,omitempty"` 42 Services []string `json:"services,omitempty"` 43 // ExplicitServices is used when there are explicit services that should be 44 // restarted. This is used for the `snap restart snap-name.svc1` case, 45 // where we create a task with specific services to work on - in this case 46 // ExplicitServices ends up being the list of services that were explicitly 47 // mentioned by the user to be restarted, regardless of their state. This is 48 // needed because in the case that one does `snap restart snap-name`, 49 // Services gets populated with all services in the snap, which we now 50 // interpret to mean that only inactive services of that set are to be 51 // restarted, but there could be additional explicit services that need to 52 // be restarted at the same time in the case that someone does something 53 // like `snap restart snap-name snap-name.svc1`, we will restart all the 54 // inactive and not disabled services in snap-name, and also svc1 regardless 55 // of the state svc1 is in. 56 ExplicitServices []string `json:"explicit-services,omitempty"` 57 } 58 59 func (m *ServiceManager) doServiceControl(t *state.Task, _ *tomb.Tomb) error { 60 st := t.State() 61 st.Lock() 62 defer st.Unlock() 63 64 perfTimings := state.TimingsForTask(t) 65 defer perfTimings.Save(st) 66 67 var sc ServiceAction 68 err := t.Get("service-action", &sc) 69 if err != nil { 70 return fmt.Errorf("internal error: cannot get service-action: %v", err) 71 } 72 73 var snapst snapstate.SnapState 74 if err := snapstate.Get(st, sc.SnapName, &snapst); err != nil { 75 return err 76 } 77 info, err := snapst.CurrentInfo() 78 if err != nil { 79 return err 80 } 81 82 svcs := info.Services() 83 if len(svcs) == 0 { 84 return nil 85 } 86 87 var services []*snap.AppInfo 88 if len(sc.Services) == 0 { 89 // no services specified, take all services of the snap 90 services = info.Services() 91 } else { 92 for _, svc := range sc.Services { 93 app := info.Apps[svc] 94 if app == nil { 95 return fmt.Errorf("no such service: %s", svc) 96 } 97 if !app.IsService() { 98 return fmt.Errorf("%s is not a service", svc) 99 } 100 services = append(services, app) 101 } 102 } 103 104 meter := snapstate.NewTaskProgressAdapterUnlocked(t) 105 106 var startupOrdered []*snap.AppInfo 107 if sc.Action != "stop" { 108 startupOrdered, err = snap.SortServices(services) 109 if err != nil { 110 return err 111 } 112 } 113 114 // Note - state must be unlocked when calling wrappers below. 115 switch sc.Action { 116 case "stop": 117 disable := sc.ActionModifier == "disable" 118 flags := &wrappers.StopServicesFlags{ 119 Disable: disable, 120 } 121 st.Unlock() 122 err := wrappers.StopServices(services, flags, snap.StopReasonOther, meter, perfTimings) 123 st.Lock() 124 if err != nil { 125 return err 126 } 127 if disable { 128 // re-read snapst after reacquiring the lock as it could have changed. 129 if err := snapstate.Get(st, sc.SnapName, &snapst); err != nil { 130 return err 131 } 132 changed, err := updateSnapstateServices(&snapst, nil, services) 133 if err != nil { 134 return err 135 } 136 if changed { 137 snapstate.Set(st, sc.SnapName, &snapst) 138 } 139 } 140 case "start": 141 enable := sc.ActionModifier == "enable" 142 flags := &wrappers.StartServicesFlags{ 143 Enable: enable, 144 } 145 st.Unlock() 146 err = wrappers.StartServices(startupOrdered, nil, flags, meter, perfTimings) 147 st.Lock() 148 if err != nil { 149 return err 150 } 151 if enable { 152 // re-read snapst after reacquiring the lock as it could have changed. 153 if err := snapstate.Get(st, sc.SnapName, &snapst); err != nil { 154 return err 155 } 156 changed, err := updateSnapstateServices(&snapst, startupOrdered, nil) 157 if err != nil { 158 return err 159 } 160 if changed { 161 snapstate.Set(st, sc.SnapName, &snapst) 162 } 163 } 164 case "restart": 165 st.Unlock() 166 err := wrappers.RestartServices(startupOrdered, sc.ExplicitServices, nil, meter, perfTimings) 167 st.Lock() 168 return err 169 case "reload-or-restart": 170 flags := &wrappers.RestartServicesFlags{Reload: true} 171 st.Unlock() 172 err := wrappers.RestartServices(startupOrdered, sc.ExplicitServices, flags, meter, perfTimings) 173 st.Lock() 174 return err 175 default: 176 return fmt.Errorf("unhandled service action: %q", sc.Action) 177 } 178 return nil 179 }