-
Notifications
You must be signed in to change notification settings - Fork 353
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
* remove concurrent test specs from https redirect test * code formatting * watch, initial * eskip file watch client * add watching to the cmd line option routes file * documentation
- Loading branch information
Showing
9 changed files
with
496 additions
and
52 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,11 @@ | ||
/* | ||
Package eskipfile implements the DataClient interface for reading the skipper route definitions from an eskip | ||
formatted file. | ||
(See the DataClient interface in the skipper/routing package and the eskip | ||
format in the skipper/eskip package.) | ||
The package provides two implementations: one without file watch (legacy version) and one with file watch. When | ||
running the skipper command, the one with watch is used. | ||
*/ | ||
package eskipfile |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,2 @@ | ||
foo: Path("/foo") -> setPath("/") -> "https://foo.example.org"; | ||
bar: Path("/bar") -> setPath("/") -> "https://bar.example.org"; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,55 @@ | ||
package eskipfile | ||
|
||
import ( | ||
"net/http" | ||
"net/url" | ||
"testing" | ||
"time" | ||
|
||
"github.com/zalando/skipper/filters/builtin" | ||
"github.com/zalando/skipper/logging/loggingtest" | ||
"github.com/zalando/skipper/routing" | ||
) | ||
|
||
func TestOpenFails(t *testing.T) { | ||
_, err := Open("notexisting.eskip") | ||
if err == nil { | ||
t.Error("failed to fail") | ||
} | ||
} | ||
|
||
func TestOpenSucceeds(t *testing.T) { | ||
f, err := Open("fixtures/test.eskip") | ||
if err != nil { | ||
t.Error(err) | ||
return | ||
} | ||
|
||
l := loggingtest.New() | ||
defer l.Close() | ||
|
||
rt := routing.New(routing.Options{ | ||
FilterRegistry: builtin.MakeRegistry(), | ||
DataClients: []routing.DataClient{f}, | ||
Log: l, | ||
PollTimeout: 180 * time.Millisecond, | ||
}) | ||
defer rt.Close() | ||
|
||
if err := l.WaitFor("route settings applied", 120*time.Millisecond); err != nil { | ||
t.Error(err) | ||
return | ||
} | ||
|
||
check := func(id, path string) { | ||
r, _ := rt.Route(&http.Request{URL: &url.URL{Path: path}}) | ||
if r == nil || r.Id != id { | ||
t.Error("failed to load file") | ||
t.FailNow() | ||
return | ||
} | ||
} | ||
|
||
check("foo", "/foo") | ||
check("bar", "/bar") | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,149 @@ | ||
package eskipfile | ||
|
||
import ( | ||
"io/ioutil" | ||
"os" | ||
"reflect" | ||
|
||
"github.com/zalando/skipper/eskip" | ||
) | ||
|
||
type watchResponse struct { | ||
routes []*eskip.Route | ||
deletedIDs []string | ||
err error | ||
} | ||
|
||
// WatchClient implements a route configuration client with file watching. Use the Watch function to initialize | ||
// instances of it. | ||
type WatchClient struct { | ||
fileName string | ||
routes map[string]*eskip.Route | ||
getAll chan (chan<- watchResponse) | ||
getUpdates chan (chan<- watchResponse) | ||
quit chan struct{} | ||
} | ||
|
||
// Watch creates a route configuration client with file watching. Watch doesn't follow file system nodes, it | ||
// always reads from the file identified by the initially provided file name. | ||
func Watch(name string) *WatchClient { | ||
c := &WatchClient{ | ||
fileName: name, | ||
getAll: make(chan (chan<- watchResponse)), | ||
getUpdates: make(chan (chan<- watchResponse)), | ||
quit: make(chan struct{}), | ||
} | ||
|
||
go c.watch() | ||
return c | ||
} | ||
|
||
func mapRoutes(r []*eskip.Route) map[string]*eskip.Route { | ||
m := make(map[string]*eskip.Route) | ||
for i := range r { | ||
m[r[i].Id] = r[i] | ||
} | ||
|
||
return m | ||
} | ||
|
||
func (c *WatchClient) storeRoutes(r []*eskip.Route) { | ||
c.routes = mapRoutes(r) | ||
} | ||
|
||
func (c *WatchClient) diffStoreRoutes(r []*eskip.Route) (upsert []*eskip.Route, deletedIDs []string) { | ||
for i := range r { | ||
if !reflect.DeepEqual(r[i], c.routes[r[i].Id]) { | ||
upsert = append(upsert, r[i]) | ||
} | ||
} | ||
|
||
m := mapRoutes(r) | ||
for id := range c.routes { | ||
if _, keep := m[id]; !keep { | ||
deletedIDs = append(deletedIDs, id) | ||
} | ||
} | ||
|
||
c.routes = m | ||
return | ||
} | ||
|
||
func (c *WatchClient) deleteAllListIDs() []string { | ||
var ids []string | ||
for id := range c.routes { | ||
ids = append(ids, id) | ||
} | ||
|
||
c.routes = nil | ||
return ids | ||
} | ||
|
||
func (c *WatchClient) loadAll() watchResponse { | ||
content, err := ioutil.ReadFile(c.fileName) | ||
if err != nil { | ||
return watchResponse{err: err} | ||
} | ||
|
||
r, err := eskip.Parse(string(content)) | ||
if err != nil { | ||
return watchResponse{err: err} | ||
} | ||
|
||
c.storeRoutes(r) | ||
return watchResponse{routes: r} | ||
} | ||
|
||
func (c *WatchClient) loadUpdates() watchResponse { | ||
content, err := ioutil.ReadFile(c.fileName) | ||
if err != nil { | ||
if _, isPerr := err.(*os.PathError); isPerr { | ||
deletedIDs := c.deleteAllListIDs() | ||
return watchResponse{deletedIDs: deletedIDs} | ||
} | ||
|
||
return watchResponse{err: err} | ||
} | ||
|
||
r, err := eskip.Parse(string(content)) | ||
if err != nil { | ||
return watchResponse{err: err} | ||
} | ||
|
||
upsert, del := c.diffStoreRoutes(r) | ||
return watchResponse{routes: upsert, deletedIDs: del} | ||
} | ||
|
||
func (c *WatchClient) watch() { | ||
for { | ||
select { | ||
case req := <-c.getAll: | ||
req <- c.loadAll() | ||
case req := <-c.getUpdates: | ||
req <- c.loadUpdates() | ||
case <-c.quit: | ||
return | ||
} | ||
} | ||
} | ||
|
||
// LoadAll returns the parsed route definitions found in the file. | ||
func (c *WatchClient) LoadAll() ([]*eskip.Route, error) { | ||
req := make(chan watchResponse) | ||
c.getAll <- req | ||
rsp := <-req | ||
return rsp.routes, rsp.err | ||
} | ||
|
||
// LoadUpdate returns differential updates when a watched file has changed. | ||
func (c *WatchClient) LoadUpdate() ([]*eskip.Route, []string, error) { | ||
req := make(chan watchResponse) | ||
c.getUpdates <- req | ||
rsp := <-req | ||
return rsp.routes, rsp.deletedIDs, rsp.err | ||
} | ||
|
||
// Close stops watching the configured file and providing updates. | ||
func (c *WatchClient) Close() { | ||
close(c.quit) | ||
} |
Oops, something went wrong.