Initial commit

This commit is contained in:
Donny
2019-04-22 20:46:32 +08:00
commit 49ab8aadd1
25441 changed files with 4055000 additions and 0 deletions

30
app/admin/main/cache/BUILD vendored Normal file
View File

@@ -0,0 +1,30 @@
package(default_visibility = ["//visibility:public"])
load(
"@io_bazel_rules_go//go:def.bzl",
"go_library",
"go_test",
)
filegroup(
name = "package-srcs",
srcs = glob(["**"]),
tags = ["automanaged"],
visibility = ["//visibility:private"],
)
filegroup(
name = "all-srcs",
srcs = [
":package-srcs",
"//app/admin/main/cache/cmd:all-srcs",
"//app/admin/main/cache/conf:all-srcs",
"//app/admin/main/cache/dao:all-srcs",
"//app/admin/main/cache/http:all-srcs",
"//app/admin/main/cache/model:all-srcs",
"//app/admin/main/cache/script:all-srcs",
"//app/admin/main/cache/service:all-srcs",
],
tags = ["automanaged"],
visibility = ["//visibility:public"],
)

44
app/admin/main/cache/CHANGELOG.md vendored Normal file
View File

@@ -0,0 +1,44 @@
# Changelog
# V1.5.1
1. 增加删除集群关联关系
# V1.5.0
1. 同步overlord-mesos集群信息
# V1.4.0
1. 同步opscache集群
# V1.3.0
1. 增加服务树权限筛选
# V1.2.4
1. 增加一键导入tw.yml文件接口
# V1.2.3
1. 修复默认值update无效
# V1.2.2
1. 修正字段reject=>eject
# V1.2.1
1. 修复sql
# V1.2.0
1. 支持删除集群
2. 支持按机房拉取配置
# V1.1.0
1. 支持按名字添加节点
2. 添加集群迁移脚本
# v1.0.1
1. 修复toml格式
2. 增加listenAddr listenProto字段
3. 允许修改集群信息
# v1.0.0
1. 根据appid获取toml文件配置
2. 向集群添加删除节点
3. 获取集群列表
4. 创建集群

8
app/admin/main/cache/CONTRIBUTORS.md vendored Normal file
View File

@@ -0,0 +1,8 @@
# Owner
lintanghui
# Author
lintanghui
# Reviewer
haoguanwei

13
app/admin/main/cache/OWNERS vendored Normal file
View File

@@ -0,0 +1,13 @@
# See the OWNERS docs at https://go.k8s.io/owners
approvers:
- lintanghui
labels:
- admin
- admin/main/cache
- main
options:
no_parent_owners: true
reviewers:
- haoguanwei
- lintanghui

12
app/admin/main/cache/README.md vendored Normal file
View File

@@ -0,0 +1,12 @@
# cache
# 项目简介
1.
# 编译环境
# 依赖包
# 编译执行

42
app/admin/main/cache/cmd/BUILD vendored Normal file
View File

@@ -0,0 +1,42 @@
package(default_visibility = ["//visibility:public"])
load(
"@io_bazel_rules_go//go:def.bzl",
"go_binary",
"go_library",
)
go_binary(
name = "cmd",
embed = [":go_default_library"],
tags = ["automanaged"],
)
go_library(
name = "go_default_library",
srcs = ["main.go"],
data = ["test.toml"],
importpath = "go-common/app/admin/main/cache/cmd",
tags = ["automanaged"],
visibility = ["//visibility:public"],
deps = [
"//app/admin/main/cache/conf:go_default_library",
"//app/admin/main/cache/http:go_default_library",
"//library/ecode/tip:go_default_library",
"//library/log:go_default_library",
],
)
filegroup(
name = "package-srcs",
srcs = glob(["**"]),
tags = ["automanaged"],
visibility = ["//visibility:private"],
)
filegroup(
name = "all-srcs",
srcs = [":package-srcs"],
tags = ["automanaged"],
visibility = ["//visibility:public"],
)

50
app/admin/main/cache/cmd/db.sql vendored Normal file
View File

@@ -0,0 +1,50 @@
use bilibili_apm_v2;
CREATE TABLE `overlord_appid` (
`id` int(11) unsigned NOT NULL AUTO_INCREMENT COMMENT '主键',
`tree_id` int(11) NOT NULL DEFAULT '0' COMMENT '服务树id',
`app_id` varchar(50) NOT NULL DEFAULT '' COMMENT '业务appid',
`cid` int(11) NOT NULL DEFAULT '0' COMMENT '关联集群id',
`ctime` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
`mtime` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间',
PRIMARY KEY (`id`),
UNIQUE KEY `uk_appids_name` (`tree_id`, `cid`),
KEY `ix_appid` (`app_id`),
KEY `ix_mtime` (`mtime`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8 COMMENT='业务关联集群';
CREATE TABLE `overlord_cluster` (
`id` int(11) unsigned NOT NULL AUTO_INCREMENT COMMENT '主键',
`name` varchar(50) NOT NULL DEFAULT '' COMMENT '集群名字',
`type` varchar(20) NOT NULL DEFAULT '' COMMENT '集群类型',
`zone` varchar(20) NOT NULL DEFAULT 'sh001' COMMENT '机房',
`hash_method` varchar(20) NOT NULL DEFAULT '' COMMENT 'hash方法',
`hash_distribution` varchar(20) NOT NULL DEFAULT '' COMMENT '分布策略',
`hashtag` varchar(10) NOT NULL DEFAULT '' COMMENT 'hash tag',
`listen_proto` varchar(10) NOT NULL DEFAULT 'tcp' COMMENT '监听协议',
`listen_addr` varchar(30) NOT NULL DEFAULT '' COMMENT '监听地址',
`nodeconn` int(11) NOT NULL DEFAULT '1' COMMENT '跟节点连接数',
`dial` int(11) NOT NULL DEFAULT '1000' COMMENT 'dial 超时',
`read` int(11) NOT NULL DEFAULT '1000' COMMENT 'read超时',
`write` int(11) NOT NULL DEFAULT '1000' COMMENT 'write 超时',
`ping_fail_limit` int(11) NOT NULL DEFAULT '3' COMMENT '失败检测次数',
`auto_eject` tinyint(4) NOT NULL DEFAULT '1' COMMENT 'auto eject',
`ctime` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
`mtime` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间',
PRIMARY KEY (`id`),
UNIQUE KEY `uk_name` (`name`),
KEY `ix_mtime` (`mtime`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8 COMMENT='集群';
CREATE TABLE `overlord_node` (
`id` int(11) unsigned NOT NULL AUTO_INCREMENT COMMENT '主键',
`cid` int(11) NOT NULL DEFAULT '0' COMMENT '关联集群id',
`alias` varchar(50) NOT NULL DEFAULT '' COMMENT '节点别名',
`addr` varchar(50) NOT NULL DEFAULT '' COMMENT '节点地址',
`weight` int(11) NOT NULL DEFAULT '1' COMMENT '节点权重',
`ctime` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
`mtime` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间',
PRIMARY KEY (`id`),
UNIQUE KEY `uk_cid_alias` (`cid`,`alias`),
KEY `ix_mtime` (`mtime`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8 COMMENT='节点';

39
app/admin/main/cache/cmd/main.go vendored Normal file
View File

@@ -0,0 +1,39 @@
package main
import (
"flag"
"os"
"os/signal"
"syscall"
"go-common/app/admin/main/cache/conf"
"go-common/app/admin/main/cache/http"
ecode "go-common/library/ecode/tip"
"go-common/library/log"
)
func main() {
flag.Parse()
if err := conf.Init(); err != nil {
panic(err)
}
log.Init(conf.Conf.Log)
defer log.Close()
log.Info("cache start")
ecode.Init(conf.Conf.Ecode)
http.Init(conf.Conf)
c := make(chan os.Signal, 1)
signal.Notify(c, syscall.SIGHUP, syscall.SIGQUIT, syscall.SIGTERM, syscall.SIGINT)
for {
s := <-c
log.Info("cache get a signal %s", s.String())
switch s {
case syscall.SIGQUIT, syscall.SIGTERM, syscall.SIGINT:
log.Info("cache exit")
return
case syscall.SIGHUP:
default:
return
}
}
}

12
app/admin/main/cache/cmd/test.toml vendored Normal file
View File

@@ -0,0 +1,12 @@
[mysql]
dsn = "test:test@tcp(172.16.33.205:3308)/bilibili_apm_v2?timeout=5s&readTimeout=5s&writeTimeout=5s&parseTime=true&loc=Local&charset=utf8,utf8mb4"
active = 5
idle = 1
idleTimeout = "4h"
[httpClient]
key = "test"
secret = "e6c4c252dc7e3d8a90805eecd7c73396"
dial = "1s"
timeout = "10s"
keepAlive = "60s"

37
app/admin/main/cache/conf/BUILD vendored Normal file
View File

@@ -0,0 +1,37 @@
package(default_visibility = ["//visibility:public"])
load(
"@io_bazel_rules_go//go:def.bzl",
"go_library",
)
go_library(
name = "go_default_library",
srcs = ["conf.go"],
importpath = "go-common/app/admin/main/cache/conf",
tags = ["automanaged"],
visibility = ["//visibility:public"],
deps = [
"//library/conf:go_default_library",
"//library/database/orm:go_default_library",
"//library/ecode/tip:go_default_library",
"//library/log:go_default_library",
"//library/net/http/blademaster:go_default_library",
"//library/net/http/blademaster/middleware/permit:go_default_library",
"//vendor/github.com/BurntSushi/toml:go_default_library",
],
)
filegroup(
name = "package-srcs",
srcs = glob(["**"]),
tags = ["automanaged"],
visibility = ["//visibility:private"],
)
filegroup(
name = "all-srcs",
srcs = [":package-srcs"],
tags = ["automanaged"],
visibility = ["//visibility:public"],
)

85
app/admin/main/cache/conf/conf.go vendored Normal file
View File

@@ -0,0 +1,85 @@
package conf
import (
"errors"
"flag"
"go-common/library/conf"
"go-common/library/database/orm"
ecode "go-common/library/ecode/tip"
"go-common/library/log"
bm "go-common/library/net/http/blademaster"
"go-common/library/net/http/blademaster/middleware/permit"
"github.com/BurntSushi/toml"
)
var (
confPath string
client *conf.Client
// Conf config
Conf = &Config{}
)
// Config .
type Config struct {
Log *log.Config
BM *bm.ServerConfig
// identify
Auth *permit.Config
MySQL *orm.Config
Ecode *ecode.Config
HTTPClient *bm.ClientConfig
}
func init() {
flag.StringVar(&confPath, "conf", "", "default config path")
}
// Init init conf
func Init() error {
if confPath != "" {
return local()
}
return remote()
}
func local() (err error) {
_, err = toml.DecodeFile(confPath, &Conf)
return
}
func remote() (err error) {
if client, err = conf.New(); err != nil {
return
}
if err = load(); err != nil {
return
}
go func() {
for range client.Event() {
log.Info("config reload")
if load() != nil {
log.Error("config reload error (%v)", err)
}
}
}()
return
}
func load() (err error) {
var (
s string
ok bool
tmpConf *Config
)
if s, ok = client.Toml2(); !ok {
return errors.New("load config center error")
}
if _, err = toml.Decode(s, &tmpConf); err != nil {
return errors.New("could not decode config")
}
*Conf = *tmpConf
return
}

61
app/admin/main/cache/dao/BUILD vendored Normal file
View File

@@ -0,0 +1,61 @@
package(default_visibility = ["//visibility:public"])
load(
"@io_bazel_rules_go//go:def.bzl",
"go_test",
"go_library",
)
go_test(
name = "go_default_test",
srcs = [
"dao_test.go",
"opscache_test.go",
"overlord_test.go",
"tree_test.go",
],
embed = [":go_default_library"],
rundir = ".",
tags = ["automanaged"],
deps = [
"//app/admin/main/cache/conf:go_default_library",
"//vendor/github.com/smartystreets/goconvey/convey:go_default_library",
"//vendor/gopkg.in/h2non/gock.v1:go_default_library",
],
)
go_library(
name = "go_default_library",
srcs = [
"dao.go",
"opscache.go",
"overlord.go",
"tree.go",
],
importpath = "go-common/app/admin/main/cache/dao",
tags = ["automanaged"],
visibility = ["//visibility:public"],
deps = [
"//app/admin/main/cache/conf:go_default_library",
"//app/admin/main/cache/model:go_default_library",
"//library/database/orm:go_default_library",
"//library/ecode:go_default_library",
"//library/log:go_default_library",
"//library/net/http/blademaster:go_default_library",
"//vendor/github.com/jinzhu/gorm:go_default_library",
],
)
filegroup(
name = "package-srcs",
srcs = glob(["**"]),
tags = ["automanaged"],
visibility = ["//visibility:private"],
)
filegroup(
name = "all-srcs",
srcs = [":package-srcs"],
tags = ["automanaged"],
visibility = ["//visibility:public"],
)

38
app/admin/main/cache/dao/dao.go vendored Normal file
View File

@@ -0,0 +1,38 @@
package dao
import (
"context"
"go-common/app/admin/main/cache/conf"
"go-common/library/database/orm"
bm "go-common/library/net/http/blademaster"
"github.com/jinzhu/gorm"
)
// Dao dao.
type Dao struct {
c *conf.Config
DB *gorm.DB
client *bm.Client
}
// New new a dao and return.
func New(c *conf.Config) (d *Dao) {
d = &Dao{
c: c,
DB: orm.NewMySQL(c.MySQL),
client: bm.NewClient(c.HTTPClient),
}
return
}
// Ping check connection of db , mc.
func (d *Dao) Ping(c context.Context) (err error) {
return
}
// Close close connection of db , mc.
func (d *Dao) Close() {
}

45
app/admin/main/cache/dao/dao_test.go vendored Normal file
View File

@@ -0,0 +1,45 @@
package dao
import (
"flag"
"os"
"strings"
"testing"
"go-common/app/admin/main/cache/conf"
gock "gopkg.in/h2non/gock.v1"
)
var (
d *Dao
)
func TestMain(m *testing.M) {
if os.Getenv("DEPLOY_ENV") != "" {
flag.Set("app_id", "main.common-arch.cache-admin")
flag.Set("conf_token", "02ee6e25ae6d3a2b3bb0e7311362d890")
flag.Set("tree_id", "42048")
flag.Set("conf_version", "docker-1")
flag.Set("deploy_env", "uat")
flag.Set("conf_host", "config.bilibili.co")
flag.Set("conf_path", "/tmp")
flag.Set("region", "sh")
flag.Set("zone", "sh001")
} else {
flag.Set("conf", "../cmd/test.toml")
}
flag.Parse()
if err := conf.Init(); err != nil {
panic(err)
}
d = New(conf.Conf)
m.Run()
os.Exit(0)
}
func httpMock(method, url string) *gock.Request {
r := gock.New(url)
r.Method = strings.ToUpper(method)
return r
}

39
app/admin/main/cache/dao/opscache.go vendored Normal file
View File

@@ -0,0 +1,39 @@
package dao
import (
"context"
"go-common/app/admin/main/cache/model"
"go-common/library/log"
)
var (
opsMcURI = "http://ops-cache.bilibili.co/manager/redisapp/get_all_memcache_json"
opsRedisURI = "http://ops-cache.bilibili.co/manager/redisapp/get_all_redis_json"
)
// OpsMemcaches get all ops mc.
func (d *Dao) OpsMemcaches(c context.Context) (mcs []*model.OpsCacheMemcache, err error) {
var res struct {
Data []*model.OpsCacheMemcache `json:"data"`
}
if err = d.client.Get(c, opsMcURI, "", nil, &res); err != nil {
log.Error("ops memcache url(%s) error(%v)", opsMcURI, err)
return
}
mcs = res.Data
return
}
// OpsRediss get all ops redis.
func (d *Dao) OpsRediss(c context.Context) (mcs []*model.OpsCacheRedis, err error) {
var res struct {
Data []*model.OpsCacheRedis `json:"data"`
}
if err = d.client.Get(c, opsRedisURI, "", nil, &res); err != nil {
log.Error("ops redis url(%s) error(%v)", opsRedisURI, err)
return
}
mcs = res.Data
return
}

View File

@@ -0,0 +1,32 @@
package dao
import (
"context"
"testing"
"github.com/smartystreets/goconvey/convey"
)
func TestOpsMemcaches(t *testing.T) {
convey.Convey("get OpsMemcaches", t, func(ctx convey.C) {
ctx.Convey("When http response code != 0", func(ctx convey.C) {
mcs, err := d.OpsMemcaches(context.Background())
ctx.Convey("Then err should not be nil", func(ctx convey.C) {
ctx.So(err, convey.ShouldBeNil)
ctx.So(mcs, convey.ShouldNotBeNil)
})
})
})
}
func TestOpsRediss(t *testing.T) {
convey.Convey("get OpsRediss", t, func(ctx convey.C) {
ctx.Convey("When http response code != 0", func(ctx convey.C) {
mcs, err := d.OpsRediss(context.Background())
ctx.Convey("Then err should not be nil", func(ctx convey.C) {
ctx.So(err, convey.ShouldBeNil)
ctx.So(mcs, convey.ShouldNotBeNil)
})
})
})
}

65
app/admin/main/cache/dao/overlord.go vendored Normal file
View File

@@ -0,0 +1,65 @@
package dao
import (
"context"
"net"
"strconv"
"go-common/app/admin/main/cache/model"
"go-common/library/log"
)
var (
apiserverURI = "http://cache-mng.bilibili.co/api/v1/appids/%s"
)
// OverlordClusters get all overlord clusters.
func (d *Dao) OverlordClusters(c context.Context, zone, appid string) (ocs []*model.OverlordCluster, err error) {
var res struct {
Data []*model.OverlordApiserver `json:"grouped_clusters"`
}
if err = d.client.RESTfulGet(c, apiserverURI, "", nil, &res, appid); err != nil {
log.Error("overlord cluster url(%s) appid(%s) error(%v)", apiserverURI, appid, err)
return
}
GETALL:
for _, oa := range res.Data {
if zone == "" || oa.Group == zone {
for _, oc := range oa.Clusters {
cluster := &model.OverlordCluster{
Name: oc.Name,
Type: oc.Type,
Zone: zone,
HashMethod: "fnv1a_64",
HashDistribution: "ketama",
HashTag: "{}",
ListenProto: "tcp",
ListenAddr: net.JoinHostPort("0.0.0.0", strconv.Itoa(oc.FrontEndPort)),
DailTimeout: 1000,
ReadTimeout: 1000,
WriteTimeout: 1000,
NodeConn: 2,
PingFailLimit: 3,
PingAutoEject: true,
}
for _, oci := range oc.Instances {
if oc.Type == "redis_cluster" && oci.Role != "master" {
continue
}
on := &model.OverlordNode{
Alias: oci.Alias,
Addr: net.JoinHostPort(oci.IP, strconv.Itoa(oci.Port)),
Weight: oci.Weight,
}
cluster.Nodes = append(cluster.Nodes, on)
}
ocs = append(ocs, cluster)
}
}
}
if len(ocs) == 0 && zone != "" {
zone = ""
goto GETALL
}
return
}

View File

@@ -0,0 +1,20 @@
package dao
import (
"context"
"testing"
"github.com/smartystreets/goconvey/convey"
)
func TestOverlordClusters(t *testing.T) {
convey.Convey("get OverlordClusters", t, func(ctx convey.C) {
ctx.Convey("When http response code != 0", func(ctx convey.C) {
ocs, err := d.OverlordClusters(context.Background(), "", "main.common-arch.overlord")
ctx.Convey("Then err should not be nil", func(ctx convey.C) {
ctx.So(err, convey.ShouldBeNil)
ctx.So(ocs, convey.ShouldNotBeNil)
})
})
})
}

201
app/admin/main/cache/dao/tree.go vendored Normal file
View File

@@ -0,0 +1,201 @@
package dao
import (
"bytes"
"context"
"fmt"
"net/http"
"strings"
"time"
"go-common/app/admin/main/cache/model"
"go-common/library/ecode"
"go-common/library/log"
)
var (
tokenURI = "http://easyst.bilibili.co/v1/token"
dataURI = "http://easyst.bilibili.co/v1/node/apptree"
authURI = "http://easyst.bilibili.co/v1/auth"
nodeURI = "http://easyst.bilibili.co/v1/node/bilibili%s"
appsURI = "http://easyst.bilibili.co/v1/node/role/app"
prefix = []byte("bilibili.")
)
// Token get Token.
func (d *Dao) Token(c context.Context, body string) (msg map[string]interface{}, err error) {
var (
req *http.Request
)
if req, err = http.NewRequest("POST", tokenURI, strings.NewReader(body)); err != nil {
log.Error("Token url(%s) error(%v)", tokenURI, err)
return
}
var res struct {
Code int `json:"code"`
Data map[string]interface{} `json:"data"`
Message string `json:"message"`
Status int `json:"status"`
}
if err = d.client.Do(c, req, &res); err != nil {
log.Error("d.Token url(%s) res(%+v) err(%v)", tokenURI, res, err)
return
}
if res.Code != 90000 {
err = fmt.Errorf("error code :%d", res.Code)
log.Error("Status url(%s) res(%v)", tokenURI, res)
return
}
msg = res.Data
return
}
// Auth get Token.
func (d *Dao) Auth(c context.Context, cookie string) (msg map[string]interface{}, err error) {
var (
req *http.Request
)
if req, err = http.NewRequest("GET", authURI, nil); err != nil {
log.Error("Token url(%s) error(%v)", tokenURI, err)
return
}
req.Header.Set("Cookie", cookie)
var res struct {
Code int `json:"code"`
Data map[string]interface{} `json:"data"`
Message string `json:"message"`
Status int `json:"status"`
}
if err = d.client.Do(c, req, &res); err != nil {
log.Error("d.Token url(%s) res(%s) err(%v)", tokenURI, res, err)
return
}
if res.Code != 90000 {
err = fmt.Errorf("error code :%d", res.Code)
log.Error("Status url(%s) res(%v)", tokenURI, res)
return
}
msg = res.Data
return
}
// Tree get service tree.
func (d *Dao) Tree(c context.Context, token string) (data interface{}, err error) {
var (
req *http.Request
tmp map[string]interface{}
ok bool
)
if req, err = http.NewRequest("GET", dataURI, nil); err != nil {
log.Error("Status url(%s) error(%v)", dataURI, err)
return
}
req.Header.Set("X-Authorization-Token", token)
req.Header.Set("Content-Type", "application/json")
var res struct {
Code int `json:"code"`
Data map[string]map[string]interface{} `json:"data"`
Message string `json:"message"`
Status int `json:"status"`
}
if err = d.client.Do(c, req, &res); err != nil {
log.Error("d.Status url(%s) res($s) err(%v)", dataURI, res, err)
return
}
if res.Code != 90000 {
err = fmt.Errorf("error code :%d", res.Code)
log.Error("Status url(%s) res(%v)", dataURI, res)
return
}
if tmp, ok = res.Data["bilibili"]; ok {
data, ok = tmp["children"]
}
if !ok {
err = ecode.NothingFound
}
return
}
// Role get service tree.
func (d *Dao) Role(c context.Context, token string) (nodes *model.CacheData, err error) {
var (
req *http.Request
)
if req, err = http.NewRequest("GET", appsURI, nil); err != nil {
log.Error("Status url(%s) error(%v)", dataURI, err)
return
}
req.Header.Set("X-Authorization-Token", token)
req.Header.Set("Content-Type", "application/json")
var res struct {
Code int `json:"code"`
Data []*model.RoleNode `json:"data"`
Message string `json:"message"`
Status int `json:"status"`
}
if err = d.client.Do(c, req, &res); err != nil {
log.Error("d.Status url(%s) res($s) err(%v)", dataURI, res, err)
return
}
if res.Code != 90000 {
err = fmt.Errorf("error code :%d", res.Code)
log.Error("Status url(%s) res(%v)", dataURI, res)
return
}
nodes = &model.CacheData{Data: make(map[int64]*model.RoleNode)}
nodes.CTime = time.Now()
for _, node := range res.Data {
if bytes.Equal(prefix, []byte(node.Path)[0:9]) {
node.Path = string([]byte(node.Path)[9:])
}
nodes.Data[node.ID] = node
}
return
}
// NodeTree get service tree.
func (d *Dao) NodeTree(c context.Context, token, bu, team string) (nodes []*model.Node, err error) {
var (
req *http.Request
node string
)
if len(bu) != 0 {
node = "." + bu
}
if len(team) != 0 {
node = "." + team
}
if len(node) == 0 {
nodes = append(nodes, &model.Node{Name: "main", Path: "main"})
nodes = append(nodes, &model.Node{Name: "ai", Path: "ai"})
return
}
if req, err = http.NewRequest("GET", fmt.Sprintf(nodeURI, node), nil); err != nil {
log.Error("Status url(%s) error(%v)", dataURI, err)
return
}
req.Header.Set("X-Authorization-Token", token)
req.Header.Set("Content-Type", "application/json")
var res struct {
Code int `json:"code"`
Data *model.Res `json:"data"`
Message string `json:"message"`
Status int `json:"status"`
}
if err = d.client.Do(c, req, &res); err != nil {
log.Error("d.Status url(%s) res($s) err(%v)", dataURI, res, err)
return
}
if res.Code != 90000 {
err = fmt.Errorf("error code :%d", res.Code)
log.Error("Status url(%s) error(%v)", dataURI, err)
return
}
for _, tnode := range res.Data.Data {
if bytes.Equal(prefix, []byte(tnode.Path)[0:9]) {
tnode.Path = string([]byte(tnode.Path)[9:])
}
nodes = append(nodes, &model.Node{Name: tnode.Name, Path: tnode.Path})
}
return
}

137
app/admin/main/cache/dao/tree_test.go vendored Normal file
View File

@@ -0,0 +1,137 @@
package dao
import (
"context"
"net/http"
"testing"
"github.com/smartystreets/goconvey/convey"
gock "gopkg.in/h2non/gock.v1"
)
var token = "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ1c2VyX25hbWUiOiJtYWluIiwicGxhdGZvcm1faWQiOiIyRjNiOGZEVkdsTW5qOGFDRGxNYVciLCJleHAiOjE1NDA2NDE3MjQsImlzcyI6Im1haW4ifQ.SxKceDl69N1su3kDO70c4P1NuPhXOxp5rXpM3n8Jyig"
func TestToken(t *testing.T) {
d.client.SetTransport(gock.DefaultTransport)
convey.Convey("get token", t, func(ctx convey.C) {
ctx.Convey("When http response code != 0", func(ctx convey.C) {
httpMock("POST", "http://easyst.bilibili.co/v1/token").Reply(200).JSON(`{
"code": 90000,
"data": {
"token": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ1c2VyX25hbWUiOiJtYWluIiwicGxhdGZvcm1faWQiOiIyRjNiOGZEVkdsTW5qOGFDRGxNYVciLCJleHAiOjE1NDA2NDE3MjQsImlzcyI6Im1haW4ifQ.SxKceDl69N1su3kDO70c4P1NuPhXOxp5rXpM3n8Jyig",
"user_name": "main",
"secret": "",
"expired": 1540641724
},
"message": "success",
"status": 200
}`)
data, err := d.Token(context.Background(), "")
ctx.Convey("Then err should not be nil", func(ctx convey.C) {
ctx.So(err, convey.ShouldBeNil)
ctx.So(data, convey.ShouldNotBeNil)
})
})
ctx.Reset(func() {
gock.Off()
d.client.SetTransport(http.DefaultClient.Transport)
})
})
}
func TestAuth(t *testing.T) {
d.client.SetTransport(gock.DefaultTransport)
convey.Convey("get auth", t, func(ctx convey.C) {
ctx.Convey("When http response code != 0", func(ctx convey.C) {
httpMock("GET", "http://easyst.bilibili.co/v1/auth").Reply(200).JSON(`{"code":90000,"message":"success"}`)
_, err := d.Auth(context.Background(), "sven-apm=afab110001a20fa3a1c9b5bd67564ccd71c6253406725184bfa5d88c7ece9d17; Path=/; Domain=bilibili.co; Expires=Tue, 23 Oct 2018 07:25:02 GMT; Max-Age=1800; HttpOnly")
ctx.Convey("Then err should not be nil", func(ctx convey.C) {
ctx.So(err, convey.ShouldBeNil)
// ctx.So(data, convey.ShouldNotBeNil)
})
})
ctx.Reset(func() {
gock.Off()
d.client.SetTransport(http.DefaultClient.Transport)
})
})
}
func TestRole(t *testing.T) {
d.client.SetTransport(gock.DefaultTransport)
convey.Convey("TestRole", t, func(ctx convey.C) {
ctx.Convey("When http response code != 0", func(ctx convey.C) {
httpMock("GET", "http://easyst.bilibili.co/v1/node/role/app").Reply(200).JSON(`{"code":90000,"message":"success"}`)
data, err := d.Role(context.Background(), token)
ctx.Convey("Then err should not be nil", func(ctx convey.C) {
ctx.So(err, convey.ShouldBeNil)
ctx.So(data, convey.ShouldNotBeNil)
})
})
ctx.Reset(func() {
gock.Off()
d.client.SetTransport(http.DefaultClient.Transport)
})
})
}
func TestNodeTree(t *testing.T) {
d.client.SetTransport(gock.DefaultTransport)
convey.Convey("NodeTree", t, func(ctx convey.C) {
ctx.Convey("When http response code != 0", func(ctx convey.C) {
httpMock("GET", "http://easyst.bilibili.co/v1/node/bilibili.main.common-arch").Reply(200).JSON(`{
"status": 200,
"message": "success",
"code": 90000,
"data": {
"count": 0,
"results": 20,
"page": 1,
"data": null
}
}`)
_, err := d.NodeTree(context.Background(), token, "main.common-arch", "")
ctx.Convey("Then err should not be nil", func(ctx convey.C) {
ctx.So(err, convey.ShouldBeNil)
// ctx.So(data, convey.ShouldNotBeNil)
})
})
ctx.Reset(func() {
gock.Off()
d.client.SetTransport(http.DefaultClient.Transport)
})
})
}
func TestTree(t *testing.T) {
d.client.SetTransport(gock.DefaultTransport)
convey.Convey("TestTree", t, func(ctx convey.C) {
ctx.Convey("When http response code != 0", func(ctx convey.C) {
httpMock("GET", "http://easyst.bilibili.co/v1/node/apptree").Reply(200).JSON(`{
"code": 90000,
"data": {
"bilibili": {
"id": 0,
"name": "bilibili",
"alias": "哔哩哔哩",
"type": 1,
"path": "bilibili",
"tags": {},
"children": null
}
},
"message": "success",
"status": 200
}`)
_, err := d.Tree(context.Background(), token)
ctx.Convey("Then err should not be nil", func(ctx convey.C) {
ctx.So(err, convey.ShouldBeNil)
// ctx.So(data, convey.ShouldNotBeNil)
})
})
ctx.Reset(func() {
gock.Off()
d.client.SetTransport(http.DefaultClient.Transport)
})
})
}

41
app/admin/main/cache/http/BUILD vendored Normal file
View File

@@ -0,0 +1,41 @@
package(default_visibility = ["//visibility:public"])
load(
"@io_bazel_rules_go//go:def.bzl",
"go_library",
)
go_library(
name = "go_default_library",
srcs = [
"cache.go",
"http.go",
"overlord.go",
],
importpath = "go-common/app/admin/main/cache/http",
tags = ["automanaged"],
visibility = ["//visibility:public"],
deps = [
"//app/admin/main/cache/conf:go_default_library",
"//app/admin/main/cache/model:go_default_library",
"//app/admin/main/cache/service:go_default_library",
"//library/ecode:go_default_library",
"//library/log:go_default_library",
"//library/net/http/blademaster:go_default_library",
"//vendor/gopkg.in/yaml.v2:go_default_library",
],
)
filegroup(
name = "package-srcs",
srcs = glob(["**"]),
tags = ["automanaged"],
visibility = ["//visibility:private"],
)
filegroup(
name = "all-srcs",
srcs = [":package-srcs"],
tags = ["automanaged"],
visibility = ["//visibility:public"],
)

214
app/admin/main/cache/http/cache.go vendored Normal file
View File

@@ -0,0 +1,214 @@
package http
import (
"encoding/json"
"net"
"strconv"
"strings"
"go-common/app/admin/main/cache/model"
"go-common/library/ecode"
bm "go-common/library/net/http/blademaster"
yaml "gopkg.in/yaml.v2"
)
// @params ClustersReq
// @router get /x/admin/cache/clusters
// @response ClustersResp
func clusters(ctx *bm.Context) {
req := new(model.ClusterReq)
if err := ctx.Bind(req); err != nil {
return
}
req.Cookie = ctx.Request.Header.Get("Cookie")
ctx.JSON(srv.Clusters(ctx, req))
}
// @params AddClusterReq
// @router post /x/admin/cache/cluster/add
// @response EmpResp
func addCluster(ctx *bm.Context) {
req := new(model.AddClusterReq)
if err := ctx.Bind(req); err != nil {
return
}
ctx.JSON(srv.AddCluster(ctx, req))
}
// @params delClusterReq
// @router post /x/admin/cache/cluster/del
// @response EmpResp
func delCluster(ctx *bm.Context) {
req := new(model.DelClusterReq)
if err := ctx.Bind(req); err != nil {
return
}
ctx.JSON(srv.DelCluster(ctx, req))
}
// @params ClusterReq
// @router get /x/admin/cache/cluster
// @response []Cluster
func cluster(ctx *bm.Context) {
req := new(model.ClusterReq)
if err := ctx.Bind(req); err != nil {
return
}
if req.AppID != "" {
resp, err := srv.Cluster(ctx, req)
if err != nil {
ctx.JSON(nil, err)
return
}
ctx.JSON(&model.ClusterResp{Clusters: resp}, nil)
} else {
req.Cookie = ctx.Request.Header.Get("Cookie")
ctx.JSON(srv.Clusters(ctx, req))
}
}
// @params ClusterDtlReq
// @router get /x/admin/cache/cluster/detail
// @response ClusterDtlResp
func clusterDtl(ctx *bm.Context) {
req := new(model.ClusterDtlReq)
if err := ctx.Bind(req); err != nil {
return
}
ctx.JSON(srv.ClusterDtl(ctx, req))
}
// @params ModifyClusterReq
// @router post /x/admin/cache/cluster/modify
// @response EmpResp
func modifyCluster(ctx *bm.Context) {
req := new(model.ModifyClusterReq)
if err := ctx.Bind(req); err != nil {
return
}
ctx.JSON(srv.ModifyCluster(ctx, req))
}
func toml(ctx *bm.Context) {
req := new(model.ClusterReq)
if err := ctx.Bind(req); err != nil {
return
}
resp, err := srv.Toml(ctx, req)
if err != nil {
ctx.Status(500)
return
}
ctx.Writer.Write(resp)
}
// @params addFromYml
// @router post /x/admin/cache/cluster/from/yml
// @response EmpResp
func addFromYml(ctx *bm.Context) {
req := new(model.ClusterFromYml)
if err := ctx.Bind(req); err != nil {
ctx.JSONMap(map[string]interface{}{
"message": "参数有问题app_id,zone,tw_yml",
}, ecode.RequestErr)
return
}
type server struct {
AutoEjectHosts bool `yaml:"auto_eject_hosts"`
Backlog int `yaml:"backlog"`
Distribution string `yaml:"distribution"`
Hash string `yaml:"hash"`
Listen string `yaml:"listen"`
Preconnect bool `yaml:"preconnect"`
Timeout int `yaml:"timeout"`
Redis bool `yaml:"redis"`
ServerConnections int `yaml:"server_connections"`
ServerFailureLimit int `yaml:"server_failure_limit"`
ServerRetryTimeout int `yaml:"server_retry_timeout"`
Servers []string `yaml:"servers"`
}
type node struct {
Addr string `json:"addr"`
Weigth int64 `json:"weight"`
Alias string `json:"alias"`
}
confs := make(map[string]server)
err := yaml.Unmarshal([]byte(req.TwYml), &confs)
if err != nil {
ctx.JSONMap(map[string]interface{}{
"message": "解析twemproxy.yml文件失败",
}, ecode.RequestErr)
return
}
mcPort := 11211
rdPort := 26379
for name, conf := range confs {
ctp := "memcache"
if conf.Redis {
ctp = "redis"
}
if conf.Hash != "fnv1a_64" {
ctx.JSONMap(map[string]interface{}{
"message": "不支持除了fnv1a_64之外的hash方法",
}, ecode.RequestErr)
return
}
addr := "0.0.0.0:"
_, port, err := net.SplitHostPort(conf.Listen)
if err == nil {
addr = addr + port
} else {
if conf.Redis {
addr = addr + strconv.Itoa(rdPort)
rdPort++
} else {
addr = addr + strconv.Itoa(mcPort)
mcPort++
}
}
clst := &model.AddClusterReq{
Type: ctp,
AppID: req.AppID,
Zone: req.Zone,
HashMethod: "fnv1a_64",
HashDistribution: "ketama",
HashTag: "",
Name: name,
DailTimeout: 1000,
ReadTimeout: 1000,
WriteTimeout: 1000,
NodeConn: 2,
PingFailLimit: 3,
PingAutoEject: true,
ListenProto: "tcp",
ListenAddr: addr,
}
if _, err := srv.AddCluster(ctx, clst); err != nil {
ctx.JSONMap(map[string]interface{}{
"message": "添加cluster失败:" + name + " " + err.Error(),
}, ecode.RequestErr)
return
}
var nodes []*node
for _, n := range conf.Servers {
ss := strings.Split(n, " ")
idx := strings.LastIndex(ss[0], ":")
weight, _ := strconv.ParseInt(ss[0][idx+1:], 10, 64)
nodes = append(nodes, &node{Addr: ss[0][:idx], Weigth: weight, Alias: ss[1]})
}
ns, _ := json.Marshal(nodes)
cn := &model.ModifyClusterReq{
Name: name,
Action: 1,
Nodes: string(ns),
}
if _, err := srv.ModifyCluster(ctx, cn); err != nil {
ctx.JSONMap(map[string]interface{}{
"message": "添加cluster node失败:" + name + " " + err.Error(),
}, ecode.RequestErr)
return
}
}
}

69
app/admin/main/cache/http/http.go vendored Normal file
View File

@@ -0,0 +1,69 @@
package http
import (
"net/http"
"go-common/app/admin/main/cache/conf"
"go-common/app/admin/main/cache/service"
"go-common/library/log"
bm "go-common/library/net/http/blademaster"
)
var (
srv *service.Service
)
// Init init
func Init(c *conf.Config) {
srv = service.New(c)
engine := bm.DefaultServer(c.BM)
router(engine)
if err := engine.Start(); err != nil {
log.Error("engine.Start error(%v)", err)
panic(err)
}
}
func router(e *bm.Engine) {
e.Ping(ping)
e.Register(register)
g := e.Group("/x/admin/cache")
{
g.GET("/clusters", clusters)
g.GET("/cluster", cluster)
g.GET("/cluster/detail", clusterDtl)
g.POST("/cluster/add", addCluster)
g.POST("/cluster/del", delCluster)
g.POST("/cluster/node/modify", modifyCluster)
g.GET("/cluster/toml", toml)
g.POST("/cluster/from/yml", addFromYml)
}
ol := e.Group("/x/admin/cache/overlord")
{
ol.GET("/clusters", overlordClusters)
ol.POST("/del/cluster", overlordDelCluster)
ol.POST("/del/node", overlordDelNode)
ol.GET("/ops/names", overlordOpsClusterNames)
ol.GET("/ops/nodes", overlordOpsNodes)
ol.POST("/import/ops/cluster", overlordImportCluster)
ol.POST("/new/ops/node", overlordClusterNewNode)
ol.POST("/replace/ops/node", overlordClusterReplaceNode)
ol.GET("/app/clusters", overlordAppClusters)
ol.GET("/app/can/bind/clusters", overlordAppNeedClusters)
ol.POST("/app/cluster/bind", overlordAppClusterBind)
ol.POST("/app/cluster/del", overlordAppClusterDel)
ol.GET("/app/appids", overlordAppAppIDs)
ol.GET("/app/toml", overlordToml)
}
}
func ping(c *bm.Context) {
if err := srv.Ping(c); err != nil {
log.Error("cache-admin ping error(%v)", err)
c.AbortWithStatus(http.StatusServiceUnavailable)
}
}
func register(c *bm.Context) {
c.JSON(map[string]interface{}{}, nil)
}

166
app/admin/main/cache/http/overlord.go vendored Normal file
View File

@@ -0,0 +1,166 @@
package http
import (
"go-common/app/admin/main/cache/model"
bm "go-common/library/net/http/blademaster"
)
// @params OverlordReq
// @router get /x/admin/cache/overlord/clusters
// @response OverlordResp
func overlordClusters(ctx *bm.Context) {
req := new(model.OverlordReq)
if err := ctx.Bind(req); err != nil {
return
}
ctx.JSON(srv.OverlordClusters(ctx, req))
}
// @params OverlordReq
// @router get /x/admin/cache/overlord/ops/names
// @response EmpResp
func overlordOpsClusterNames(ctx *bm.Context) {
req := new(model.OverlordReq)
if err := ctx.Bind(req); err != nil {
return
}
ctx.JSON(srv.OpsClusterNames(ctx, req))
}
// @params OverlordReq
// @router get /x/admin/cache/overlord/ops/nodes
// @response EmpResp
func overlordOpsNodes(ctx *bm.Context) {
req := new(model.OverlordReq)
if err := ctx.Bind(req); err != nil {
return
}
ctx.JSON(srv.OpsClusterNodes(ctx, req))
}
// @params OverlordReq
// @router get /x/admin/cache/overlord/import/ops/cluster
// @response EmpResp
func overlordImportCluster(ctx *bm.Context) {
req := new(model.OverlordReq)
if err := ctx.Bind(req); err != nil {
return
}
ctx.JSON(srv.ImportOpsCluster(ctx, req))
}
// @params OverlordReq
// @router get /x/admin/cache/overlord/new/ops/node
// @response EmpResp
func overlordClusterNewNode(ctx *bm.Context) {
req := new(model.OverlordReq)
if err := ctx.Bind(req); err != nil {
return
}
ctx.JSON(srv.ImportOpsNode(ctx, req))
}
// @params OverlordReq
// @router get /x/admin/cache/overlord/replace/ops/node
// @response EmpResp
func overlordClusterReplaceNode(ctx *bm.Context) {
req := new(model.OverlordReq)
if err := ctx.Bind(req); err != nil {
return
}
ctx.JSON(srv.ReplaceOpsNode(ctx, req))
}
// @params OverlordReq
// @router get /x/admin/cache/overlord/cluster/del
// @response EmpResp
func overlordDelCluster(ctx *bm.Context) {
req := new(model.OverlordReq)
if err := ctx.Bind(req); err != nil {
return
}
ctx.JSON(srv.DelOverlordCluster(ctx, req))
}
// @params OverlordReq
// @router get /x/admin/cache/overlord/node/del
// @response EmpResp
func overlordDelNode(ctx *bm.Context) {
req := new(model.OverlordReq)
if err := ctx.Bind(req); err != nil {
return
}
ctx.JSON(srv.DelOverlordNode(ctx, req))
}
// @params OverlordReq
// @router get /x/admin/cache/overlord/app/clusters
// @response OverlordResp
func overlordAppClusters(ctx *bm.Context) {
req := new(model.OverlordReq)
if err := ctx.Bind(req); err != nil {
return
}
req.Cookie = ctx.Request.Header.Get("Cookie")
ctx.JSON(srv.OverlordAppClusters(ctx, req))
}
// @params OverlordReq
// @router get /x/admin/cache/overlord/app/can/bind/clusters
// @response OverlordResp
func overlordAppNeedClusters(ctx *bm.Context) {
req := new(model.OverlordReq)
if err := ctx.Bind(req); err != nil {
return
}
ctx.JSON(srv.OverlordAppCanBindClusters(ctx, req))
}
// @params OverlordReq
// @router get /x/admin/cache/overlord/app/cluster/bind
// @response OverlordResp
func overlordAppClusterBind(ctx *bm.Context) {
req := new(model.OverlordReq)
if err := ctx.Bind(req); err != nil {
return
}
req.Cookie = ctx.Request.Header.Get("Cookie")
ctx.JSON(srv.OverlordAppClusterBind(ctx, req))
}
// @params OverlordReq
// @router get /x/admin/cache/overlord/app/cluster/del
// @response OverlordResp
func overlordAppClusterDel(ctx *bm.Context) {
req := new(model.OverlordReq)
if err := ctx.Bind(req); err != nil {
return
}
req.Cookie = ctx.Request.Header.Get("Cookie")
ctx.JSON(srv.OverlordAppClusterDel(ctx, req))
}
// @params OverlordReq
// @router get /x/admin/cache/overlord/app/appids
// @response OverlordResp
func overlordAppAppIDs(ctx *bm.Context) {
req := new(model.OverlordReq)
if err := ctx.Bind(req); err != nil {
return
}
req.Cookie = ctx.Request.Header.Get("Cookie")
ctx.JSON(srv.OverlordAppAppIDs(ctx, req))
}
func overlordToml(ctx *bm.Context) {
req := new(model.OverlordReq)
if err := ctx.Bind(req); err != nil {
return
}
resp, err := srv.OverlordToml(ctx, req)
if err != nil {
ctx.Status(500)
return
}
ctx.Writer.Write(resp)
}

33
app/admin/main/cache/model/BUILD vendored Normal file
View File

@@ -0,0 +1,33 @@
package(default_visibility = ["//visibility:public"])
load(
"@io_bazel_rules_go//go:def.bzl",
"go_library",
)
go_library(
name = "go_default_library",
srcs = [
"model.go",
"opscache.go",
"overlord.go",
"tree.go",
],
importpath = "go-common/app/admin/main/cache/model",
tags = ["automanaged"],
visibility = ["//visibility:public"],
)
filegroup(
name = "package-srcs",
srcs = glob(["**"]),
tags = ["automanaged"],
visibility = ["//visibility:private"],
)
filegroup(
name = "all-srcs",
srcs = [":package-srcs"],
tags = ["automanaged"],
visibility = ["//visibility:public"],
)

132
app/admin/main/cache/model/model.go vendored Normal file
View File

@@ -0,0 +1,132 @@
package model
// ClustersReq params of cluster list.
type ClustersReq struct {
PN int `form:"pn" default:"1"`
PS int `form:"ps" default:"20"`
}
// ClusterResp resp result of clusters.
type ClusterResp struct {
Clusters []*Cluster `json:"clusters"`
Total int64 `json:"total"` // 总数量
}
// TableName gorm table name.
func (*Cluster) TableName() string {
return "cluster"
}
// Cluster resp result of clusters.
type Cluster struct {
ID int64 `gorm:"column:id" json:"id" toml:"id"`
Name string `json:"name" gorm:"column:name" toml:"name"` // 集群名字
Type string `json:"type" gorm:"column:type" toml:"cache_type"` // 缓存类型. (memcache,redis,redis-cluster)
AppID string `json:"app_id" gorm:"column:appids" toml:"app_id"`
Zone string `json:"zone" gorm:"column:zone" toml:"zone"` // 机房
HashMethod string `json:"hash_method" gorm:"column:hash_method" toml:"hash_method"` // 哈希方法 默认sha1
HashDistribution string `json:"hash_distribution" gorm:"column:hash_distribution" toml:"hash_distribution"` // key分布策略 默认为ketama一致性hash
HashTag string `json:"hash_tag" gorm:"column:hashtag" toml:"hash_tag"` // key hash 标识
DailTimeout int32 `json:"dail_timeout" gorm:"column:dial" toml:"dail_timeout"` // dial 超时
ReadTimeout int32 `json:"read_timeout" gorm:"column:read" toml:"read_timeout"`
WriteTimeout int32 `json:"write_timeout" gorm:"column:write" toml:"write_timeout"` // read 超时
NodeConn int8 `json:"node_conn" gorm:"column:nodeconn" toml:"node_connections"` // 集群内节点连接数
PingFailLimit int32 `json:"ping_fail_limit" gorm:"column:ping_fail_limit" toml:"ping_fail_limit"` // 节点失败检测次数
PingAutoEject bool `json:"ping_auto_eject" gorm:"column:auto_eject" toml:"ping_auto_eject"` // 是否自动剔除节点
ListenProto string `json:"listen_proto" toml:"listen_proto"`
ListenAddr string `json:"listen_addr" toml:"listen_addr"`
Servers []string `json:"-" gorm:"-" toml:"servers"`
Hit int `json:"hit" gorm:"-" toml:"-"` // 集群命中率
QPS int `json:"qps" gorm:"-" toml:"-"` // 集群qps
State int `json:"state" gorm:"-" toml:"-"` // 集群状态 0-online ;1-offline
MemRatio int `json:"mem_ratio" gorm:"-" toml:"-"` // 内存使用率
Nodes []NodeDtl `json:"nodes" gorm:"-" toml:"-"`
}
// AddClusterReq params of add a cluster.
type AddClusterReq struct {
ID int64 `json:"id" form:"id"` // 主键id 更新的时候使用
Type string `json:"type" form:"type" validate:"required"` // 缓存类型(memcache,redis,redis_cluster)
AppID string `json:"app_id" form:"app_id"` // 集群关联的appid
Zone string `json:"zone" form:"zone"` // 机房
HashMethod string `json:"hash_method" form:"hash_method" default:"fnv1a_64"` // 哈希方法 默认fvn1a_64
HashDistribution string `json:"hash_distribution" form:"hash_distribution" default:"ketama"` // key分布策略 默认为ketama一致性hash
HashTag string `json:"hash_tag" form:"hash_tag"` // key hash 标识
Name string `json:"name" form:"name" validate:"required"` // 集群名字
DailTimeout int32 `json:"dail_timeout" form:"dail_timeout" default:"100"` // dial 超时
ReadTimeout int32 `json:"read_timeout" form:"read_timeout" default:"100"` // read 超时
WriteTimeout int32 `json:"write_timeout" form:"write_timeout" default:"100"` // write 超时
NodeConn int8 `json:"node_conn" form:"node_conn" default:"10"` // 集群内及诶单连接数
PingFailLimit int32 `json:"ping_fail_limit" form:"ping_fail_limit"` // 节点失败检测次数
PingAutoEject bool `json:"ping_auto_eject" form:"ping_auto_eject"` // 是否自动剔除节点
ListenProto string `json:"listen_proto" form:"listen_proto" default:"tcp"` // 协议
ListenAddr string `json:"listen_addr" form:"listen_addr"` // 监听地址
}
// DelClusterReq params of del cluster.
type DelClusterReq struct {
ID int64 `form:"id"` // 集群主键id
}
// ClusterReq get cluster by appid or cluster name.
type ClusterReq struct {
AppID string `json:"app_id" form:"app_id"` // 关联的appid
Zone string `json:"zone" form:"zone"` // 机房信息
Type string `json:"type" form:"type"` // 缓存类型
PN int `form:"pn" default:"1"`
PS int `form:"ps" default:"20"`
// Cluster string `json:"cluster" form:"cluster"` // 集群名字
Cookie string `form:"-"`
}
// ModifyClusterReq params of modify cluster detail.
type ModifyClusterReq struct {
Name string `json:"name" form:"name"`
ID int64 `json:"id" form:"id"` // 集群id
Action int8 `json:"action" form:"action"` // 操作(1 添加节点2 删除节点删除节点时只需要传alias)
Nodes string `json:"nodes" form:"nodes"` // 节点信息 json数组 [{"id":11,"addr":"11","weight":1,"alias":"alias"}]
// Addrs []string `json:"addrs" form:"addrs,split"` // 节点地址
// Weight []int8 `json:"weight" form:"weight,split"` // 节点权重,必须与地址一一对应
// Alias []string `json:"alias" form:"alias,split"` // 节点别名,必须与地址一一对应
}
// ClusterDtlReq params of get cluster detail.
type ClusterDtlReq struct {
ID int64 `json:"id" form:"id"` // 集群id
}
// ClusterDtlResp resp result of cluster detail.
type ClusterDtlResp struct {
Nodes []NodeDtl `json:"nodes"`
}
// ClusterFromYml get cluster from tw yml.
type ClusterFromYml struct {
AppID string `json:"app_id" form:"app_id"` // 关联的appid
Zone string `json:"zone" form:"zone"` // 机房信息
TwYml string `json:"tw_yml" form:"tw_yml"`
}
// TableName gorm table name.
func (*NodeDtl) TableName() string {
return "nodes"
}
// NodeDtl cluster node detaiwl
type NodeDtl struct {
ID int64 `json:"id" gorm:"column:id"`
Cid int64 `json:"cid" gorm:"column:cid"`
Addr string `json:"addr" gorm:"column:addr"`
Weight int8 `json:"weight" gorm:"column:weight"`
Alias string `json:"alias" gorm:"column:alias"`
State int8 `json:"state" gorm:"column:state"`
QPS int64 `json:"qps" gorm:"-"`
MemUse float32 `json:"mem_use" gorm:"-"`
MemToal float32 `json:"mem_toal" gorm:"-"`
}
// EmpResp is empty resp.
type EmpResp struct {
}

20
app/admin/main/cache/model/opscache.go vendored Normal file
View File

@@ -0,0 +1,20 @@
package model
// OpsCacheMemcache ops cache mc
type OpsCacheMemcache struct {
Labels struct {
Name string `json:"name"`
Project string `json:"project"`
}
Targets []string `json:"targets"`
}
// OpsCacheRedis ops cache redis
type OpsCacheRedis struct {
Labels struct {
Name string `json:"name"`
Project string `json:"project"`
}
Type string `json:"type"`
Targets []string `json:"master_targets"`
}

128
app/admin/main/cache/model/overlord.go vendored Normal file
View File

@@ -0,0 +1,128 @@
package model
// OverlordReq .
type OverlordReq struct {
Name string `json:"name" form:"name"`
Zone string `json:"zone" form:"zone"`
Type string `json:"type" form:"type"`
Alias string `json:"alias" form:"alias"`
Addr string `json:"addr" form:"addr"`
AppID string `json:"appid" form:"appid"`
PN int `form:"pn" default:"1"`
PS int `form:"ps" default:"20"`
Cookie string `json:"-"`
}
// OverlordResp .
type OverlordResp struct {
Names []string `json:"names,omitempty"`
Addrs []string `json:"addrs,omitempty"`
Cluster *OverlordCluster `json:"cluster,omitempty"`
Clusters []*OverlordCluster `json:"clusters,omitempty"`
Total int64 `json:"total"`
Nodes []*OverlordNode `json:"nodes,omitempty"`
Apps []*OverlordApp `json:"apps,omitempty"`
AppIDs []string `json:"appids,omitempty"`
}
// TableName gorm table name.
func (*OverlordCluster) TableName() string {
return "overlord_cluster"
}
// OverlordCluster .
type OverlordCluster struct {
ID int64 `json:"id" gorm:"column:id"`
Name string `json:"name" gorm:"column:name"` // 集群名字
Type string `json:"type" gorm:"column:type"` // 缓存类型. (memcache,redis,redis-cluster)
Zone string `json:"zone" gorm:"column:zone"` // 机房
HashMethod string `json:"hash_method" gorm:"column:hash_method"` // 哈希方法 默认sha1
HashDistribution string `json:"hash_distribution" gorm:"column:hash_distribution"` // key分布策略 默认为ketama一致性hash
HashTag string `json:"hash_tag" gorm:"column:hashtag"` // key hash 标识
ListenProto string `json:"listen_proto" gorm:"column:listen_proto"`
ListenAddr string `json:"listen_addr" gorm:"column:listen_addr"`
DailTimeout int32 `json:"dail_timeout" gorm:"column:dial"` // dial 超时
ReadTimeout int32 `json:"read_timeout" gorm:"column:read"` // read 超时
WriteTimeout int32 `json:"write_timeout" gorm:"column:write"` // write 超时
NodeConn int8 `json:"node_conn" gorm:"column:nodeconn"` // 集群内节点连接数
PingFailLimit int32 `json:"ping_fail_limit" gorm:"column:ping_fail_limit"` // 节点失败检测次数
PingAutoEject bool `json:"ping_auto_eject" gorm:"column:auto_eject"` // 是否自动剔除节点
Nodes []*OverlordNode `json:"nodes" gorm:"-"`
}
// TableName gorm table name.
func (*OverlordNode) TableName() string {
return "overlord_node"
}
// OverlordNode .
type OverlordNode struct {
Cid int64 `json:"cid" gorm:"column:cid"`
Alias string `json:"alias" gorm:"column:alias"`
Addr string `json:"addr" gorm:"column:addr"`
Weight int8 `json:"weight" gorm:"column:weight"`
}
// TableName gorm table name.
func (*OverlordApp) TableName() string {
return "overlord_appid"
}
// OverlordApp .
type OverlordApp struct {
ID int64 `json:"-" gorm:"column:id"`
TreeID int64 `json:"treeid" gorm:"column:tree_id"`
AppID string `json:"appid" gorm:"column:app_id"`
Cid int64 `json:"cid" gorm:"column:cid"`
Cluster *OverlordCluster `json:"cluster,omitempty"`
}
// OverlordApiserver resp result of clusters.
type OverlordApiserver struct {
Group string `json:"group"`
Clusters []struct {
Name string `json:"name"`
Type string `json:"cache_type"`
// HashMethod string `json:"hash_method"`
// HashDistribution string `json:"hash_distribution"`
// HashTag string `json:"hash_tag"`
// DailTimeout int32 `json:"dail_timeout"`
// ReadTimeout int32 `json:"read_timeout"`
// WriteTimeout int32 `json:"write_timeout"`
// NodeConn int8 `json:"node_connections"`
// PingFailLimit int32 `json:"ping_fail_limit"`
// PingAutoEject bool `json:"ping_auto_eject"`
FrontEndPort int `json:"front_end_port"`
Instances []struct {
IP string `json:"ip"`
Port int `json:"port"`
Weight int8 `json:"weight"`
Alias string `json:"alias"`
State string `json:"state"`
Role string `json:"role"`
} `json:"instances"`
} `json:"clusters"`
}
// OverlordToml resp result of clusters.
type OverlordToml struct {
Name string `toml:"name"`
Type string `toml:"cache_type"`
HashMethod string `toml:"hash_method"`
HashDistribution string `toml:"hash_distribution"`
HashTag string `toml:"hash_tag"`
DailTimeout int32 `toml:"dail_timeout"`
ReadTimeout int32 `toml:"read_timeout"`
WriteTimeout int32 `toml:"write_timeout"`
NodeConn int8 `toml:"node_connections"`
PingFailLimit int32 `toml:"ping_fail_limit"`
PingAutoEject bool `toml:"ping_auto_eject"`
ListenProto string `toml:"listen_proto"`
ListenAddr string `toml:"listen_addr"`
Servers []string `toml:"servers"`
}

43
app/admin/main/cache/model/tree.go vendored Normal file
View File

@@ -0,0 +1,43 @@
package model
import "time"
// Res res.
type Res struct {
Count int `json:"count"`
Data []*TreeNode `json:"data"`
Page int `json:"page"`
Results int `json:"results"`
}
// TreeNode TreeNode.
type TreeNode struct {
Alias string `json:"alias"`
CreatedAt string `json:"created_at"`
Name string `json:"name"`
Path string `json:"path"`
Tags interface{} `json:"tags"`
Type int `json:"type"`
}
// Node node.
type Node struct {
Name string `json:"name"`
Path string `json:"path"`
TreeID int64 `json:"tree_id"`
}
//CacheData ...
type CacheData struct {
Data map[int64]*RoleNode `json:"data"`
CTime time.Time `json:"ctime"`
}
//RoleNode roleNode .
type RoleNode struct {
ID int64 `json:"id"`
Name string `json:"name"`
Path string `json:"path"`
Type int8 `json:"type"`
Role int8 `json:"role"`
}

36
app/admin/main/cache/script/BUILD vendored Normal file
View File

@@ -0,0 +1,36 @@
package(default_visibility = ["//visibility:public"])
load(
"@io_bazel_rules_go//go:def.bzl",
"go_binary",
"go_library",
)
go_binary(
name = "script",
embed = [":go_default_library"],
tags = ["automanaged"],
)
go_library(
name = "go_default_library",
srcs = ["main.go"],
importpath = "go-common/app/admin/main/cache/script",
tags = ["automanaged"],
visibility = ["//visibility:public"],
deps = ["//vendor/gopkg.in/yaml.v2:go_default_library"],
)
filegroup(
name = "package-srcs",
srcs = glob(["**"]),
tags = ["automanaged"],
visibility = ["//visibility:private"],
)
filegroup(
name = "all-srcs",
srcs = [":package-srcs"],
tags = ["automanaged"],
visibility = ["//visibility:public"],
)

169
app/admin/main/cache/script/main.go vendored Normal file
View File

@@ -0,0 +1,169 @@
package main
import (
"encoding/json"
"flag"
"fmt"
"io/ioutil"
"net/http"
"net/url"
"os"
"strconv"
"strings"
"gopkg.in/yaml.v2"
)
var (
infile string
appname string
host string
)
var usage = func() {
fmt.Fprintf(os.Stderr, "Usage of cache admin import conf tool:\n")
flag.PrintDefaults()
}
func main() {
flag.StringVar(&appname, "app", "", "server app name")
flag.StringVar(&host, "h", "http://sven.bilibili.co", "sven host")
flag.StringVar(&infile, "in", "twemproxy.yml", "server yml file")
flag.Usage = usage
flag.Parse()
if appname == "" {
panic("app name cannot be nil")
}
b, err := ioutil.ReadFile(infile)
if err != nil {
panic(err)
}
confs := make(map[string]server)
err = yaml.Unmarshal(b, &confs)
if err != nil {
panic(err)
}
// one service can't use 5168 memcaches at the same time.
redisPort := 26379
mcPort := 21211
for name, conf := range confs {
var (
addr string
cacheType string
)
if conf.Redis {
addr = fmt.Sprintf("0.0.0.0:%d", redisPort)
cacheType = "redis"
redisPort++
} else {
addr = fmt.Sprintf("0.0.0.0:%d", mcPort)
cacheType = "memcache"
mcPort++
}
addCluster(appname, name, addr, cacheType, conf.AutoEjectHosts, conf.ServerFailureLimit)
var nodes []*node
for _, n := range conf.Servers {
ss := strings.Split(n, " ")
idx := strings.LastIndex(ss[0], ":")
weight, _ := strconv.ParseInt(ss[0][idx+1:], 10, 64)
nodes = append(nodes, &node{Addr: ss[0][:idx], Weigth: weight, Alias: ss[1]})
}
addNode(name, nodes)
}
}
type server struct {
AutoEjectHosts bool `yaml:"auto_eject_hosts"`
Backlog int `yaml:"backlog"`
Distribution string `yaml:"distribution"`
Hash string `yaml:"hash"`
Listen string `yaml:"listen"`
Preconnect bool `yaml:"preconnect"`
Timeout int `yaml:"timeout"`
Redis bool `yaml:"redis"`
ServerConnections int `yaml:"server_connections"`
ServerFailureLimit int `yaml:"server_failure_limit"`
ServerRetryTimeout int `yaml:"server_retry_timeout"`
Servers []string `yaml:"servers"`
}
const (
readTimeout = "1000"
writeTimeout = "1000"
conn = "20"
dialTimeout = "100"
proto = "tcp"
)
var (
addCluURL = "%s/x/admin/cache/cluster/add"
addNodeURL = "%s/x/admin/cache/cluster/node/modify"
)
func addCluster(app, cluster, addr, tp string, reject bool, fail int) {
params := url.Values{}
params.Add("dail_timeout", dialTimeout)
params.Add("ping_fail_limit", strconv.FormatInt(int64(fail), 10))
params.Add("ping_auto_reject", strconv.FormatBool(reject))
params.Add("hash_distribution", "ketama")
params.Add("read_timeout", readTimeout)
params.Add("write_timeout", writeTimeout)
params.Add("node_conn", conn)
params.Add("type", tp)
params.Add("app_id", app)
params.Add("name", cluster)
params.Add("hash_method", "fnv1a_64")
params.Add("listen_proto", proto)
params.Add("listen_addr", addr)
url := fmt.Sprintf(addCluURL, host)
req, err := http.NewRequest("POST", url, strings.NewReader(params.Encode()))
if err != nil {
fmt.Printf("add cluster req err %v", err)
return
}
req.Header.Set("Content-Type", "application/x-www-form-urlencoded")
resp, err := http.DefaultClient.Do(req)
if err != nil {
fmt.Println("add cluster err", err)
return
}
defer resp.Body.Close()
bs, err := ioutil.ReadAll(resp.Body)
fmt.Printf("add cluster req %v resp %v err %v\n", params.Encode(), string(bs), err)
}
type node struct {
Addr string `json:"addr"`
Weigth int64 `json:"weight"`
Alias string `json:"alias"`
}
func addNode(name string, nodes []*node) {
params := url.Values{}
bs, err := json.Marshal(nodes)
if err != nil {
fmt.Println("node marshal err", err)
return
}
params.Add("action", "1")
params.Add("name", name)
params.Add("nodes", string(bs))
url := fmt.Sprintf(addNodeURL, host)
req, err := http.NewRequest("POST", url, strings.NewReader(params.Encode()))
if err != nil {
fmt.Printf("add node req err %v", err)
return
}
req.Header.Set("Content-Type", "application/x-www-form-urlencoded")
resp, err := http.DefaultClient.Do(req)
if err != nil {
fmt.Println("add node err", err)
return
}
defer resp.Body.Close()
bs, err = ioutil.ReadAll(resp.Body)
fmt.Printf("add node req %v resp %v err %v\n", params.Encode(), string(bs), err)
}

53
app/admin/main/cache/service/BUILD vendored Normal file
View File

@@ -0,0 +1,53 @@
package(default_visibility = ["//visibility:public"])
load(
"@io_bazel_rules_go//go:def.bzl",
"go_test",
"go_library",
)
go_test(
name = "go_default_test",
srcs = ["service_test.go"],
embed = [":go_default_library"],
rundir = ".",
tags = ["automanaged"],
deps = [
"//app/admin/main/cache/conf:go_default_library",
"//app/admin/main/cache/model:go_default_library",
"//vendor/github.com/smartystreets/goconvey/convey:go_default_library",
],
)
go_library(
name = "go_default_library",
srcs = [
"opscache.go",
"overlord.go",
"service.go",
],
importpath = "go-common/app/admin/main/cache/service",
tags = ["automanaged"],
visibility = ["//visibility:public"],
deps = [
"//app/admin/main/cache/conf:go_default_library",
"//app/admin/main/cache/dao:go_default_library",
"//app/admin/main/cache/model:go_default_library",
"//library/ecode:go_default_library",
"//vendor/github.com/BurntSushi/toml:go_default_library",
],
)
filegroup(
name = "package-srcs",
srcs = glob(["**"]),
tags = ["automanaged"],
visibility = ["//visibility:private"],
)
filegroup(
name = "all-srcs",
srcs = [":package-srcs"],
tags = ["automanaged"],
visibility = ["//visibility:public"],
)

View File

@@ -0,0 +1,24 @@
package service
import (
"context"
"time"
)
func (s *Service) loadOpsCache() {
mcs, err := s.dao.OpsMemcaches(context.Background())
if err == nil {
s.opsMcs = mcs
}
rds, err := s.dao.OpsRediss(context.Background())
if err == nil {
s.opsRds = rds
}
}
func (s *Service) loadOpsproc() {
for {
s.loadOpsCache()
time.Sleep(time.Minute)
}
}

514
app/admin/main/cache/service/overlord.go vendored Normal file
View File

@@ -0,0 +1,514 @@
package service
import (
"bytes"
"context"
"fmt"
"strconv"
"go-common/app/admin/main/cache/model"
"go-common/library/ecode"
"github.com/BurntSushi/toml"
)
// OpsClusterNames .
func (s *Service) OpsClusterNames(c context.Context, arg *model.OverlordReq) (resp *model.OverlordResp, err error) {
resp = &model.OverlordResp{}
if arg.Type == "memcache" {
for _, opsmc := range s.opsMcs {
resp.Names = append(resp.Names, opsmc.Labels.Name)
}
} else if arg.Type == "redis" {
for _, opsrd := range s.opsRds {
if opsrd.Type == "redis_standalone" {
resp.Names = append(resp.Names, opsrd.Labels.Name)
}
}
} else if arg.Type == "redis_cluster" {
for _, opsrd := range s.opsRds {
if opsrd.Type == "redis_cluster" {
resp.Names = append(resp.Names, opsrd.Labels.Name)
}
}
} else {
err = fmt.Errorf("unsupport type:%s", arg.Type)
}
return
}
// OpsClusterNodes .
func (s *Service) OpsClusterNodes(c context.Context, arg *model.OverlordReq) (resp *model.OverlordResp, err error) {
resp = &model.OverlordResp{}
if arg.Type == "memcache" {
for _, opsmc := range s.opsMcs {
if arg.Name == opsmc.Labels.Name {
resp.Addrs = opsmc.Targets
return
}
}
} else if arg.Type == "redis" {
for _, opsrd := range s.opsRds {
if opsrd.Type == "redis_standalone" && arg.Name == opsrd.Labels.Name {
resp.Addrs = opsrd.Targets
return
}
}
} else if arg.Type == "redis_cluster" {
for _, opsrd := range s.opsRds {
if opsrd.Type == "redis_cluster" && arg.Name == opsrd.Labels.Name {
resp.Addrs = opsrd.Targets
return
}
}
} else {
err = fmt.Errorf("unsupport type:%s", arg.Type)
}
return
}
// ImportOpsCluster .
func (s *Service) ImportOpsCluster(c context.Context, arg *model.OverlordReq) (resp *model.EmpResp, err error) {
exist := 0
if err = s.dao.DB.Model(&model.OverlordCluster{}).Where("name=?", arg.Name).Count(&exist).Error; err != nil {
return
}
if exist > 0 {
return
}
var targets []string
if arg.Type == "memcache" {
for _, opsmc := range s.opsMcs {
if arg.Name == opsmc.Labels.Name {
targets = opsmc.Targets
break
}
}
} else if arg.Type == "redis" {
for _, opsrd := range s.opsRds {
if opsrd.Type == "redis_standalone" && arg.Name == opsrd.Labels.Name {
targets = opsrd.Targets
break
}
}
} else if arg.Type == "redis_cluster" {
for _, opsrd := range s.opsRds {
if opsrd.Type == "redis_cluster" && arg.Name == opsrd.Labels.Name {
targets = opsrd.Targets
break
}
}
} else {
err = fmt.Errorf("unsupport type:%s", arg.Type)
return
}
port := 0
if err = s.dao.DB.Model(&model.OverlordCluster{}).Where("type=?", arg.Type).Count(&port).Error; err != nil {
return
}
if arg.Type == "memcache" {
port += 11211
} else {
port += 26379
}
tranDB := s.dao.DB.Begin()
oc := &model.OverlordCluster{
Name: arg.Name,
Type: arg.Type,
Zone: arg.Zone,
HashMethod: "fnv1a_64",
HashDistribution: "ketama",
HashTag: "",
ListenProto: "tcp",
ListenAddr: "0.0.0.0:" + strconv.Itoa(port),
DailTimeout: 1000,
ReadTimeout: 1000,
WriteTimeout: 1000,
NodeConn: 2,
PingFailLimit: 3,
PingAutoEject: true,
}
if err = tranDB.Create(oc).Error; err != nil {
tranDB.Rollback()
return
}
for i, target := range targets {
ocn := &model.OverlordNode{
Cid: oc.ID,
Alias: arg.Name + strconv.Itoa(i+1),
Addr: target,
Weight: 1,
}
if err = tranDB.Create(ocn).Error; err != nil {
tranDB.Rollback()
return
}
}
err = tranDB.Commit().Error
return
}
// OverlordClusters .
func (s *Service) OverlordClusters(c context.Context, arg *model.OverlordReq) (resp *model.OverlordResp, err error) {
resp = &model.OverlordResp{}
if arg.Name != "" {
err = s.dao.DB.Where("zone=? AND type=? AND name like ?", arg.Zone, arg.Type, "%"+arg.Name+"%").Order("id desc").Offset((arg.PN - 1) * arg.PS).Limit(arg.PS).Find(&resp.Clusters).Error
s.dao.DB.Model(&model.OverlordCluster{}).Where("zone=? AND type=? AND name like ?", arg.Zone, arg.Type, arg.Name).Count(&resp.Total)
} else {
err = s.dao.DB.Where("zone=? AND type=?", arg.Zone, arg.Type).Order("id desc").Offset((arg.PN - 1) * arg.PS).Limit(arg.PS).Find(&resp.Clusters).Error
s.dao.DB.Model(&model.OverlordCluster{}).Where("zone=? AND type=?", arg.Zone, arg.Type).Count(&resp.Total)
}
if err != nil {
return
}
for _, cluster := range resp.Clusters {
var ens *model.OverlordResp
if ens, err = s.ExistOverlordNodes(c, &model.OverlordReq{Name: cluster.Name}); err != nil {
return
}
cluster.Nodes = ens.Nodes
}
return
}
// ExistOverlordNodes .
func (s *Service) ExistOverlordNodes(c context.Context, arg *model.OverlordReq) (resp *model.OverlordResp, err error) {
cluster := &model.OverlordCluster{}
if err = s.dao.DB.Model(cluster).Where("name=?", arg.Name).First(cluster).Error; err != nil {
fmt.Printf("get cluster err %v\n", err)
return
}
if cluster.ID == 0 {
err = fmt.Errorf("cluster not exist:%s", arg.Name)
return
}
var exists []*model.OverlordNode
if err = s.dao.DB.Where("cid=?", cluster.ID).Order("id").Find(&exists).Error; err != nil {
return
}
resp = &model.OverlordResp{}
resp.Cluster = cluster
resp.Nodes = exists
return
}
// NotExistOverlordAddrs .
func (s *Service) NotExistOverlordAddrs(c context.Context, arg *model.OverlordReq) (resp *model.OverlordResp, err error) {
cluster := &model.OverlordCluster{}
if err = s.dao.DB.Model(cluster).Where("name=?", arg.Name).First(cluster).Error; err != nil {
return
}
if cluster.ID == 0 {
err = fmt.Errorf("cluster not exist:%s", arg.Name)
return
}
var targets []string
tp := arg.Type
if tp == "memcache" {
for _, opsmc := range s.opsMcs {
if arg.Name == opsmc.Labels.Name {
targets = opsmc.Targets
break
}
}
} else if tp == "redis" {
for _, opsrd := range s.opsRds {
if arg.Name == opsrd.Labels.Name {
targets = opsrd.Targets
if opsrd.Type == "redis_cluster" {
tp = "redis_cluster"
}
break
}
}
} else {
err = fmt.Errorf("unsupport type:%s", arg.Type)
return
}
var exists []*model.OverlordNode
if err = s.dao.DB.Where("cid=?", cluster.ID).Order("id").Find(&exists).Error; err != nil {
return
}
resp = &model.OverlordResp{}
NEXT:
for _, target := range targets {
for _, exist := range exists {
if target == exist.Addr {
continue NEXT
}
}
resp.Addrs = append(resp.Addrs, target)
}
return
}
// ImportOpsNode .
func (s *Service) ImportOpsNode(c context.Context, arg *model.OverlordReq) (resp *model.EmpResp, err error) {
nen, err := s.NotExistOverlordAddrs(c, arg)
if err != nil {
return
}
en, err := s.ExistOverlordNodes(c, arg)
if err != nil {
return
}
i := len(en.Nodes)
tranDB := s.dao.DB.Begin()
for _, target := range nen.Addrs {
ocn := &model.OverlordNode{
Cid: en.Cluster.ID,
Alias: arg.Name + strconv.Itoa(i+1),
Addr: target,
Weight: 1,
}
if err = tranDB.Create(ocn).Error; err != nil {
tranDB.Rollback()
return
}
i++
}
err = tranDB.Commit().Error
return
}
// ReplaceOpsNode .
func (s *Service) ReplaceOpsNode(c context.Context, arg *model.OverlordReq) (resp *model.EmpResp, err error) {
nen, err := s.NotExistOverlordAddrs(c, arg)
if err != nil {
return
}
if len(nen.Addrs) == 0 {
err = fmt.Errorf("cluster have not new node:%s", arg.Name)
return
}
en, err := s.ExistOverlordNodes(c, arg)
if err != nil {
return
}
for _, n := range en.Nodes {
if n.Alias != arg.Alias && n.Addr == arg.Addr {
err = fmt.Errorf("cluster:%s node:%s used by other node:%s ", arg.Name, arg.Addr, n.Alias)
return
}
}
node := &model.OverlordNode{}
if err = s.dao.DB.Model(node).Where("cid=? AND alias=?", en.Cluster.ID, arg.Alias).First(node).Error; err != nil {
return
}
if node.Addr == arg.Addr {
return
}
err = s.dao.DB.Model(node).Where("alias=? AND addr=?", node.Alias, node.Addr).Update("addr", arg.Addr).Error
return
}
// DelOverlordCluster .
func (s *Service) DelOverlordCluster(c context.Context, arg *model.OverlordReq) (resp *model.EmpResp, err error) {
en, err := s.ExistOverlordNodes(c, arg)
if err != nil {
return
}
if err = s.dao.DB.Delete(en.Cluster).Error; err != nil {
return
}
err = s.dao.DB.Delete(&model.OverlordNode{}, "cid=?", en.Cluster.ID).Error
return
}
// DelOverlordNode .
func (s *Service) DelOverlordNode(c context.Context, arg *model.OverlordReq) (resp *model.EmpResp, err error) {
en, err := s.ExistOverlordNodes(c, arg)
if err != nil {
return
}
err = s.dao.DB.Delete(&model.OverlordNode{}, "cid=? AND alias=? AND addr=?", en.Cluster.ID, arg.Alias, arg.Addr).Error
return
}
// OverlordAppClusters .
func (s *Service) OverlordAppClusters(c context.Context, arg *model.OverlordReq) (resp *model.OverlordResp, err error) {
appids, err := s.appids(c, arg.Cookie, arg.AppID)
if err != nil {
err = ecode.AccessDenied
return
}
resp = &model.OverlordResp{}
if len(appids) <= 1 {
err = s.dao.DB.Where("app_id like ?", "%"+arg.AppID+"%").Order("id desc").Offset((arg.PN - 1) * arg.PS).Limit(arg.PS).Find(&resp.Apps).Error
s.dao.DB.Model(&model.OverlordApp{}).Where("app_id like ?", arg.AppID).Count(&resp.Total)
} else if len(appids) > 1 {
err = s.dao.DB.Where("app_id in (?)", appids).Order("id desc").Offset((arg.PN - 1) * arg.PS).Limit(arg.PS).Find(&resp.Apps).Error
s.dao.DB.Model(&model.OverlordApp{}).Where("app_id in (?)", appids).Count(&resp.Total)
}
if err != nil || len(resp.Apps) == 0 {
return
}
var cids []int64
for _, app := range resp.Apps {
cids = append(cids, app.Cid)
}
var clusters []*model.OverlordCluster
if err = s.dao.DB.Find(&clusters, "id in (?)", cids).Error; err != nil {
return
}
for _, cluster := range clusters {
var ens *model.OverlordResp
if ens, err = s.ExistOverlordNodes(c, &model.OverlordReq{Name: cluster.Name}); err != nil {
return
}
cluster.Nodes = ens.Nodes
for _, app := range resp.Apps {
if cluster.ID == app.Cid {
app.Cluster = cluster
}
}
}
if len(appids) <= 1 {
// 当使用appid查询时填充overlord-mesos的数据
if ocs, err := s.dao.OverlordClusters(c, "", arg.AppID); err == nil {
clusters = append(clusters, ocs...)
}
}
return
}
// OverlordAppCanBindClusters .
func (s *Service) OverlordAppCanBindClusters(c context.Context, arg *model.OverlordReq) (resp *model.OverlordResp, err error) {
resp = &model.OverlordResp{}
err = s.dao.DB.Where("zone=? AND type=?", arg.Zone, arg.Type).Find(&resp.Clusters).Error
if err != nil {
return
}
for _, cluster := range resp.Clusters {
resp.Names = append(resp.Names, cluster.Name)
}
resp.Clusters = nil
return
}
// OverlordAppClusterBind .
func (s *Service) OverlordAppClusterBind(c context.Context, arg *model.OverlordReq) (resp *model.EmpResp, err error) {
treeid, err := s.treeid(c, arg.Cookie, arg.AppID)
if err != nil || treeid == 0 {
err = ecode.AccessDenied
return
}
cluster := &model.OverlordCluster{}
if err = s.dao.DB.Model(cluster).Where("zone=? AND type=? AND name=?", arg.Zone, arg.Type, arg.Name).First(cluster).Error; err != nil {
return
}
app := &model.OverlordApp{
TreeID: treeid,
AppID: arg.AppID,
Cid: cluster.ID,
}
err = s.dao.DB.Create(app).Error
return
}
// OverlordAppClusterDel .
func (s *Service) OverlordAppClusterDel(c context.Context, arg *model.OverlordReq) (resp *model.EmpResp, err error) {
treeid, err := s.treeid(c, arg.Cookie, arg.AppID)
if err != nil || treeid == 0 {
err = ecode.AccessDenied
return
}
cluster := &model.OverlordCluster{}
if err = s.dao.DB.Model(cluster).Where("zone=? AND type=? AND name=?", arg.Zone, arg.Type, arg.Name).First(cluster).Error; err != nil {
return
}
app := &model.OverlordApp{}
if err = s.dao.DB.Model(app).Where("app_id=? AND cid=?", arg.AppID, cluster.ID).First(app).Error; err != nil {
return
}
err = s.dao.DB.Table(app.TableName()).Delete(app).Error
return
}
// OverlordAppAppIDs .
func (s *Service) OverlordAppAppIDs(c context.Context, arg *model.OverlordReq) (resp *model.OverlordResp, err error) {
appids, err := s.appids(c, arg.Cookie, "")
if err != nil {
err = ecode.AccessDenied
return
}
resp = &model.OverlordResp{}
resp.AppIDs = appids
return
}
// OverlordToml return a toml file of cluster infos.
func (s *Service) OverlordToml(c context.Context, arg *model.OverlordReq) (resp []byte, err error) {
var apps []*model.OverlordApp
if err = s.dao.DB.Where("app_id=?", arg.AppID).Find(&apps).Error; err != nil {
return
}
var cids []int64
for _, app := range apps {
cids = append(cids, app.Cid)
}
var clusters []*model.OverlordCluster
// TODO(felix): 待都走overlord-mesos后干掉
if err = s.dao.DB.Where("zone=? AND id in (?)", arg.Zone, cids).Find(&clusters).Error; err != nil {
return
}
if len(clusters) == 0 {
// TODO(felix): 待都走overlord-mesos后干掉
if err = s.dao.DB.Where("zone='sh001' AND id in (?)", cids).Find(&clusters).Error; err != nil {
return
}
}
var ocs []*model.OverlordCluster
if ocs, err = s.dao.OverlordClusters(c, arg.Zone, arg.AppID); err == nil {
if len(ocs) == 0 {
ocs, err = s.dao.OverlordClusters(c, "sh001", arg.AppID)
}
if len(ocs) > 0 {
clusters = append(clusters, ocs...)
}
}
t := struct {
Clusters []*model.OverlordToml `toml:"clusters"`
}{}
for _, cluster := range clusters {
ot := &model.OverlordToml{
Name: cluster.Name,
Type: cluster.Type,
HashMethod: cluster.HashMethod,
HashDistribution: cluster.HashDistribution,
HashTag: cluster.HashTag,
ListenProto: cluster.ListenProto,
ListenAddr: cluster.ListenAddr,
DailTimeout: cluster.DailTimeout,
ReadTimeout: cluster.ReadTimeout,
WriteTimeout: cluster.WriteTimeout,
NodeConn: cluster.NodeConn,
PingFailLimit: cluster.PingFailLimit,
PingAutoEject: cluster.PingAutoEject,
}
var nodes []*model.OverlordNode
if len(cluster.Nodes) == 0 {
if err = s.dao.DB.Where("cid=?", cluster.ID).Order("id").Find(&nodes).Error; err != nil {
return
}
} else {
nodes = cluster.Nodes
}
var servers []string
for _, node := range nodes {
var server string
if cluster.Type == "redis_cluster" {
server = node.Addr
} else {
server = fmt.Sprintf("%s:%d %s", node.Addr, node.Weight, node.Alias)
}
servers = append(servers, server)
}
ot.Servers = servers
t.Clusters = append(t.Clusters, ot)
}
buf := bytes.NewBuffer(resp)
err = toml.NewEncoder(buf).Encode(t)
resp = buf.Bytes()
return
}

256
app/admin/main/cache/service/service.go vendored Normal file
View File

@@ -0,0 +1,256 @@
package service
import (
"bytes"
"context"
"encoding/json"
"fmt"
"strings"
"go-common/app/admin/main/cache/conf"
"go-common/app/admin/main/cache/dao"
"go-common/app/admin/main/cache/model"
"go-common/library/ecode"
"github.com/BurntSushi/toml"
)
// Service struct
type Service struct {
c *conf.Config
dao *dao.Dao
opsMcs []*model.OpsCacheMemcache
opsRds []*model.OpsCacheRedis
}
// New init
func New(c *conf.Config) (s *Service) {
s = &Service{
c: c,
dao: dao.New(c),
}
go s.loadOpsproc()
return s
}
// Ping Service
func (s *Service) Ping(c context.Context) (err error) {
return
}
// Close Service
func (s *Service) Close() {
s.dao.Close()
}
func (s *Service) appids(c context.Context, cookie, appid string) (appids []string, err error) {
msg, err := s.dao.Auth(c, cookie)
if err != nil {
err = ecode.AccessDenied
return
}
tmp, ok := msg["token"]
if !ok {
err = ecode.NothingFound
return
}
token, ok := tmp.(string)
if !ok {
err = ecode.NothingFound
return
}
nodes, err := s.dao.Role(c, token)
if err != nil {
return
}
if appid == "" {
for _, node := range nodes.Data {
appids = append(appids, node.Path)
}
} else {
for _, node := range nodes.Data {
if appid == node.Path {
appids = []string{appid}
break
}
}
}
return
}
func (s *Service) treeid(c context.Context, cookie, appid string) (treeid int64, err error) {
if appid == "" {
err = ecode.AccessDenied
return
}
msg, err := s.dao.Auth(c, cookie)
if err != nil {
err = ecode.AccessDenied
return
}
tmp, ok := msg["token"]
if !ok {
err = ecode.NothingFound
return
}
token, ok := tmp.(string)
if !ok {
err = ecode.NothingFound
return
}
nodes, err := s.dao.Role(c, token)
if err != nil {
return
}
for _, node := range nodes.Data {
if appid == node.Path {
treeid = node.ID
return
}
}
return
}
// Clusters get clusters.
func (s *Service) Clusters(c context.Context, req *model.ClusterReq) (resp *model.ClusterResp, err error) {
appids, err := s.appids(c, req.Cookie, req.AppID)
if err != nil {
err = ecode.AccessDenied
return
}
resp = new(model.ClusterResp)
if len(appids) == 0 {
return
}
if err = s.dao.DB.Where("appids in (?) AND zone=? AND type=?", appids, req.Zone, req.Type).Order("id").Offset((req.PN - 1) * req.PS).Limit(req.PS).Find(&resp.Clusters).Error; err != nil {
return
}
var count int64
s.dao.DB.Model(&model.Cluster{}).Where("appids in (?) AND zone=? AND type=?", appids, req.Zone, req.Type).Count(&count)
resp.Total = count
return
}
// AddCluster add new cluster.
func (s *Service) AddCluster(c context.Context, req *model.AddClusterReq) (resp *model.EmpResp, err error) {
cluster := &model.Cluster{
Name: req.Name,
Type: req.Type,
AppID: req.AppID,
Zone: req.Zone,
HashMethod: req.HashMethod,
HashDistribution: req.HashDistribution,
HashTag: req.HashTag,
DailTimeout: req.DailTimeout,
ReadTimeout: req.ReadTimeout,
WriteTimeout: req.WriteTimeout,
NodeConn: req.NodeConn,
ListenAddr: req.ListenAddr,
ListenProto: req.ListenProto,
PingFailLimit: req.PingFailLimit,
PingAutoEject: req.PingAutoEject,
}
if req.ID == 0 {
err = s.dao.DB.Create(cluster).Error
} else {
cluster.ID = req.ID
s.dao.DB.Save(cluster)
}
return
}
// DelCluster del cluster of req id.
func (s *Service) DelCluster(c context.Context, req *model.DelClusterReq) (resp *model.EmpResp, err error) {
err = s.dao.DB.Exec("DELETE FROM cluster where id= ?", req.ID).Error
if err != nil {
return
}
err = s.dao.DB.Exec("DELETE FROM nodes where cid= ?", req.ID).Error
return
}
// Cluster search cluster by appid or cluster name.
func (s *Service) Cluster(c context.Context, req *model.ClusterReq) (resp []*model.Cluster, err error) {
if req.Type != "" {
err = s.dao.DB.Where("appids=? AND zone=? AND type=?", req.AppID, req.Zone, req.Type).Find(&resp).Error
} else {
err = s.dao.DB.Where("appids=? AND zone=?", req.AppID, req.Zone).Find(&resp).Error
}
if err != nil {
return
}
for _, clu := range resp {
err = s.dao.DB.Where("cid = ?", clu.ID).Find(&clu.Nodes).Error
if err != nil {
return nil, err
}
}
return
}
// ModifyCluster add or del cluster nodes.
func (s *Service) ModifyCluster(c context.Context, req *model.ModifyClusterReq) (resp *model.EmpResp, err error) {
var nodes []*model.NodeDtl
err = json.Unmarshal([]byte(req.Nodes), &nodes)
if err != nil {
return
}
var id = req.ID
if req.Name != "" {
var cluster = &model.Cluster{}
err = s.dao.DB.Where("name = ?", req.Name).First(cluster).Error
if err != nil {
return
}
id = cluster.ID
}
if req.Action == 2 {
var alias []string
for _, ali := range nodes {
alias = append(alias, ali.Alias)
}
//err = s.dao.DB.Delete(&nodes).Error
err = s.dao.DB.Exec("DELETE FROM nodes WHERE alias in (?) ", strings.Join(alias, ",")).Error
return
} else if req.Action == 1 {
// var nodes []*model.NodeDtl
for _, node := range nodes {
node.Cid = id
err = s.dao.DB.Create(node).Error
}
return
}
return
}
// ClusterDtl get cluster detail about nodes info.
func (s *Service) ClusterDtl(c context.Context, req *model.ClusterDtlReq) (resp *model.ClusterDtlResp, err error) {
resp = new(model.ClusterDtlResp)
err = s.dao.DB.Where("cid = ?", req.ID).Find(&resp.Nodes).Error
// TODO(lintanghui):get node info
return
}
// Toml return a toml file of cluster infos.
func (s *Service) Toml(c context.Context, req *model.ClusterReq) (resp []byte, err error) {
clusters, err := s.Cluster(c, req)
if err != nil {
return
}
for _, cluster := range clusters {
for _, node := range cluster.Nodes {
cluster.Servers = append(cluster.Servers, fmt.Sprintf("%s:%d %s", node.Addr, node.Weight, node.Alias))
}
}
buf := bytes.NewBuffer(resp)
t := struct {
Clusters []*model.Cluster `toml:"clusters"`
}{
Clusters: clusters,
}
err = toml.NewEncoder(buf).Encode(t)
resp = buf.Bytes()
return
}

View File

@@ -0,0 +1,91 @@
package service
import (
"context"
"flag"
"os"
"path/filepath"
"testing"
"go-common/app/admin/main/cache/conf"
"go-common/app/admin/main/cache/model"
. "github.com/smartystreets/goconvey/convey"
)
var (
svr *Service
)
func TestMain(m *testing.M) {
var (
err error
)
dir, _ := filepath.Abs("../cmd/test.toml")
if err = flag.Set("conf", dir); err != nil {
panic(err)
}
if err = conf.Init(); err != nil {
panic(err)
}
svr = New(conf.Conf)
os.Exit(m.Run())
}
func TestCluster(t *testing.T) {
Convey("test cluster ", t, func() {
req := &model.ClusterReq{
PN: 1,
PS: 10,
}
resp, err := svr.Clusters(context.TODO(), req)
So(err, ShouldBeNil)
So(resp, ShouldNotBeNil)
t.Logf("resp %v", resp.Clusters[0])
})
}
func TestAddCluster(t *testing.T) {
Convey("test add cluster ", t, func() {
req := &model.AddClusterReq{
Type: "memcache",
AppID: "test",
HashMethod: "sha1",
HashDistribution: "ketama",
}
_, err := svr.AddCluster(context.TODO(), req)
So(err, ShouldBeNil)
})
}
func TestSearchCluster(t *testing.T) {
Convey("test search cluster", t, func() {
req := &model.ClusterReq{
AppID: "test",
}
resp, err := svr.Cluster(context.TODO(), req)
So(err, ShouldBeNil)
So(resp, ShouldNotBeNil)
t.Logf("search resp %+v", resp)
})
}
func TestModifyCluster(t *testing.T) {
Convey("test add cluster nodes", t, func() {
req := &model.ModifyClusterReq{
ID: 1,
Action: 1,
Nodes: `[{"addr":"11","alias":"test1","weight":1}]`,
}
_, err := svr.ModifyCluster(context.TODO(), req)
So(err, ShouldBeNil)
})
Convey("test get cluster detail", t, func() {
req := &model.ClusterDtlReq{
ID: 1,
}
resp, err := svr.ClusterDtl(context.TODO(), req)
So(err, ShouldBeNil)
So(len(resp.Nodes), ShouldEqual, 2)
})
}

574
app/admin/main/cache/swagger.json vendored Normal file
View File

@@ -0,0 +1,574 @@
{
"swagger": "2.0",
"info": {
"title": "go-common api",
"description": "api",
"version": "1.0",
"contact": {
"email": "lintanghui@bilibili.com"
},
"license": {
"name": "Apache 2.0",
"url": "http://www.apache.org/licenses/LICENSE-2.0.html"
}
},
"paths": {
"/x/admin/cache/cluster": {
"get": {
"operationId": "/x/admin/cache/cluster",
"parameters": [
{
"in": "query",
"name": "app_id",
"description": "关联的appid",
"required": true,
"type": "string"
}
],
"responses": {
"200": {
"description": "服务成功响应内容",
"schema": {
"type": "object",
"properties": {
"code": {
"description": "错误码描述",
"type": "integer"
},
"data": {
"type": "array",
"items": {
"$ref": "#/definitions/Cluster",
"type": "object"
}
},
"message": {
"description": "错误码文本描述",
"type": "string"
},
"ttl": {
"description": "客户端限速时间",
"type": "integer",
"format": "int64"
}
}
}
}
}
}
},
"/x/admin/cache/cluster/add": {
"post": {
"operationId": "/x/admin/cache/cluster/add",
"parameters": [
{
"in": "query",
"name": "hash_tag",
"description": "key hash 标识",
"required": true,
"type": "string"
},
{
"in": "query",
"name": "name",
"description": "集群名字",
"required": true,
"type": "string"
},
{
"in": "query",
"name": "node_conn",
"description": "集群内及诶单连接数 默认值 10",
"required": true,
"type": "integer",
"format": "int32"
},
{
"in": "query",
"name": "type",
"description": "缓存类型(memcache,redis,redis-cluster)",
"required": true,
"type": "string"
},
{
"in": "query",
"name": "hash_method",
"description": "哈希方法 默认fvn1a_64 默认值 fnv1a_64",
"required": true,
"type": "string"
},
{
"in": "query",
"name": "ping_fail_limit",
"description": "节点失败检测次数",
"required": true,
"type": "integer",
"format": "int32"
},
{
"in": "query",
"name": "dail_timeout",
"description": "dial 超时 默认值 100",
"required": true,
"type": "integer",
"format": "int32"
},
{
"in": "query",
"name": "read_timeout",
"description": "read 超时 默认值 100",
"required": true,
"type": "integer",
"format": "int32"
},
{
"in": "query",
"name": "listen_proto",
"description": "协议 默认值 tcp",
"required": true,
"type": "string"
},
{
"in": "query",
"name": "listen_addr",
"description": "监听地址",
"required": true,
"type": "string"
},
{
"in": "query",
"name": "app_id",
"description": "集群关联的appid",
"required": true,
"type": "string"
},
{
"in": "query",
"name": "write_timeout",
"description": "write 超时 默认值 100",
"required": true,
"type": "integer",
"format": "int32"
},
{
"in": "query",
"name": "ping_auto_reject",
"description": "是否自动剔除节点",
"required": true,
"type": "boolean"
},
{
"in": "query",
"name": "id",
"description": "主键id 更新的时候使用",
"required": true,
"type": "integer",
"format": "int64"
},
{
"in": "query",
"name": "hash_distribution",
"description": "key分布策略 默认为ketama一致性hash 默认值 ketama",
"required": true,
"type": "string"
}
],
"responses": {
"200": {
"description": "服务成功响应内容",
"schema": {
"type": "object",
"properties": {
"code": {
"description": "错误码描述",
"type": "integer"
},
"data": {
"$ref": "#/definitions/EmpResp",
"type": "object"
},
"message": {
"description": "错误码文本描述",
"type": "string"
},
"ttl": {
"description": "客户端限速时间",
"type": "integer",
"format": "int64"
}
}
}
}
}
}
},
"/x/admin/cache/cluster/detail": {
"get": {
"operationId": "/x/admin/cache/cluster/detail",
"parameters": [
{
"in": "query",
"name": "id",
"description": "集群id",
"required": true,
"type": "integer",
"format": "int64"
},
{
"in": "query",
"name": "Name",
"description": "集群名字",
"type": "string"
}
],
"responses": {
"200": {
"description": "服务成功响应内容",
"schema": {
"type": "object",
"properties": {
"code": {
"description": "错误码描述",
"type": "integer"
},
"data": {
"$ref": "#/definitions/ClusterDtlResp",
"type": "object"
},
"message": {
"description": "错误码文本描述",
"type": "string"
},
"ttl": {
"description": "客户端限速时间",
"type": "integer",
"format": "int64"
}
}
}
}
}
}
},
"/x/admin/cache/cluster/modify": {
"post": {
"operationId": "/x/admin/cache/cluster/modify",
"parameters": [
{
"in": "query",
"name": "Nodes",
"description": "节点信息 json数组 [{\"id\":11,\"addr\":\"11\",\"weight\":1,\"alias\":\"alias\"}]",
"type": "string"
},
{
"in": "query",
"name": "id",
"description": "集群id",
"required": true,
"type": "integer",
"format": "int64"
},
{
"in": "query",
"name": "action",
"description": "操作(1 添加节点2 删除节点删除节点时只需要传alias)",
"required": true,
"type": "integer",
"format": "int32"
}
],
"responses": {
"200": {
"description": "服务成功响应内容",
"schema": {
"type": "object",
"properties": {
"code": {
"description": "错误码描述",
"type": "integer"
},
"data": {
"$ref": "#/definitions/EmpResp",
"type": "object"
},
"message": {
"description": "错误码文本描述",
"type": "string"
},
"ttl": {
"description": "客户端限速时间",
"type": "integer",
"format": "int64"
}
}
}
}
}
}
},
"/x/admin/cache/clusters": {
"get": {
"operationId": "/x/admin/cache/clusters",
"parameters": [
{
"in": "query",
"name": "PN",
"description": " 默认值 1",
"type": "integer",
"format": "int64"
},
{
"in": "query",
"name": "PS",
"description": " 默认值 20",
"type": "integer",
"format": "int64"
}
],
"responses": {
"200": {
"description": "服务成功响应内容",
"schema": {
"type": "object",
"properties": {
"code": {
"description": "错误码描述",
"type": "integer"
},
"data": {
"$ref": "#/definitions/ClustersResp",
"type": "object"
},
"message": {
"description": "错误码文本描述",
"type": "string"
},
"ttl": {
"description": "客户端限速时间",
"type": "integer",
"format": "int64"
}
}
}
}
}
}
}
},
"definitions": {
"Cluster": {
"title": "Cluster",
"description": "Cluster resp result of clusters.",
"required": [
"id",
"name",
"type",
"app_id",
"hash_method",
"hash_distribution",
"hash_tag",
"dail_timeout",
"read_timeout",
"write_timeout",
"node_conn",
"ping_fail_limit",
"ping_auto_reject",
"listen_proto",
"listen_addr",
"hit",
"qps",
"state",
"mem_ratio",
"nodes"
],
"type": "object",
"properties": {
"app_id": {
"type": "string"
},
"dail_timeout": {
"description": "dial 超时",
"type": "integer",
"format": "int32"
},
"hash_distribution": {
"description": "key分布策略 默认为ketama一致性hash",
"type": "string"
},
"hash_method": {
"description": "哈希方法 默认sha1",
"type": "string"
},
"hash_tag": {
"description": "key hash 标识",
"type": "string"
},
"hit": {
"description": "集群命中率",
"type": "integer",
"format": "int64"
},
"id": {
"type": "integer",
"format": "int64"
},
"listen_addr": {
"type": "string"
},
"listen_proto": {
"type": "string"
},
"mem_ratio": {
"description": "内存使用率",
"type": "integer",
"format": "int64"
},
"name": {
"description": "集群名字",
"type": "string"
},
"node_conn": {
"description": "集群内节点连接数",
"type": "integer",
"format": "int32"
},
"nodes": {
"type": "array",
"items": {
"$ref": "#/definitions/NodeDtl",
"type": "object"
}
},
"ping_auto_reject": {
"description": "是否自动剔除节点",
"type": "boolean"
},
"ping_fail_limit": {
"description": "节点失败检测次数",
"type": "integer",
"format": "int32"
},
"qps": {
"description": "集群qps",
"type": "integer",
"format": "int64"
},
"read_timeout": {
"type": "integer",
"format": "int32"
},
"state": {
"description": "集群状态 0-online ;1-offline",
"type": "integer",
"format": "int64"
},
"type": {
"description": "缓存类型. (memcache,redis,redis-cluster)",
"type": "string"
},
"write_timeout": {
"description": "read 超时",
"type": "integer",
"format": "int32"
}
}
},
"ClusterDtlResp": {
"title": "ClusterDtlResp",
"description": "ClusterDtlResp resp result of cluster detail.",
"required": [
"nodes"
],
"type": "object",
"properties": {
"nodes": {
"type": "array",
"items": {
"$ref": "#/definitions/NodeDtl",
"type": "object"
}
}
}
},
"ClustersResp": {
"title": "ClustersResp",
"description": "ClustersResp resp result of clusters.",
"required": [
"clusters",
"total"
],
"type": "object",
"properties": {
"clusters": {
"type": "array",
"items": {
"$ref": "#/definitions/Cluster",
"type": "object"
}
},
"total": {
"description": "总数量",
"type": "integer",
"format": "int64"
}
}
},
"EmpResp": {
"title": "EmpResp",
"description": "EmpResp is empty resp.",
"type": "object"
},
"NodeDtl": {
"title": "NodeDtl",
"description": "NodeDtl cluster node detaiwl",
"required": [
"id",
"cid",
"addr",
"weight",
"alias",
"state",
"qps",
"mem_use",
"mem_toal"
],
"type": "object",
"properties": {
"addr": {
"type": "string"
},
"alias": {
"type": "string"
},
"cid": {
"type": "integer",
"format": "int64"
},
"id": {
"type": "integer",
"format": "int64"
},
"mem_toal": {
"type": "number",
"format": "float"
},
"mem_use": {
"type": "number",
"format": "float"
},
"qps": {
"type": "integer",
"format": "int64"
},
"state": {
"type": "integer",
"format": "int32"
},
"weight": {
"type": "integer",
"format": "int32"
}
}
}
}
}