Skip to content

Commit 0a9d157

Browse files
committed
feat: add admin api
1 parent c2a3583 commit 0a9d157

File tree

6 files changed

+301
-10
lines changed

6 files changed

+301
-10
lines changed

client/admin.go

+137
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,137 @@
1+
package client
2+
3+
import (
4+
"fmt"
5+
6+
"github.com/emicklei/go-restful/v3"
7+
restfulspec "github.com/polarismesh/go-restful-openapi/v2"
8+
9+
"github.com/polaris-contrib/polaris-server-remote-plugin-common/log"
10+
)
11+
12+
// Resource plugin client resources handler.
13+
type Resource struct {
14+
}
15+
16+
// NewResource returns a new Resource
17+
func NewResource() *Resource {
18+
return &Resource{}
19+
}
20+
21+
// Plugin plugin model
22+
type Plugin struct {
23+
Name string `json:"name"`
24+
Path string `json:"path"`
25+
Config *Config `json:"config"`
26+
}
27+
28+
// WebService return restful web service
29+
func (resource *Resource) WebService() *restful.WebService {
30+
ws := new(restful.WebService)
31+
ws.Path("/admin/plugins").Consumes(restful.MIME_JSON).Produces(restful.MIME_JSON)
32+
tags := []string{"plugin"}
33+
34+
ws.Route(
35+
ws.GET("").To(resource.findAll).
36+
Doc("get all plugin").
37+
Metadata(restfulspec.KeyOpenAPITags, tags).
38+
Writes([]Plugin{}).
39+
Returns(200, "OK", []Plugin{}).
40+
DefaultReturns("OK", []Plugin{}),
41+
)
42+
43+
ws.Route(
44+
ws.PUT("{name}/disable").To(resource.disable).
45+
Doc("disable the given name plugin").
46+
Metadata(restfulspec.KeyOpenAPITags, tags).
47+
Returns(200, "OK", nil).
48+
DefaultReturns("OK", nil),
49+
)
50+
51+
ws.Route(
52+
ws.PUT("{name}/enable").To(resource.disable).
53+
Doc("enable the given name plugin").
54+
Metadata(restfulspec.KeyOpenAPITags, tags).
55+
Returns(200, "OK", nil).
56+
DefaultReturns("OK", nil),
57+
)
58+
59+
return ws
60+
}
61+
62+
// findAll find and return all plugins
63+
//
64+
// GET http://localhost:9050/admin/plugins
65+
func (resource *Resource) findAll(_ *restful.Request, res *restful.Response) {
66+
var plugins []Plugin
67+
for name, plugin := range factory.pluginSet {
68+
plugins = append(plugins, Plugin{
69+
Name: name,
70+
Path: plugin.pluginPath,
71+
Config: plugin.config,
72+
})
73+
}
74+
_ = res.WriteEntity(plugins)
75+
}
76+
77+
// disable disable one plugin by plugin name.
78+
//
79+
// PUT http://localhost:9050/admin/plugins/{name}/disable
80+
func (resource *Resource) disable(req *restful.Request, res *restful.Response) {
81+
name := req.PathParameter("name")
82+
if name == "" {
83+
responseError(res, 400, fmt.Errorf("plugin name is required"))
84+
return
85+
}
86+
87+
plugin := factory.Get(name)
88+
if plugin == nil {
89+
responseError(res, 404, fmt.Errorf("plugin with name %s is not exists", name))
90+
return
91+
}
92+
93+
err := plugin.Disable()
94+
if err != nil {
95+
responseError(res, 500, fmt.Errorf("fail to disable plugin: %w", err))
96+
return
97+
}
98+
99+
_ = res.WriteEntity(map[string]interface{}{"success": "true"})
100+
return
101+
}
102+
103+
// enable enable one plugin by plugin name.
104+
//
105+
// PUT http://localhost:9050/admin/plugins/{name}/enable
106+
func (resource *Resource) enable(req *restful.Request, res *restful.Response) {
107+
name := req.PathParameter("name")
108+
if name == "" {
109+
responseError(res, 400, fmt.Errorf("plugin name is required"))
110+
return
111+
}
112+
113+
plugin := factory.Get(name)
114+
if plugin == nil {
115+
responseError(res, 404, fmt.Errorf("plugin with name %s is not exists", name))
116+
return
117+
}
118+
119+
err := plugin.Open()
120+
if err != nil {
121+
responseError(res, 500, fmt.Errorf("fail to enable plugin: %w", err))
122+
return
123+
}
124+
_ = res.WriteEntity(map[string]interface{}{"success": "true"})
125+
return
126+
}
127+
128+
// ResError error response
129+
type ResError struct {
130+
Err error `json:"err"`
131+
}
132+
133+
func responseError(res *restful.Response, code int, err error) {
134+
log.Error("admin api returns a error response", "error_response", err)
135+
_ = res.WriteHeaderAndJson(code, map[string]interface{}{"error": err.Error()}, restful.MIME_JSON)
136+
return
137+
}

client/admin_test.go

+66
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,66 @@
1+
package client
2+
3+
import (
4+
"fmt"
5+
"net/http"
6+
"os"
7+
"os/exec"
8+
"testing"
9+
10+
"github.com/emicklei/go-restful/v3"
11+
12+
"github.com/polaris-contrib/polaris-server-remote-plugin-common/log"
13+
)
14+
15+
func TestMain(m *testing.M) {
16+
cmd := &exec.Cmd{
17+
Path: "/usr/bin/make",
18+
Args: append([]string{"/usr/bin/make"}, "build"),
19+
Dir: "../",
20+
}
21+
22+
err := cmd.Run()
23+
if err != nil {
24+
log.Fatal("got error", "error", err.Error())
25+
os.Exit(-1)
26+
}
27+
28+
m.Run()
29+
}
30+
31+
// TestAdminAPI 测试 API server
32+
func TestAdminAPI(t *testing.T) {
33+
if _, err := Register(
34+
&Config{
35+
Name: "remote-rate-limit-server-v1",
36+
Mode: RumModelLocal,
37+
Local: LocalConfig{
38+
MaxProcs: 1,
39+
Path: "../remote-rate-limit-server-v1",
40+
},
41+
},
42+
); err != nil {
43+
log.Fatal("server-v1 register failed", "error", err.Error())
44+
return
45+
}
46+
47+
if _, err := Register(
48+
&Config{
49+
Name: "remote-rate-limit-server-v2",
50+
Mode: RumModelLocal,
51+
Local: LocalConfig{
52+
Path: "../remote-rate-limit-server-v2",
53+
},
54+
},
55+
); err != nil {
56+
log.Fatal("server-v2 register failed", "error", err.Error())
57+
}
58+
59+
restful.DefaultContainer.Add(NewResource().WebService())
60+
adminPort := 9050
61+
62+
log.Info(fmt.Sprintf("request the admin api using http://localhost:%d", adminPort))
63+
if err := http.ListenAndServe(fmt.Sprintf(":%d", adminPort), nil); err != nil {
64+
log.Fatal("plugin admin serve error", "error", err)
65+
}
66+
}

client/client.go

+5
Original file line numberDiff line numberDiff line change
@@ -76,6 +76,11 @@ func (c *Client) Close() error {
7676
func (c *Client) Disable() error {
7777
c.Lock()
7878
defer c.Unlock()
79+
80+
if c.enable == false {
81+
return fmt.Errorf("plugin %s alread disbled", c.pluginName)
82+
}
83+
7984
c.enable = false
8085

8186
if c.pluginClient != nil {

client/factory.go

+28-6
Original file line numberDiff line numberDiff line change
@@ -19,24 +19,46 @@ package client
1919

2020
import "sync"
2121

22-
var pluginSet = make(map[string]*Client)
23-
var locker sync.Mutex
22+
// factory
23+
var factory = NewFactory()
24+
25+
// Factory plugin client factory.
26+
type Factory struct {
27+
// pluginSet plugin named client set.
28+
pluginSet map[string]*Client
29+
// locker locker for pluginSet update/delete
30+
locker sync.Mutex
31+
}
32+
33+
// NewFactory returns a new Factory.
34+
func NewFactory() *Factory {
35+
return &Factory{pluginSet: make(map[string]*Client)}
36+
}
37+
38+
// Get return client by name, return nil if the named plugin not exists.
39+
func (f *Factory) Get(name string) *Client {
40+
client, ex := f.pluginSet[name]
41+
if !ex {
42+
return nil
43+
}
44+
return client
45+
}
2446

2547
// Register called by plugin client and start up the plugin main process.
2648
func Register(config *Config) (*Client, error) {
27-
locker.Lock()
28-
defer locker.Unlock()
49+
factory.locker.Lock()
50+
defer factory.locker.Unlock()
2951

3052
name := config.Name
31-
if c, ok := pluginSet[name]; ok {
53+
if c, ok := factory.pluginSet[name]; ok {
3254
return c, nil
3355
}
3456

3557
c, err := newClient(config)
3658
if err != nil {
3759
return nil, err
3860
}
39-
pluginSet[name] = c
61+
factory.pluginSet[name] = c
4062

4163
return c, nil
4264
}

go.mod

+16-2
Original file line numberDiff line numberDiff line change
@@ -3,28 +3,42 @@ module github.com/polaris-contrib/polaris-server-remote-plugin-common
33
go 1.17
44

55
require (
6+
github.com/emicklei/go-restful/v3 v3.10.0
67
github.com/fsnotify/fsnotify v1.6.0
78
github.com/golang/mock v1.6.0
89
github.com/golang/protobuf v1.5.2
910
github.com/hashicorp/go-hclog v1.3.1
1011
github.com/hashicorp/go-plugin v1.4.6
12+
github.com/polarismesh/go-restful-openapi/v2 v2.0.0-20220928152401-083908d10219
1113
go.uber.org/zap v1.23.0
1214
golang.org/x/time v0.2.0
1315
google.golang.org/grpc v1.50.1
1416
google.golang.org/protobuf v1.28.1
1517
)
1618

1719
require (
20+
github.com/PuerkitoBio/purell v1.1.1 // indirect
21+
github.com/PuerkitoBio/urlesc v0.0.0-20170810143723-de5bf2ad4578 // indirect
1822
github.com/fatih/color v1.13.0 // indirect
23+
github.com/go-openapi/jsonpointer v0.19.5 // indirect
24+
github.com/go-openapi/jsonreference v0.19.6 // indirect
25+
github.com/go-openapi/spec v0.20.4 // indirect
26+
github.com/go-openapi/swag v0.19.15 // indirect
1927
github.com/hashicorp/yamux v0.0.0-20180604194846-3520598351bb // indirect
28+
github.com/josharian/intern v1.0.0 // indirect
29+
github.com/json-iterator/go v1.1.12 // indirect
30+
github.com/mailru/easyjson v0.7.6 // indirect
2031
github.com/mattn/go-colorable v0.1.12 // indirect
2132
github.com/mattn/go-isatty v0.0.14 // indirect
2233
github.com/mitchellh/go-testing-interface v0.0.0-20171004221916-a61a99592b77 // indirect
34+
github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421 // indirect
35+
github.com/modern-go/reflect2 v1.0.2 // indirect
2336
github.com/oklog/run v1.0.0 // indirect
2437
go.uber.org/atomic v1.10.0 // indirect
2538
go.uber.org/multierr v1.8.0 // indirect
26-
golang.org/x/net v0.0.0-20210405180319-a5a99cb37ef4 // indirect
39+
golang.org/x/net v0.0.0-20210421230115-4e50805a0758 // indirect
2740
golang.org/x/sys v0.0.0-20220908164124-27713097b956 // indirect
28-
golang.org/x/text v0.3.3 // indirect
41+
golang.org/x/text v0.3.7 // indirect
2942
google.golang.org/genproto v0.0.0-20200526211855-cb27e3aa2013 // indirect
43+
gopkg.in/yaml.v2 v2.4.0 // indirect
3044
)

0 commit comments

Comments
 (0)