Skip to content

Commit

Permalink
Add create router version feature (#179)
Browse files Browse the repository at this point in the history
* Add create router version endpoint

* Edit error message

* Update OpenAPI specs and API with new endpoint

* Add tests for CreateRouterVersion

* Add UI components for creating router versions without deployment

* Refactor UI submit event

* Add SDK method to create new router version

* Add tests for create version SDK method

* Refactor error handling

* Rename buttons

* Change button colours to shades of primary colour

* Add custom CSS colours

* Refactor variables in router view

* Refactor confirmation messages

* Update put operation to create router versions to a post operation

* Add documentation for creating routers with the UI

* Add SDK sample to reflect saving of router versions

* Remove redundant words from OpenAPI spec descriptions

* Rename UpdateSummary to RouterUpdateSummary for consistency

* Rename callback functions for clarity

* Refactor VersionComparisonView to receive two onSubmit handlers

* Simplify edit a router sample

* Refactor create_version to now be a classmethod of RouterVersion

* Refactor CreateRouterVersion endpoint to use RouterVersionConfig

* Edit UI to use refactored schema for creating router version

* Refactor create_version to now be a classmethod of RouterVersion

* Pin cloudpickle to requirements

* Add e2e test for creating router versions

* Fix e2e test

* Fix e2e test bug

* Refactor OpenAPI specs and SDK

* Simplify router version creation

* Refactor CreateOrUpdateRouterRequest

* Add convenience method to create routers to Router class

* Fix lint comments
  • Loading branch information
deadlycoconuts authored Mar 22, 2022
1 parent d18df98 commit 2d9310e
Show file tree
Hide file tree
Showing 41 changed files with 1,327 additions and 441 deletions.
355 changes: 196 additions & 159 deletions api/api/openapi.bundle.yaml

Large diffs are not rendered by default.

123 changes: 80 additions & 43 deletions api/api/specs/routers.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@ paths:
parameters:
- in: "path"
name: "project_id"
description: "project id of the project to retrieve routers from"
description: "id of the project to retrieve routers from"
schema:
<<: *id
required: true
Expand All @@ -45,7 +45,7 @@ paths:
parameters:
- in: "path"
name: "project_id"
description: "project id of the project to save router"
description: "id of the project to save router"
schema:
<<: *id
required: true
Expand Down Expand Up @@ -74,7 +74,7 @@ paths:
parameters:
- in: "path"
name: "project_id"
description: "project id of the project to retrieve routers from"
description: "id of the project to retrieve routers from"
schema:
<<: *id
required: true
Expand All @@ -101,7 +101,7 @@ paths:
parameters:
- in: "path"
name: "project_id"
description: "project id of the project of the router"
description: "id of the project of the router"
schema:
<<: *id
required: true
Expand Down Expand Up @@ -136,7 +136,7 @@ paths:
parameters:
- in: "path"
name: "project_id"
description: "project id of the project of the router"
description: "id of the project of the router"
schema:
<<: *id
required: true
Expand Down Expand Up @@ -227,7 +227,7 @@ paths:
parameters:
- in: "path"
name: "project_id"
description: "project id of the project to retrieve routers from"
description: "id of the project to retrieve routers from"
schema:
<<: *id
required: true
Expand All @@ -250,6 +250,40 @@ paths:
description: "Invalid project_id or router_id"
404:
description: "No router versions found"
post:
tags: *tags
summary: "Create router version without deploying it"
parameters:
- in: "path"
name: "project_id"
description: "id of the project of the router"
schema:
<<: *id
required: true
- in: "path"
name: "router_id"
description: "id of the router to create a new version for"
schema:
<<: *id
required: true
requestBody:
description: "router configuration to save"
required: true
content:
application/json:
schema:
$ref: "#/components/schemas/RouterVersionConfig"
responses:
200:
description: "OK"
content:
application/json:
schema:
$ref: "#/components/schemas/RouterVersion"
400:
description: "Invalid project_id, router_id or router configuration"
500:
description: "Unable to save configuration"

"/projects/{project_id}/routers/{router_id}/versions/{version}":
get:
Expand All @@ -258,7 +292,7 @@ paths:
parameters:
- in: "path"
name: "project_id"
description: "project id of the project to retrieve routers from"
description: "id of the project to retrieve routers from"
schema:
<<: *id
required: true
Expand Down Expand Up @@ -292,7 +326,7 @@ paths:
parameters:
- in: "path"
name: "project_id"
description: "project id of the project of the router"
description: "id of the project of the router"
schema:
<<: *id
required: true
Expand Down Expand Up @@ -565,43 +599,46 @@ components:
type: "string"
pattern: '^[a-z0-9-]*$'
config:
$ref: "#/components/schemas/RouterVersionConfig"

RouterVersionConfig:
type: "object"
required:
- routes
- default_route_id
- experiment_engine
- timeout
- log_config
properties:
routes:
type: "array"
items:
$ref: "#/components/schemas/Route"
rules:
type: "array"
items:
$ref: "#/components/schemas/TrafficRule"
default_route_id:
type: "string"
experiment_engine:
$ref: "experiment-engines.yaml#/components/schemas/ExperimentConfig"
resource_request:
$ref: "#/components/schemas/ResourceRequest"
timeout:
<<: *timeout
log_config:
type: "object"
required:
- routes
- default_route_id
- experiment_engine
- timeout
- log_config
properties:
routes:
type: "array"
items:
$ref: "#/components/schemas/Route"
rules:
type: "array"
items:
$ref: "#/components/schemas/TrafficRule"
default_route_id:
type: "string"
experiment_engine:
$ref: "experiment-engines.yaml#/components/schemas/ExperimentConfig"
resource_request:
$ref: "#/components/schemas/ResourceRequest"
timeout:
<<: *timeout
log_config:
type: "object"
properties:
result_logger_type:
$ref: "#/components/schemas/ResultLoggerType"
bigquery_config:
$ref: "#/components/schemas/BigQueryConfig"
kafka_config:
$ref: "#/components/schemas/KafkaConfig"
enricher:
$ref: "#/components/schemas/Enricher"
ensembler:
$ref: "#/components/schemas/RouterEnsemblerConfig"
result_logger_type:
$ref: "#/components/schemas/ResultLoggerType"
bigquery_config:
$ref: "#/components/schemas/BigQueryConfig"
kafka_config:
$ref: "#/components/schemas/KafkaConfig"
enricher:
$ref: "#/components/schemas/Enricher"
ensembler:
$ref: "#/components/schemas/RouterEnsemblerConfig"

Route:
type: "object"
Expand Down
1 change: 1 addition & 0 deletions api/e2e/test/00_all_e2e_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -70,6 +70,7 @@ func TestEndToEnd(t *testing.T) {
t.Parallel()
t.Run("CreateRouter_KnativeServices", TestCreateRouter)
t.Run("UpdateRouter_InvalidConfig", TestUpdateRouterInvalidConfig)
t.Run("CreateRouterVersion", TestCreateRouterVersion)
t.Run("UndeployRouter", TestUndeployRouter)
t.Run("DeployRouterVersion_InvalidConfig", TestDeployRouterInvalidConfig)
t.Run("DeployRouter", TestDeployValidConfig)
Expand Down
148 changes: 148 additions & 0 deletions api/e2e/test/03_create_router_version_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,148 @@
//go:build e2e
// +build e2e

package e2e

import (
"bytes"
"context"
"fmt"
"io/ioutil"
"net/http"
"path/filepath"
"testing"

"github.com/tidwall/gjson"

"github.com/gojek/turing/api/turing/models"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
)

/*
Pre:
testCreateRouter run successfully.
Steps:
Create a router version with a valid router config
b. GET router version 3 > shows status "undeployed"
c. GET router > config still shows version 1
d. Test cluster state that all deployments exist
e. Make a request to the router, validate the response.
*/
func TestCreateRouterVersion(t *testing.T) {
// Read existing router that MUST have already exists from previous create router e2e test
// Router name is assumed to follow this format: e2e-experiment-{{.TestID}}
routerName := "e2e-experiment-" + globalTestContext.TestID
t.Log(fmt.Sprintf("Retrieving router with name '%s' created from previous test step", routerName))
existingRouter, err := getRouterByName(
globalTestContext.httpClient, globalTestContext.APIBasePath, globalTestContext.ProjectID, routerName)
require.NoError(t, err)

// Read router config test data
data := makeRouterPayload(filepath.Join("testdata", "create_router_version.json.tmpl"), globalTestContext)

// Update router
url := fmt.Sprintf(
"%s/projects/%d/routers/%d/versions",
globalTestContext.APIBasePath,
globalTestContext.ProjectID,
existingRouter.ID,
)
t.Log("Creating router version: POST " + url)
req, err := http.NewRequest("POST", url, bytes.NewReader(data))
require.NoError(t, err)
req.Header.Set("Content-Type", "application/json")
resp, err := globalTestContext.httpClient.Do(req)
require.NoError(t, err)
assert.Equal(t, http.StatusOK, resp.StatusCode, readBody(t, resp))

// Get router version 3
t.Log("Testing GET router version")
routerVersion, err := getRouterVersion(
globalTestContext.httpClient,
globalTestContext.APIBasePath,
globalTestContext.ProjectID,
int(existingRouter.ID),
3,
)
require.NoError(t, err)
assert.Equal(t, models.RouterVersionStatusUndeployed, routerVersion.Status)

t.Log("Ensure existing router did not update the version to the new version i.e. the version still unchanged at 1")
router, err := getRouter(
globalTestContext.httpClient,
globalTestContext.APIBasePath,
globalTestContext.ProjectID,
int(existingRouter.ID),
)
require.NoError(t, err)
require.NotNil(t, router.CurrRouterVersion)
assert.Equal(t, 1, int(router.CurrRouterVersion.Version))

downstream, err := getRouterDownstream(globalTestContext.clusterClients,
globalTestContext.ProjectName,
fmt.Sprintf("%s-turing-router", router.Name))
assert.NoError(t, err)
assert.Equal(t, downstream, fmt.Sprintf("%s-turing-router-%d.%s.%s",
router.Name, 1, globalTestContext.ProjectName, globalTestContext.KServiceDomain))

// Check that previous enricher, router, ensembler deployments exist
t.Log("Checking cluster state")
assert.True(t, isDeploymentExists(
globalTestContext.clusterClients,
globalTestContext.ProjectName,
fmt.Sprintf("%s-turing-router-%d-0-deployment", router.Name, 1)))
assert.True(t, isDeploymentExists(
globalTestContext.clusterClients,
globalTestContext.ProjectName,
fmt.Sprintf("%s-turing-enricher-%d-0-deployment", router.Name, 1)))
assert.True(t, isDeploymentExists(
globalTestContext.clusterClients,
globalTestContext.ProjectName,
fmt.Sprintf("%s-turing-ensembler-%d-0-deployment", router.Name, 1)))

// Make request to router
t.Log("Testing router endpoint")
router, err = getRouter(
globalTestContext.httpClient,
globalTestContext.APIBasePath,
globalTestContext.ProjectID,
int(router.ID),
)
require.NoError(t, err)
assert.Equal(t,
fmt.Sprintf(
"http://%s-turing-router.%s.%s/v1/predict",
router.Name,
globalTestContext.ProjectName,
globalTestContext.KServiceDomain,
),
router.Endpoint,
)
req, err = http.NewRequestWithContext(
context.Background(),
http.MethodPost,
router.Endpoint,
ioutil.NopCloser(bytes.NewReader([]byte(`{}`))),
)
require.NoError(t, err)
resp, err = globalTestContext.httpClient.Do(req)
require.NoError(t, err)
responseBytes, err := ioutil.ReadAll(resp.Body)
defer resp.Body.Close()
require.NoError(t, err)
actualResponse := gjson.GetBytes(responseBytes, "json.response").String()
expectedResponse := `{
"experiment": {},
"route_responses": [
{
"data": {
"version": "control"
},
"is_default": true,
"route": "control"
}
]
}`
assert.JSONEq(t, expectedResponse, actualResponse)
}
File renamed without changes.
File renamed without changes.
File renamed without changes.
Loading

0 comments on commit 2d9310e

Please sign in to comment.