Skip to content
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

fix(import routes): merge route when route have the same name #2330

Merged
merged 11 commits into from
Mar 22, 2022
Merged
Show file tree
Hide file tree
Changes from 4 commits
Commits
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
1 change: 1 addition & 0 deletions api/internal/core/entity/entity.go
Original file line number Diff line number Diff line change
Expand Up @@ -85,6 +85,7 @@ type Route struct {
ServiceProtocol string `json:"service_protocol,omitempty"`
Labels map[string]string `json:"labels,omitempty"`
EnableWebsocket bool `json:"enable_websocket,omitempty"`
RouteId string `json:"route_id,omitempty"`
kevinw66 marked this conversation as resolved.
Show resolved Hide resolved
Status Status `json:"status"`
}

Expand Down
4 changes: 4 additions & 0 deletions api/internal/handler/data_loader/route_export.go
Original file line number Diff line number Diff line change
Expand Up @@ -224,6 +224,10 @@ func (h *Handler) RouteToOpenAPI3(c droplet.Context, routes []*entity.Route) (*o
extensions["x-apisix-vars"] = route.Vars
}

if route.ID != nil {
extensions["x-apisix-route_id"] = route.ID
}

// Parse Route URIs
paths, paramsRefs = ParseRouteUris(route, paths, paramsRefs, pathItem, _pathNumber())

Expand Down
40 changes: 39 additions & 1 deletion api/internal/handler/data_loader/route_import.go
Original file line number Diff line number Diff line change
Expand Up @@ -137,6 +137,26 @@ func (h *ImportHandler) Import(c droplet.Context) (interface{}, error) {
}
}

// merge route
idRoute := make(map[string]*entity.Route)
for _, route := range routes {
if route.RouteId == "" {
route.RouteId = route.ID.(string)
}
//
if existRoute, ok := idRoute[route.RouteId]; ok {
uris := append(existRoute.Uris, route.Uris...)
existRoute.Uris = uris
} else {
idRoute[route.RouteId] = route
kevinw66 marked this conversation as resolved.
Show resolved Hide resolved
kevinw66 marked this conversation as resolved.
Show resolved Hide resolved
}
}

routes = make([]*entity.Route, 0, len(idRoute))
for _, route := range idRoute {
routes = append(routes, route)
}

// create route
for _, route := range routes {
if Force && route.ID != nil {
Expand Down Expand Up @@ -168,7 +188,25 @@ func checkRouteExist(ctx context.Context, routeStore *store.GenericStore, route
return false
}

if !(item.Host == route.Host && item.URI == route.URI && utils.StringSliceEqual(item.Uris, route.Uris) &&
itemUris := item.Uris
if item.URI != "" {
if itemUris == nil {
itemUris = []string{item.URI}
} else {
itemUris = append(itemUris, item.URI)
}
}

routeUris := route.Uris
if route.URI != "" {
if routeUris == nil {
routeUris = []string{route.URI}
} else {
routeUris = append(routeUris, route.URI)
}
}

if !(item.Host == route.Host && utils.StringSliceContains(itemUris, routeUris) &&
utils.StringSliceEqual(item.RemoteAddrs, route.RemoteAddrs) && item.RemoteAddr == route.RemoteAddr &&
utils.StringSliceEqual(item.Hosts, route.Hosts) && item.Priority == route.Priority &&
utils.ValueEqual(item.Vars, route.Vars) && item.FilterFunc == route.FilterFunc) {
Expand Down
16 changes: 16 additions & 0 deletions api/internal/utils/utils.go
Original file line number Diff line number Diff line change
Expand Up @@ -172,6 +172,22 @@ func ValidateLuaCode(code string) error {
return err
}

func StringSliceContains(a, b []string) bool {
if (a == nil) != (b == nil) {
return false
}

for i := range a {
for j := range b {
if a[i] == b[j] {
return true
}
}
}

return false
}

//
func StringSliceEqual(a, b []string) bool {
if (a == nil) != (b == nil) {
Expand Down
133 changes: 133 additions & 0 deletions api/test/e2e/route_import_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -577,3 +577,136 @@ func TestRoute_export_import(t *testing.T) {
testCaseCheck(tc, t)
}
}

func TestRoute_export_import_merge(t *testing.T) {
// create routes
tests := []HttpTestCase{
{
Desc: "Create a route",
Object: ManagerApiExpect(t),
Method: http.MethodPut,
Path: "/apisix/admin/routes/r1",
Body: `{
"id": "r1",
"uris": ["/test1", "/test2"],
"name": "route_all",
"desc": "所有",
"methods": ["GET"],
kevinw66 marked this conversation as resolved.
Show resolved Hide resolved
"hosts": ["test.com"],
"status": 1,
"upstream": {
"nodes": {
"` + UpstreamIp + `:1980": 1
},
"type": "roundrobin"
}
}`,
Headers: map[string]string{"Authorization": token},
ExpectStatus: http.StatusOK,
Sleep: sleepTime,
},
}
for _, tc := range tests {
testCaseCheck(tc, t)
}

// export routes
time.Sleep(sleepTime)
tmpPath := "/tmp/export.json"
headers := map[string]string{
"Authorization": token,
}
body, status, err := httpGet(ManagerAPIHost+"/apisix/admin/export/routes", headers)
assert.Nil(t, err)
assert.Equal(t, http.StatusOK, status)

content := gjson.Get(string(body), "data")
err = ioutil.WriteFile(tmpPath, []byte(content.Raw), 0644)
assert.Nil(t, err)

// import routes (should failed -- duplicate)
files := []UploadFile{
{Name: "file", Filepath: tmpPath},
}
respBody, status, err := PostFile(ManagerAPIHost+"/apisix/admin/import/routes", nil, files, headers)
assert.Nil(t, err)
assert.Equal(t, 400, status)
assert.True(t, strings.Contains(string(respBody), "duplicate"))
time.Sleep(sleepTime)

// delete routes
tests = []HttpTestCase{
{
Desc: "delete the route1 just created",
Object: ManagerApiExpect(t),
Method: http.MethodDelete,
Path: "/apisix/admin/routes/r1",
Headers: map[string]string{"Authorization": token},
ExpectStatus: http.StatusOK,
},
}
for _, tc := range tests {
testCaseCheck(tc, t)
}

// import again
time.Sleep(sleepTime)
respBody, status, err = PostFile(ManagerAPIHost+"/apisix/admin/import/routes", nil, files, headers)
assert.Nil(t, err)
assert.Equal(t, 200, status)
assert.True(t, strings.Contains(string(respBody), `"data":{"paths":2,"routes":1}`))
time.Sleep(sleepTime)

// sleep for data sync
time.Sleep(sleepTime)

request, _ := http.NewRequest("GET", ManagerAPIHost+"/apisix/admin/routes", nil)
request.Header.Add("Authorization", token)
resp, err := http.DefaultClient.Do(request)
assert.Nil(t, err)
defer resp.Body.Close()
respBody, _ = ioutil.ReadAll(resp.Body)
list := gjson.Get(string(respBody), "data.rows").Value().([]interface{})

assert.Equal(t, 1, len(list))

// verify route data
tests = []HttpTestCase{}
for _, item := range list {
route := item.(map[string]interface{})
tcDataVerify := HttpTestCase{
Desc: "verify data of route2",
Object: ManagerApiExpect(t),
Method: http.MethodGet,
Path: "/apisix/admin/routes/" + route["id"].(string),
Headers: map[string]string{"Authorization": token},
ExpectStatus: http.StatusOK,
ExpectBody: []string{`"methods":["GET"]`,
`"uris":["/test1","/test2"]`,
`"desc":"所有`,
`"hosts":["test.com"]`,
`"upstream":{"nodes":[{"host":"` + UpstreamIp + `","port":1980,"weight":1}],"type":"roundrobin"}`,
},
Sleep: sleepTime,
}
tests = append(tests, tcDataVerify)
}

// delete test data
for _, item := range list {
route := item.(map[string]interface{})
tc := HttpTestCase{
Desc: "delete route",
Object: ManagerApiExpect(t),
Method: http.MethodDelete,
Path: "/apisix/admin/routes/" + route["id"].(string),
Headers: map[string]string{"Authorization": token},
ExpectStatus: http.StatusOK,
}
tests = append(tests, tc)
}

for _, tc := range tests {
testCaseCheck(tc, t)
}
}