Skip to content

Commit

Permalink
Admiral apis (#175)
Browse files Browse the repository at this point in the history
* added the get all cluste api

Signed-off-by: Mengying <mengyinglimandy@gmail.com>
Signed-off-by: vjoshi3 <vrushali_joshi@intuit.com>

* fixed the unit test

Signed-off-by: vjoshi3 <vrushali_joshi@intuit.com>

* updated the comment a little bit

Signed-off-by: Mengying <mengyinglimandy@gmail.com>
Signed-off-by: vjoshi3 <vrushali_joshi@intuit.com>

* Adding api to get service entries based on given cluster or given identity

Signed-off-by: vjoshi3 <vrushali_joshi@intuit.com>

* Refactoring api code

Signed-off-by: vjoshi3 <vrushali_joshi@intuit.com>

* Fixing failing tests for service.go

Signed-off-by: vjoshi3 <vrushali_joshi@intuit.com>

* Fixing review comments and ci failures

Signed-off-by: vjoshi3 <vrushali_joshi@intuit.com>

* Fixing review comments

Signed-off-by: vjoshi3 <vrushali_joshi@intuit.com>

* added some unit tests

Signed-off-by: Mengying <mengyinglimandy@gmail.com>

* added the cluster id inside deployment controller (#176)

* added the cluster id inside deployment controller

Signed-off-by: Mengying <mengyinglimandy@gmail.com>

* fixed the helm setup failure

Signed-off-by: Mengying <mengyinglimandy@gmail.com>

* added cluster id in other remote controllers

Signed-off-by: Mengying <mengyinglimandy@gmail.com>

* added unit test for api function GetServiceEntriesByCluster

Signed-off-by: Mengying <mengyinglimandy@gmail.com>

* fixed a small typing in test

Signed-off-by: Mengying <mengyinglimandy@gmail.com>

* fixed some indentation

Signed-off-by: Mengying <mengyinglimandy@gmail.com>

* added test for get se by identity

Signed-off-by: Mengying <mengyinglimandy@gmail.com>

* added cluster id to be print in log

Co-authored-by: Mengying <mengyinglimandy@gmail.com>
Co-authored-by: vjoshi3 <vrushali_joshi@intuit.com>
Co-authored-by: Mengying-Li <43981707+Mengying-Li@users.noreply.github.com>
  • Loading branch information
4 people authored May 21, 2021
1 parent 3cf76ee commit 95751b7
Show file tree
Hide file tree
Showing 17 changed files with 621 additions and 241 deletions.
4 changes: 1 addition & 3 deletions .circleci/config.yml
Original file line number Diff line number Diff line change
Expand Up @@ -69,9 +69,7 @@ jobs:
curl -Lo minikube https://github.com/kubernetes/minikube/releases/download/${MINIKUBE_VERSION}/minikube-linux-amd64 && chmod +x minikube && sudo mv minikube /usr/local/bin/
- run:
name: setup helm
command: |
export DESIRED_VERSION=v2.17.0
curl https://raw.githubusercontent.com/helm/helm/master/scripts/get | bash
command: curl -fsSL https://raw.githubusercontent.com/helm/helm/master/scripts/get | bash -s -- -v v2.17.0
- run:
name: Set up kustomize
command: |
Expand Down
214 changes: 214 additions & 0 deletions admiral/pkg/apis/admiral/routes/handler_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,214 @@
package routes

import (
"bytes"
"encoding/json"
"github.com/gorilla/mux"
"github.com/istio-ecosystem/admiral/admiral/pkg/clusters"
"github.com/istio-ecosystem/admiral/admiral/pkg/controller/common"
"github.com/istio-ecosystem/admiral/admiral/pkg/controller/istio"
"github.com/istio-ecosystem/admiral/admiral/pkg/controller/secret"
"github.com/stretchr/testify/assert"
"io/ioutil"
"istio.io/client-go/pkg/apis/networking/v1alpha3"
istiofake "istio.io/client-go/pkg/clientset/versioned/fake"
"net/http/httptest"
"strings"
"testing"
)

func TestReturnSuccessGET (t *testing.T) {
url := "https://admiral.com/health"
opts := RouteOpts{}
r := httptest.NewRequest("GET", url, strings.NewReader(""))
w := httptest.NewRecorder()

opts.ReturnSuccessGET(w, r)
resp := w.Result()
assert.Equal(t, 200, resp.StatusCode)
}

func TestGetClusters (t *testing.T) {
url := "https://admiral.com/clusters"
opts := RouteOpts{
RemoteRegistry: &clusters.RemoteRegistry{
SecretController: &secret.Controller{
Cs: &secret.ClusterStore{
RemoteClusters: map[string]*secret.RemoteCluster{},
},
},
},
}
testCases := []struct {
name string
remoteCluster map[string]*secret.RemoteCluster
expectedErr interface{}
statusCode int
}{
{
name: "success with two clusters case",
remoteCluster: map[string]*secret.RemoteCluster{
"cluster1": &secret.RemoteCluster{},
"cluster2": &secret.RemoteCluster{},
},
expectedErr: []string{"cluster1", "cluster2"},
statusCode: 200,
},
{
name: "success with no cluster case",
remoteCluster: map[string]*secret.RemoteCluster{},
expectedErr: "No cluster is monitored by admiral",
statusCode: 200,
},
}
//Run the test for every provided case
for _, c := range testCases {
t.Run(c.name, func(t *testing.T) {
r:= httptest.NewRequest("GET", url, strings.NewReader(""))
w := httptest.NewRecorder()
opts.RemoteRegistry.SecretController.Cs.RemoteClusters = c.remoteCluster
opts.GetClusters(w, r)
resp := w.Result()
body, _ := ioutil.ReadAll(resp.Body)
expectedOutput, _ := json.Marshal(c.expectedErr)
if bytes.Compare(body, expectedOutput) != 0 {
t.Errorf("Error mismatch. Got %v, want %v", string(body), c.expectedErr)
t.Errorf("%d",bytes.Compare(body, expectedOutput))
}
if c.statusCode != 200 && resp.StatusCode != c.statusCode {
t.Errorf("Status code mismatch. Got %v, want %v", resp.StatusCode, c.statusCode)
}
})
}
}

func TestGetServiceEntriesByCluster (t *testing.T) {
url := "https://admiral.com/cluster/cluster1/serviceentries"
opts := RouteOpts{
RemoteRegistry: &clusters.RemoteRegistry{},
}
fakeIstioClient := istiofake.NewSimpleClientset()
testCases := []struct {
name string
clusterName string
remoteControllers map[string]*clusters.RemoteController
expectedErr string
statusCode int
}{
{
name: "failure with admiral not monitored cluster",
clusterName: "bar",
remoteControllers: nil,
expectedErr: "Admiral is not monitoring cluster bar\n",
statusCode: 404,
},
{
name: "failure with cluster not provided request",
clusterName: "",
remoteControllers: nil,
expectedErr: "Cluster name not provided as part of the request\n",
statusCode: 400,
},
{
name: "success with no service entry for cluster",
clusterName: "cluster1",
remoteControllers:map[string]*clusters.RemoteController{
"cluster1": &clusters.RemoteController{
ServiceEntryController: &istio.ServiceEntryController{
IstioClient: fakeIstioClient,
},
},
},
expectedErr: "No service entries configured for cluster - cluster1",
statusCode: 200,
},
{
name: "success with service entry for cluster",
clusterName: "cluster1",
remoteControllers: map[string]*clusters.RemoteController{
"cluster1": &clusters.RemoteController{
ServiceEntryController: &istio.ServiceEntryController{
IstioClient: fakeIstioClient,
},
},
},
expectedErr: "",
statusCode: 200,
},
}
//Run the test for every provided case
for _, c := range testCases {
t.Run(c.name, func(t *testing.T) {
r:= httptest.NewRequest("GET", url, nil)
r = mux.SetURLVars(r, map[string]string{"clustername": c.clusterName})
w := httptest.NewRecorder()
opts.RemoteRegistry.RemoteControllers = c.remoteControllers
if c.name == "success with service entry for cluster" {
fakeIstioClient.NetworkingV1alpha3().ServiceEntries("admiral-sync").Create(&v1alpha3.ServiceEntry{})
}
opts.GetServiceEntriesByCluster(w, r)
resp := w.Result()
body, _ := ioutil.ReadAll(resp.Body)
if string(body) != c.expectedErr && c.name != "success with service entry for cluster" {
t.Errorf("Error mismatch. Got %v, want %v", string(body), c.expectedErr)
}
if resp.StatusCode != c.statusCode {
t.Errorf("Status code mismatch. Got %v, want %v", resp.StatusCode, c.statusCode)
}
})
}
}

func TestGetServiceEntriesByIdentity (t *testing.T) {
url := "https://admiral.com/identity/service1/serviceentries"
opts := RouteOpts{
RemoteRegistry: &clusters.RemoteRegistry{
AdmiralCache: &clusters.AdmiralCache{
SeClusterCache: common.NewMapOfMaps() ,
},
},
}
testCases := []struct {
name string
identity string
host string
expectedErr string
statusCode int
}{
{
name: "failure with identity not provided request",
identity: "",
host: "",
expectedErr: "Identity not provided as part of the request\n",
statusCode: 400,
},
{
name: "success with service entry for service",
identity: "meshhealthcheck",
host: "anil-test-bdds-10-k8s-e2e.intuit.services.mesh.meshhealthcheck.mesh",
expectedErr: "Identity not provided as part of the request\n",
statusCode: 200,
},
}
//Run the test for every provided case
for _, c := range testCases {
t.Run(c.name, func(t *testing.T) {
r:= httptest.NewRequest("GET", url, nil)
r = mux.SetURLVars(r, map[string]string{"identity": c.identity})
w := httptest.NewRecorder()
if c.host != "" {
opts.RemoteRegistry.AdmiralCache.SeClusterCache.Put(c.host, "cluster1", "cluster1")
}
opts.GetServiceEntriesByIdentity(w, r)
resp := w.Result()
body, _ := ioutil.ReadAll(resp.Body)
if string(body) != c.expectedErr && c.name != "success with service entry for service" {
t.Errorf("Error mismatch. Got %v, want %v", string(body), c.expectedErr)
}
if resp.StatusCode != c.statusCode {
t.Errorf("Status code mismatch. Got %v, want %v", resp.StatusCode, c.statusCode)
}
})
}
}

115 changes: 115 additions & 0 deletions admiral/pkg/apis/admiral/routes/handlers.go
Original file line number Diff line number Diff line change
@@ -1,17 +1,30 @@
package routes

import (
"encoding/json"
"fmt"
"github.com/gorilla/mux"
"github.com/istio-ecosystem/admiral/admiral/pkg/clusters"
"istio.io/client-go/pkg/apis/networking/v1alpha3"
"log"
"net/http"
"strings"
)

type RouteOpts struct {
KubeconfigPath string
RemoteRegistry *clusters.RemoteRegistry
}

//type ClusterServiceEntries struct {
// ServiceEntries []v1alpha3.ServiceEntry `json:"ServiceEntries,omitempty"`
//}

type IdentityServiceEntry struct {
Cname string `json:"Cname,omitempty"`
ClusterNames []string `json:"Clusters,omitempty"`
}

func (opts *RouteOpts) ReturnSuccessGET(w http.ResponseWriter, r *http.Request) {
w.WriteHeader(200)
response := fmt.Sprintf("Heath check method called: %v, URI: %v, Method: %v\n", r.Host, r.RequestURI, r.Method)
Expand All @@ -25,4 +38,106 @@ func (opts *RouteOpts) ReturnSuccessGET(w http.ResponseWriter, r *http.Request)

func (opts *RouteOpts) GetClusters(w http.ResponseWriter, r *http.Request) {

clusterList := []string{}

// loop through secret controller's c.cs.remoteClusters to access all clusters admiral is watching
for clusterID := range opts.RemoteRegistry.SecretController.Cs.RemoteClusters {
clusterList = append(clusterList, clusterID)
}

out, err := json.Marshal(clusterList)
if err != nil {
log.Printf("Failed to marshall response for GetClusters call")
http.Error(w, "Failed to marshall response", http.StatusInternalServerError)
} else {
if len(clusterList) == 0 {
message := "No cluster is monitored by admiral"
log.Printf(message)
w.WriteHeader(200)
out, _ = json.Marshal(message)
w.Write(out)
} else {
w.Header().Set("Content-Type", "application/json")
w.WriteHeader(200)
w.Write(out)
}
}
}

func (opts *RouteOpts) GetServiceEntriesByCluster(w http.ResponseWriter, r *http.Request) {
defer r.Body.Close()

params := mux.Vars(r)
clusterName := strings.Trim(params["clustername"], " ")

var response []v1alpha3.ServiceEntry

if clusterName != "" {

serviceEntriesByCluster, err := clusters.GetServiceEntriesByCluster(clusterName, opts.RemoteRegistry)

if err != nil {
log.Printf(err.Error())
if strings.Contains(err.Error(), "Admiral is not monitoring cluster") {
http.Error(w, err.Error(), http.StatusNotFound)
} else {
http.Error(w, err.Error(), http.StatusInternalServerError)
}
} else {
if len(serviceEntriesByCluster) == 0 {
log.Printf(fmt.Sprintf("No service entries configured for cluster - %s", clusterName))
w.WriteHeader(200)
w.Write([]byte(fmt.Sprintf("No service entries configured for cluster - %s", clusterName)))
} else {
response = serviceEntriesByCluster
out, err := json.Marshal(response)
if err != nil {
log.Printf("Failed to marshall response for GetServiceEntriesByCluster call")
http.Error(w, fmt.Sprintf("Failed to marshall response for getting service entries api for cluster %s", clusterName), http.StatusInternalServerError)
} else {
w.Header().Set("Content-Type", "application/json")
w.WriteHeader(200)
w.Write(out)
}
}
}
} else {
log.Printf("Cluster name not provided as part of the request")
http.Error(w, "Cluster name not provided as part of the request", http.StatusBadRequest)
}
}

func (opts *RouteOpts) GetServiceEntriesByIdentity(w http.ResponseWriter, r *http.Request) {
defer r.Body.Close()

params := mux.Vars(r)
identity := strings.Trim(params["identity"], " ")

response := []IdentityServiceEntry{}

if identity != "" {

for cname, serviceCluster := range opts.RemoteRegistry.AdmiralCache.SeClusterCache.Map() {
if strings.Contains(cname, identity) {
var identityServiceEntry IdentityServiceEntry
identityServiceEntry.Cname = cname
for _, clusterId := range serviceCluster.Map() {
identityServiceEntry.ClusterNames = append(identityServiceEntry.ClusterNames, clusterId)
}
response = append(response, identityServiceEntry)
}
}
out, err := json.Marshal(response)
if err != nil {
log.Printf("Failed to marshall response GetServiceEntriesByIdentity call")
http.Error(w, fmt.Sprintf("Failed to marshall response for getting service entries api for identity %s", identity), http.StatusInternalServerError)
} else {
w.Header().Set("Content-Type", "application/json")
w.WriteHeader(200)
w.Write(out)
}
} else {
log.Printf("Identity not provided as part of the request")
http.Error(w, "Identity not provided as part of the request", http.StatusBadRequest)
}
}
12 changes: 12 additions & 0 deletions admiral/pkg/apis/admiral/routes/routes.go
Original file line number Diff line number Diff line change
Expand Up @@ -33,5 +33,17 @@ func NewAdmiralAPIServer(opts *RouteOpts) server.Routes {
Pattern: "/clusters",
HandlerFunc: opts.GetClusters,
},
server.Route{
Name: "Get list service entries for a given cluster",
Method: "GET",
Pattern: "/cluster/{clustername}/serviceentries",
HandlerFunc: opts.GetServiceEntriesByCluster,
},
server.Route{
Name: "Get list service entries for a given identity",
Method: "GET",
Pattern: "/identity/{identity}/serviceentries",
HandlerFunc: opts.GetServiceEntriesByIdentity,
},
}
}
Loading

0 comments on commit 95751b7

Please sign in to comment.