mirror of
https://github.com/weaveworks/scope.git
synced 2026-03-03 02:00:43 +00:00
Merge pull request #299 from tomwilkie/291-image-grouping
Use container name (minus version) as id in containers-by-image view.
This commit is contained in:
@@ -4,6 +4,7 @@ import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"net/http/httptest"
|
||||
"net/url"
|
||||
"reflect"
|
||||
"testing"
|
||||
|
||||
@@ -14,6 +15,41 @@ import (
|
||||
"github.com/weaveworks/scope/test"
|
||||
)
|
||||
|
||||
func TestAll(t *testing.T) {
|
||||
ts := httptest.NewServer(Router(StaticReport{}))
|
||||
defer ts.Close()
|
||||
|
||||
body := getRawJSON(t, ts, "/api/topology")
|
||||
var topologies []APITopologyDesc
|
||||
if err := json.Unmarshal(body, &topologies); err != nil {
|
||||
t.Fatalf("JSON parse error: %s", err)
|
||||
}
|
||||
|
||||
getTopology := func(topologyURL string) {
|
||||
body := getRawJSON(t, ts, topologyURL)
|
||||
var topology APITopology
|
||||
if err := json.Unmarshal(body, &topology); err != nil {
|
||||
t.Fatalf("JSON parse error: %s", err)
|
||||
}
|
||||
|
||||
for _, node := range topology.Nodes {
|
||||
body := getRawJSON(t, ts, fmt.Sprintf("%s/%s", topologyURL, url.QueryEscape(node.ID)))
|
||||
var node APINode
|
||||
if err := json.Unmarshal(body, &node); err != nil {
|
||||
t.Fatalf("JSON parse error: %s", err)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
for _, topology := range topologies {
|
||||
getTopology(topology.URL)
|
||||
|
||||
for _, subTopology := range topology.SubTopologies {
|
||||
getTopology(subTopology.URL)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestAPITopologyApplications(t *testing.T) {
|
||||
ts := httptest.NewServer(Router(StaticReport{}))
|
||||
defer ts.Close()
|
||||
|
||||
@@ -2,12 +2,45 @@ package main
|
||||
|
||||
import (
|
||||
"net/http"
|
||||
"net/url"
|
||||
"strings"
|
||||
|
||||
"github.com/gorilla/mux"
|
||||
|
||||
"github.com/weaveworks/scope/render"
|
||||
)
|
||||
|
||||
// URLMatcher uses request.RequestURI (the raw, unparsed request) to attempt
|
||||
// to match pattern. It does this as go's URL.Parse method is broken, and
|
||||
// mistakenly unescapes the Path before parsing it. This breaks %2F (encoded
|
||||
// forward slashes) in the paths.
|
||||
func URLMatcher(pattern string) mux.MatcherFunc {
|
||||
matchParts := strings.Split(pattern, "/")
|
||||
|
||||
return func(r *http.Request, rm *mux.RouteMatch) bool {
|
||||
path := strings.SplitN(r.RequestURI, "?", 2)[0]
|
||||
parts := strings.Split(path, "/")
|
||||
if len(parts) != len(matchParts) {
|
||||
return false
|
||||
}
|
||||
|
||||
rm.Vars = map[string]string{}
|
||||
for i, part := range parts {
|
||||
unescaped, err := url.QueryUnescape(part)
|
||||
if err != nil {
|
||||
return false
|
||||
}
|
||||
match := matchParts[i]
|
||||
if strings.HasPrefix(match, "{") && strings.HasSuffix(match, "}") {
|
||||
rm.Vars[strings.Trim(match, "{}")] = unescaped
|
||||
} else if matchParts[i] != unescaped {
|
||||
return false
|
||||
}
|
||||
}
|
||||
return true
|
||||
}
|
||||
}
|
||||
|
||||
// Router gives of the HTTP dispatcher. It will always use the embedded HTML
|
||||
// resources.
|
||||
func Router(c Reporter) *mux.Router {
|
||||
@@ -17,9 +50,9 @@ func Router(c Reporter) *mux.Router {
|
||||
get.HandleFunc("/api/topology", makeTopologyList(c))
|
||||
get.HandleFunc("/api/topology/{topology}", captureTopology(c, handleTopology))
|
||||
get.HandleFunc("/api/topology/{topology}/ws", captureTopology(c, handleWs))
|
||||
get.HandleFunc("/api/topology/{topology}/{id}", captureTopology(c, handleNode))
|
||||
get.HandleFunc("/api/topology/{topology}/{local}/{remote}", captureTopology(c, handleEdge))
|
||||
get.HandleFunc("/api/origin/host/{id}", makeOriginHostHandler(c))
|
||||
get.MatcherFunc(URLMatcher("/api/topology/{topology}/{id}")).HandlerFunc(captureTopology(c, handleNode))
|
||||
get.MatcherFunc(URLMatcher("/api/topology/{topology}/{local}/{remote}")).HandlerFunc(captureTopology(c, handleEdge))
|
||||
get.MatcherFunc(URLMatcher("/api/origin/host/{id}")).HandlerFunc(makeOriginHostHandler(c))
|
||||
get.HandleFunc("/api/report", makeRawReportHandler(c))
|
||||
get.PathPrefix("/").Handler(http.FileServer(FS(false))) // everything else is static
|
||||
return router
|
||||
|
||||
30
app/router_test.go
Normal file
30
app/router_test.go
Normal file
@@ -0,0 +1,30 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"net/http"
|
||||
"reflect"
|
||||
"testing"
|
||||
|
||||
"github.com/gorilla/mux"
|
||||
)
|
||||
|
||||
type v map[string]string
|
||||
|
||||
func TestURLMatcher(t *testing.T) {
|
||||
test := func(pattern, path string, match bool, vars v) {
|
||||
routeMatch := &mux.RouteMatch{}
|
||||
if URLMatcher(pattern)(&http.Request{RequestURI: path}, routeMatch) != match {
|
||||
t.Fatalf("'%s' '%s'", pattern, path)
|
||||
}
|
||||
if match && !reflect.DeepEqual(v(routeMatch.Vars), vars) {
|
||||
t.Fatalf("%v != %v", v(routeMatch.Vars), vars)
|
||||
}
|
||||
}
|
||||
|
||||
test("/a/b/c", "/a/b/c", true, v{})
|
||||
test("/a/b/c", "/c/b/a", false, v{})
|
||||
test("/{a}/b/c", "/b/b/c", true, v{"a": "b"})
|
||||
test("/{a}/b/c", "/b/b/b", false, v{})
|
||||
test("/a/b/{c}", "/a/b/b", true, v{"c": "b"})
|
||||
test("/a/b/{c}", "/a/b/b%2Fb", true, v{"c": "b/b"})
|
||||
}
|
||||
4189
app/static.go
4189
app/static.go
File diff suppressed because it is too large
Load Diff
@@ -71,7 +71,7 @@ function getTopologies() {
|
||||
|
||||
function getNodeDetails(topologyUrl, nodeId) {
|
||||
if (topologyUrl && nodeId) {
|
||||
const url = [topologyUrl, nodeId].join('/');
|
||||
const url = [topologyUrl, encodeURIComponent(nodeId)].join('/');
|
||||
reqwest({
|
||||
url: url,
|
||||
success: function(res) {
|
||||
|
||||
@@ -227,13 +227,13 @@ var (
|
||||
}
|
||||
|
||||
RenderedContainerImages = render.RenderableNodes{
|
||||
test.ClientContainerImageID: {
|
||||
ID: test.ClientContainerImageID,
|
||||
LabelMajor: "client_image",
|
||||
test.ClientContainerImageName: {
|
||||
ID: test.ClientContainerImageName,
|
||||
LabelMajor: test.ClientContainerImageName,
|
||||
LabelMinor: "",
|
||||
Rank: test.ClientContainerImageID,
|
||||
Rank: test.ClientContainerImageName,
|
||||
Pseudo: false,
|
||||
Adjacency: report.MakeIDList(test.ServerContainerImageID),
|
||||
Adjacency: report.MakeIDList(test.ServerContainerImageName),
|
||||
Origins: report.MakeIDList(
|
||||
test.ClientContainerImageNodeID,
|
||||
test.ClientContainerNodeID,
|
||||
@@ -248,13 +248,13 @@ var (
|
||||
render.KeyBytesEgress: 30,
|
||||
},
|
||||
},
|
||||
test.ServerContainerImageID: {
|
||||
ID: test.ServerContainerImageID,
|
||||
LabelMajor: "server_image",
|
||||
test.ServerContainerImageName: {
|
||||
ID: test.ServerContainerImageName,
|
||||
LabelMajor: test.ServerContainerImageName,
|
||||
LabelMinor: "",
|
||||
Rank: test.ServerContainerImageID,
|
||||
Rank: test.ServerContainerImageName,
|
||||
Pseudo: false,
|
||||
Adjacency: report.MakeIDList(test.ClientContainerImageID, render.TheInternetID),
|
||||
Adjacency: report.MakeIDList(test.ClientContainerImageName, render.TheInternetID),
|
||||
Origins: report.MakeIDList(
|
||||
test.ServerContainerImageNodeID,
|
||||
test.ServerContainerNodeID,
|
||||
|
||||
@@ -250,6 +250,33 @@ func MapContainer2ContainerImage(n RenderableNode) (RenderableNode, bool) {
|
||||
return newDerivedNode(id, n), true
|
||||
}
|
||||
|
||||
// MapContainerImage2Name maps container images RenderableNodes to
|
||||
// RenderableNodes for each container image name.
|
||||
//
|
||||
// This mapper is unlike the other foo2bar mappers as the intention
|
||||
// is not to join the information with another topology. Therefore
|
||||
// it outputs a properly-formed node with labels etc.
|
||||
func MapContainerImage2Name(n RenderableNode) (RenderableNode, bool) {
|
||||
if n.Pseudo {
|
||||
return n, true
|
||||
}
|
||||
|
||||
name, ok := n.NodeMetadata[docker.ImageName]
|
||||
if !ok {
|
||||
return RenderableNode{}, false
|
||||
}
|
||||
|
||||
parts := strings.SplitN(name, ":", 2)
|
||||
if len(parts) == 2 {
|
||||
name = parts[0]
|
||||
}
|
||||
|
||||
node := newDerivedNode(name, n)
|
||||
node.LabelMajor = name
|
||||
node.Rank = name
|
||||
return node, true
|
||||
}
|
||||
|
||||
// MapAddress2Host maps address RenderableNodes to host RenderableNodes.
|
||||
//
|
||||
// Otherthan pseudo nodes, we can assume all nodes have a HostID
|
||||
|
||||
@@ -48,17 +48,20 @@ var ContainerRenderer = MakeReduce(
|
||||
|
||||
// ContainerImageRenderer is a Renderer which produces a renderable container
|
||||
// image graph by merging the container graph and the container image topology.
|
||||
var ContainerImageRenderer = MakeReduce(
|
||||
Map{
|
||||
MapFunc: MapContainer2ContainerImage,
|
||||
Renderer: ContainerRenderer,
|
||||
},
|
||||
LeafMap{
|
||||
Selector: report.SelectContainerImage,
|
||||
Mapper: MapContainerImageIdentity,
|
||||
Pseudo: PanicPseudoNode,
|
||||
},
|
||||
)
|
||||
var ContainerImageRenderer = Map{
|
||||
MapFunc: MapContainerImage2Name,
|
||||
Renderer: MakeReduce(
|
||||
Map{
|
||||
MapFunc: MapContainer2ContainerImage,
|
||||
Renderer: ContainerRenderer,
|
||||
},
|
||||
LeafMap{
|
||||
Selector: report.SelectContainerImage,
|
||||
Mapper: MapContainerImageIdentity,
|
||||
Pseudo: PanicPseudoNode,
|
||||
},
|
||||
),
|
||||
}
|
||||
|
||||
// AddressRenderer is a Renderer which produces a renderable address
|
||||
// graph from the address topology.
|
||||
|
||||
@@ -51,6 +51,8 @@ var (
|
||||
ServerContainerImageID = "imageid456"
|
||||
ClientContainerImageNodeID = report.MakeContainerNodeID(ClientHostID, ClientContainerImageID)
|
||||
ServerContainerImageNodeID = report.MakeContainerNodeID(ServerHostID, ServerContainerImageID)
|
||||
ClientContainerImageName = "image/client"
|
||||
ServerContainerImageName = "image/server"
|
||||
|
||||
ClientAddressNodeID = report.MakeAddressNodeID(ClientHostID, "10.10.10.20")
|
||||
ServerAddressNodeID = report.MakeAddressNodeID(ServerHostID, "192.168.1.1")
|
||||
@@ -178,12 +180,12 @@ var (
|
||||
NodeMetadatas: report.NodeMetadatas{
|
||||
ClientContainerImageNodeID: report.NodeMetadata{
|
||||
docker.ImageID: ClientContainerImageID,
|
||||
docker.ImageName: "client_image",
|
||||
docker.ImageName: ClientContainerImageName,
|
||||
report.HostNodeID: ClientHostNodeID,
|
||||
},
|
||||
ServerContainerImageNodeID: report.NodeMetadata{
|
||||
docker.ImageID: ServerContainerImageID,
|
||||
docker.ImageName: "server_image",
|
||||
docker.ImageName: ServerContainerImageName,
|
||||
report.HostNodeID: ServerHostNodeID,
|
||||
},
|
||||
},
|
||||
|
||||
Reference in New Issue
Block a user