You are viewing a plain text version of this content. The canonical link for it is here.
Posted to notifications@apisix.apache.org by bz...@apache.org on 2022/03/22 03:23:08 UTC
[apisix-dashboard] branch master updated: fix(import routes): merge route when route have the same name (#2330)
This is an automated email from the ASF dual-hosted git repository.
bzp2010 pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/apisix-dashboard.git
The following commit(s) were added to refs/heads/master by this push:
new a7e700a fix(import routes): merge route when route have the same name (#2330)
a7e700a is described below
commit a7e700a7baabd9bd5f7250e9f102dd94109f202d
Author: 吴治国 <wz...@gmail.com>
AuthorDate: Tue Mar 22 11:23:00 2022 +0800
fix(import routes): merge route when route have the same name (#2330)
---
api/internal/handler/data_loader/route_export.go | 4 +
api/internal/handler/data_loader/route_import.go | 36 +++++-
api/internal/utils/utils.go | 16 +++
api/test/e2e/route_import_test.go | 134 +++++++++++++++++++++
api/test/e2enew/route/route_export_test.go | 16 +++
.../integration/route/import_export_route.spec.js | 11 +-
6 files changed, 212 insertions(+), 5 deletions(-)
diff --git a/api/internal/handler/data_loader/route_export.go b/api/internal/handler/data_loader/route_export.go
index dd0c3c5..a61d5d1 100644
--- a/api/internal/handler/data_loader/route_export.go
+++ b/api/internal/handler/data_loader/route_export.go
@@ -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-id"] = route.ID
+ }
+
// Parse Route URIs
paths, paramsRefs = ParseRouteUris(route, paths, paramsRefs, pathItem, _pathNumber())
diff --git a/api/internal/handler/data_loader/route_import.go b/api/internal/handler/data_loader/route_import.go
index e34a852..4f17dd0 100644
--- a/api/internal/handler/data_loader/route_import.go
+++ b/api/internal/handler/data_loader/route_import.go
@@ -137,6 +137,22 @@ func (h *ImportHandler) Import(c droplet.Context) (interface{}, error) {
}
}
+ // merge route
+ idRoute := make(map[string]*entity.Route)
+ for _, route := range routes {
+ if existRoute, ok := idRoute[route.ID.(string)]; ok {
+ uris := append(existRoute.Uris, route.Uris...)
+ existRoute.Uris = uris
+ } else {
+ idRoute[route.ID.(string)] = route
+ }
+ }
+
+ 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 {
@@ -168,7 +184,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) {
diff --git a/api/internal/utils/utils.go b/api/internal/utils/utils.go
index 4d8150b..c7962be 100644
--- a/api/internal/utils/utils.go
+++ b/api/internal/utils/utils.go
@@ -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) {
diff --git a/api/test/e2e/route_import_test.go b/api/test/e2e/route_import_test.go
index a57533b..3043abb 100644
--- a/api/test/e2e/route_import_test.go
+++ b/api/test/e2e/route_import_test.go
@@ -577,3 +577,137 @@ 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","POST","PUT","DELETE"],
+ "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","POST","PUT","DELETE"]`,
+ `"/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)
+ }
+}
diff --git a/api/test/e2enew/route/route_export_test.go b/api/test/e2enew/route/route_export_test.go
index 2497f98..7e245df 100644
--- a/api/test/e2enew/route/route_export_test.go
+++ b/api/test/e2enew/route/route_export_test.go
@@ -56,6 +56,7 @@ var _ = ginkgo.Describe("Route", func() {
"security": [],
"x-apisix-enable_websocket": false,
"x-apisix-hosts": ["foo.com", "*.bar.com"],
+ "x-apisix-id":"r1",
"x-apisix-labels": {
"build": "16",
"env": "production",
@@ -89,6 +90,7 @@ var _ = ginkgo.Describe("Route", func() {
"security": [],
"x-apisix-enable_websocket": false,
"x-apisix-hosts": ["foo.com", "*.bar.com"],
+ "x-apisix-id":"r1",
"x-apisix-labels": {
"build": "16",
"env": "production",
@@ -183,6 +185,7 @@ var _ = ginkgo.Describe("Route", func() {
"security": [],
"x-apisix-enable_websocket": false,
"x-apisix-host": "*.bar.com",
+ "x-apisix-id":"r2",
"x-apisix-labels": {
"build": "16",
"env": "production",
@@ -216,6 +219,7 @@ var _ = ginkgo.Describe("Route", func() {
"security": [],
"x-apisix-enable_websocket": false,
"x-apisix-host": "*.bar.com",
+ "x-apisix-id":"r2",
"x-apisix-labels": {
"build": "16",
"env": "production",
@@ -403,6 +407,7 @@ var _ = ginkgo.Describe("Route", func() {
}
},
"x-apisix-enable_websocket": false,
+ "x-apisix-id":"r3",
"x-apisix-labels": {
"build": "16",
"env": "production",
@@ -583,6 +588,7 @@ var _ = ginkgo.Describe("Route", func() {
},
"security": [],
"x-apisix-enable_websocket": false,
+ "x-apisix-id":"r4",
"x-apisix-labels": {
"build": "16",
"env": "production",
@@ -771,6 +777,7 @@ var _ = ginkgo.Describe("Route", func() {
},
"security": [],
"x-apisix-enable_websocket": false,
+ "x-apisix-id":"r5",
"x-apisix-labels": {
"build": "16",
"env": "production",
@@ -955,6 +962,7 @@ var _ = ginkgo.Describe("Route", func() {
},
"security": [],
"x-apisix-enable_websocket": false,
+ "x-apisix-id":"r8",
"x-apisix-plugins": {
"prometheus": {
"disable": false
@@ -1077,6 +1085,7 @@ var _ = ginkgo.Describe("Route", func() {
"security": [],
"summary": "所有",
"x-apisix-enable_websocket": false,
+ "x-apisix-id":"r9",
"x-apisix-labels": {
"API_VERSION": "v1",
"test": "1"
@@ -1276,6 +1285,7 @@ var _ = ginkgo.Describe("Route", func() {
"security": [],
"summary": "所有",
"x-apisix-enable_websocket": false,
+ "x-apisix-id":"r10",
"x-apisix-labels": {
"API_VERSION": "v1",
"test": "1"
@@ -1901,6 +1911,7 @@ var _ = ginkgo.Describe("Route", func() {
},
"summary": "所有",
"x-apisix-enable_websocket": false,
+ "x-apisix-id":"r1",
"x-apisix-labels": {
"build": "16",
"env": "production",
@@ -2043,6 +2054,7 @@ var _ = ginkgo.Describe("Route", func() {
},
"summary": "所有",
"x-apisix-enable_websocket": false,
+ "x-apisix-id":"r2",
"x-apisix-labels": {
"build": "16",
"env": "production",
@@ -2197,6 +2209,7 @@ var _ = ginkgo.Describe("Route", func() {
"summary": "所有",
"x-apisix-enable_websocket": false,
"x-apisix-hosts": ["test.com"],
+ "x-apisix-id":"r1",
"x-apisix-priority": 0,
"x-apisix-status": 1
}
@@ -2300,6 +2313,7 @@ var _ = ginkgo.Describe("Route", func() {
"summary": "所有",
"x-apisix-enable_websocket": false,
"x-apisix-hosts": ["test.com"],
+ "x-apisix-id":"r1",
"x-apisix-priority": 0,
"x-apisix-status": 1,
"x-apisix-upstream": {
@@ -2322,6 +2336,7 @@ var _ = ginkgo.Describe("Route", func() {
"summary": "所有1",
"x-apisix-enable_websocket": false,
"x-apisix-hosts": ["test.com"],
+ "x-apisix-id":"r2",
"x-apisix-priority": 0,
"x-apisix-status": 1,
"x-apisix-upstream": {
@@ -2344,6 +2359,7 @@ var _ = ginkgo.Describe("Route", func() {
"summary": "所有2",
"x-apisix-enable_websocket": false,
"x-apisix-hosts": ["test.com"],
+ "x-apisix-id":"r3",
"x-apisix-priority": 0,
"x-apisix-status": 1,
"x-apisix-upstream": {
diff --git a/web/cypress/integration/route/import_export_route.spec.js b/web/cypress/integration/route/import_export_route.spec.js
index ec93db3..c27708e 100644
--- a/web/cypress/integration/route/import_export_route.spec.js
+++ b/web/cypress/integration/route/import_export_route.spec.js
@@ -131,16 +131,19 @@ context('import and export routes', () => {
cy.log(`found file ${jsonFile}`);
cy.log('**confirm downloaded json file**');
cy.readFile(jsonFile).then((fileContent) => {
- expect(JSON.stringify(fileContent)).to.equal(JSON.stringify(this.exportFile.jsonFile));
+ const json = fileContent;
+ delete json['paths']['/{params}']['post']['x-apisix-id'];
+ expect(JSON.stringify(json)).to.equal(JSON.stringify(this.exportFile.jsonFile));
});
});
cy.task('findFile', data.yamlMask).then((yamlFile) => {
cy.log(`found file ${yamlFile}`);
cy.log('**confirm downloaded yaml file**');
cy.readFile(yamlFile).then((fileContent) => {
- expect(JSON.stringify(yaml.load(fileContent), null, null)).to.equal(
- JSON.stringify(this.exportFile.yamlFile),
- );
+ const json = yaml.load(fileContent);
+ delete json['paths']['/{params}']['post']['x-apisix-id'];
+ delete json['paths']['/{params}-APISIX-REPEAT-URI-2']['post']['x-apisix-id'];
+ expect(JSON.stringify(json, null, null)).to.equal(JSON.stringify(this.exportFile.yamlFile));
});
});
});