forked from upwrd/sift
/
sift.go
441 lines (394 loc) · 14.4 KB
/
sift.go
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
// Package sift - the Simple Interface of Functional Things.
//
// SIFT makes it easy for developers to write code which understands and
// manipulates connected devices. A SIFT Server presents an authoritative,
// centralized repository of Devices (physical units) and their functional
// Components. SIFT Components are generically typed, meaning a developer can
// manipulate any number of Light Emitters or Media Players without
// understanding their specific implementations (e.g. Philips Hue / Google
// Chromecast) and their implementation details (such as wireless protocols
// and API specifics).
package sift
import (
"code.google.com/p/go-uuid/uuid"
"fmt"
"github.com/thejerf/suture"
"github.com/upwrd/sift/adapter"
"github.com/upwrd/sift/adapter/chromecast"
cbtcp "github.com/upwrd/sift/adapter/connectedbytcp"
"github.com/upwrd/sift/adapter/example"
"github.com/upwrd/sift/auth"
"github.com/upwrd/sift/db"
"github.com/upwrd/sift/lib"
"github.com/upwrd/sift/logging"
"github.com/upwrd/sift/network/ipv4"
"github.com/upwrd/sift/notif"
"github.com/upwrd/sift/types"
log "gopkg.in/inconshreveable/log15.v2"
logext "gopkg.in/inconshreveable/log15.v2/ext"
"os"
"os/signal"
"sync"
"syscall"
"time"
)
// A suggested default database name.
const DefaultDBFilepath = "sift.db"
const (
ipv4ScanFrequency = 5 * time.Second
adapterTimeout = 15 * time.Second
updateChanWidth = 1000
numAdapterUpdateListeners = 5
numConfirmedUpdateListeners = 5
)
// Log is used to log messages for the sift package. Logs are disabled by
// default; use sift/logging.SetLevel() to set log levels for all packages, or
// Log.SetHandler() to set a custom handler for this package (see:
// https://godoc.org/gopkg.in/inconshreveable/log15.v2)
var Log = logging.Log.New("pkg", "sift")
// SetLogLevel is a convenient wrapper to sift/logging.SetLevelStr()
// Use it to quickly set the log level for all loggers in this package.
// For more control over logging, manipulate logging.Log directly, as outlined
// by https://godoc.org/gopkg.in/inconshreveable/log15.v2#hdr-Library_Use
// (see also: github.com/inconshreveable/log15)
func SetLogLevel(lvlstr string) { logging.SetLevelStr(lvlstr) }
// Server maintains the state of SIFT objects and provides methods for
// listening to, retrieving, and manipulating them.
// You should always initialize Servers with a call to NewServer()
type Server struct {
// SiftDB provides direct access to the underlying sqlite database through
// Jason Moiron's wonderful sqlx API (see: github.com/jmoiron/sqlx)
*db.SiftDB
dbpath string
auth.Authorizor // Provides login/authorize methods
notif.Provider // Provides notification pub/sub methods
notif.Receiver // Adds methods to post notifications
// Factories, adapters and their updates
factoriesByDescriptionID map[string]adapter.Factory
adapters map[string]adapter.Adapter
updatesFromAdapters chan updatePackage
prioritizer lib.IPrioritizer
// Scanners
ipv4Scan ipv4.IContinuousScanner
// Others
stop chan struct{}
stopped chan struct{}
interceptingStopSignals bool
log log.Logger
}
// NewServer constructs a new SIFT Server, using the SIFT database at the
// provided path (or creating a new one it does not exist). Be sure to start
// the Server with Serve()
func NewServer(dbpath string) (*Server, error) {
newDB, err := db.Open(dbpath)
if err != nil {
return nil, fmt.Errorf("could not open sift db: %v", err)
}
authorizor := auth.New()
notifier := notif.New(authorizor)
return &Server{
SiftDB: newDB,
dbpath: dbpath,
Authorizor: authorizor,
Provider: notifier,
Receiver: notifier,
factoriesByDescriptionID: make(map[string]adapter.Factory),
adapters: make(map[string]adapter.Adapter),
updatesFromAdapters: make(chan updatePackage, updateChanWidth),
prioritizer: lib.NewPrioritizer(nil), // uses default sorting
ipv4Scan: ipv4.NewContinousScanner(ipv4ScanFrequency),
stop: make(chan struct{}),
stopped: make(chan struct{}),
log: Log.New("obj", "server", "id", logext.RandId(8)),
}, nil
}
// Serve starts running the SIFT server. Most of the time you'll want to call
// in a goroutine; or as a Suture Service (see github.com/thejerf/suture)
func (s *Server) Serve() {
s.stopOnExitSignal() // capture ^c and SIGTERM, and close gracefully (see: http://stackoverflow.com/a/18158859/3088592)
supervisor := suture.NewSimple("sift server")
supervisor.Add(s.ipv4Scan)
go supervisor.ServeBackground()
// Listen for updates from adapters and consider them.
var wg1 sync.WaitGroup
for i := 0; i < numAdapterUpdateListeners; i++ {
wg1.Add(1)
go func() {
for update := range s.updatesFromAdapters {
// Consider the update through the prioritizer
if err := s.prioritizer.Consider(update.AdapterDescription, update.update); err != nil {
s.log.Error("error while prioritizing update", "err", err)
}
}
wg1.Done()
}()
}
// Listen for updates from the prioritizer channel. These are updates
// which should be reflected in the sift data model.
var wg2 sync.WaitGroup
for i := 0; i < numConfirmedUpdateListeners; i++ {
wg2.Add(1)
go func() {
for update := range s.prioritizer.OutputChan() {
s.handleUpdate(update)
}
wg2.Done()
}()
}
// Wait for less-frequent signals
for {
select {
case <-s.stop:
// stop gracefully
s.log.Debug("sift server stopping due to stop signal")
// stopTimer := time.NewTimer(30 * time.Second)
// workersStopped := make(chan struct{})
// go func() {
// close(s.updatesFromAdapters) // will cause adapter update listeners to stop
// wg1.Wait()
// // TODO: CLOSE PRIORITIZER
// wg2.Wait()
// workersStopped <- struct{}{}
// }()
// select {
// case <-stopTimer.C:
// s.log.Warn("timed out waiting for workers to stop, closing instead")
// case <-workersStopped:
// }
if err := s.SiftDB.Close(); err != nil {
s.log.Crit("could not gracefully close sift database", "err", err)
}
s.stopped <- struct{}{}
return
case ipv4Service := <-s.ipv4Scan.FoundServices():
// new IPv4 service found
go s.tryHandlingIPv4Service(ipv4Service)
}
}
}
// Stop stops the Server (does not block)
func (s *Server) Stop() {
s.stop <- struct{}{}
}
// StopAndWait stops the SIFT server and waits for it to complete.
func (s *Server) StopAndWait(timeout time.Duration) error {
s.Stop()
select {
case <-s.stopped:
return nil
case <-time.NewTimer(timeout).C:
return fmt.Errorf("timed out after %v", timeout)
}
}
type updatePackage struct {
lib.AdapterDescription
update interface{}
}
// AddAdapterFactory adds an AdapterFactory to the Server. Once added, the
// Server will begin searching for services matching the AdapterFactory's
// description. If any are found, the Server will use the AdapterFactory to
// create an Adapter to handle the sevice.
func (s *Server) AddAdapterFactory(factory adapter.Factory) (string, error) {
var id string
switch typed := factory.(type) {
default:
s.log.Warn("unhandled adapter factory type", "name", factory.Name(), "type", fmt.Sprintf("%T", factory))
return "", fmt.Errorf("unhandled adapter factory type: %T", factory)
case adapter.IPv4Factory:
id = s.ipv4Scan.AddDescription(typed.GetIPv4Description())
s.factoriesByDescriptionID[id] = factory
}
s.log.Info("added adapter factory", "name", factory.Name(), "id", id)
return id, nil
}
var defaultAdapterFactories = []func() adapter.Factory{
func() adapter.Factory { return example.NewFactory(55442) }, // SIFT example server
func() adapter.Factory { return cbtcp.NewFactory() }, // Connected by TCP
func() adapter.Factory { return chromecast.NewFactory() }, // Google Chromecast
}
// AddDefaults adds default Adapter Factories to the SIFT server
func (s *Server) AddDefaults() error {
for _, factoryFn := range defaultAdapterFactories {
factory := factoryFn()
if _, err := s.AddAdapterFactory(factory); err != nil {
return fmt.Errorf("error adding default factory %+v: %v", factory, err)
}
}
return nil
}
func (s *Server) addAdapter(adapter adapter.Adapter) string {
id := uuid.New()
s.adapters[id] = adapter
return id
}
func (s *Server) removeAdapter(id string) {
delete(s.adapters, id)
}
//
// Handling updates from adapters
//
func (s *Server) handleUpdate(update interface{}) {
switch typed := update.(type) {
case lib.DeviceUpdated:
s.log.Debug("received notice of updated device", "update", typed)
s.handleDeviceUpdated(typed)
case lib.DeviceDeleted:
s.log.Debug("received notice of deleted device", "delete", typed)
s.handleDeviceDeleted(typed)
}
}
func (s *Server) handleDeviceUpdated(update lib.DeviceUpdated) {
s.log.Debug("handling device update", "update", update)
// upsert the updated Device, and get the changes
resp, err := s.SiftDB.UpsertDevice(update.ID, update.NewState)
if err != nil {
s.log.Warn("could not upsert device indicated in update", "err", err, "update", update)
panic(fmt.Sprintf("could not upsert device indicated in update: %v", err))
}
// notify listeners of changes
for name, comp := range resp.UpsertedComponents {
id := types.ComponentID{Name: name, DeviceID: resp.DeviceID}
s.PostComponent(id, comp, notif.Update)
}
for name, comp := range resp.DeletedComponents {
id := types.ComponentID{Name: name, DeviceID: resp.DeviceID}
s.PostComponent(id, comp, notif.Delete)
}
if resp.HasDeviceChanged {
s.PostDevice(resp.DeviceID, update.NewState, notif.Update)
}
}
func (s *Server) handleDeviceDeleted(update lib.DeviceDeleted) {
s.log.Crit("STUB: server2.handleDeviceDeleted()", "update", update)
}
// EnactIntent attempts to fulfill an intent, usually to change the state of
// a particular Component. For a list of possible intents, see sift/types
func (s *Server) EnactIntent(target types.ComponentID, intent types.Intent) error {
if err := s.sanityCheck(); err != nil {
return err
}
s.log.Debug("submitting intent", "target", target, "intent", intent)
// Translate the internal intent (using types.ComponentID) into an external
// intent (using types.ExternalComponentID)
externalDevID, err := s.SiftDB.GetExternalDeviceID(target.DeviceID)
if err != nil {
return fmt.Errorf("could not get external ID for device %v: %v", target.DeviceID, err)
}
if externalDevID.Manufacturer == "" || externalDevID.ID == "" {
return fmt.Errorf("no active adapters are currently handling component %v", target)
}
// Determine the highest-priority Adapter currently serving the connected Device
adapterID := s.prioritizer.GetHighestPriorityAdapterForDevice(externalDevID)
adapter, ok := s.adapters[adapterID]
if !ok {
return fmt.Errorf("could not find adapter matching highest priority ID '%v': %v", adapterID, err)
}
// Pass the external intent to the Adapter
externalTarget := types.ExternalComponentID{Device: externalDevID, Name: target.Name}
s.log.Debug("passing external intent to adapter", "target", target, "intent", intent, "adapter", adapter)
return adapter.EnactIntent(externalTarget, intent)
}
//IPv4
// tryHandlingIPv4Service will walk through each of the provided
func (s *Server) tryHandlingIPv4Service(n ipv4.ServiceFoundNotification) {
for _, id := range n.MatchingDescriptionIDs {
// Find the factory matching the id
if factory, ok := s.factoriesByDescriptionID[id]; ok {
// ...it should be an IPv4 Factory
if asIPv4Factory, ok := factory.(adapter.IPv4Factory); !ok {
s.log.Error("expected an IPv4 factory, got something different!", "got", fmt.Sprintf("%T", factory))
} else {
// build a context for the given IP
context, statusChan := ipv4.BuildContext(n.IP, s.dbpath, factory.Name())
// build a new adapter from the factory, which will attempt to handle the context
adapter := asIPv4Factory.HandleIPv4(context)
adapterID := s.addAdapter(adapter)
// keep listening to the adapter until it fails or times out
adapterDied := make(chan bool, 10)
go func() {
for update := range adapter.UpdateChan() {
// pass the update and adapter to the main channel
pkg := updatePackage{
AdapterDescription: lib.AdapterDescription{
Type: lib.ControllerTypeIPv4,
ID: adapterID,
},
update: update,
}
// This passes the update to be eval'd by the server
s.updatesFromAdapters <- pkg
// This signals that the Adapter should be allowed to
// continue handling the context (NOTE: it should also be
// heartbeating)
adapterDied <- false
}
adapterDied <- true
}()
timer := time.NewTimer(adapterTimeout)
waitUntilFailure:
for {
timer.Reset(adapterTimeout)
select {
case <-timer.C:
s.log.Debug("adapter timed out")
break waitUntilFailure // timed out
case itDied := <-adapterDied:
// if it died, break. if it didn't, keep going
if itDied {
s.log.Debug("adapter died")
break waitUntilFailure
}
case status, more := <-statusChan:
if !more {
s.log.Debug("adapter status channel closed")
break waitUntilFailure
}
// The adapter can send messages through the context.Status channel.
// If the value is ipv4.DriverStatusHandling, it is treated as a
// keep-alive heartbeat message. Any other status indicates that
// the adapter is no longer handling the service.
if status != ipv4.AdapterStatusHandling {
s.log.Debug("adapter returned non-handling status", "status", status)
break waitUntilFailure
}
}
}
// If we've reached this point, the adapter is done.
// Kill it, and move on to the next viable adapter
ipv4.KillContext(context)
s.removeAdapter(adapterID)
}
}
}
// If we've reached this point, all viable adapters (if any) have failed.
// Release the IP; if it's still there, it will be picked up on the next
// scan and tried again.
s.ipv4Scan.Unlock(n.IP)
}
func (s *Server) sanityCheck() error {
if s == nil {
return fmt.Errorf("Server receiver cannot be nil")
}
if s.prioritizer == nil {
st, err := NewServer(DefaultDBFilepath)
if err != nil {
return fmt.Errorf("error instantiating SIFT server: %v", err)
}
s = st
}
return nil
}
func (s *Server) stopOnExitSignal() {
if !s.interceptingStopSignals {
c := make(chan os.Signal, 1)
signal.Notify(c, os.Interrupt)
signal.Notify(c, syscall.SIGTERM)
go func() {
<-c
fmt.Printf("\n\ncaught SIGTERM, shutting down gracefully...")
s.Stop()
<-s.stopped
os.Exit(1)
}()
}
}