diff --git a/api/errno/error.go b/api/errno/error.go index 68f313d028..100408515a 100644 --- a/api/errno/error.go +++ b/api/errno/error.go @@ -57,6 +57,7 @@ var ( DBRouteUpdateError = Message{"010206", "Route update failed: %s", 500} DBRouteDeleteError = Message{"010207", "Route deletion failed: %s", 500} DBRouteReduplicateError = Message{"010208", "Route name is reduplicate : %s", 400} + SetRouteUngroupError = Message{"010209", "Set route ungroup err", 500} // 03 plugins ApisixPluginListError = Message{"010301", "find APISIX plugin list failed: %s", 500} diff --git a/api/route/route.go b/api/route/route.go index b6ace35e9e..5be339f5a7 100644 --- a/api/route/route.go +++ b/api/route/route.go @@ -201,12 +201,17 @@ func updateRoute(c *gin.Context) { } routeGroup := &service.RouteGroupDao{} isCreateGroup := false + isUngroup := false if len(strings.Trim(routeRequest.RouteGroupId, "")) == 0 { - isCreateGroup = true - routeGroup.ID = uuid.NewV4() - // create route group - routeGroup.Name = routeRequest.RouteGroupName - routeRequest.RouteGroupId = routeGroup.ID.String() + if len(strings.Trim(routeRequest.RouteGroupName, "")) > 0 { + isCreateGroup = true + routeGroup.ID = uuid.NewV4() + // create route group + routeGroup.Name = routeRequest.RouteGroupName + routeRequest.RouteGroupId = routeGroup.ID.String() + } else { + isUngroup = true + } } logger.Info(routeRequest.Plugins) db := conf.DB() @@ -253,6 +258,15 @@ func updateRoute(c *gin.Context) { } } } + if isUngroup { + if err := tx.Model(&service.Route{}).Where("id = ?", rid).Update(map[string]interface{}{"route_group_id": "", "route_group_name": ""}).Error; err != nil { + tx.Rollback() + e := errno.FromMessage(errno.SetRouteUngroupError) + logger.Error(e.Msg) + c.AbortWithStatusJSON(http.StatusInternalServerError, e.Response()) + return + } + } if err := tx.Commit().Error; err == nil { // update content_admin_api if rd, err := service.ToRoute(routeRequest, arr, uuid.FromStringOrNil(rid), resp); err != nil { @@ -343,11 +357,13 @@ func createRoute(c *gin.Context) { routeGroup := &service.RouteGroupDao{} isCreateGroup := false if len(strings.Trim(routeRequest.RouteGroupId, "")) == 0 { - isCreateGroup = true - routeGroup.ID = uuid.NewV4() // create route group - routeGroup.Name = routeRequest.RouteGroupName - routeRequest.RouteGroupId = routeGroup.ID.String() + if len(strings.Trim(routeRequest.RouteGroupName, "")) > 0 { + isCreateGroup = true + routeGroup.ID = uuid.NewV4() + routeGroup.Name = routeRequest.RouteGroupName + routeRequest.RouteGroupId = routeGroup.ID.String() + } } logger.Info(routeRequest.Plugins) db := conf.DB() diff --git a/api/route/route_group_test.go b/api/route/route_group_test.go index ce939db4a7..9b6f0ff560 100644 --- a/api/route/route_group_test.go +++ b/api/route/route_group_test.go @@ -17,45 +17,115 @@ package route import ( + "github.com/apisix/manager-api/conf" + "github.com/apisix/manager-api/service" "net/http" "testing" ) -func TestRouteGroupCurd(t *testing.T) { +func TestCreateRouteGroup(t *testing.T) { // create ok handler. Post(uriPrefix+"/routegroups"). Header("Authorization", token). JSON(`{ - "name": "routegroup_test", + "name": "create_route_group_test", "description": "test description" }`). Expect(t). Status(http.StatusOK). End() +} - //c1, _ := service.GetConsumerByUserName("e2e_test_consumer1") - id := "8954a39b-330e-4b85-89f5-d1bbfd785b5b" - //update ok +func TestDuplicateGroupName(t *testing.T) { + // duplicate name handler. - Put(uriPrefix+"/routegroups/"+id). + Post(uriPrefix+"/routegroups"). Header("Authorization", token). JSON(`{ - "name": "routegroup_test2", + "name": "create_route_group_test", "description": "test description" }`). Expect(t). - Status(http.StatusOK). + Status(http.StatusInternalServerError). End() - // duplicate username +} + +func TestUpdateRouteGroup(t *testing.T) { + routeGroup, _ := getRouteGroupByName("create_route_group_test") + //update ok handler. - Post(uriPrefix+"/routegroups"). + Put(uriPrefix+"/routegroups/"+routeGroup.ID.String()). Header("Authorization", token). JSON(`{ - "name": "routegroup_test", + "name": "update_route_group_test", "description": "test description" }`). Expect(t). + Status(http.StatusOK). + End() +} + +func TestDeleteRouteGroupHasRoutes(t *testing.T) { + routeGroup, _ := getRouteGroupByName("update_route_group_test") + // create route + handler.Post(uriPrefix+"/routes"). + Header("Authorization", token). + JSON(`{ + "name":"api-test-for-delete-group", + "desc":"api-test", + "priority":0, + "protocols":["http"], + "hosts":["test.com"], + "paths":["/*"], + "methods":["GET","HEAD","POST","PUT","DELETE","OPTIONS","PATCH"], + "status":false, + "upstream_protocol":"keep", + "plugins":{}, + "uris":["/*"], + "vars":[], + "upstream":{"type":"roundrobin","nodes":{"127.0.0.1:443":1}, + "timeout":{"connect":6000,"send":6000,"read":6000}}, + "upstream_header":{}, + "route_group_id":"` + routeGroup.ID.String() + `", + "route_group_name":"` + routeGroup.Name + `" +}`).Expect(t). + Status(http.StatusOK). + End() + // delete fail + handler. + Delete(uriPrefix+"/routegroups/"+routeGroup.ID.String()). + Header("Authorization", token). + Expect(t). Status(http.StatusInternalServerError). End() + // get api-test-for-group + route, _ := getRouteByName("api-test-for-delete-group") + // delete route + handler. + Delete(uriPrefix+"/routes/"+route.ID.String()). + Header("Authorization", token). + Expect(t). + Status(http.StatusOK). + End() +} + +func TestDeleteRouteGroup(t *testing.T) { + routeGroup, _ := getRouteGroupByName("update_route_group_test") + // delete ok + handler. + Delete(uriPrefix+"/routegroups/"+routeGroup.ID.String()). + Header("Authorization", token). + Expect(t). + Status(http.StatusOK). + End() +} + +func getRouteGroupByName(name string) (*service.RouteGroupDao, error) { + db := conf.DB() + routeGroup := &service.RouteGroupDao{} + if err := db.Table("route_group").Where("name = ?", name).First(&routeGroup).Error; err != nil { + return nil, err + } + return routeGroup, nil } diff --git a/api/route/route_test.go b/api/route/route_test.go new file mode 100644 index 0000000000..97442c0b61 --- /dev/null +++ b/api/route/route_test.go @@ -0,0 +1,143 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package route + +import ( + "net/http" + "testing" + "github.com/apisix/manager-api/conf" + "github.com/apisix/manager-api/service" +) + +func TestCreateRouteForUngroup(t *testing.T) { + // create route with no route group -- test ungroup + handler.Post(uriPrefix+"/routes"). + Header("Authorization", token). + JSON(`{ + "name":"api-test-no-group", + "desc":"api-test-no-group", + "priority":0, + "protocols":["http"], + "hosts":["test.com"], + "paths":["/*"], + "methods":["GET","HEAD","POST","PUT","DELETE","OPTIONS","PATCH"], + "status":false, + "upstream_protocol":"keep", + "plugins":{}, + "uris":["/*"], + "vars":[], + "upstream":{"type":"roundrobin","nodes":{"127.0.0.1:443":1}, + "timeout":{"connect":6000,"send":6000,"read":6000}}, + "upstream_header":{}, + "route_group_id":"", + "route_group_name":"" +}`).Expect(t). + Status(http.StatusOK). + End() +} + +func TestUpdateRouteWithCreateRouteGroup(t *testing.T) { + route, _ := getRouteByName("api-test-no-group") + + // update route for create route group + handler.Put(uriPrefix+"/routes/"+route.ID.String()). + Header("Authorization", token). + JSON(`{ + "name":"api-test-no-group", + "desc":"api-test-no-group", + "priority":0, + "protocols":["http"], + "hosts":["test.com"], + "paths":["/*"], + "methods":["GET","HEAD","POST","PUT","DELETE","OPTIONS","PATCH"], + "status":false, + "upstream_protocol":"keep", + "plugins":{}, + "uris":["/*"], + "vars":[], + "upstream":{"type":"roundrobin","nodes":{"127.0.0.1:443":1}, + "timeout":{"connect":6000,"send":6000,"read":6000}}, + "upstream_header":{}, + "route_group_id":"", + "route_group_name":"route-update-test-create-group" +}`).Expect(t). + Status(http.StatusOK). + End() +} + +func TestCreateRouteWithCreateNewGroup(t *testing.T) { + // create route with new route group + handler.Post(uriPrefix+"/routes"). + Header("Authorization", token). + JSON(`{ + "name":"api-test-new-group", + "desc":"api-test-new-group", + "priority":0, + "protocols":["http"], + "hosts":["test.com"], + "paths":["/*"], + "methods":["GET","HEAD","POST","PUT","DELETE","OPTIONS","PATCH"], + "status":false, + "upstream_protocol":"keep", + "plugins":{}, + "uris":["/*"], + "vars":[], + "upstream":{"type":"roundrobin","nodes":{"127.0.0.1:443":1}, + "timeout":{"connect":6000,"send":6000,"read":6000}}, + "upstream_header":{}, + "route_group_id":"", + "route_group_name":"route-create-test-create-group" +}`).Expect(t). + Status(http.StatusOK). + End() +} + +func TestCreateRouteWithDuplicateGroupName(t *testing.T) { + // create route with duplicate route group name + handler.Post(uriPrefix+"/routes"). + Header("Authorization", token). + JSON(`{ + "name":"api-test", + "desc":"api-test", + "priority":0, + "protocols":["http"], + "hosts":["test.com"], + "paths":["/*"], + "methods":["GET","HEAD","POST","PUT","DELETE","OPTIONS","PATCH"], + "status":false, + "upstream_protocol":"keep", + "plugins":{}, + "uris":["/*"], + "vars":[], + "upstream":{"type":"roundrobin","nodes":{"127.0.0.1:443":1}, + "timeout":{"connect":6000,"send":6000,"read":6000}}, + "upstream_header":{}, + "route_group_id":"", + "route_group_name":"route-create-test-create-group" +}`).Expect(t). + Status(http.StatusInternalServerError). + End() +} + +func getRouteByName(name string) (*service.Route, error) { + db := conf.DB() + route := &service.Route{} + if err := db.Table("routes").Where("name = ?", name).First(&route).Error; err != nil { + return nil, err + } + return route, nil +} diff --git a/api/service/route_group.go b/api/service/route_group.go index b016883944..8eb9a4d472 100644 --- a/api/service/route_group.go +++ b/api/service/route_group.go @@ -66,7 +66,25 @@ func (rd *RouteGroupDao) GetRouteGroupList(routeGroupList *[]RouteGroupDao, sear func (rd *RouteGroupDao) UpdateRouteGroup() error { db := conf.DB() - return db.Model(&RouteGroupDao{}).Update(rd).Error + tx := db.Begin() + defer func() { + if r := recover(); r != nil { + tx.Rollback() + } + }() + if err := tx.Model(&RouteGroupDao{}).Update(rd).Error; err != nil { + tx.Rollback() + return err + } + if err := tx.Table("routes").Where("route_group_id = ?", rd.ID.String()).Update("route_group_name", rd.Name).Error; err != nil { + tx.Rollback() + return err + } + if err := tx.Commit().Error; err != nil { + tx.Rollback() + return err + } + return nil } func (rd *RouteGroupDao) DeleteRouteGroup() error { diff --git a/api/service/route_group_test.go b/api/service/route_group_test.go new file mode 100644 index 0000000000..cb656b336f --- /dev/null +++ b/api/service/route_group_test.go @@ -0,0 +1,120 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package service + +import ( + "testing" + + uuid "github.com/satori/go.uuid" + "github.com/stretchr/testify/assert" +) + +var gid = uuid.NewV4() +var gid2 = uuid.NewV4() + +func TestCreateRouteGroup(t *testing.T) { + routeGroup := &RouteGroupDao{ + Base: Base{}, + Name: "route_group_test", + Description: "route_group_test", + } + routeGroup.ID = gid + // create ok + err := routeGroup.CreateRouteGroup() + as := assert.New(t) + as.Nil(err) +} + +func TestGetRouteGroup(t *testing.T) { + // get group ok + as := assert.New(t) + getGroup := &RouteGroupDao{} + err, i := getGroup.FindRouteGroup(gid.String()) + as.Nil(err) + as.Equal("route_group_test", getGroup.Name) + as.Equal(1, i) +} + +func TestRouteGroupNameDuplicate(t *testing.T) { + // name duplicate + as := assert.New(t) + routeGroup2 := &RouteGroupDao{ + Base: Base{}, + Name: "route_group_test", + Description: "route_group_test", + } + routeGroup2.ID = gid2 + err := routeGroup2.CreateRouteGroup() + as.NotNil(err) + // ok + routeGroup2.Name = "route_group_test2" + err = routeGroup2.CreateRouteGroup() + as.Nil(err) +} + +func TestGetRouteGupList(t *testing.T) { + as := assert.New(t) + // list ok + routeGroups := []RouteGroupDao{} + routeGroup := &RouteGroupDao{} + err, i3 := routeGroup.GetRouteGroupList(&routeGroups, "", 1, 2) + as.Nil(err) + as.Equal(true, i3 >= 2) + as.Equal(2, len(routeGroups)) +} + +func TestUpdateRouteGroup(t *testing.T) { + as := assert.New(t) + // update ok + routeGroup := &RouteGroupDao{ + Base: Base{}, + Name: "route_group_test_update", + Description: "route_group_test_update", + } + routeGroup.ID = gid + err := routeGroup.UpdateRouteGroup() + as.Nil(err) +} + +func TestDeleteRouteGroup(t *testing.T) { + as := assert.New(t) + routeGroup := &RouteGroupDao{ + Base: Base{}, + Name: "route_group_test_update", + Description: "route_group_test_update", + } + routeGroup.ID = gid + // delete ok + err := routeGroup.DeleteRouteGroup() + as.Nil(err) + + deleteGroup := &RouteGroupDao{} + err, i2 := deleteGroup.FindRouteGroup(gid.String()) + as.Nil(err) + as.Equal("", deleteGroup.Name) + as.Equal(0, i2) + + routeGroup2 := &RouteGroupDao{ + Base: Base{}, + Name: "route_group_test", + Description: "route_group_test", + } + routeGroup2.ID = gid2 + err = routeGroup2.DeleteRouteGroup() + as.Nil(err) +}