-
Notifications
You must be signed in to change notification settings - Fork 339
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
feat(kuma-cp) add GlobalInsights api endpoint (#3018)
* feat(kuma-cp) add GlobalInsights api endpoint `/global-insights` is an endpoint which will return aggregated statistics for global resources, like `Zone`, `ZoneIngress` or `Mesh`. It's purpose is similar to the `MeshInsights`, but for global resources, and will be used in our GUI to simplify displaying statistics of these objects. I've spent to much time thinking about the approach for adding this endpoint and finally decided to go with the easiest one, which is just asking for `Zone`s, `ZoneIngress`es and `Mesh`es every time endpoint is hitted as the `GlobalInsights` is different than other resources like `MeshInsights` as there always will be one. The assumption is also, that there won't be a lot of the resources we'll be asking for every endpoint hit. Other solutions considered were: 1. Adding `GlobalInsights` as the real resource, like `MeshInsights`, but then we would have to create separate k8s' CRD, for just the resource which would exist only one at a time. Also, as it's the global resource, reconciliation of it would be more complex. 2. Storing the internal data in Config resource (which maps to json string in k8s' ConfigMap), but I really think it wouldn't be right, as I think it would be not obvious and harder to maintain in the future. The complexity of reconciliation would be the same as in the option above. I think this solution is very easy, and if anyhow it will become performance bottleneck, we can just improve it/refactor it, without breaking backward compatibility. Signed-off-by: Bart Smykla <bartek@smykla.com>
- Loading branch information
1 parent
a7c04ee
commit 636e31c
Showing
3 changed files
with
211 additions
and
0 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
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,77 @@ | ||
package api_server | ||
|
||
import ( | ||
"time" | ||
|
||
"github.com/emicklei/go-restful" | ||
|
||
"github.com/kumahq/kuma/pkg/core" | ||
"github.com/kumahq/kuma/pkg/core/resources/apis/mesh" | ||
"github.com/kumahq/kuma/pkg/core/resources/apis/system" | ||
"github.com/kumahq/kuma/pkg/core/resources/manager" | ||
"github.com/kumahq/kuma/pkg/core/resources/rbac" | ||
rest_errors "github.com/kumahq/kuma/pkg/core/rest/errors" | ||
) | ||
|
||
type globalInsightsEndpoints struct { | ||
resManager manager.ResourceManager | ||
resourceAccess rbac.ResourceAccess | ||
} | ||
|
||
type globalInsightsStat struct { | ||
Total uint32 `json:"total"` | ||
} | ||
|
||
type globalInsightsResponse struct { | ||
Type string `json:"type"` | ||
CreationTime time.Time `json:"creationTime"` | ||
Meshes globalInsightsStat `json:"meshes"` | ||
Zones globalInsightsStat `json:"zones"` | ||
ZoneIngresses globalInsightsStat `json:"zoneIngresses"` | ||
} | ||
|
||
func newGlobalInsightsResponse(meshes, zones, zoneIngresses globalInsightsStat) *globalInsightsResponse { | ||
return &globalInsightsResponse{ | ||
Type: "GlobalInsights", | ||
CreationTime: core.Now(), | ||
Meshes: meshes, | ||
Zones: zones, | ||
ZoneIngresses: zoneIngresses, | ||
} | ||
} | ||
|
||
func (r *globalInsightsEndpoints) addEndpoint(ws *restful.WebService) { | ||
ws.Route(ws.GET("/global-insights").To(r.inspectGlobalResources). | ||
Doc("Inspect all global resources"). | ||
Returns(200, "OK", nil)) | ||
} | ||
|
||
func (r *globalInsightsEndpoints) inspectGlobalResources(request *restful.Request, response *restful.Response) { | ||
meshes := &mesh.MeshResourceList{} | ||
if err := r.resManager.List(request.Request.Context(), meshes); err != nil { | ||
rest_errors.HandleError(response, err, "Could not retrieve global insights") | ||
return | ||
} | ||
|
||
zones := &system.ZoneResourceList{} | ||
if err := r.resManager.List(request.Request.Context(), zones); err != nil { | ||
rest_errors.HandleError(response, err, "Could not retrieve global insights") | ||
return | ||
} | ||
|
||
zoneIngresses := &mesh.ZoneIngressResourceList{} | ||
if err := r.resManager.List(request.Request.Context(), zoneIngresses); err != nil { | ||
rest_errors.HandleError(response, err, "Could not retrieve global insights") | ||
return | ||
} | ||
|
||
insights := newGlobalInsightsResponse( | ||
globalInsightsStat{Total: uint32(len(meshes.Items))}, | ||
globalInsightsStat{Total: uint32(len(zones.Items))}, | ||
globalInsightsStat{Total: uint32(len(zoneIngresses.Items))}, | ||
) | ||
|
||
if err := response.WriteAsJson(insights); err != nil { | ||
rest_errors.HandleError(response, err, "Could not retrieve global insights") | ||
} | ||
} |
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,128 @@ | ||
package api_server_test | ||
|
||
import ( | ||
"context" | ||
"io/ioutil" | ||
"net/http" | ||
"time" | ||
|
||
. "github.com/onsi/ginkgo" | ||
. "github.com/onsi/gomega" | ||
|
||
api_server "github.com/kumahq/kuma/pkg/api-server" | ||
config "github.com/kumahq/kuma/pkg/config/api-server" | ||
"github.com/kumahq/kuma/pkg/core" | ||
core_mesh "github.com/kumahq/kuma/pkg/core/resources/apis/mesh" | ||
"github.com/kumahq/kuma/pkg/core/resources/apis/system" | ||
core_model "github.com/kumahq/kuma/pkg/core/resources/model" | ||
"github.com/kumahq/kuma/pkg/core/resources/store" | ||
"github.com/kumahq/kuma/pkg/metrics" | ||
"github.com/kumahq/kuma/pkg/plugins/resources/memory" | ||
) | ||
|
||
var _ = Describe("Global Insights Endpoints", func() { | ||
var apiServer *api_server.ApiServer | ||
var resourceStore store.ResourceStore | ||
var stop chan struct{} | ||
|
||
BeforeEach(func() { | ||
core.Now = func() time.Time { | ||
now, _ := time.Parse(time.RFC3339, "2018-07-17T16:05:36.995+00:00") | ||
return now | ||
} | ||
|
||
resourceStore = memory.NewStore() | ||
|
||
metrics, err := metrics.NewMetrics("Standalone") | ||
Expect(err).ToNot(HaveOccurred()) | ||
|
||
apiServer = createTestApiServer(resourceStore, config.DefaultApiServerConfig(), true, metrics) | ||
|
||
client := resourceApiClient{ | ||
address: apiServer.Address(), | ||
path: "/global-insights", | ||
} | ||
|
||
stop = make(chan struct{}) | ||
|
||
go func() { | ||
defer GinkgoRecover() | ||
Expect(apiServer.Start(stop)).To(Succeed()) | ||
}() | ||
|
||
waitForServer(&client) | ||
}, 5) | ||
|
||
AfterEach(func() { | ||
close(stop) | ||
core.Now = time.Now | ||
}) | ||
|
||
BeforeEach(func() { | ||
Expect(resourceStore.Create( | ||
context.Background(), | ||
system.NewZoneResource(), | ||
store.CreateByKey("zone-1", core_model.NoMesh), | ||
)).To(Succeed()) | ||
|
||
Expect(resourceStore.Create( | ||
context.Background(), | ||
system.NewZoneResource(), | ||
store.CreateByKey("zone-2", core_model.NoMesh), | ||
)).To(Succeed()) | ||
|
||
Expect(resourceStore.Create( | ||
context.Background(), | ||
core_mesh.NewZoneIngressResource(), | ||
store.CreateByKey("zone-ingress-1", core_model.NoMesh), | ||
)).To(Succeed()) | ||
|
||
Expect(resourceStore.Create( | ||
context.Background(), | ||
core_mesh.NewMeshResource(), | ||
store.CreateByKey("mesh-1", core_model.NoMesh), | ||
)).To(Succeed()) | ||
|
||
Expect(resourceStore.Create( | ||
context.Background(), | ||
core_mesh.NewMeshResource(), | ||
store.CreateByKey("mesh-2", core_model.NoMesh), | ||
)).To(Succeed()) | ||
|
||
Expect(resourceStore.Create( | ||
context.Background(), | ||
core_mesh.NewMeshResource(), | ||
store.CreateByKey("mesh-3", core_model.NoMesh), | ||
)).To(Succeed()) | ||
}) | ||
|
||
globalInsightsJSON := ` | ||
{ | ||
"type": "GlobalInsights", | ||
"creationTime": "2018-07-17T16:05:36.995Z", | ||
"meshes": { | ||
"total": 3 | ||
}, | ||
"zones": { | ||
"total": 2 | ||
}, | ||
"zoneIngresses": { | ||
"total": 1 | ||
} | ||
} | ||
` | ||
|
||
Describe("On GET", func() { | ||
It("should return an existing resource", func() { | ||
// when | ||
response, err := http.Get("http://" + apiServer.Address() + "/global-insights") | ||
Expect(err).ToNot(HaveOccurred()) | ||
|
||
// then | ||
Expect(response.StatusCode).To(Equal(200)) | ||
body, err := ioutil.ReadAll(response.Body) | ||
Expect(err).ToNot(HaveOccurred()) | ||
Expect(body).To(MatchJSON(globalInsightsJSON)) | ||
}) | ||
}) | ||
}) |
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