diff --git a/api/conf/conf.yaml b/api/conf/conf.yaml index 212f1e575b..70efac79ce 100644 --- a/api/conf/conf.yaml +++ b/api/conf/conf.yaml @@ -44,6 +44,8 @@ conf: logs/access.log # supports relative path, absolute path, standard output # such as: logs/access.log, /tmp/logs/access.log, /dev/stdout, /dev/stderr # log example: 2020-12-09T16:38:09.039+0800 INFO filter/logging.go:46 /apisix/admin/routes/r1 {"status": 401, "host": "127.0.0.1:9000", "query": "asdfsafd=adf&a=a", "requestId": "3d50ecb8-758c-46d1-af5b-cd9d1c820156", "latency": 0, "remoteIP": "127.0.0.1", "method": "PUT", "errs": []} + max_cpu: 0 # supports tweaking with the number of OS threads are going to be used for parallelism. Default value: 0 [will use max number of available cpu cores considering hyperthreading (if any)]. If the value is negative, is will not touch the existing parallelism profile. + authentication: secret: secret # secret for jwt token generation. diff --git a/api/internal/conf/conf.go b/api/internal/conf/conf.go index ca8c112772..0e30df4d92 100644 --- a/api/internal/conf/conf.go +++ b/api/internal/conf/conf.go @@ -22,6 +22,7 @@ import ( "log" "os" "path/filepath" + "runtime" "github.com/tidwall/gjson" "gopkg.in/yaml.v2" @@ -96,6 +97,7 @@ type Conf struct { Listen Listen Log Log AllowList []string `yaml:"allow_list"` + MaxCpu int `yaml:"max_cpu"` } type User struct { @@ -139,14 +141,14 @@ func setConf() { if configurationContent, err := ioutil.ReadFile(filePath); err != nil { panic(fmt.Sprintf("fail to read configuration: %s", filePath)) } else { - //configuration := gjson.ParseBytes(configurationContent) + // configuration := gjson.ParseBytes(configurationContent) config := Config{} err := yaml.Unmarshal(configurationContent, &config) if err != nil { log.Printf("conf: %s, error: %v", configurationContent, err) } - //listen + // listen if config.Conf.Listen.Port != 0 { ServerPort = config.Conf.Listen.Port } @@ -160,7 +162,7 @@ func setConf() { initEtcdConfig(config.Conf.Etcd) } - //error log + // error log if config.Conf.Log.ErrorLog.Level != "" { ErrorLogLevel = config.Conf.Log.ErrorLog.Level } @@ -187,7 +189,10 @@ func setConf() { AllowList = config.Conf.AllowList - //auth + // set degree of parallelism + initParallelism(config.Conf.MaxCpu) + + // auth initAuthentication(config.Authentication) initPlugins(config.Plugins) @@ -249,3 +254,16 @@ func initEtcdConfig(conf Etcd) { Prefix: prefix, } } + +// initialize parallelism settings +func initParallelism(choiceCores int) { + if choiceCores < 1 { + return + } + maxSupportedCores := runtime.NumCPU() + + if choiceCores > maxSupportedCores { + choiceCores = maxSupportedCores + } + runtime.GOMAXPROCS(choiceCores) +} diff --git a/api/internal/core/entity/query.go b/api/internal/core/entity/query.go deleted file mode 100644 index ae3834269f..0000000000 --- a/api/internal/core/entity/query.go +++ /dev/null @@ -1,142 +0,0 @@ -/* - * 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 entity - -import ( - "strings" - - "github.com/apisix/manager-api/internal/utils" -) - -type PropertyName string - -const ( - IdProperty = "id" - NameProperty = "name" - SniProperty = "sni" - SnisProperty = "snis" - CreateTimeProperty = "create_time" - UpdateTimeProperty = "update_time" -) - -type ComparableValue interface { - Compare(ComparableValue) int - Contains(ComparableValue) bool -} - -type ComparingString string - -func (comparing ComparingString) Compare(compared ComparableValue) int { - other := compared.(ComparingString) - return strings.Compare(string(comparing), string(other)) -} - -func (comparing ComparingString) Contains(compared ComparableValue) bool { - other := compared.(ComparingString) - return strings.Contains(string(comparing), string(other)) -} - -type ComparingStringArray []string - -func (comparing ComparingStringArray) Compare(compared ComparableValue) int { - other := compared.(ComparingString) - res := -1 - for _, str := range comparing { - result := strings.Compare(str, string(other)) - if result == 0 { - res = 0 - break - } - } - return res -} - -func (comparing ComparingStringArray) Contains(compared ComparableValue) bool { - other := compared.(ComparingString) - res := false - for _, str := range comparing { - if strings.Contains(str, string(other)) { - res = true - break - } - } - return res -} - -type ComparingInt int64 - -func int64Compare(a, b int64) int { - if a > b { - return 1 - } else if a == b { - return 0 - } - return -1 -} - -func (comparing ComparingInt) Compare(compared ComparableValue) int { - other := compared.(ComparingInt) - return int64Compare(int64(comparing), int64(other)) -} - -func (comparing ComparingInt) Contains(compared ComparableValue) bool { - return comparing.Compare(compared) == 0 -} - -func (info BaseInfo) GetProperty(name PropertyName) ComparableValue { - switch name { - case IdProperty: - id := utils.InterfaceToString(info.ID) - return ComparingString(id) - case CreateTimeProperty: - return ComparingInt(info.CreateTime) - case UpdateTimeProperty: - return ComparingInt(info.UpdateTime) - default: - return nil - } -} - -func (route Route) GetProperty(name PropertyName) ComparableValue { - switch name { - case NameProperty: - return ComparingString(route.Name) - default: - return nil - } -} - -func (upstream Upstream) GetProperty(name PropertyName) ComparableValue { - switch name { - case NameProperty: - return ComparingString(upstream.Name) - default: - return nil - } -} - -func (ssl SSL) GetProperty(name PropertyName) ComparableValue { - switch name { - case SniProperty: - return ComparingString(ssl.Sni) - case SnisProperty: - return ComparingStringArray(ssl.Snis) - default: - return nil - } -} diff --git a/api/internal/core/store/query.go b/api/internal/core/store/query.go deleted file mode 100644 index e4c27c1c2f..0000000000 --- a/api/internal/core/store/query.go +++ /dev/null @@ -1,137 +0,0 @@ -// Copyright 2017 The Kubernetes Authors. -// -// Licensed 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 store - -import ( - "github.com/apisix/manager-api/internal/core/entity" - "github.com/apisix/manager-api/internal/log" -) - -type Query struct { - Sort *Sort - Filter *Filter - Pagination *Pagination -} - -type Sort struct { - List []SortBy -} - -type SortBy struct { - Property entity.PropertyName - Ascending bool -} - -var NoSort = &Sort{ - List: []SortBy{}, -} - -type Filter struct { - List []FilterBy -} - -type FilterBy struct { - Property entity.PropertyName - Value entity.ComparableValue -} - -var NoFilter = &Filter{ - List: []FilterBy{}, -} - -type Pagination struct { - PageSize int `json:"page_size" form:"page_size" auto_read:"page_size"` - PageNumber int `json:"page" form:"page" auto_read:"page"` -} - -func NewPagination(PageSize, pageNumber int) *Pagination { - return &Pagination{PageSize, pageNumber} -} - -func (p *Pagination) IsValid() bool { - return p.PageSize >= 0 && p.PageNumber >= 0 -} - -func (p *Pagination) IsAvailable(itemsCount, startingIndex int) bool { - return itemsCount > startingIndex && p.PageSize > 0 -} - -func (p *Pagination) Index(itemsCount int) (startIndex int, endIndex int) { - startIndex = p.PageSize * p.PageNumber - endIndex = startIndex + p.PageSize - - if endIndex > itemsCount { - endIndex = itemsCount - } - - return startIndex, endIndex -} - -func NewQuery(sort *Sort, filter *Filter, pagination *Pagination) *Query { - return &Query{ - Sort: sort, - Filter: filter, - Pagination: pagination, - } -} - -func NewSort(sortRaw []string) *Sort { - if sortRaw == nil || len(sortRaw)%2 == 1 { - log.Info("empty sort for query") - return NoSort - } - list := []SortBy{} - for i := 0; i+1 < len(sortRaw); i += 2 { - var ascending bool - orderOption := sortRaw[i] - if orderOption == "a" { - ascending = true - } else if orderOption == "d" { - ascending = false - } else { - return NoSort - } - - propertyName := sortRaw[i+1] - sortBy := SortBy{ - Property: entity.PropertyName(propertyName), - Ascending: ascending, - } - list = append(list, sortBy) - } - return &Sort{ - List: list, - } -} - -func NewFilter(filterRaw []string) *Filter { - if filterRaw == nil || len(filterRaw)%2 == 1 { - log.Info("empty filter for query") - return NoFilter - } - list := []FilterBy{} - for i := 0; i+1 < len(filterRaw); i += 2 { - propertyName := filterRaw[i] - propertyValue := filterRaw[i+1] - filterBy := FilterBy{ - Property: entity.PropertyName(propertyName), - Value: entity.ComparingString(propertyValue), - } - list = append(list, filterBy) - } - return &Filter{ - List: list, - } -} diff --git a/api/internal/core/store/selector.go b/api/internal/core/store/selector.go deleted file mode 100644 index 4aac32cda6..0000000000 --- a/api/internal/core/store/selector.go +++ /dev/null @@ -1,128 +0,0 @@ -// Copyright 2017 The Kubernetes Authors. -// -// Licensed 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 store - -import ( - "sort" - - "github.com/apisix/manager-api/internal/core/entity" -) - -type Row interface { - GetProperty(entity.PropertyName) entity.ComparableValue -} - -type Selector struct { - List []Row - Query *Query -} - -func (self Selector) Len() int { return len(self.List) } - -func (self Selector) Swap(i, j int) { - self.List[i], self.List[j] = self.List[j], self.List[i] -} - -func (self Selector) Less(i, j int) bool { - for _, sortBy := range self.Query.Sort.List { - a := self.List[i].GetProperty(sortBy.Property) - b := self.List[j].GetProperty(sortBy.Property) - if a == nil || b == nil { - break - } - cmp := a.Compare(b) - if cmp == 0 { - continue - } else { - return (cmp == -1 && sortBy.Ascending) || (cmp == 1 && !sortBy.Ascending) - } - } - return false -} - -func (self *Selector) Sort() *Selector { - sort.Sort(*self) - return self -} - -func (self *Selector) Filter() *Selector { - filteredList := []Row{} - for _, c := range self.List { - matches := true - for _, filterBy := range self.Query.Filter.List { - v := c.GetProperty(filterBy.Property) - if v == nil || v.Compare(filterBy.Value) != 0 { - matches = false - break - } - } - if matches { - filteredList = append(filteredList, c) - } - } - - self.List = filteredList - return self -} - -func (self *Selector) Paginate() *Selector { - pagination := self.Query.Pagination - dataList := self.List - TotalSize := len(dataList) - startIndex, endIndex := pagination.Index(TotalSize) - - if startIndex == 0 && endIndex == 0 { - return self - } - - if !pagination.IsValid() { - self.List = []Row{} - return self - } - - if startIndex > TotalSize { - self.List = []Row{} - return self - } - - if endIndex >= TotalSize { - self.List = dataList[startIndex:] - return self - } - - self.List = dataList[startIndex:endIndex] - return self -} - -func NewFilterSelector(list []Row, query *Query) []Row { - selector := Selector{ - List: list, - Query: query, - } - filtered := selector.Filter() - paged := filtered.Paginate() - return paged.List -} - -func DefaultSelector(list []Row, query *Query) ([]Row, int) { - selector := Selector{ - List: list, - Query: query, - } - filtered := selector.Filter() - filteredTotal := len(filtered.List) - paged := filtered.Sort().Paginate() - return paged.List, filteredTotal -} diff --git a/api/internal/core/store/selector_test.go b/api/internal/core/store/selector_test.go deleted file mode 100644 index 8e52551c46..0000000000 --- a/api/internal/core/store/selector_test.go +++ /dev/null @@ -1,269 +0,0 @@ -// Copyright 2017 The Kubernetes Authors. -// -// Licensed 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 store - -import ( - "reflect" - "testing" - - "github.com/apisix/manager-api/internal/core/entity" -) - -type PaginationTestCase struct { - Info string - Pagination *Pagination - ExpectedOrder []int -} - -type SortTestCase struct { - Info string - Sort *Sort - ExpectedOrder []int -} - -type FilterTestCase struct { - Info string - Filter *Filter - ExpectedOrder []int -} - -type TestRow struct { - Name string - CreateTime int64 - Id int - Snis []string -} - -func (self TestRow) GetProperty(name entity.PropertyName) entity.ComparableValue { - switch name { - case entity.NameProperty: - return entity.ComparingString(self.Name) - case entity.SnisProperty: - return entity.ComparingStringArray(self.Snis) - case entity.CreateTimeProperty: - return entity.ComparingInt(self.CreateTime) - default: - return nil - } -} - -func toRows(std []TestRow) []Row { - rows := make([]Row, len(std)) - for i := range std { - rows[i] = std[i] - } - return rows -} - -func fromRows(rows []Row) []TestRow { - std := make([]TestRow, len(rows)) - for i := range std { - std[i] = rows[i].(TestRow) - } - return std -} - -func getDataList() []Row { - return toRows([]TestRow{ - {"b", 1, 1, []string{"a", "b"}}, - {"a", 2, 2, []string{"c", "d"}}, - {"a", 3, 3, []string{"f", "e"}}, - {"c", 4, 4, []string{"g", "h"}}, - {"c", 5, 5, []string{"k", "j"}}, - {"d", 6, 6, []string{"i", "h"}}, - {"e", 7, 7, []string{"t", "r"}}, - {"e", 8, 8, []string{"q", "w"}}, - {"f", 9, 9, []string{"x", "z"}}, - {"a", 10, 10, []string{"v", "n"}}, - }) -} - -func getOrder(dataList []TestRow) []int { - ordered := []int{} - for _, e := range dataList { - ordered = append(ordered, e.Id) - } - return ordered -} - -func TestSort(t *testing.T) { - testCases := []SortTestCase{ - { - "no sort - do not change the original order", - NoSort, - []int{1, 2, 3, 4, 5, 6, 7, 8, 9, 10}, - }, - { - "ascending sort by 1 property - all items sorted by this property", - NewSort([]string{"a", "create_time"}), - []int{1, 2, 3, 4, 5, 6, 7, 8, 9, 10}, - }, - { - "descending sort by 1 property - all items sorted by this property", - NewSort([]string{"d", "create_time"}), - []int{10, 9, 8, 7, 6, 5, 4, 3, 2, 1}, - }, - { - "sort by 2 properties - items should first be sorted by first property and later by second", - NewSort([]string{"a", "name", "d", "create_time"}), - []int{10, 3, 2, 1, 5, 4, 6, 8, 7, 9}, - }, - { - "empty sort list - no sort", - NewSort([]string{}), - []int{1, 2, 3, 4, 5, 6, 7, 8, 9, 10}, - }, - { - "nil - no sort", - NewSort(nil), - []int{1, 2, 3, 4, 5, 6, 7, 8, 9, 10}, - }, - // Invalid arguments to the NewSortQuery - { - "sort by few properties where at least one property name is invalid - no sort", - NewSort([]string{"a", "INVALID_PROPERTY", "d", "creationTimestamp"}), - []int{1, 2, 3, 4, 5, 6, 7, 8, 9, 10}, - }, - { - "sort by few properties where at least one order option is invalid - no sort", - NewSort([]string{"d", "name", "INVALID_ORDER", "creationTimestamp"}), - []int{1, 2, 3, 4, 5, 6, 7, 8, 9, 10}, - }, - { - "sort by few properties where one order tag is missing property - no sort", - NewSort([]string{""}), - []int{1, 2, 3, 4, 5, 6, 7, 8, 9, 10}, - }, - { - "sort by few properties where one order tag is missing property - no sort", - NewSort([]string{"d", "name", "a", "creationTimestamp", "a"}), - []int{1, 2, 3, 4, 5, 6, 7, 8, 9, 10}, - }, - } - for _, testCase := range testCases { - selector := Selector{ - List: getDataList(), - Query: &Query{Sort: testCase.Sort}, - } - sortedData := fromRows(selector.Sort().List) - order := getOrder(sortedData) - if !reflect.DeepEqual(order, testCase.ExpectedOrder) { - t.Errorf(`Sort: %s. Received invalid items for %+v. Got %v, expected %v.`, - testCase.Info, testCase.Sort, order, testCase.ExpectedOrder) - } - } - -} - -func TestPagination(t *testing.T) { - testCases := []PaginationTestCase{ - { - "no pagination - all existing elements should be returned", - NewPagination(0, 0), - []int{1, 2, 3, 4, 5, 6, 7, 8, 9, 10}, - }, - { - "request one item from existing page - element should be returned", - NewPagination(1, 5), - []int{6}, - }, - { - "request one item from non existing page - no elements should be returned", - NewPagination(1, 10), - []int{}, - }, - { - "request 2 items from existing page - 2 elements should be returned", - NewPagination(2, 1), - []int{3, 4}, - }, - { - "request 3 items from partially existing page - last few existing should be returned", - NewPagination(3, 3), - []int{10}, - }, - { - "request more than total number of elements from page 1 - all existing elements should be returned", - NewPagination(11, 0), - []int{1, 2, 3, 4, 5, 6, 7, 8, 9, 10}, - }, - { - "request 3 items from non existing page - no elements should be returned", - NewPagination(3, 4), - []int{}, - }, - { - "Invalid pagination - all elements should be returned", - NewPagination(-1, 4), - []int{}, - }, - { - "Invalid pagination - all elements should be returned", - NewPagination(1, -4), - []int{}, - }, - } - for _, testCase := range testCases { - selector := Selector{ - List: getDataList(), - Query: &Query{Pagination: testCase.Pagination}, - } - paginatedData := fromRows(selector.Paginate().List) - order := getOrder(paginatedData) - if !reflect.DeepEqual(order, testCase.ExpectedOrder) { - t.Errorf(`Pagination: %s. Received invalid items for %+v. Got %v, expected %v.`, - testCase.Info, testCase.Pagination, order, testCase.ExpectedOrder) - } - } - -} - -func TestFilter(t *testing.T) { - testCases := []FilterTestCase{ - { - "no sort - do not change the original order", - NewFilter(nil), - []int{1, 2, 3, 4, 5, 6, 7, 8, 9, 10}, - }, - { - "string filter", - NewFilter([]string{"name", "a"}), - []int{2, 3, 10}, - }, - { - "string array filter", - NewFilter([]string{"snis", "x"}), - []int{9}, - }, - { - "multi filter", - NewFilter([]string{"snis", "t", "name", "e"}), - []int{7}, - }, - } - for _, testCase := range testCases { - selector := Selector{ - List: getDataList(), - Query: &Query{Filter: testCase.Filter}, - } - filteredData := fromRows(selector.Filter().List) - order := getOrder(filteredData) - if !reflect.DeepEqual(order, testCase.ExpectedOrder) { - t.Errorf(`Filter: %s. Received invalid items for %+v. Got %v, expected %v.`, - testCase.Info, testCase.Filter, order, testCase.ExpectedOrder) - } - } - -} diff --git a/api/internal/core/store/store.go b/api/internal/core/store/store.go index ce1c107552..9f13f6290b 100644 --- a/api/internal/core/store/store.go +++ b/api/internal/core/store/store.go @@ -35,6 +35,11 @@ import ( "github.com/apisix/manager-api/internal/utils/runtime" ) +type Pagination struct { + PageSize int `json:"page_size" form:"page_size" auto_read:"page_size"` + PageNumber int `json:"page" form:"page" auto_read:"page"` +} + type Interface interface { Get(ctx context.Context, key string) (interface{}, error) List(ctx context.Context, input ListInput) (*ListOutput, error) diff --git a/api/internal/handler/ssl/ssl.go b/api/internal/handler/ssl/ssl.go index a536f20696..da69f3634d 100644 --- a/api/internal/handler/ssl/ssl.go +++ b/api/internal/handler/ssl/ssl.go @@ -51,6 +51,35 @@ func NewHandler() (handler.RouteRegister, error) { }, nil } +func checkSniExists(rows []interface{}, sni string) bool { + for _, item := range rows { + ssl := item.(*entity.SSL) + + if ssl.Sni == sni { + return true + } + + if inArray(sni, ssl.Snis) { + return true + } + + // Wildcard Domain + firstDot := strings.Index(sni, ".") + if firstDot > 0 && sni[0:1] != "*" { + wildcardDomain := "*" + sni[firstDot:] + if ssl.Sni == wildcardDomain { + return true + } + + if inArray(wildcardDomain, ssl.Snis) { + return true + } + } + } + + return false +} + func (h *Handler) ApplyRoute(r *gin.Engine) { r.GET("/apisix/admin/ssl/:id", wgin.Wraps(h.Get, wrapper.InputType(reflect.TypeOf(GetInput{})))) @@ -403,40 +432,9 @@ type ExistInput struct { Name string `auto_read:"name,query"` } -func toRows(list *store.ListOutput) []store.Row { - rows := make([]store.Row, list.TotalSize) - for i := range list.Rows { - rows[i] = list.Rows[i].(*entity.SSL) - } - return rows -} - -func checkValueExists(rows []store.Row, field, value string) bool { - selector := store.Selector{ - List: rows, - Query: &store.Query{Filter: store.NewFilter([]string{field, value})}, - } - - list := selector.Filter().List - - return len(list) > 0 -} - -func checkSniExists(rows []store.Row, sni string) bool { - if res := checkValueExists(rows, "sni", sni); res { - return true - } - if res := checkValueExists(rows, "snis", sni); res { - return true - } - //extensive domain - firstDot := strings.Index(sni, ".") - if firstDot > 0 && sni[0:1] != "*" { - sni = "*" + sni[firstDot:] - if res := checkValueExists(rows, "sni", sni); res { - return true - } - if res := checkValueExists(rows, "snis", sni); res { +func inArray(key string, array []string) bool { + for _, item := range array { + if key == item { return true } } @@ -490,8 +488,8 @@ func (h *Handler) Exist(c droplet.Context) (interface{}, error) { } for _, host := range input.Hosts { - res := checkSniExists(toRows(ret), host) - if !res { + exist := checkSniExists(ret.Rows, host) + if !exist { return &data.SpecCodeResponse{StatusCode: http.StatusNotFound}, consts.InvalidParam("SSL cert not exists for sni:" + host) } diff --git a/api/test/e2enew/base/base.go b/api/test/e2enew/base/base.go index 15fd1850a5..6fa319c8c5 100644 --- a/api/test/e2enew/base/base.go +++ b/api/test/e2enew/base/base.go @@ -269,12 +269,11 @@ func CleanResource(resource string) { for _, item := range list { resourceObj := item.(map[string]interface{}) tc := HttpTestCase{ - Desc: "delete " + resource + "/" + resourceObj["id"].(string), - Object: ManagerApiExpect(), - Method: http.MethodDelete, - Path: "/apisix/admin/" + resource + "/" + resourceObj["id"].(string), - Headers: map[string]string{"Authorization": GetToken()}, - ExpectStatus: http.StatusOK, + Desc: "delete " + resource + "/" + resourceObj["id"].(string), + Object: ManagerApiExpect(), + Method: http.MethodDelete, + Path: "/apisix/admin/" + resource + "/" + resourceObj["id"].(string), + Headers: map[string]string{"Authorization": GetToken()}, } RunTestCase(tc) } diff --git a/api/test/e2enew/healthz/healthz_suite_test.go b/api/test/e2enew/healthz/healthz_suite_test.go new file mode 100644 index 0000000000..ddedaa5b0e --- /dev/null +++ b/api/test/e2enew/healthz/healthz_suite_test.go @@ -0,0 +1,27 @@ +/* + * 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 healthz + +import ( + "testing" + + "github.com/onsi/ginkgo" +) + +func TestHealthz(t *testing.T) { + ginkgo.RunSpecs(t, "healthz suite") +} diff --git a/api/test/e2enew/healthz/healthz_test.go b/api/test/e2enew/healthz/healthz_test.go new file mode 100644 index 0000000000..b46e7588dd --- /dev/null +++ b/api/test/e2enew/healthz/healthz_test.go @@ -0,0 +1,38 @@ +/* + * 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 healthz + +import ( + "net/http" + + "github.com/onsi/ginkgo" + + "e2enew/base" +) + +var _ = ginkgo.Describe("Healthy check", func() { + ginkgo.It("ping manager-api", func() { + base.RunTestCase(base.HttpTestCase{ + Object: base.ManagerApiExpect(), + Method: http.MethodGet, + Path: "/ping", + ExpectStatus: http.StatusOK, + ExpectBody: "pong", + Sleep: base.SleepTime, + }) + }) +}) diff --git a/api/test/e2enew/plugin/plugin_suite_test.go b/api/test/e2enew/plugin/plugin_suite_test.go new file mode 100644 index 0000000000..2775ba32fa --- /dev/null +++ b/api/test/e2enew/plugin/plugin_suite_test.go @@ -0,0 +1,27 @@ +/* + * 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 plugin + +import ( + "testing" + + "github.com/onsi/ginkgo" +) + +func TestPlugin(t *testing.T) { + ginkgo.RunSpecs(t, "plugin suite") +} diff --git a/api/test/e2enew/plugin/plugin_test.go b/api/test/e2enew/plugin/plugin_test.go new file mode 100644 index 0000000000..c3d88c63cd --- /dev/null +++ b/api/test/e2enew/plugin/plugin_test.go @@ -0,0 +1,84 @@ +/* + * 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 plugin + +import ( + "net/http" + + "github.com/onsi/ginkgo" + "github.com/onsi/ginkgo/extensions/table" + + "e2enew/base" +) + +var _ = ginkgo.Describe("Plugin Basic", func() { + table.DescribeTable("test plugin basic", func(testCase base.HttpTestCase) { + base.RunTestCase(testCase) + }, + table.Entry("get all plugins", base.HttpTestCase{ + Object: base.ManagerApiExpect(), + Method: http.MethodGet, + Path: "/apisix/admin/plugins", + Query: "all=true", + Headers: map[string]string{"Authorization": base.GetToken()}, + ExpectStatus: http.StatusOK, + ExpectBody: []string{"request-id", "syslog", "echo", "proxy-mirror"}, + Sleep: base.SleepTime, + }), + table.Entry("get all plugins", base.HttpTestCase{ + Object: base.ManagerApiExpect(), + Method: http.MethodGet, + Path: "/apisix/admin/plugins", + Query: "all=false", + Headers: map[string]string{"Authorization": base.GetToken()}, + ExpectStatus: http.StatusOK, + ExpectBody: []string{"request-id", "syslog", "echo", "proxy-mirror"}, + Sleep: base.SleepTime, + }), + ) + + table.DescribeTable("test schema basic", func(testCase base.HttpTestCase) { + base.RunTestCase(testCase) + }, + table.Entry("get consumer schema", base.HttpTestCase{ + Object: base.ManagerApiExpect(), + Method: http.MethodGet, + Path: "/apisix/admin/schema/plugins/jwt-auth", + Query: "schema_type=consumer", + Headers: map[string]string{"Authorization": base.GetToken()}, + ExpectStatus: http.StatusOK, + ExpectBody: "{\"dependencies\":{\"algorithm\":{\"oneOf\":[{\"properties\":{\"algorithm\":" + + "{\"default\":\"HS256\",\"enum\":[\"HS256\",\"HS512\"]}}},{\"properties\":{\"algorithm\":" + + "{\"enum\":[\"RS256\"]},\"private_key\":{\"type\":\"string\"},\"public_key\":{\"type\":\"string\"}}," + + "\"required\":[\"private_key\",\"public_key\"]}]}},\"properties\":{\"algorithm\":{\"default\":" + + "\"HS256\",\"enum\":[\"HS256\",\"HS512\",\"RS256\"],\"type\":\"string\"},\"base64_secret\"" + + ":{\"default\":false,\"type\":\"boolean\"},\"exp\":{\"default\":86400,\"minimum\":1,\"type\":" + + "\"integer\"},\"key\":{\"type\":\"string\"},\"secret\":{\"type\":\"string\"}}," + + "\"required\":[\"key\"],\"type\":\"object\"}", + Sleep: base.SleepTime, + }), + table.Entry("get require-id plugin", base.HttpTestCase{ + Object: base.ManagerApiExpect(), + Method: http.MethodGet, + Path: "/apisix/admin/schema/plugins/jwt-auth", + Headers: map[string]string{"Authorization": base.GetToken()}, + ExpectStatus: http.StatusOK, + ExpectBody: "{\"$comment\":\"this is a mark for our injected plugin schema\",\"additionalProperties\":false,\"properties\":{\"disable\":{\"type\":\"boolean\"}},\"type\":\"object\"}", + Sleep: base.SleepTime, + }), + ) +}) diff --git a/api/test/e2enew/plugin_config/plugin_config_test.go b/api/test/e2enew/plugin_config/plugin_config_test.go index 3314cab87e..f9f70012e5 100644 --- a/api/test/e2enew/plugin_config/plugin_config_test.go +++ b/api/test/e2enew/plugin_config/plugin_config_test.go @@ -60,11 +60,12 @@ var _ = ginkgo.Describe("Plugin Config", func() { Headers: map[string]string{"Authorization": base.GetToken()}, ExpectStatus: http.StatusOK, }), - table.Entry("create plugin config 2", base.HttpTestCase{ + table.Entry("create plugin config by Post", base.HttpTestCase{ Object: base.ManagerApiExpect(), - Path: "/apisix/admin/plugin_configs/2", - Method: http.MethodPut, + Path: "/apisix/admin/plugin_configs", + Method: http.MethodPost, Body: `{ + "id": "2", "plugins": { "response-rewrite": { "headers": { diff --git a/api/test/e2enew/ssl/ssl_suite_test.go b/api/test/e2enew/ssl/ssl_suite_test.go index 5969613e42..a5b7c05a1d 100644 --- a/api/test/e2enew/ssl/ssl_suite_test.go +++ b/api/test/e2enew/ssl/ssl_suite_test.go @@ -25,12 +25,11 @@ import ( "e2enew/base" ) -func TestRoute(t *testing.T) { +func TestSSL(t *testing.T) { ginkgo.RunSpecs(t, "ssl suite") } var _ = ginkgo.AfterSuite(func() { base.CleanResource("ssl") - base.CleanResource("routes") time.Sleep(base.SleepTime) }) diff --git a/api/test/e2enew/ssl/ssl_test.go b/api/test/e2enew/ssl/ssl_test.go index d93371d488..76c31576d2 100644 --- a/api/test/e2enew/ssl/ssl_test.go +++ b/api/test/e2enew/ssl/ssl_test.go @@ -26,38 +26,80 @@ import ( "time" "github.com/onsi/ginkgo" + "github.com/onsi/ginkgo/extensions/table" "github.com/stretchr/testify/assert" "e2enew/base" ) var _ = ginkgo.Describe("SSL Basic", func() { - ginkgo.It("test ssl basic", func() { - // build test body - t := ginkgo.GinkgoT() - testCert, err := ioutil.ReadFile("../../certs/test2.crt") - assert.Nil(t, err) - testKey, err := ioutil.ReadFile("../../certs/test2.key") - assert.Nil(t, err) - apisixKey, err := ioutil.ReadFile("../../certs/apisix.key") - assert.Nil(t, err) - body, err := json.Marshal(map[string]interface{}{ - "id": "1", - "cert": string(testCert), - "key": string(testKey), - "labels": map[string]string{ - "build": "16", - "env": "production", - "version": "v3", + t := ginkgo.GinkgoT() + var ( + testCert []byte + testKey []byte + apisixKey []byte + validBody []byte + validBody2 []byte + invalidBody []byte + createRouteBody []byte + ) + + var err error + testCert, err = ioutil.ReadFile("../../certs/test2.crt") + assert.Nil(t, err) + testKey, err = ioutil.ReadFile("../../certs/test2.key") + assert.Nil(t, err) + apisixKey, err = ioutil.ReadFile("../../certs/apisix.key") + assert.Nil(t, err) + + validBody, err = json.Marshal(map[string]interface{}{ + "id": "1", + "cert": string(testCert), + "key": string(testKey), + "labels": map[string]string{ + "build": "16", + "env": "production", + "version": "v3", + }, + }) + assert.Nil(t, err) + validBody2, err = json.Marshal(map[string]interface{}{ + "id": "1", + "cert": string(testCert), + "key": string(testKey), + "labels": map[string]string{ + "build": "16", + "env": "production", + "version": "v2", + }, + }) + assert.Nil(t, err) + + invalidBody, err = json.Marshal(map[string]string{ + "id": "1", + "cert": string(testCert), + "key": string(apisixKey), + }) + assert.Nil(t, err) + + tempBody := map[string]interface{}{ + "uri": "/hello_", + "hosts": []string{"test2.com", "*.test2.com"}, + "upstream": map[string]interface{}{ + "nodes": []map[string]interface{}{ + { + "host": base.UpstreamIp, + "port": 1980, + "weight": 1, + }, }, - }) - assert.Nil(t, err) - invalidBody, err := json.Marshal(map[string]string{ - "id": "1", - "cert": string(testCert), - "key": string(apisixKey), - }) - assert.Nil(t, err) + "type": "roundrobin", + }, + } + createRouteBody, err = json.Marshal(tempBody) + assert.Nil(t, err) + + ginkgo.It("without certificate", func() { // Before configuring SSL, make a HTTPS request http.DefaultTransport.(*http.Transport).TLSClientConfig = &tls.Config{InsecureSkipVerify: true} http.DefaultTransport.(*http.Transport).DialContext = func(ctx context.Context, network, addr string) (net.Conn, error) { @@ -67,78 +109,112 @@ var _ = ginkgo.Describe("SSL Basic", func() { dialer := &net.Dialer{} return dialer.DialContext(ctx, network, addr) } - _, err = http.Get("https://www.test2.com:9443") + + _, err := http.Get("https://www.test2.com:9443") assert.NotNil(t, err) assert.EqualError(t, err, "Get https://www.test2.com:9443: remote error: tls: internal error") - //create ssl fail - key and cert not match - base.RunTestCase(base.HttpTestCase{ + }) + + table.DescribeTable("test ssl basic", func(testCase base.HttpTestCase) { + base.RunTestCase(testCase) + }, + table.Entry("create ssl failed", base.HttpTestCase{ Object: base.ManagerApiExpect(), Method: http.MethodPost, Path: "/apisix/admin/ssl", Body: string(invalidBody), Headers: map[string]string{"Authorization": base.GetToken()}, ExpectStatus: http.StatusBadRequest, - }) - //create ssl successfully - base.RunTestCase(base.HttpTestCase{ + ExpectBody: "SSL parse failed: key and cert don't match", + Sleep: base.SleepTime, + }), + table.Entry("create ssl successfully", base.HttpTestCase{ Object: base.ManagerApiExpect(), Method: http.MethodPost, Path: "/apisix/admin/ssl", - Body: string(body), + Body: string(validBody), Headers: map[string]string{"Authorization": base.GetToken()}, ExpectStatus: http.StatusOK, Sleep: base.SleepTime, - }) - }) - ginkgo.It("check ssl labels", func() { - base.RunTestCase(base.HttpTestCase{ + }), + table.Entry("validate ssl cert and key (valid)", base.HttpTestCase{ + Object: base.ManagerApiExpect(), + Method: http.MethodPost, + Path: "/apisix/admin/check_ssl_cert", + Body: string(validBody), + Headers: map[string]string{"Authorization": base.GetToken()}, + ExpectBody: "\"code\":0,\"message\":\"\"", + ExpectStatus: http.StatusOK, + }), + table.Entry("validate ssl cert and key (valid)", base.HttpTestCase{ + Object: base.ManagerApiExpect(), + Method: http.MethodPost, + Path: "/apisix/admin/check_ssl_cert", + Body: string(invalidBody), + Headers: map[string]string{"Authorization": base.GetToken()}, + ExpectBody: "key and cert don't match", + ExpectStatus: http.StatusOK, + }), + table.Entry("check ssl labels", base.HttpTestCase{ Object: base.ManagerApiExpect(), Method: http.MethodGet, Path: "/apisix/admin/ssl/1", Headers: map[string]string{"Authorization": base.GetToken()}, ExpectStatus: http.StatusOK, ExpectBody: "\"labels\":{\"build\":\"16\",\"env\":\"production\",\"version\":\"v3\"", - }) - }) - ginkgo.It("create route", func() { - t := ginkgo.GinkgoT() - var createRouteBody map[string]interface{} = map[string]interface{}{ - "uri": "/hello_", - "hosts": []string{"test2.com", "*.test2.com"}, - "upstream": map[string]interface{}{ - "nodes": []map[string]interface{}{ - { - "host": base.UpstreamIp, - "port": 1980, - "weight": 1, - }, - }, - "type": "roundrobin", - }, - } - _createRouteBody, err := json.Marshal(createRouteBody) - assert.Nil(t, err) - base.RunTestCase(base.HttpTestCase{ + }), + table.Entry("update ssl", base.HttpTestCase{ + Object: base.ManagerApiExpect(), + Method: http.MethodPut, + Path: "/apisix/admin/ssl/1", + Body: string(validBody2), + Headers: map[string]string{"Authorization": base.GetToken()}, + ExpectStatus: http.StatusOK, + Sleep: base.SleepTime, + }), + table.Entry("check ssl labels", base.HttpTestCase{ + Object: base.ManagerApiExpect(), + Method: http.MethodGet, + Path: "/apisix/admin/ssl/1", + Headers: map[string]string{"Authorization": base.GetToken()}, + ExpectStatus: http.StatusOK, + ExpectBody: "\"labels\":{\"build\":\"16\",\"env\":\"production\",\"version\":\"v2\"", + Sleep: base.SleepTime, + }), + table.Entry("check host exist", base.HttpTestCase{ + Object: base.ManagerApiExpect(), + Method: http.MethodPost, + Path: "/apisix/admin/check_ssl_exists", + Body: `{"hosts": ["www.test2.com"]}`, + Headers: map[string]string{"Authorization": base.GetToken()}, + ExpectStatus: http.StatusOK, + }), + table.Entry("check host not exist", base.HttpTestCase{ + Object: base.ManagerApiExpect(), + Method: http.MethodPost, + Path: "/apisix/admin/check_ssl_exists", + Body: `{"hosts": ["www.test3.com"]}`, + Headers: map[string]string{"Authorization": base.GetToken()}, + ExpectStatus: http.StatusNotFound, + ExpectBody: "SSL cert not exists for sni:www.test3.com", + }), + table.Entry("create route", base.HttpTestCase{ Object: base.ManagerApiExpect(), Method: http.MethodPut, Path: "/apisix/admin/routes/r1", - Body: string(_createRouteBody), + Body: string(createRouteBody), Headers: map[string]string{"Authorization": base.GetToken()}, ExpectStatus: http.StatusOK, - }) - }) - ginkgo.It("get the route just created to trigger removing `key`", func() { - base.RunTestCase(base.HttpTestCase{ + }), + table.Entry("get the route just created to trigger removing `key`", base.HttpTestCase{ Object: base.ManagerApiExpect(), Method: http.MethodGet, Path: "/apisix/admin/routes/r1", Headers: map[string]string{"Authorization": base.GetToken()}, ExpectStatus: http.StatusOK, Sleep: base.SleepTime, - }) - }) - ginkgo.It("hit the route just created using HTTPS", func() { - base.RunTestCase(base.HttpTestCase{ + }), + table.Entry("hit the route just created using HTTPS", base.HttpTestCase{ Object: base.APISIXHTTPSExpect(), Method: http.MethodGet, Path: "/hello_", @@ -146,10 +222,8 @@ var _ = ginkgo.Describe("SSL Basic", func() { Headers: map[string]string{"Host": "www.test2.com"}, ExpectBody: "hello world\n", Sleep: base.SleepTime, - }) - }) - ginkgo.It("disable SSL", func() { - base.RunTestCase(base.HttpTestCase{ + }), + table.Entry("disable SSL", base.HttpTestCase{ Object: base.ManagerApiExpect(), Method: http.MethodPatch, Path: "/apisix/admin/ssl/1", @@ -159,18 +233,21 @@ var _ = ginkgo.Describe("SSL Basic", func() { Headers: map[string]string{"Authorization": base.GetToken()}, ExpectStatus: http.StatusOK, ExpectBody: "\"status\":0", - }) - }) + }), + ) + ginkgo.It("test disable SSL HTTPS request", func() { // try again after disable SSL, make a HTTPS request - t := ginkgo.GinkgoT() time.Sleep(time.Duration(500) * time.Millisecond) _, err := http.Get("https://www.test2.com:9443") assert.NotNil(t, err) assert.EqualError(t, err, "Get https://www.test2.com:9443: remote error: tls: internal error") }) - ginkgo.It("enable SSL", func() { - base.RunTestCase(base.HttpTestCase{ + + table.DescribeTable("test ssl basic", func(testCase base.HttpTestCase) { + base.RunTestCase(testCase) + }, + table.Entry("enable SSL", base.HttpTestCase{ Object: base.ManagerApiExpect(), Method: http.MethodPatch, Path: "/apisix/admin/ssl/1/status", @@ -181,10 +258,8 @@ var _ = ginkgo.Describe("SSL Basic", func() { }, ExpectStatus: http.StatusOK, ExpectBody: "\"status\":1", - }) - }) - ginkgo.It("hit the route using HTTPS, make sure enable successful", func() { - base.RunTestCase(base.HttpTestCase{ + }), + table.Entry("hit the route using HTTPS, make sure enable successful", base.HttpTestCase{ Object: base.APISIXHTTPSExpect(), Method: http.MethodGet, Path: "/hello_", @@ -192,34 +267,27 @@ var _ = ginkgo.Describe("SSL Basic", func() { ExpectStatus: http.StatusOK, ExpectBody: "hello world\n", Sleep: base.SleepTime, - }) - }) - ginkgo.It("delete SSL", func() { - base.RunTestCase(base.HttpTestCase{ + }), + table.Entry("delete SSL", base.HttpTestCase{ Object: base.ManagerApiExpect(), Method: http.MethodDelete, Path: "/apisix/admin/ssl/1", Headers: map[string]string{"Authorization": base.GetToken()}, ExpectStatus: http.StatusOK, - }) - }) - ginkgo.It("delete route", func() { - base.RunTestCase(base.HttpTestCase{ + }), + table.Entry("delete route", base.HttpTestCase{ Object: base.ManagerApiExpect(), Method: http.MethodDelete, Path: "/apisix/admin/routes/r1", Headers: map[string]string{"Authorization": base.GetToken()}, ExpectStatus: http.StatusOK, - }) - }) - ginkgo.It("hit the route just deleted", func() { - base.RunTestCase(base.HttpTestCase{ + }), + table.Entry("hit the route just deleted", base.HttpTestCase{ Object: base.APISIXExpect(), Method: http.MethodGet, Path: "/hello_", ExpectStatus: http.StatusNotFound, ExpectBody: "{\"error_msg\":\"404 Route Not Found\"}\n", Sleep: base.SleepTime, - }) - }) + })) }) diff --git a/docs/en/latest/FAQ.md b/docs/en/latest/FAQ.md index 448d0f530d..a83f005e4a 100644 --- a/docs/en/latest/FAQ.md +++ b/docs/en/latest/FAQ.md @@ -39,7 +39,7 @@ Since the Dashboard caches the jsonschema data of the plugins in Apache APISIX, 1. Confirm that your APISIX is running and has enabled control API (enabled by default and only runs local access) Refer to the beginning in: - https://github.com/apache/apisix/blob/master/docs/en/latest/control-api.md + [https://apisix.apache.org/docs/apisix/control-api](https://apisix.apache.org/docs/apisix/control-api) 2. Execute the following commands to export jsonchema on your APISIX server (if it is configured for non-local access, it does not need to be executed on your APISIX server, and the access IP and port should be modified accordingly) @@ -47,7 +47,7 @@ Since the Dashboard caches the jsonschema data of the plugins in Apache APISIX, curl 127.0.0.1:9090/v1/schema > schema.json ``` -Refer to https://github.com/apache/apisix/blob/master/docs/en/latest/control-api.md#get-v1schema +Refer to [https://apisix.apache.org/docs/apisix/control-api#get-v1schema](https://apisix.apache.org/docs/apisix/control-api#get-v1schema) 3. Copy the exported `schema.json` to the `conf` directory in the Dashboard working directory (About working directory, please refer to https://github.com/apache/apisix-dashboard/blob/master/docs/en/latest/deploy.md#working-directory) diff --git a/docs/en/latest/IMPORT_OPENAPI_USER_GUIDE.md b/docs/en/latest/IMPORT_OPENAPI_USER_GUIDE.md index 67281bb1fb..45c5d15638 100644 --- a/docs/en/latest/IMPORT_OPENAPI_USER_GUIDE.md +++ b/docs/en/latest/IMPORT_OPENAPI_USER_GUIDE.md @@ -41,7 +41,7 @@ when we import routes from OAS3.0, some fields in OAS will be missed because the ## Extended fields -There are some fields required in APISIX Route but are not included in the properties of OAS3.0, we added some extended fields such as upstream, plugins, hosts and so on. All extensions start with x-apisix. See [reference](https://github.com/apache/apisix/blob/master/doc/admin-api.md#route) For more details of the APISIX Route Properties +There are some fields required in APISIX Route but are not included in the properties of OAS3.0, we added some extended fields such as upstream, plugins, hosts and so on. All extensions start with x-apisix. See [reference](https://apisix.apache.org/docs/apisix/admin-api/#route) For more details of the APISIX Route Properties | Extended fields | APISIX Route Properties | | ------------------------- | ----------------------- | diff --git a/docs/en/latest/back-end-e2e.md b/docs/en/latest/back-end-e2e.md index d6aabf3a00..7b81dd5c0a 100644 --- a/docs/en/latest/back-end-e2e.md +++ b/docs/en/latest/back-end-e2e.md @@ -29,7 +29,7 @@ This document describes how to use E2E test locally. 2. To start the `manager-api` project locally, please refer to [develop](./develop.md) web section. -3. To start the etcd locally, please refer to [etcd start](https://github.com/apache/apisix/blob/master/docs/en/latest/install-dependencies.md) web section. +3. To start the etcd locally, please refer to [etcd start](https://apisix.apache.org/docs/apisix/install-dependencies/) web section. 4. To start the `apisix` project locally, please refer to [apisix start](https://github.com/apache/apisix#get-started) web section. diff --git a/web/src/components/RawDataEditor/RawDataEditor.tsx b/web/src/components/RawDataEditor/RawDataEditor.tsx index 9274148faf..ad8e209936 100644 --- a/web/src/components/RawDataEditor/RawDataEditor.tsx +++ b/web/src/components/RawDataEditor/RawDataEditor.tsx @@ -50,7 +50,7 @@ const RawDataEditor: React.FC = ({ visible, readonly = true, type, data = icon={} onClick={() => { window.open( - `https://github.com/apache/apisix/blob/master/doc/admin-api.md#${type}`, + `https://apisix.apache.org/docs/apisix/admin-api#${type}`, ); }} key={1} diff --git a/web/src/pages/Route/List.tsx b/web/src/pages/Route/List.tsx index e82dfbea06..dcc8e3dbad 100644 --- a/web/src/pages/Route/List.tsx +++ b/web/src/pages/Route/List.tsx @@ -494,7 +494,7 @@ const Page: React.FC = () => {

{formatMessage({ id: 'page.route.instructions' })}:

1.{' '} diff --git a/web/src/pages/ServerInfo/List.tsx b/web/src/pages/ServerInfo/List.tsx index 9823381ead..8d297fc846 100644 --- a/web/src/pages/ServerInfo/List.tsx +++ b/web/src/pages/ServerInfo/List.tsx @@ -111,7 +111,7 @@ const ServerInfo: React.FC = () => { {formatMessage({ id: 'page.systemStatus.desc' })}