Skip to content

Commit 41e2905

Browse files
tthebstvishalbollu
andauthored
Add traffic splitter API kind (#1213)
* updated kind with apisplitter * added apisplitter useconfig * apisplit yaml example * created apisplitter resource barebones * added apisplitter validations * migrated logic to overloading api config * basic post spec validation done * adding splitting logic to virtual service * working splitter update * api gateway support for trafficsplit * delete useless stuff for trafficsplitter * add get command for trafficsplitter * add trafficsplit table and get all deployments * cleanup and comments * cli update to get status of trafficsplitter * fix test error * fix comparison on virtual services * new traffic split get table * add new get all table for trafficsplitter * add endpoint duplication check * add check if api used by apisplitter * fix bug virtual service naming and remove print statements * apisplitter not supported on local check * imporve cli output format * fixes and formatting * allow 0 weight trafficsplitter * update cli trafficsplit get table * improve error messages * Update apisplit.yaml * rewrite virtual service destination definition * code review improvments * review improvements * code review improvements * remove apisplit example * correct table for cortex get * remove print statements * code review improvements * add API uniqueness check && review improvements * Nits * Only allow logs command for Sync APIs Co-authored-by: vishal <vishalbollu@users.noreply.github.com> Co-authored-by: Vishal Bollu <vishal.bollu@gmail.com>
1 parent 43728d1 commit 41e2905

File tree

16 files changed

+912
-169
lines changed

16 files changed

+912
-169
lines changed

Diff for: cli/cmd/get.go

+166-39
Original file line numberDiff line numberDiff line change
@@ -34,7 +34,6 @@ import (
3434
"github.com/cortexlabs/cortex/pkg/lib/errors"
3535
"github.com/cortexlabs/cortex/pkg/lib/exit"
3636
"github.com/cortexlabs/cortex/pkg/lib/json"
37-
"github.com/cortexlabs/cortex/pkg/lib/sets/strset"
3837
s "github.com/cortexlabs/cortex/pkg/lib/strings"
3938
"github.com/cortexlabs/cortex/pkg/lib/table"
4039
"github.com/cortexlabs/cortex/pkg/lib/telemetry"
@@ -49,18 +48,21 @@ import (
4948
)
5049

5150
const (
52-
_titleEnvironment = "env"
53-
_titleAPI = "api"
54-
_titleStatus = "status"
55-
_titleUpToDate = "up-to-date"
56-
_titleStale = "stale"
57-
_titleRequested = "requested"
58-
_titleFailed = "failed"
59-
_titleLastupdated = "last update"
60-
_titleAvgRequest = "avg request"
61-
_title2XX = "2XX"
62-
_title4XX = "4XX"
63-
_title5XX = "5XX"
51+
_titleEnvironment = "env"
52+
_titleAPI = "api"
53+
_titleAPISplitter = "api splitter"
54+
_titleAPIs = "apis"
55+
_apiSplitterWeights = "weights"
56+
_titleStatus = "status"
57+
_titleUpToDate = "up-to-date"
58+
_titleStale = "stale"
59+
_titleRequested = "requested"
60+
_titleFailed = "failed"
61+
_titleLastupdated = "last update"
62+
_titleAvgRequest = "avg request"
63+
_title2XX = "2XX"
64+
_title4XX = "4XX"
65+
_title5XX = "5XX"
6466
)
6567

6668
var (
@@ -146,8 +148,11 @@ func getAPIsInAllEnvironments() (string, error) {
146148
}
147149

148150
var allSyncAPIs []schema.SyncAPI
149-
var allEnvs []string
151+
var allAPISplitters []schema.APISplitter
152+
var allEnvsSyncAPI []string
153+
var allEnvsAPISplitter []string
150154
errorsMap := map[string]error{}
155+
// get apis from both environments
151156
for _, env := range cliConfig.Environments {
152157
var apisRes schema.GetAPIsResponse
153158
var err error
@@ -159,34 +164,35 @@ func getAPIsInAllEnvironments() (string, error) {
159164

160165
if err == nil {
161166
for range apisRes.SyncAPIs {
162-
allEnvs = append(allEnvs, env.Name)
167+
allEnvsSyncAPI = append(allEnvsSyncAPI, env.Name)
168+
}
169+
for range apisRes.APISplitter {
170+
allEnvsAPISplitter = append(allEnvsAPISplitter, env.Name)
163171
}
164-
165172
allSyncAPIs = append(allSyncAPIs, apisRes.SyncAPIs...)
173+
if env.Provider == types.AWSProviderType {
174+
allAPISplitters = append(allAPISplitters, apisRes.APISplitter...)
175+
}
166176
} else {
167177
errorsMap[env.Name] = err
168178
}
169179
}
170180

171181
out := ""
182+
var apiSplitTable table.Table
183+
var syncAPITable table.Table
172184

185+
// build different table depending on kinds that are deployed
173186
if len(allSyncAPIs) == 0 {
174-
if len(errorsMap) == 1 {
175-
// Print the error if there is just one
176-
exit.Error(errors.FirstErrorInMap(errorsMap))
177-
}
178-
// if all envs errored, skip it "no apis are deployed" since it's misleading
179-
if len(errorsMap) != len(cliConfig.Environments) {
180-
out += console.Bold("no apis are deployed") + "\n"
181-
}
187+
apiSplitTable = apiSplitterListTable(allAPISplitters, allEnvsAPISplitter)
188+
out = apiSplitTable.MustFormat()
189+
} else if len(allAPISplitters) == 0 {
190+
syncAPITable = apiTable(allSyncAPIs, allEnvsSyncAPI)
191+
out = syncAPITable.MustFormat()
182192
} else {
183-
t := apiTable(allSyncAPIs, allEnvs)
184-
185-
if strset.New(allEnvs...).IsEqual(strset.New(types.LocalProviderType.String())) {
186-
hideReplicaCountColumns(&t)
187-
}
188-
189-
out += t.MustFormat()
193+
apiSplitTable = apiSplitterListTable(allAPISplitters, allEnvsAPISplitter)
194+
syncAPITable = apiTable(allSyncAPIs, allEnvsSyncAPI)
195+
out = syncAPITable.MustFormat() + "\n" + apiSplitTable.MustFormat()
190196
}
191197

192198
if len(errorsMap) == 1 {
@@ -229,7 +235,7 @@ func getAPIs(env cliconfig.Environment, printEnv bool) (string, error) {
229235
}
230236
}
231237

232-
if len(apisRes.SyncAPIs) == 0 {
238+
if len(apisRes.SyncAPIs) == 0 && len(apisRes.APISplitter) == 0 {
233239
return console.Bold("no apis are deployed"), nil
234240
}
235241

@@ -238,14 +244,30 @@ func getAPIs(env cliconfig.Environment, printEnv bool) (string, error) {
238244
envNames = append(envNames, env.Name)
239245
}
240246

241-
t := apiTable(apisRes.SyncAPIs, envNames)
242-
243-
t.FindHeaderByTitle(_titleEnvironment).Hidden = true
247+
var apiSplitTable table.Table
248+
var syncAPITable table.Table
249+
var out string
244250

245-
out := t.MustFormat()
251+
// build different table depending on kinds that are deployed
252+
if len(apisRes.SyncAPIs) == 0 {
253+
apiSplitTable = apiSplitterListTable(apisRes.APISplitter, envNames)
254+
apiSplitTable.FindHeaderByTitle(_titleEnvironment).Hidden = true
255+
out = apiSplitTable.MustFormat()
256+
} else if len(apisRes.APISplitter) == 0 {
257+
syncAPITable = apiTable(apisRes.SyncAPIs, envNames)
258+
syncAPITable.FindHeaderByTitle(_titleEnvironment).Hidden = true
259+
out = syncAPITable.MustFormat()
260+
} else {
261+
apiSplitTable = apiSplitterListTable(apisRes.APISplitter, envNames)
262+
syncAPITable = apiTable(apisRes.SyncAPIs, envNames)
263+
apiSplitTable.FindHeaderByTitle(_titleEnvironment).Hidden = true
264+
syncAPITable.FindHeaderByTitle(_titleEnvironment).Hidden = true
265+
out = syncAPITable.MustFormat() + "\n" + apiSplitTable.MustFormat()
266+
}
246267

247268
if env.Provider == types.LocalProviderType {
248-
hideReplicaCountColumns(&t)
269+
// apisplitter not supported in local
270+
hideReplicaCountColumns(&syncAPITable)
249271
mismatchedVersionAPIsErrorMessage, _ := getLocalVersionMismatchedAPIsMessage()
250272
if len(mismatchedVersionAPIsErrorMessage) > 0 {
251273
out += "\n" + mismatchedVersionAPIsErrorMessage
@@ -292,7 +314,111 @@ func getAPI(env cliconfig.Environment, apiName string) (string, error) {
292314
return "", err
293315
}
294316
}
295-
return syncAPITable(apiRes.SyncAPI, env)
317+
if apiRes.SyncAPI != nil {
318+
return syncAPITable(apiRes.SyncAPI, env)
319+
}
320+
if apiRes.APISplitter != nil {
321+
return apiSplitterTable(apiRes.APISplitter, env)
322+
}
323+
return "", nil
324+
}
325+
326+
func apiSplitterTable(apiSplitter *schema.APISplitter, env cliconfig.Environment) (string, error) {
327+
var out string
328+
329+
lastUpdated := time.Unix(apiSplitter.Spec.LastUpdated, 0)
330+
out += console.Bold("kind: ") + apiSplitter.Spec.Kind.String() + "\n\n"
331+
out += console.Bold("last updated: ") + libtime.SinceStr(&lastUpdated) + "\n\n"
332+
333+
t, err := trafficSplitTable(*apiSplitter, env)
334+
if err != nil {
335+
return "", err
336+
}
337+
t.FindHeaderByTitle(_titleEnvironment).Hidden = true
338+
339+
out += t.MustFormat()
340+
341+
apiEndpoint := apiSplitter.BaseURL
342+
if env.Provider == types.AWSProviderType {
343+
apiEndpoint = urls.Join(apiSplitter.BaseURL, *apiSplitter.Spec.Networking.Endpoint)
344+
if apiSplitter.Spec.Networking.APIGateway == userconfig.NoneAPIGatewayType {
345+
apiEndpoint = strings.Replace(apiEndpoint, "https://", "http://", 1)
346+
}
347+
}
348+
349+
out += "\n" + console.Bold("endpoint: ") + apiEndpoint
350+
351+
out += fmt.Sprintf("\n%s curl %s -X POST -H \"Content-Type: application/json\" -d @sample.json\n", console.Bold("curl:"), apiEndpoint)
352+
353+
out += titleStr("configuration") + strings.TrimSpace(apiSplitter.Spec.UserStr(env.Provider))
354+
355+
return out, nil
356+
}
357+
358+
func trafficSplitTable(apiSplitter schema.APISplitter, env cliconfig.Environment) (table.Table, error) {
359+
rows := make([][]interface{}, 0, len(apiSplitter.Spec.APIs))
360+
361+
for _, api := range apiSplitter.Spec.APIs {
362+
apiRes, err := cluster.GetAPI(MustGetOperatorConfig(env.Name), api.Name)
363+
if err != nil {
364+
return table.Table{}, err
365+
}
366+
lastUpdated := time.Unix(apiRes.SyncAPI.Spec.LastUpdated, 0)
367+
rows = append(rows, []interface{}{
368+
env.Name,
369+
apiRes.SyncAPI.Spec.Name,
370+
api.Weight,
371+
apiRes.SyncAPI.Status.Message(),
372+
apiRes.SyncAPI.Status.Requested,
373+
libtime.SinceStr(&lastUpdated),
374+
latencyStr(&apiRes.SyncAPI.Metrics),
375+
code2XXStr(&apiRes.SyncAPI.Metrics),
376+
code5XXStr(&apiRes.SyncAPI.Metrics),
377+
})
378+
}
379+
380+
return table.Table{
381+
Headers: []table.Header{
382+
{Title: _titleEnvironment},
383+
{Title: _titleAPIs},
384+
{Title: _apiSplitterWeights},
385+
{Title: _titleStatus},
386+
{Title: _titleRequested},
387+
{Title: _titleLastupdated},
388+
{Title: _titleAvgRequest},
389+
{Title: _title2XX},
390+
{Title: _title5XX},
391+
},
392+
Rows: rows,
393+
}, nil
394+
}
395+
396+
func apiSplitterListTable(apiSplitter []schema.APISplitter, envNames []string) table.Table {
397+
rows := make([][]interface{}, 0, len(apiSplitter))
398+
for i, splitAPI := range apiSplitter {
399+
lastUpdated := time.Unix(splitAPI.Spec.LastUpdated, 0)
400+
var apis []string
401+
for _, api := range splitAPI.Spec.APIs {
402+
apis = append(apis, api.Name+":"+s.Int(api.Weight))
403+
}
404+
apisStr := s.TruncateEllipses(strings.Join(apis, " "), 100)
405+
rows = append(rows, []interface{}{
406+
envNames[i],
407+
splitAPI.Spec.Name,
408+
apisStr,
409+
libtime.SinceStr(&lastUpdated),
410+
})
411+
}
412+
413+
return table.Table{
414+
Headers: []table.Header{
415+
{Title: _titleEnvironment},
416+
{Title: _titleAPISplitter},
417+
{Title: _titleAPIs},
418+
{Title: _titleLastupdated},
419+
},
420+
Rows: rows,
421+
}
296422
}
297423

298424
func syncAPITable(syncAPI *schema.SyncAPI, env cliconfig.Environment) (string, error) {
@@ -302,6 +428,8 @@ func syncAPITable(syncAPI *schema.SyncAPI, env cliconfig.Environment) (string, e
302428
t.FindHeaderByTitle(_titleEnvironment).Hidden = true
303429
t.FindHeaderByTitle(_titleAPI).Hidden = true
304430

431+
out += console.Bold("kind: ") + syncAPI.Spec.Kind.String() + "\n\n"
432+
305433
out += t.MustFormat()
306434

307435
if env.Provider != types.LocalProviderType && syncAPI.Spec.Monitoring != nil {
@@ -340,7 +468,6 @@ func syncAPITable(syncAPI *schema.SyncAPI, env cliconfig.Environment) (string, e
340468

341469
func apiTable(syncAPIs []schema.SyncAPI, envNames []string) table.Table {
342470
rows := make([][]interface{}, 0, len(syncAPIs))
343-
344471
var totalFailed int32
345472
var totalStale int32
346473
var total4XX int

Diff for: pkg/lib/k8s/virtual_service.go

+26-17
Original file line numberDiff line numberDiff line change
@@ -33,17 +33,35 @@ var _virtualServiceTypeMeta = kmeta.TypeMeta{
3333
}
3434

3535
type VirtualServiceSpec struct {
36-
Name string
37-
Gateways []string
36+
Name string
37+
Gateways []string
38+
Destinations []Destination
39+
Path string
40+
Rewrite *string
41+
Labels map[string]string
42+
Annotations map[string]string
43+
}
44+
45+
type Destination struct {
3846
ServiceName string
39-
ServicePort int32
40-
Path string
41-
Rewrite *string
42-
Labels map[string]string
43-
Annotations map[string]string
47+
Weight int32
48+
Port uint32
4449
}
4550

4651
func VirtualService(spec *VirtualServiceSpec) *istioclientnetworking.VirtualService {
52+
destinations := []*istionetworking.HTTPRouteDestination{}
53+
for _, destination := range spec.Destinations {
54+
destinations = append(destinations, &istionetworking.HTTPRouteDestination{
55+
Destination: &istionetworking.Destination{
56+
Host: destination.ServiceName,
57+
Port: &istionetworking.PortSelector{
58+
Number: destination.Port,
59+
},
60+
},
61+
Weight: destination.Weight,
62+
})
63+
}
64+
4765
virtualService := &istioclientnetworking.VirtualService{
4866
TypeMeta: _virtualServiceTypeMeta,
4967
ObjectMeta: kmeta.ObjectMeta{
@@ -65,16 +83,7 @@ func VirtualService(spec *VirtualServiceSpec) *istioclientnetworking.VirtualServ
6583
},
6684
},
6785
},
68-
Route: []*istionetworking.HTTPRouteDestination{
69-
{
70-
Destination: &istionetworking.Destination{
71-
Host: spec.ServiceName,
72-
Port: &istionetworking.PortSelector{
73-
Number: uint32(spec.ServicePort),
74-
},
75-
},
76-
},
77-
},
86+
Route: destinations,
7887
},
7988
},
8089
},

Diff for: pkg/operator/endpoints/logs.go

+5-1
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@ import (
2020
"net/http"
2121

2222
"github.com/cortexlabs/cortex/pkg/operator/resources"
23+
"github.com/cortexlabs/cortex/pkg/types/userconfig"
2324
"github.com/gorilla/mux"
2425
"github.com/gorilla/websocket"
2526
)
@@ -32,11 +33,14 @@ func ReadLogs(w http.ResponseWriter, r *http.Request) {
3233
respondError(w, r, err)
3334
return
3435
}
35-
3636
if deployedResource == nil {
3737
respondError(w, r, resources.ErrorAPINotDeployed(apiName))
3838
return
3939
}
40+
if deployedResource.Kind != userconfig.SyncAPIKind {
41+
respondError(w, r, resources.ErrorOperationNotSupportedForKind(deployedResource.Kind))
42+
return
43+
}
4044

4145
upgrader := websocket.Upgrader{}
4246
socket, err := upgrader.Upgrade(w, r, nil)

0 commit comments

Comments
 (0)