github.com/google/fleetspeak@v0.1.15-0.20240426164851-4f31f62c1aea/fleetspeak/src/client/services.go (about) 1 // Copyright 2017 Google Inc. 2 // 3 // Licensed under the Apache License, Version 2.0 (the "License"); 4 // you may not use this file except in compliance with the License. 5 // You may obtain a copy of the License at 6 // 7 // https://www.apache.org/licenses/LICENSE-2.0 8 // 9 // Unless required by applicable law or agreed to in writing, software 10 // distributed under the License is distributed on an "AS IS" BASIS, 11 // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 // See the License for the specific language governing permissions and 13 // limitations under the License. 14 15 package client 16 17 import ( 18 "context" 19 "crypto/rand" 20 "errors" 21 "fmt" 22 "io" 23 "sync" 24 "time" 25 26 log "github.com/golang/glog" 27 "github.com/google/fleetspeak/fleetspeak/src/client/service" 28 "github.com/google/fleetspeak/fleetspeak/src/client/stats" 29 "github.com/google/fleetspeak/fleetspeak/src/common" 30 "google.golang.org/protobuf/encoding/prototext" 31 "google.golang.org/protobuf/proto" 32 33 fspb "github.com/google/fleetspeak/fleetspeak/src/common/proto/fleetspeak" 34 ) 35 36 const inboxSize = 100 37 38 // A serviceConfiguration manages and communicates the services installed on a 39 // client. In normal use it is a singleton. 40 type serviceConfiguration struct { 41 services map[string]*serviceData 42 lock sync.RWMutex // Protects the structure of services. 43 client *Client 44 factories map[string]service.Factory // Used to look up correct factory when configuring services. 45 } 46 47 func (c *serviceConfiguration) ProcessMessage(ctx context.Context, m *fspb.Message) error { 48 c.lock.RLock() 49 target := c.services[m.Destination.ServiceName] 50 c.lock.RUnlock() 51 52 if target == nil { 53 return fmt.Errorf("destination service not installed") 54 } 55 select { 56 case target.inbox <- m: 57 58 target.countLock.Lock() 59 target.acceptCount++ 60 target.countLock.Unlock() 61 62 return nil 63 case <-ctx.Done(): 64 return ctx.Err() 65 } 66 } 67 68 // InstallSignedService installs a service provided in signed service 69 // configuration format. Note however that this is meant for backwards 70 // compatibility and the signature is not checked. 71 // 72 // Currently, we assume that service configurations are kept in a secured 73 // location. 74 func (c *serviceConfiguration) InstallSignedService(sd *fspb.SignedClientServiceConfig) error { 75 var cfg fspb.ClientServiceConfig 76 if err := proto.Unmarshal(sd.ServiceConfig, &cfg); err != nil { 77 return fmt.Errorf("Unable to parse service config [%v], ignoring: %v", sd.Signature, err) 78 } 79 80 ll: 81 for _, l := range cfg.RequiredLabels { 82 if l.ServiceName == "client" { 83 for _, cl := range c.client.cfg.ClientLabels { 84 if cl.Label == l.Label { 85 continue ll 86 } 87 } 88 return fmt.Errorf("service config [%s] requires label %v", cfg.Name, l) 89 } 90 } 91 92 if err := c.InstallService(&cfg, sd.Signature); err != nil { 93 return fmt.Errorf("installing [%s]: %w", cfg.Name, err) 94 } 95 return nil 96 } 97 98 func validateServiceName(sname string) error { 99 if sname == "" || sname == "system" || sname == "client" { 100 return fmt.Errorf("illegal service name [%v]", sname) 101 } 102 return nil 103 } 104 105 func (c *serviceConfiguration) InstallService(cfg *fspb.ClientServiceConfig, sig []byte) error { 106 if err := validateServiceName(cfg.Name); err != nil { 107 return fmt.Errorf("can't install service: %v", err) 108 } 109 110 f := c.factories[cfg.Factory] 111 if f == nil { 112 return fmt.Errorf("factory not found: %q", cfg.Factory) 113 } 114 s, err := f(cfg) 115 if err != nil { 116 return fmt.Errorf("unable to create service with factory %q: %v", cfg.Factory, err) 117 } 118 119 d := serviceData{ 120 config: c, 121 name: cfg.Name, 122 serviceConfig: cfg, 123 service: s, 124 inbox: make(chan *fspb.Message, inboxSize), 125 } 126 if err := d.start(); err != nil { 127 return fmt.Errorf("unable to start service: %v", err) 128 } 129 130 ctx := context.TODO() 131 d.working.Add(1) 132 go func() { 133 defer d.working.Done() 134 ctx = context.WithoutCancel(ctx) 135 d.processingLoop(ctx) 136 }() 137 138 c.lock.Lock() 139 old := c.services[cfg.Name] 140 c.services[cfg.Name] = &d 141 c.client.config.RecordRunningService(cfg.Name, sig) 142 c.lock.Unlock() 143 144 if old != nil { 145 old.stop() 146 } 147 148 b, err := prototext.Marshal(cfg) 149 if err != nil { 150 return err 151 } 152 log.Infof("Started service %v with config:\n%s", cfg.Name, string(b)) 153 return nil 154 } 155 156 func (c *serviceConfiguration) removeService(sname string) (*serviceData, error) { 157 c.lock.Lock() 158 defer c.lock.Unlock() 159 srv := c.services[sname] 160 if srv == nil { 161 return nil, fmt.Errorf("falied to remove non-existent service: %v", sname) 162 } 163 delete(c.services, sname) 164 return srv, nil 165 } 166 167 func (c *serviceConfiguration) RestartService(sname string) error { 168 if err := validateServiceName(sname); err != nil { 169 return fmt.Errorf("can't restart service: %v", err) 170 } 171 172 srv, err := c.removeService(sname) 173 if err != nil { 174 return err 175 } 176 177 srv.stop() 178 179 if err := c.InstallService(srv.serviceConfig, nil); err != nil { 180 return fmt.Errorf("can't reinstall service '%s' on restart: %v", sname, err) 181 } 182 183 return nil 184 } 185 186 // Counts returns the number of accepted and processed messages for each 187 // service. 188 func (c *serviceConfiguration) Counts() (accepted, processed map[string]uint64) { 189 am := make(map[string]uint64) 190 pm := make(map[string]uint64) 191 c.lock.RLock() 192 defer c.lock.RUnlock() 193 194 for _, sd := range c.services { 195 sd.countLock.Lock() 196 a, p := sd.acceptCount, sd.processedCount 197 sd.countLock.Unlock() 198 am[sd.name] = a 199 pm[sd.name] = p 200 } 201 return am, pm 202 } 203 204 func (c *serviceConfiguration) Stop() { 205 c.lock.Lock() 206 defer c.lock.Unlock() 207 for _, sd := range c.services { 208 sd.stop() 209 } 210 c.services = make(map[string]*serviceData) 211 } 212 213 // A serviceData contains the data we have about a configured service, wrapping 214 // a Service interface and mediating communication between it and the rest of 215 // the Fleetspeak client. 216 type serviceData struct { 217 config *serviceConfiguration 218 name string 219 serviceConfig *fspb.ClientServiceConfig 220 working sync.WaitGroup 221 service service.Service 222 inbox chan *fspb.Message 223 224 countLock sync.Mutex // Protects acceptCount, processCount 225 acceptCount, processedCount uint64 226 } 227 228 // Send implements service.Context. 229 func (d *serviceData) Send(ctx context.Context, am service.AckMessage) error { 230 m := am.M 231 id := d.config.client.config.ClientID().Bytes() 232 233 m.Source = &fspb.Address{ 234 ClientId: id, 235 ServiceName: d.name, 236 } 237 238 if len(m.SourceMessageId) == 0 { 239 b := make([]byte, 16) 240 if _, err := rand.Read(b); err != nil { 241 return fmt.Errorf("unable to create random source message id: %v", err) 242 } 243 m.SourceMessageId = b 244 } 245 246 return d.config.client.ProcessMessage(ctx, am) 247 } 248 249 // GetLocalInfo implements service.Context. 250 func (d *serviceData) GetLocalInfo() *service.LocalInfo { 251 ret := &service.LocalInfo{ 252 ClientID: d.config.client.config.ClientID(), 253 Labels: d.config.client.config.Labels(), 254 } 255 256 d.config.lock.RLock() 257 defer d.config.lock.RUnlock() 258 for s := range d.config.services { 259 if s != "system" { 260 ret.Services = append(ret.Services, s) 261 } 262 } 263 return ret 264 } 265 266 // GetFileIfModified implements service.Context. 267 func (d *serviceData) GetFileIfModified(ctx context.Context, name string, modSince time.Time) (io.ReadCloser, time.Time, error) { 268 if d.config.client.com == nil { 269 // happens during tests 270 return nil, time.Time{}, errors.New("file not found") 271 } 272 return d.config.client.com.GetFileIfModified(ctx, d.name, name, modSince) 273 } 274 275 // Stats implements service.Context. 276 func (d *serviceData) Stats() stats.Collector { 277 return d.config.client.stats 278 } 279 280 // processingLoop reads Fleetspeak message from d.inbox 281 // and terminates once this channel is closed. 282 func (d *serviceData) processingLoop(ctx context.Context) { 283 for { 284 m, ok := <-d.inbox 285 286 d.countLock.Lock() 287 d.processedCount++ 288 cnt := d.processedCount 289 d.countLock.Unlock() 290 291 if cnt&0x1f == 0 { 292 select { 293 case d.config.client.processingBeacon <- struct{}{}: 294 default: 295 } 296 } 297 298 if !ok { 299 return 300 } 301 id, err := common.BytesToMessageID(m.MessageId) 302 if err != nil { 303 log.Errorf("ignoring message with bad message id: [%v]", m.MessageId) 304 continue 305 } 306 err = d.service.ProcessMessage(ctx, m) 307 if m.MessageType == "Die" && m.Destination != nil && m.Destination.ServiceName == "system" { 308 // Die messages are special and pre-acked on the server side. 309 // Don't send another ack or error report. 310 continue 311 } 312 if err != nil { 313 d.config.client.errs <- &fspb.MessageErrorData{ 314 MessageId: id.Bytes(), 315 Error: err.Error(), 316 } 317 } else { 318 d.config.client.acks <- id 319 } 320 321 } 322 } 323 324 func (d *serviceData) start() error { 325 return d.service.Start(d) 326 } 327 328 func (d *serviceData) stop() { 329 close(d.inbox) 330 d.working.Wait() 331 d.service.Stop() 332 }