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

feat: implement API to get apisix instances status #958

Merged
merged 10 commits into from
Dec 18, 2020
10 changes: 10 additions & 0 deletions api/internal/core/entity/entity.go
Original file line number Diff line number Diff line change
Expand Up @@ -225,3 +225,13 @@ type Script struct {
ID string `json:"id"`
Script interface{} `json:"script,omitempty"`
}

type ServerInfo struct {
BaseInfo
LastReportTime int64 `json:"last_report_time,omitempty"`
UpTime int64 `json:"up_time,omitempty"`
BootTime int64 `json:"boot_time,omitempty"`
EtcdVersion string `json:"etcd_version,omitempty"`
Hostname string `json:"hostname,omitempty"`
Version string `json:"version,omitempty"`
}
25 changes: 19 additions & 6 deletions api/internal/core/store/storehub.go
Original file line number Diff line number Diff line change
Expand Up @@ -28,12 +28,13 @@ import (
type HubKey string

const (
HubKeyConsumer HubKey = "consumer"
HubKeyRoute HubKey = "route"
HubKeyService HubKey = "service"
HubKeySsl HubKey = "ssl"
HubKeyUpstream HubKey = "upstream"
HubKeyScript HubKey = "script"
HubKeyConsumer HubKey = "consumer"
HubKeyRoute HubKey = "route"
HubKeyService HubKey = "service"
HubKeySsl HubKey = "ssl"
HubKeyUpstream HubKey = "upstream"
HubKeyScript HubKey = "script"
HubKeyServerInfo HubKey = `server_info`
)

var (
Expand Down Expand Up @@ -144,5 +145,17 @@ func InitStores() error {
return err
}

err = InitStore(HubKeyServerInfo, GenericStoreOption{
BasePath: "/apisix/data_plane/server_info",
ObjType: reflect.TypeOf(entity.ServerInfo{}),
KeyFunc: func(obj interface{}) string {
tokers marked this conversation as resolved.
Show resolved Hide resolved
r := obj.(*entity.ServerInfo)
return utils.InterfaceToString(r.ID)
},
})
if err != nil {
return err
}

return nil
}
89 changes: 89 additions & 0 deletions api/internal/handler/server_info/server_info.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,89 @@
/*
* 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 server_info

import (
"reflect"
"strings"

"github.com/gin-gonic/gin"
"github.com/shiningrush/droplet"
"github.com/shiningrush/droplet/wrapper"
wgin "github.com/shiningrush/droplet/wrapper/gin"

"github.com/apisix/manager-api/internal/core/entity"
"github.com/apisix/manager-api/internal/core/store"
"github.com/apisix/manager-api/internal/handler"
)

type Handler struct {
serverInfoStore store.Interface
}

func NewHandler() (handler.RouteRegister, error) {
return &Handler{
serverInfoStore: store.GetStore(store.HubKeyServerInfo),
}, nil
}

func (h *Handler) ApplyRoute(r *gin.Engine) {
r.GET("/apisix/server_info/:id", wgin.Wraps(h.Get,
wrapper.InputType(reflect.TypeOf(GetInput{}))))
r.GET("/apisix/server_info", wgin.Wraps(h.List,
wrapper.InputType(reflect.TypeOf(ListInput{}))))
}

type GetInput struct {
ID string `auto_read:"id,path" validate:"required"`
}

func (h *Handler) Get(c droplet.Context) (interface{}, error) {
input := c.Input().(*GetInput)

r, err := h.serverInfoStore.Get(input.ID)
if err != nil {
return handler.SpecCodeResponse(err), err
}

return r, nil
}

type ListInput struct {
store.Pagination
Hostname string `auto_read:"hostname,query"`
}

func (h *Handler) List(c droplet.Context) (interface{}, error) {
input := c.Input().(*ListInput)

ret, err := h.serverInfoStore.List(store.ListInput{
Predicate: func(obj interface{}) bool {
if input.Hostname != "" {
return strings.Contains(obj.(*entity.ServerInfo).Hostname, input.Hostname)
}
return true
},
PageSize: input.PageSize,
PageNumber: input.PageNumber,
})

if err != nil {
return nil, err
}

return ret, nil
}
222 changes: 222 additions & 0 deletions api/internal/handler/server_info/server_info_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,222 @@
/*
* 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 server_info

import (
"errors"
"testing"

"github.com/shiningrush/droplet"
"github.com/shiningrush/droplet/data"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/mock"

"github.com/apisix/manager-api/internal/core/entity"
"github.com/apisix/manager-api/internal/core/store"
)

func TestHandler_Get(t *testing.T) {
var (
tests = []struct {
caseDesc string
giveInput *GetInput
giveErr error
giveRet interface{}
wantErr error
wantGetKey string
wantRet interface{}
}{
{
caseDesc: "get server_info",
giveInput: &GetInput{ID: "server_1"},
giveRet: &entity.ServerInfo{
BaseInfo: entity.BaseInfo{ID: "server_1"},
UpTime: 10,
LastReportTime: 1608195454,
BootTime: 1608195454,
Hostname: "gentoo",
Version: "v3",
},
wantGetKey: "server_1",
wantRet: &entity.ServerInfo{
BaseInfo: entity.BaseInfo{ID: "server_1"},
UpTime: 10,
LastReportTime: 1608195454,
BootTime: 1608195454,
Hostname: "gentoo",
Version: "v3",
},
},
{
caseDesc: "get server_info not exist",
giveInput: &GetInput{ID: "server_3"},
giveRet: &data.SpecCodeResponse{Response: data.Response{Code: 0}, StatusCode: 404},
giveErr: errors.New("not found"),
wantGetKey: "server_3",
wantRet: &data.SpecCodeResponse{Response: data.Response{Code: 0}, StatusCode: 404},
wantErr: errors.New("not found"),
},
}
)

for _, tc := range tests {
t.Run(tc.caseDesc, func(t *testing.T) {
getCalled := true
mStore := &store.MockInterface{}
mStore.On("Get", mock.Anything).Run(func(args mock.Arguments) {
getCalled = true
assert.Equal(t, tc.wantGetKey, args.Get(0))
}).Return(tc.giveRet, tc.giveErr)

h := Handler{serverInfoStore: mStore}
ctx := droplet.NewContext()
ctx.SetInput(tc.giveInput)
ret, err := h.Get(ctx)
assert.True(t, getCalled)
assert.Equal(t, tc.wantErr, err)
assert.Equal(t, tc.wantRet, ret)
})
}
}

func TestHandler_List(t *testing.T) {
var (
tests = []struct {
caseDesc string
giveInput *ListInput
giveData []interface{}
giveErr error
wantErr error
wantGetKey *ListInput
wantRet interface{}
}{
{
caseDesc: "list server_info",
giveInput: &ListInput{Hostname: ""},
giveData: []interface{}{
&entity.ServerInfo{
BaseInfo: entity.BaseInfo{ID: "server_1"},
UpTime: 10,
LastReportTime: 1608195454,
BootTime: 1608195454,
Hostname: "gentoo",
Version: "v3",
},
&entity.ServerInfo{
BaseInfo: entity.BaseInfo{ID: "server_2"},
UpTime: 10,
LastReportTime: 1608195454,
BootTime: 1608195454,
Hostname: "ubuntu",
Version: "v2",
},
},
wantRet: &store.ListOutput{
Rows: []interface{}{
&entity.ServerInfo{
BaseInfo: entity.BaseInfo{ID: "server_1"},
UpTime: 10,
LastReportTime: 1608195454,
BootTime: 1608195454,
Hostname: "gentoo",
Version: "v3",
},
&entity.ServerInfo{
BaseInfo: entity.BaseInfo{ID: "server_2"},
UpTime: 10,
LastReportTime: 1608195454,
BootTime: 1608195454,
Hostname: "ubuntu",
Version: "v2",
},
},
TotalSize: 2,
},
},
{
caseDesc: "list server_info with hostname",
giveInput: &ListInput{Hostname: "ubuntu"},
giveData: []interface{}{
&entity.ServerInfo{
BaseInfo: entity.BaseInfo{ID: "server_1"},
UpTime: 10,
LastReportTime: 1608195454,
BootTime: 1608195454,
Hostname: "gentoo",
Version: "v3",
},
&entity.ServerInfo{
BaseInfo: entity.BaseInfo{ID: "server_2"},
UpTime: 10,
LastReportTime: 1608195454,
BootTime: 1608195454,
Hostname: "ubuntu",
Version: "v2",
},
},
wantRet: &store.ListOutput{
Rows: []interface{}{
&entity.ServerInfo{
BaseInfo: entity.BaseInfo{ID: "server_2"},
UpTime: 10,
LastReportTime: 1608195454,
BootTime: 1608195454,
Hostname: "ubuntu",
Version: "v2",
},
},
TotalSize: 1,
},
},
}
)

for _, tc := range tests {
t.Run(tc.caseDesc, func(t *testing.T) {
getCalled := true
mStore := &store.MockInterface{}
mStore.On("List", mock.Anything).Run(func(args mock.Arguments) {
getCalled = true
}).Return(func(input store.ListInput) *store.ListOutput {
var res []interface{}
for _, c := range tc.giveData {
if input.Predicate(c) {
if input.Format != nil {
res = append(res, input.Format(c))
} else {
res = append(res, c)
}
}
}

return &store.ListOutput{
Rows: res,
TotalSize: len(res),
}
}, tc.giveErr)

h := Handler{serverInfoStore: mStore}
ctx := droplet.NewContext()
ctx.SetInput(tc.giveInput)
ret, err := h.List(ctx)
assert.True(t, getCalled)
assert.Equal(t, tc.wantErr, err)
assert.Equal(t, tc.wantRet, ret)
})
}
}
2 changes: 2 additions & 0 deletions api/internal/route.go
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,7 @@ import (
"github.com/apisix/manager-api/internal/handler/healthz"
"github.com/apisix/manager-api/internal/handler/plugin"
"github.com/apisix/manager-api/internal/handler/route"
"github.com/apisix/manager-api/internal/handler/server_info"
"github.com/apisix/manager-api/internal/handler/service"
"github.com/apisix/manager-api/internal/handler/ssl"
"github.com/apisix/manager-api/internal/handler/upstream"
Expand Down Expand Up @@ -65,6 +66,7 @@ func SetUpRouter() *gin.Engine {
plugin.NewHandler,
healthz.NewHandler,
authentication.NewHandler,
server_info.NewHandler,
}

for i := range factories {
Expand Down
Loading