Skip to content

Add traffic splitter API kind #1213

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 45 commits into from
Jul 30, 2020
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
45 commits
Select commit Hold shift + click to select a range
5a436ca
updated kind with apisplitter
tthebst Jun 29, 2020
a15751a
added apisplitter useconfig
tthebst Jun 29, 2020
076f817
apisplit yaml example
tthebst Jun 29, 2020
71ed5a4
created apisplitter resource barebones
tthebst Jun 29, 2020
96fb362
added apisplitter validations
tthebst Jun 29, 2020
3de9271
migrated logic to overloading api config
tthebst Jun 30, 2020
1f2b225
basic post spec validation done
tthebst Jul 2, 2020
2a66b27
adding splitting logic to virtual service
tthebst Jul 2, 2020
bd5159a
working splitter update
tthebst Jul 3, 2020
840ac1b
api gateway support for trafficsplit
tthebst Jul 3, 2020
bee84df
delete useless stuff for trafficsplitter
tthebst Jul 3, 2020
a0dc90f
merge master into branch && resolv conflict
tthebst Jul 3, 2020
e32770d
add get command for trafficsplitter
tthebst Jul 4, 2020
7f3203f
add trafficsplit table and get all deployments
tthebst Jul 4, 2020
6c8bda0
cleanup and comments
tthebst Jul 6, 2020
f61d9a5
cli update to get status of trafficsplitter
tthebst Jul 6, 2020
d2ece7d
fix test error
tthebst Jul 6, 2020
b7f0ef6
fix comparison on virtual services
tthebst Jul 6, 2020
c8b3aa3
new traffic split get table
tthebst Jul 6, 2020
42dcf9b
add new get all table for trafficsplitter
tthebst Jul 6, 2020
e428df3
add endpoint duplication check
tthebst Jul 6, 2020
3aa441e
add check if api used by apisplitter
tthebst Jul 8, 2020
528e143
fix bug virtual service naming and remove print statements
tthebst Jul 8, 2020
764a22e
apisplitter not supported on local check
tthebst Jul 8, 2020
caba5a9
imporve cli output format
tthebst Jul 8, 2020
b41d640
fixes and formatting
tthebst Jul 8, 2020
699534f
allow 0 weight trafficsplitter
tthebst Jul 8, 2020
fbc6f1d
Merge branch 'master' into trafficsplit
tthebst Jul 9, 2020
982e2c6
update cli trafficsplit get table
tthebst Jul 11, 2020
6effac9
improve error messages
tthebst Jul 11, 2020
80ce487
Merge branch 'trafficsplit' of github.com:cortexlabs/cortex into traf…
tthebst Jul 11, 2020
4180e84
Merge branch 'master' into trafficsplit
vishalbollu Jul 14, 2020
49a909d
Update apisplit.yaml
vishalbollu Jul 14, 2020
d22e9d8
rewrite virtual service destination definition
tthebst Jul 14, 2020
3bc5ab4
code review improvments
tthebst Jul 15, 2020
cdbeb86
review improvements
tthebst Jul 16, 2020
dc9c7bb
code review improvements
tthebst Jul 22, 2020
5e2bc69
remove apisplit example
tthebst Jul 22, 2020
d784c5a
correct table for cortex get
tthebst Jul 22, 2020
493c1d1
remove print statements
tthebst Jul 22, 2020
7dcf104
code review improvements
tthebst Jul 26, 2020
b414dda
add API uniqueness check && review improvements
tthebst Jul 28, 2020
3f0afe5
Nits
vishalbollu Jul 29, 2020
b8b8ba8
Only allow logs command for Sync APIs
vishalbollu Jul 29, 2020
da67d06
Merge branch 'master' into trafficsplit
vishalbollu Jul 29, 2020
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
205 changes: 166 additions & 39 deletions cli/cmd/get.go
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,6 @@ import (
"github.com/cortexlabs/cortex/pkg/lib/errors"
"github.com/cortexlabs/cortex/pkg/lib/exit"
"github.com/cortexlabs/cortex/pkg/lib/json"
"github.com/cortexlabs/cortex/pkg/lib/sets/strset"
s "github.com/cortexlabs/cortex/pkg/lib/strings"
"github.com/cortexlabs/cortex/pkg/lib/table"
"github.com/cortexlabs/cortex/pkg/lib/telemetry"
Expand All @@ -49,18 +48,21 @@ import (
)

const (
_titleEnvironment = "env"
_titleAPI = "api"
_titleStatus = "status"
_titleUpToDate = "up-to-date"
_titleStale = "stale"
_titleRequested = "requested"
_titleFailed = "failed"
_titleLastupdated = "last update"
_titleAvgRequest = "avg request"
_title2XX = "2XX"
_title4XX = "4XX"
_title5XX = "5XX"
_titleEnvironment = "env"
_titleAPI = "api"
_titleAPISplitter = "api splitter"
_titleAPIs = "apis"
_apiSplitterWeights = "weights"
_titleStatus = "status"
_titleUpToDate = "up-to-date"
_titleStale = "stale"
_titleRequested = "requested"
_titleFailed = "failed"
_titleLastupdated = "last update"
_titleAvgRequest = "avg request"
_title2XX = "2XX"
_title4XX = "4XX"
_title5XX = "5XX"
)

var (
Expand Down Expand Up @@ -146,8 +148,11 @@ func getAPIsInAllEnvironments() (string, error) {
}

var allSyncAPIs []schema.SyncAPI
var allEnvs []string
var allAPISplitters []schema.APISplitter
var allEnvsSyncAPI []string
var allEnvsAPISplitter []string
errorsMap := map[string]error{}
// get apis from both environments
for _, env := range cliConfig.Environments {
var apisRes schema.GetAPIsResponse
var err error
Expand All @@ -159,34 +164,35 @@ func getAPIsInAllEnvironments() (string, error) {

if err == nil {
for range apisRes.SyncAPIs {
allEnvs = append(allEnvs, env.Name)
allEnvsSyncAPI = append(allEnvsSyncAPI, env.Name)
}
for range apisRes.APISplitter {
allEnvsAPISplitter = append(allEnvsAPISplitter, env.Name)
}

allSyncAPIs = append(allSyncAPIs, apisRes.SyncAPIs...)
if env.Provider == types.AWSProviderType {
allAPISplitters = append(allAPISplitters, apisRes.APISplitter...)
}
} else {
errorsMap[env.Name] = err
}
}

out := ""
var apiSplitTable table.Table
var syncAPITable table.Table

// build different table depending on kinds that are deployed
if len(allSyncAPIs) == 0 {
if len(errorsMap) == 1 {
// Print the error if there is just one
exit.Error(errors.FirstErrorInMap(errorsMap))
}
// if all envs errored, skip it "no apis are deployed" since it's misleading
if len(errorsMap) != len(cliConfig.Environments) {
out += console.Bold("no apis are deployed") + "\n"
}
apiSplitTable = apiSplitterListTable(allAPISplitters, allEnvsAPISplitter)
out = apiSplitTable.MustFormat()
} else if len(allAPISplitters) == 0 {
syncAPITable = apiTable(allSyncAPIs, allEnvsSyncAPI)
out = syncAPITable.MustFormat()
} else {
t := apiTable(allSyncAPIs, allEnvs)

if strset.New(allEnvs...).IsEqual(strset.New(types.LocalProviderType.String())) {
hideReplicaCountColumns(&t)
}

out += t.MustFormat()
apiSplitTable = apiSplitterListTable(allAPISplitters, allEnvsAPISplitter)
syncAPITable = apiTable(allSyncAPIs, allEnvsSyncAPI)
out = syncAPITable.MustFormat() + "\n" + apiSplitTable.MustFormat()
}

if len(errorsMap) == 1 {
Expand Down Expand Up @@ -229,7 +235,7 @@ func getAPIs(env cliconfig.Environment, printEnv bool) (string, error) {
}
}

if len(apisRes.SyncAPIs) == 0 {
if len(apisRes.SyncAPIs) == 0 && len(apisRes.APISplitter) == 0 {
return console.Bold("no apis are deployed"), nil
}

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

t := apiTable(apisRes.SyncAPIs, envNames)

t.FindHeaderByTitle(_titleEnvironment).Hidden = true
var apiSplitTable table.Table
var syncAPITable table.Table
var out string

out := t.MustFormat()
// build different table depending on kinds that are deployed
if len(apisRes.SyncAPIs) == 0 {
apiSplitTable = apiSplitterListTable(apisRes.APISplitter, envNames)
apiSplitTable.FindHeaderByTitle(_titleEnvironment).Hidden = true
out = apiSplitTable.MustFormat()
} else if len(apisRes.APISplitter) == 0 {
syncAPITable = apiTable(apisRes.SyncAPIs, envNames)
syncAPITable.FindHeaderByTitle(_titleEnvironment).Hidden = true
out = syncAPITable.MustFormat()
} else {
apiSplitTable = apiSplitterListTable(apisRes.APISplitter, envNames)
syncAPITable = apiTable(apisRes.SyncAPIs, envNames)
apiSplitTable.FindHeaderByTitle(_titleEnvironment).Hidden = true
syncAPITable.FindHeaderByTitle(_titleEnvironment).Hidden = true
out = syncAPITable.MustFormat() + "\n" + apiSplitTable.MustFormat()
}

if env.Provider == types.LocalProviderType {
hideReplicaCountColumns(&t)
// apisplitter not supported in local
hideReplicaCountColumns(&syncAPITable)
mismatchedVersionAPIsErrorMessage, _ := getLocalVersionMismatchedAPIsMessage()
if len(mismatchedVersionAPIsErrorMessage) > 0 {
out += "\n" + mismatchedVersionAPIsErrorMessage
Expand Down Expand Up @@ -292,7 +314,111 @@ func getAPI(env cliconfig.Environment, apiName string) (string, error) {
return "", err
}
}
return syncAPITable(apiRes.SyncAPI, env)
if apiRes.SyncAPI != nil {
return syncAPITable(apiRes.SyncAPI, env)
}
if apiRes.APISplitter != nil {
return apiSplitterTable(apiRes.APISplitter, env)
}
return "", nil
}

func apiSplitterTable(apiSplitter *schema.APISplitter, env cliconfig.Environment) (string, error) {
var out string

lastUpdated := time.Unix(apiSplitter.Spec.LastUpdated, 0)
out += console.Bold("kind: ") + apiSplitter.Spec.Kind.String() + "\n\n"
out += console.Bold("last updated: ") + libtime.SinceStr(&lastUpdated) + "\n\n"

t, err := trafficSplitTable(*apiSplitter, env)
if err != nil {
return "", err
}
t.FindHeaderByTitle(_titleEnvironment).Hidden = true

out += t.MustFormat()

apiEndpoint := apiSplitter.BaseURL
if env.Provider == types.AWSProviderType {
apiEndpoint = urls.Join(apiSplitter.BaseURL, *apiSplitter.Spec.Networking.Endpoint)
if apiSplitter.Spec.Networking.APIGateway == userconfig.NoneAPIGatewayType {
apiEndpoint = strings.Replace(apiEndpoint, "https://", "http://", 1)
}
}

out += "\n" + console.Bold("endpoint: ") + apiEndpoint

out += fmt.Sprintf("\n%s curl %s -X POST -H \"Content-Type: application/json\" -d @sample.json\n", console.Bold("curl:"), apiEndpoint)

out += titleStr("configuration") + strings.TrimSpace(apiSplitter.Spec.UserStr(env.Provider))

return out, nil
}

func trafficSplitTable(apiSplitter schema.APISplitter, env cliconfig.Environment) (table.Table, error) {
rows := make([][]interface{}, 0, len(apiSplitter.Spec.APIs))

for _, api := range apiSplitter.Spec.APIs {
apiRes, err := cluster.GetAPI(MustGetOperatorConfig(env.Name), api.Name)
if err != nil {
return table.Table{}, err
}
lastUpdated := time.Unix(apiRes.SyncAPI.Spec.LastUpdated, 0)
rows = append(rows, []interface{}{
env.Name,
apiRes.SyncAPI.Spec.Name,
api.Weight,
apiRes.SyncAPI.Status.Message(),
apiRes.SyncAPI.Status.Requested,
libtime.SinceStr(&lastUpdated),
latencyStr(&apiRes.SyncAPI.Metrics),
code2XXStr(&apiRes.SyncAPI.Metrics),
code5XXStr(&apiRes.SyncAPI.Metrics),
})
}

return table.Table{
Headers: []table.Header{
{Title: _titleEnvironment},
{Title: _titleAPIs},
{Title: _apiSplitterWeights},
{Title: _titleStatus},
{Title: _titleRequested},
{Title: _titleLastupdated},
{Title: _titleAvgRequest},
{Title: _title2XX},
{Title: _title5XX},
},
Rows: rows,
}, nil
}

func apiSplitterListTable(apiSplitter []schema.APISplitter, envNames []string) table.Table {
rows := make([][]interface{}, 0, len(apiSplitter))
for i, splitAPI := range apiSplitter {
lastUpdated := time.Unix(splitAPI.Spec.LastUpdated, 0)
var apis []string
for _, api := range splitAPI.Spec.APIs {
apis = append(apis, api.Name+":"+s.Int(api.Weight))
}
apisStr := s.TruncateEllipses(strings.Join(apis, " "), 100)
rows = append(rows, []interface{}{
envNames[i],
splitAPI.Spec.Name,
apisStr,
libtime.SinceStr(&lastUpdated),
})
}

return table.Table{
Headers: []table.Header{
{Title: _titleEnvironment},
{Title: _titleAPISplitter},
{Title: _titleAPIs},
{Title: _titleLastupdated},
},
Rows: rows,
}
}

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

out += console.Bold("kind: ") + syncAPI.Spec.Kind.String() + "\n\n"

out += t.MustFormat()

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

func apiTable(syncAPIs []schema.SyncAPI, envNames []string) table.Table {
rows := make([][]interface{}, 0, len(syncAPIs))

var totalFailed int32
var totalStale int32
var total4XX int
Expand Down
43 changes: 26 additions & 17 deletions pkg/lib/k8s/virtual_service.go
Original file line number Diff line number Diff line change
Expand Up @@ -33,17 +33,35 @@ var _virtualServiceTypeMeta = kmeta.TypeMeta{
}

type VirtualServiceSpec struct {
Name string
Gateways []string
Name string
Gateways []string
Destinations []Destination
Path string
Rewrite *string
Labels map[string]string
Annotations map[string]string
}

type Destination struct {
ServiceName string
ServicePort int32
Path string
Rewrite *string
Labels map[string]string
Annotations map[string]string
Weight int32
Port uint32
}

func VirtualService(spec *VirtualServiceSpec) *istioclientnetworking.VirtualService {
destinations := []*istionetworking.HTTPRouteDestination{}
for _, destination := range spec.Destinations {
destinations = append(destinations, &istionetworking.HTTPRouteDestination{
Destination: &istionetworking.Destination{
Host: destination.ServiceName,
Port: &istionetworking.PortSelector{
Number: destination.Port,
},
},
Weight: destination.Weight,
})
}

virtualService := &istioclientnetworking.VirtualService{
TypeMeta: _virtualServiceTypeMeta,
ObjectMeta: kmeta.ObjectMeta{
Expand All @@ -65,16 +83,7 @@ func VirtualService(spec *VirtualServiceSpec) *istioclientnetworking.VirtualServ
},
},
},
Route: []*istionetworking.HTTPRouteDestination{
{
Destination: &istionetworking.Destination{
Host: spec.ServiceName,
Port: &istionetworking.PortSelector{
Number: uint32(spec.ServicePort),
},
},
},
},
Route: destinations,
},
},
},
Expand Down
6 changes: 5 additions & 1 deletion pkg/operator/endpoints/logs.go
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ import (
"net/http"

"github.com/cortexlabs/cortex/pkg/operator/resources"
"github.com/cortexlabs/cortex/pkg/types/userconfig"
"github.com/gorilla/mux"
"github.com/gorilla/websocket"
)
Expand All @@ -32,11 +33,14 @@ func ReadLogs(w http.ResponseWriter, r *http.Request) {
respondError(w, r, err)
return
}

if deployedResource == nil {
respondError(w, r, resources.ErrorAPINotDeployed(apiName))
return
}
if deployedResource.Kind != userconfig.SyncAPIKind {
respondError(w, r, resources.ErrorOperationNotSupportedForKind(deployedResource.Kind))
return
}

upgrader := websocket.Upgrader{}
socket, err := upgrader.Upgrade(w, r, nil)
Expand Down
Loading