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

View File

@@ -0,0 +1,138 @@
### config-service
#### Version 2.3.5
>1. 增加一个http接口直接获取当前最新发布的内容
#### Version 2.3.4
>1. log日志修改
#### Version 2.3.3
>1. sql语句中force是关键字要加``
#### Version 2.3.2
>1. 配置中心最新规则:
规则1当前版本大于0时优先级 单机强制 > 全局强制(当前版本小于等于最近的一次强制版本号时才会拉取,主要为了castr发版时能拉到最新配置) > 指定版本 > 当前发布最新版本
规则2当前版本小于等于0时没有单机强制和全局面强制逻辑优先级为 指定版本 > 当前最新发布版本
#### Version 2.3.1
>1. ut补全
#### Version 2.2.1
>1. 忽视appiont,执行强制更新功能
#### Version 2.1.4
>1. sdk 多处连接泄露修复
#### Version 2.1.3
>1. sdk 连接泄露bug修复
#### Version 2.1.2
>1. discovery注册
#### Version 2.1.1
>1. 限流
>2. 去tree_id并兼容老tree_id接口
#### Version 2.1.0
>1. bm架构
#### Version 2.0.4
>1. 增加register
#### Version 2.0.3
>1. fix sh001 limit and mv file to main
#### Version 2.0.2
>1. 移除statsd 模块
#### Version 2.0.2
>1. 版本推送不依赖redis
#### Version 2.0.1
>1. 修复file.so 接口兼容新的和老的file.so逻辑
#### Version 2.0.0
>1. 配置中心版本v4,走新库新表
>2. 支持客户端增量更新
#### Version 1.6.2
>1. push 接口推送时候如果数据库内容为空,返回失败
#### Version 1.6.1
>1. check接口增加自定义参数上报
#### Version 1.6.0
>1. 增加批量添加配置接口
>2. 增加配置拷贝接口
>3. 增加修改版本下的所有配置接口,没有就新加,有就覆盖
>4. 增加返回未配置完成版本ID 列表
#### Version 1.5.0
>1. 增加配置文件命名空间,支持公共配置
#### Version 1.4.3
>1. 再次修复数据库连接bad connection 问题
#### Version 1.4.2
>1. 修复数据库连接bad connection 问题
#### Version 1.4.1
>1. bugfix 修复缓存文件key 错误
#### Version 1.4.0
>1. 接入普罗米修斯监控
>2. 更新go-common和go-business 为最新的
>3. file.so 接口添加日志返回,配置内容本地缓存
#### Version 1.3.0
>1. 增加buildsversions接口,分别获取所有构建版本和版本id。
>2. check接口支持appoint参数返回appoint作为版本id。
#### Version 1.2.4
>1. 更改主机历史记录保存时间超过3小时没有在线。在查询的时候删除。
>2. 主机超时时间延长5秒
#### Version 1.2.3
>1. file接口去掉hostName参数version改为可选参数。更改获取逻辑直接从数据库获取单个配置文件。
#### Version 1.2.2
>1. 将返回值由json改为文件内容
#### Version 1.2.1
>1. 增加获取单个配置接口file
#### Version 1.2.0
>1. 启动参数增加token字段应用注册生成用于应用和环境权限限制
>2. 应用启动配置文件本地map缓存一份同时写入配置文件
#### Version 1.0.1
>1. APM 添加build version映射表对应于config版本号
>2. 发布版本只需要修改这个build version映射表的config版本号。
>3. 目前先去掉推送主机和主机标记功能,下个版本考虑。
>4. 程序编译时自己带build version可以直接通过映射表获取使用的配置版本。
#### Version 1.0.0
>1. 支持推APM推服务主机配置生成本地缓存版本。
>2. 支持长轮询监听新配置文件更新,并推送版本号到客户端。
>3. 支持按版本号获取配置文件。
>4. php以进程方式启动并监听配置文件更新。
>5. go以零配置启动下载配置文件并监听配置更新。

View File

@@ -0,0 +1,16 @@
# Owner
maojian
haoguanwei
lintanghui
chenzhihui
# Author
zhoujixiang
chenshangqiang
linli
# Reviewer
maojian
chenzhihui
lintanghui
haoguanwei

26
app/infra/config/OWNERS Normal file
View File

@@ -0,0 +1,26 @@
# See the OWNERS docs at https://go.k8s.io/owners
approvers:
- chenshangqiang
- chenzhihui
- haoguanwei
- linli
- lintanghui
- maojian
- zhoujixiang
labels:
- infra
- infra/config
- main
- service
- service/main/config
options:
no_parent_owners: true
reviewers:
- chenshangqiang
- chenzhihui
- haoguanwei
- linli
- lintanghui
- maojian
- zhoujixiang

View File

@@ -0,0 +1,21 @@
# go-common/business/main/service/config
##### 项目简介
> 1. 配置中心服务端,提供配置文件的管理和拉取
> 2. 支持心跳检测,配置动态下发
##### 编译环境
> 1. 请只用golang v1.7.x以上版本编译执行。
##### 依赖包
> 1. 公共依赖
##### 编译执行
> 1. 启动执行
> 2. 项目文档http://info.bilibili.co/pages/viewpage.action?pageId=1741193
##### 测试
> 1. 执行当前目录下所有测试文件,测试所有功能
##### 特别说明
> 2. 管理平台地址 http://apm-monitor.bilibili.co

View File

@@ -0,0 +1,48 @@
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 = ["config-service-example.toml"],
importpath = "go-common/app/infra/config/cmd",
tags = ["automanaged"],
visibility = ["//visibility:public"],
deps = [
"//app/infra/config/conf:go_default_library",
"//app/infra/config/http:go_default_library",
"//app/infra/config/rpc/server:go_default_library",
"//app/infra/config/service/v1:go_default_library",
"//app/infra/config/service/v2:go_default_library",
"//library/conf/env:go_default_library",
"//library/log:go_default_library",
"//library/naming:go_default_library",
"//library/naming/discovery:go_default_library",
"//library/net/ip: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,60 @@
# This is a TOML document. Boom.
pollTimeout = "30s"
pathCache = "/tmp/"
[log]
stdout = true
[antispam]
on=true
second=60
n=10
hour=12
m=500
[antispam.redis]
name = "config-service/hosts"
proto = "tcp"
addr = "172.18.33.61:6811"
idle = 100
active = 100
dialTimeout = "1s"
readTimeout = "1s"
writeTimeout = "1s"
idleTimeout = "10s"
expire = "24h"
[ecode]
domain = "172.16.33.248:6401"
[db]
name = "[config]tcp@172.16.33.205:3308"
dsn = "test:test@tcp(172.16.33.205:3308)/bilibili_apm?timeout=5s&readTimeout=5s&writeTimeout=5s&parseTime=true&loc=Local&charset=utf8,utf8mb4"
active = 5
idle = 5
queryTimeout = "500ms"
execTimeout = "500ms"
tranTimeout = "500ms"
[db.breaker]
window = "3s"
sleep = "100ms"
bucket = 10
ratio = 0.5
request = 100
[redis]
name = "config-service/hosts"
proto = "tcp"
addr = "172.18.33.61:6811"
idle = 100
active = 100
dialTimeout = "1s"
readTimeout = "1s"
writeTimeout = "1s"
idleTimeout = "10s"
[orm]
dsn = "test:test@tcp(172.16.33.205:3308)/bilibili_config?timeout=5s&readTimeout=5s&writeTimeout=5s&parseTime=true&loc=Local&charset=utf8,utf8mb4"
active = 5
idle = 5
idleTimeout = "4h"

View File

@@ -0,0 +1,82 @@
package main
import (
"context"
"flag"
"os"
"os/signal"
"syscall"
"go-common/app/infra/config/conf"
"go-common/app/infra/config/http"
"go-common/app/infra/config/rpc/server"
"go-common/app/infra/config/service/v1"
"go-common/app/infra/config/service/v2"
"go-common/library/conf/env"
"go-common/library/log"
"go-common/library/naming"
"go-common/library/naming/discovery"
xip "go-common/library/net/ip"
)
func main() {
flag.Parse()
if err := conf.Init(); err != nil {
log.Error("conf.Init() error(%v)", err)
panic(err)
}
// init log
log.Init(conf.Conf.Log)
defer log.Close()
// service init
svr2 := v2.New(conf.Conf)
svr := v1.New(conf.Conf)
rpcSvr := rpc.New(conf.Conf, svr, svr2)
http.Init(conf.Conf, svr, svr2, rpcSvr)
// start discovery register
var (
err error
cancel context.CancelFunc
)
if env.IP == "" {
ip := xip.InternalIP()
hn, _ := os.Hostname()
dis := discovery.New(nil)
ins := &naming.Instance{
Zone: env.Zone,
Env: env.DeployEnv,
AppID: "config.service",
Hostname: hn,
Addrs: []string{
"http://" + ip + ":" + env.HTTPPort,
"gorpc://" + ip + ":" + env.GORPCPort,
},
}
if cancel, err = dis.Register(context.Background(), ins); err != nil {
panic(err)
}
}
// end discovery register
// init signal
c := make(chan os.Signal, 1)
signal.Notify(c, syscall.SIGHUP, syscall.SIGQUIT, syscall.SIGTERM, syscall.SIGINT)
for {
s := <-c
log.Info("config-service get a signal %s", s.String())
switch s {
case syscall.SIGQUIT, syscall.SIGTERM, syscall.SIGINT:
if cancel != nil {
cancel()
}
rpcSvr.Close()
svr.Close()
log.Info("config-service exit")
return
case syscall.SIGHUP:
// TODO reload
default:
return
}
}
}

View File

@@ -0,0 +1,40 @@
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/infra/config/conf",
tags = ["automanaged"],
visibility = ["//visibility:public"],
deps = [
"//library/cache/redis:go_default_library",
"//library/database/orm:go_default_library",
"//library/database/sql:go_default_library",
"//library/log:go_default_library",
"//library/net/http/blademaster:go_default_library",
"//library/net/http/blademaster/middleware/antispam:go_default_library",
"//library/net/http/blademaster/middleware/verify:go_default_library",
"//library/net/rpc:go_default_library",
"//library/time: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,56 @@
package conf
import (
"flag"
"go-common/library/cache/redis"
"go-common/library/database/orm"
"go-common/library/database/sql"
"go-common/library/log"
bm "go-common/library/net/http/blademaster"
"go-common/library/net/http/blademaster/middleware/antispam"
v "go-common/library/net/http/blademaster/middleware/verify"
"go-common/library/net/rpc"
"go-common/library/time"
"github.com/BurntSushi/toml"
)
var (
confPath string
// Conf init config
Conf *Config
)
// Config config.
type Config struct {
// log
Log *log.Config
//rpc server2
RPCServer *rpc.ServerConfig
// db
DB *sql.Config
// redis
Redis *redis.Config
// timeout
PollTimeout time.Duration
// local cache
PathCache string
// orm
ORM *orm.Config
//BM
BM *bm.ServerConfig
// Antispam
Antispam *antispam.Config
Verify *v.Config
}
func init() {
flag.StringVar(&confPath, "conf", "./config-service-example.toml", "config path")
}
// Init init.
func Init() (err error) {
_, err = toml.DecodeFile(confPath, &Conf)
return
}

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 = [
"dao.go",
"file.go",
"mysql.go",
"redis.go",
],
importpath = "go-common/app/infra/config/dao/v1",
tags = ["automanaged"],
visibility = ["//visibility:public"],
deps = [
"//app/infra/config/conf:go_default_library",
"//app/infra/config/model:go_default_library",
"//library/cache/redis:go_default_library",
"//library/database/sql:go_default_library",
"//library/ecode: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"],
)

View File

@@ -0,0 +1,58 @@
package v1
import (
"context"
"time"
"go-common/app/infra/config/conf"
"go-common/library/cache/redis"
"go-common/library/database/sql"
)
// Dao dao.
type Dao struct {
// mysql
db *sql.DB
// redis
redis *redis.Pool
expire time.Duration
// cache
pathCache string
}
// New new a dao.
func New(c *conf.Config) *Dao {
d := &Dao{
// db
db: sql.NewMySQL(c.DB),
// redis
redis: redis.NewPool(c.Redis),
expire: time.Duration(c.PollTimeout),
// cache
pathCache: c.PathCache,
}
return d
}
// BeginTran begin transcation.
func (d *Dao) BeginTran(c context.Context) (tx *sql.Tx, err error) {
return d.db.Begin(c)
}
// Close close resuouces.
func (d *Dao) Close() {
if d.db != nil {
d.db.Close()
}
if d.redis != nil {
d.redis.Close()
}
}
// Ping ping is ok.
func (d *Dao) Ping(c context.Context) (err error) {
if err = d.pingRedis(c); err != nil {
return
}
return d.db.Ping(c)
}

View File

@@ -0,0 +1,70 @@
package v1
import (
"encoding/json"
"io/ioutil"
"os"
"path"
"go-common/app/infra/config/model"
"go-common/library/log"
)
// SetFile set config file.
func (d *Dao) SetFile(name string, conf *model.Content) (err error) {
b, err := json.Marshal(conf)
if err != nil {
log.Error("json.Marshal(%v) error(%v)", conf, err)
return
}
p := path.Join(d.pathCache, name)
if err = ioutil.WriteFile(p, b, 0644); err != nil {
log.Error("ioutil.WriteFile(%s) error(%v)", p, err)
}
return
}
// File return config file.
func (d *Dao) File(name string) (res *model.Content, err error) {
p := path.Join(d.pathCache, name)
b, err := ioutil.ReadFile(p)
if err != nil {
log.Error("ioutil.ReadFile(%s) error(%v)", p, err)
return
}
res = &model.Content{}
if err = json.Unmarshal(b, &res); err != nil {
log.Error("json.Unmarshal(%s) error(%v)", b, err)
}
return
}
// DelFile delete file cache.
func (d *Dao) DelFile(name string) (err error) {
p := path.Join(d.pathCache, name)
if err = os.Remove(p); err != nil {
log.Error("os.Remove(%s) error(%v)", p, err)
}
return
}
// SetFileStr save string file.
func (d *Dao) SetFileStr(name string, val string) (err error) {
p := path.Join(d.pathCache, name)
if err = ioutil.WriteFile(p, []byte(val), 0644); err != nil {
log.Error("ioutil.WriteFile(%s) error(%v)", p, err)
}
return
}
// FileStr get string file.
func (d *Dao) FileStr(name string) (file string, err error) {
p := path.Join(d.pathCache, name)
b, err := ioutil.ReadFile(p)
if err != nil {
log.Error("ioutil.ReadFile(%s) error(%v)", p, err)
return
}
file = string(b)
return
}

View File

@@ -0,0 +1,224 @@
package v1
import (
"bytes"
"context"
"go-common/app/infra/config/model"
"go-common/library/database/sql"
"go-common/library/ecode"
"go-common/library/log"
)
const (
_getToken = "SELECT token FROM service_name WHERE name=? AND environment=?"
_getBuildVersion = "SELECT b.config_id FROM service_name s, build_version b WHERE b.version=? AND s.name=? And s.environment=? AND s.id=b.service_id AND b.state=2"
_getVersions = "SELECT c.id,c.remark FROM service_config c, service_name s WHERE s.name=? AND s.environment=? AND s.id =c.service_id AND c.state=? ORDER BY c.id DESC"
_getNamespace = "SELECT id,namespace FROM service_namespace WHERE config_id=?"
_getValue = "SELECT config_id,namespace_id,name,config FROM service_config_value WHERE config_id=?"
_getFile = "SELECT config FROM service_config_value WHERE config_id=? AND name =?"
_getBuilds = "SELECT b.version FROM service_name s ,build_version b WHERE s.name=? AND s.environment=? AND s.id=b.service_id AND b.state=2 ORDER BY b.id DESC"
_getServiceID = "SELECT id FROM service_name where name=? AND environment =?"
_insertVersion = "INSERT INTO service_config(service_id,state,operator) VALUES (?,?,?)"
_insertConfigs = "INSERT INTO service_config_value(config_id,name,config,operator) VALUES "
_updateConfigs = "UPDATE service_config_value SET config=?,operator=? WHERE config_id = ? AND name = ?"
_insertLog = "INSERT INTO log(username,business,info) VALUES (?,?,?)"
)
// Token return a Secret from mysql.
func (d *Dao) Token(c context.Context, svr, env string) (token string, err error) {
row := d.db.QueryRow(c, _getToken, svr, env)
if err = row.Scan(&token); err != nil {
log.Error("row.Scan error(%v) svrName(%v)", err, svr)
if err == sql.ErrNoRows {
err = nil
return
}
}
return
}
// BuildVersion return service build version from mysql.
func (d *Dao) BuildVersion(c context.Context, svr, bver, env string) (version int64, err error) {
row := d.db.QueryRow(c, _getBuildVersion, bver, svr, env)
if err = row.Scan(&version); err != nil {
if err == sql.ErrNoRows {
version = model.UnknownVersion
err = nil
return
}
log.Error("row.Scan error(%v)", err)
}
return
}
// Values return values from mysql.
func (d *Dao) Values(c context.Context, ver int64) (rs []*model.NSValue, err error) {
rows, err := d.db.Query(c, _getValue, ver)
if err != nil {
log.Error("db.Query(%d) error(%v)", ver, err)
return
}
defer rows.Close()
for rows.Next() {
var r model.NSValue
if err = rows.Scan(&r.ConfigID, &r.NamespaceID, &r.Name, &r.Config); err != nil {
log.Error("rows.Scan error(%v)", err)
return
}
rs = append(rs, &r)
}
return
}
// Namespaces return namespaces from mysql
func (d *Dao) Namespaces(c context.Context, ver int64) (rs map[int64]string, err error) {
rows, err := d.db.Query(c, _getNamespace, ver)
if err != nil {
log.Error("db.Query(%d) error(%v)", err)
return
}
rs = make(map[int64]string)
defer rows.Close()
for rows.Next() {
var id int64
var name string
if err = rows.Scan(&id, &name); err != nil {
log.Error("rows.Scan error(%v)", err)
return
}
rs[id] = name
}
return
}
//Versions return versions from mysql
func (d *Dao) Versions(c context.Context, svr, env string, state int8) (rs []*model.ReVer, err error) {
rows, err := d.db.Query(c, _getVersions, svr, env, state)
if err != nil {
log.Error("db.Query(%s) error(%v)", svr, err)
return
}
defer rows.Close()
for rows.Next() {
var r model.ReVer
if err = rows.Scan(&r.Version, &r.Remark); err != nil {
log.Error("rows.Scan error(%v)", err)
return
}
rs = append(rs, &r)
}
return
}
// Value return values from mysql.
func (d *Dao) Value(c context.Context, fname string, ver int64) (file string, err error) {
row := d.db.QueryRow(c, _getFile, ver, fname)
if err = row.Scan(&file); err != nil {
log.Error("row.Scan error(%v)", err)
if err == sql.ErrNoRows {
err = ecode.NothingFound
return
}
}
return
}
// Builds get service builds.
func (d *Dao) Builds(c context.Context, svr, env string) (rs []string, err error) {
rows, err := d.db.Query(c, _getBuilds, svr, env)
if err != nil {
log.Error("db.Query(%s) error(%v)", svr, err)
return
}
defer rows.Close()
for rows.Next() {
var r string
if err = rows.Scan(&r); err != nil {
log.Error("rows.Scan error(%v)", err)
return
}
rs = append(rs, r)
}
return
}
// ServiceID get ServiceID.
func (d *Dao) ServiceID(c context.Context, svr, env string) (ID int64, err error) {
row := d.db.QueryRow(c, _getServiceID, svr, env)
if err != nil {
log.Error("db.Query(%s) error(%v)", svr, err)
return
}
if err = row.Scan(&ID); err != nil {
log.Error("row.Scan error(%v)", err)
if err == sql.ErrNoRows {
err = ecode.NothingFound
return
}
}
return
}
// TxInsertVer insert version.
func (d *Dao) TxInsertVer(tx *sql.Tx, svrID int64, user string) (verID int64, err error) {
row, err := tx.Exec(_insertVersion, svrID, model.ConfigIng, user)
if err != nil {
log.Error("db.insert(%d) error(%v)", svrID, err)
return
}
return row.LastInsertId()
}
// TxInsertValues insert config values.
func (d *Dao) TxInsertValues(c context.Context, tx *sql.Tx, verID int64, user string, data map[string]string) (err error) {
var (
buffer bytes.Buffer
insertTp string
stmt *sql.Stmt
is []interface{}
)
buffer.WriteString(_insertConfigs)
insertTp = "(?,?,?,?),"
for key, val := range data {
buffer.WriteString(insertTp)
is = append(is, verID)
is = append(is, key)
is = append(is, val)
is = append(is, user)
}
buffer.Truncate(buffer.Len() - 1)
if stmt, err = tx.Prepare(buffer.String()); err != nil {
log.Error("d.insert() error(%v)", err)
return
}
_, err = stmt.Exec(c, is...)
if err != nil {
log.Error("d.insert() error(%v)", err)
}
return
}
// TxUpdateValues update config values.
func (d *Dao) TxUpdateValues(tx *sql.Tx, verID int64, user string, data map[string]string) (err error) {
for key, val := range data {
if _, err = tx.Exec(_updateConfigs, val, user, verID, key); err != nil {
log.Error("db.UpdateValues(%d) error(%v)", user, err)
break
}
}
if err != nil {
log.Error("d.insert() error(%v)", err)
}
return
}
// InsertLog insert log.
func (d *Dao) InsertLog(c context.Context, user, business, info string) (err error) {
_, err = d.db.Exec(c, _insertLog, user, business, info)
if err != nil {
log.Error("db.InsertLog(%d) error(%v)", user, err)
return
}
return
}

View File

@@ -0,0 +1,100 @@
package v1
import (
"context"
"encoding/json"
"fmt"
"time"
"go-common/app/infra/config/model"
"go-common/library/cache/redis"
"go-common/library/log"
)
const (
expireDuration = 3 * time.Hour
_hostKey = "%s_%s"
)
// Hostkey host cache key
func hostKey(svr, env string) string {
return fmt.Sprintf(_hostKey, svr, env)
}
// Hosts return service hosts from redis.
func (d *Dao) Hosts(c context.Context, svr, env string) (hosts []*model.Host, err error) {
var (
dels []string
now = time.Now()
hostkey = hostKey(svr, env)
conn = d.redis.Get(c)
)
defer conn.Close()
res, err := redis.Strings(conn.Do("HGETALL", hostkey))
if err != nil {
log.Error("conn.Do(HGETALL, %s) error(%v)", hostkey, err)
return
}
for i, r := range res {
if i%2 == 0 {
continue
}
h := &model.Host{}
if err = json.Unmarshal([]byte(r), h); err != nil {
log.Error("json.Unmarshal(%s) error(%v)", r, err)
return
}
if now.Sub(h.HeartbeatTime.Time()) <= d.expire+5 {
h.State = model.HostOnline
hosts = append(hosts, h)
} else if now.Sub(h.HeartbeatTime.Time()) >= expireDuration {
dels = append(dels, h.Name)
} else {
h.State = model.HostOffline
hosts = append(hosts, h)
}
}
if len(dels) > 0 {
if _, err1 := conn.Do("HDEL", hostkey, dels); err1 != nil {
log.Error("conn.Do(HDEL, %s, %v) error(%v)", hostkey, dels, err1)
}
}
return
}
// SetHost add service host to redis.
func (d *Dao) SetHost(c context.Context, host *model.Host, svr, env string) (err error) {
hostkey := hostKey(svr, env)
b, err := json.Marshal(host)
if err != nil {
log.Error("json.Marshal(%s) error(%v)", host, err)
return
}
conn := d.redis.Get(c)
defer conn.Close()
if _, err = conn.Do("HSET", hostkey, host.Name, string(b)); err != nil {
log.Error("conn.Do(SET, %s, %s, %v) error(%v)", hostkey, host.Name, host, err)
}
return
}
// ClearHost clear all hosts.
func (d *Dao) ClearHost(c context.Context, svr, env string) (err error) {
var (
hostkey = hostKey(svr, env)
conn = d.redis.Get(c)
)
defer conn.Close()
if _, err = conn.Do("DEL", hostkey); err != nil {
log.Error("conn.Do(DEL, %s) error(%v)", hostkey, err)
}
return
}
// Ping check Redis connection
func (d *Dao) pingRedis(c context.Context) (err error) {
conn := d.redis.Get(c)
_, err = conn.Do("SET", "PING", "PONG")
conn.Close()
return
}

View File

@@ -0,0 +1,70 @@
package(default_visibility = ["//visibility:public"])
load(
"@io_bazel_rules_go//go:def.bzl",
"go_test",
"go_library",
)
go_test(
name = "go_default_test",
srcs = [
"app_test.go",
"build_test.go",
"config_test.go",
"dao_test.go",
"file_test.go",
"force_test.go",
"redis_test.go",
"tag_test.go",
],
embed = [":go_default_library"],
tags = ["automanaged"],
deps = [
"//app/infra/config/conf:go_default_library",
"//app/infra/config/model:go_default_library",
"//vendor/github.com/smartystreets/goconvey/convey:go_default_library",
],
)
go_library(
name = "go_default_library",
srcs = [
"app.go",
"build.go",
"config.go",
"dao.go",
"file.go",
"force.go",
"redis.go",
"tag.go",
],
importpath = "go-common/app/infra/config/dao/v2",
tags = ["automanaged"],
visibility = ["//visibility:public"],
deps = [
"//app/infra/config/conf:go_default_library",
"//app/infra/config/model:go_default_library",
"//library/cache/redis:go_default_library",
"//library/database/orm:go_default_library",
"//library/database/sql:go_default_library",
"//library/ecode:go_default_library",
"//library/log:go_default_library",
"//library/xstr: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"],
)

View File

@@ -0,0 +1,46 @@
package v2
import (
"go-common/app/infra/config/model"
"go-common/library/database/sql"
"go-common/library/ecode"
"go-common/library/log"
)
// AppByTree get token by Name.
func (d *Dao) AppByTree(zone, env string, treeID int64) (app *model.App, err error) {
app = &model.App{}
row := d.DB.Select("id,token").Where("tree_id = ? AND env=? AND zone=?", treeID, env, zone).Model(&model.DBApp{}).Row()
if err = row.Scan(&app.ID, &app.Token); err != nil {
log.Error("AppByTree(%v) error(%v)", treeID, err)
if err == sql.ErrNoRows {
err = ecode.NothingFound
}
}
return
}
// AppsByNameEnv get token by Name.
func (d *Dao) AppsByNameEnv(name, env string) (apps []*model.DBApp, err error) {
if err = d.DB.Where("env = ? and name like ?", env, "%"+name).Find(&apps).Error; err != nil {
log.Error("AppsByNameEnv(%v) error(%v)", name, env)
return
}
if len(apps) == 0 {
err = ecode.NothingFound
}
return
}
// AppGet ...
func (d *Dao) AppGet(zone, env, token string) (app *model.App, err error) {
app = &model.App{}
row := d.DB.Select("id,token,env,zone,tree_id").Where("token = ? AND env= ? AND zone= ?", token, env, zone).Model(&model.DBApp{}).Row()
if err = row.Scan(&app.ID, &app.Token, &app.Env, &app.Zone, &app.TreeID); err != nil {
log.Error("AppGet zone(%v) env(%v) token(%v) error(%v)", zone, env, token, err)
if err == sql.ErrNoRows {
err = ecode.NothingFound
}
}
return
}

View File

@@ -0,0 +1,51 @@
package v2
import (
"testing"
"github.com/smartystreets/goconvey/convey"
)
func TestV2AppByTree(t *testing.T) {
var (
zone = ""
env = ""
treeID = int64(0)
)
convey.Convey("AppByTree", t, func(ctx convey.C) {
app, err := d.AppByTree(zone, env, treeID)
ctx.Convey("Then err should be nil.app should not be nil.", func(ctx convey.C) {
ctx.So(err, convey.ShouldBeNil)
ctx.So(app, convey.ShouldNotBeNil)
})
})
}
func TestV2AppsByNameEnv(t *testing.T) {
var (
name = "main.common-arch.apm-admin"
env = "fat1"
)
convey.Convey("AppsByNameEnv", t, func(ctx convey.C) {
apps, err := d.AppsByNameEnv(name, env)
ctx.Convey("Then err should be nil.apps should not be nil.", func(ctx convey.C) {
ctx.So(err, convey.ShouldBeNil)
ctx.So(apps, convey.ShouldNotBeNil)
})
})
}
func TestV2AppGet(t *testing.T) {
var (
zone = "sh001"
env = "fat1"
token = "a882c5530bcc11e8ab68522233017188"
)
convey.Convey("AppGet", t, func(ctx convey.C) {
app, err := d.AppGet(zone, env, token)
ctx.Convey("Then err should be nil.app should not be nil.", func(ctx convey.C) {
ctx.So(err, convey.ShouldBeNil)
ctx.So(app, convey.ShouldNotBeNil)
})
})
}

View File

@@ -0,0 +1,77 @@
package v2
import (
"database/sql"
"go-common/app/infra/config/model"
"go-common/library/ecode"
"go-common/library/log"
)
// BuildsByAppID get builds by app id.
func (d *Dao) BuildsByAppID(appID int64) (builds []string, err error) {
var rows *sql.Rows
if rows, err = d.DB.Select("name").Model(&model.Build{}).Where("app_id = ? ", appID).Rows(); err != nil {
log.Error("BuildsByAppID(%v) error(%v)", appID, err)
return
}
defer rows.Close()
for rows.Next() {
var build string
if err = rows.Scan(&build); err != nil {
log.Error("BuildsByAppID(%v) error(%v)", appID, err)
return
}
builds = append(builds, build)
}
if len(builds) == 0 {
err = ecode.NothingFound
}
return
}
// BuildsByAppIDs get builds by app id.
func (d *Dao) BuildsByAppIDs(appIDs []int64) (builds []string, err error) {
var rows *sql.Rows
if rows, err = d.DB.Select("name").Model(&model.Build{}).Where("app_id in (?) ", appIDs).Rows(); err != nil {
log.Error("BuildsByAppIDs(%v) error(%v)", appIDs, err)
return
}
defer rows.Close()
for rows.Next() {
var build string
if err = rows.Scan(&build); err != nil {
log.Error("BuildsByAppIDs(%v) error(%v)", appIDs, err)
return
}
builds = append(builds, build)
}
if len(builds) == 0 {
err = ecode.NothingFound
}
return
}
// TagID get TagID by ID.
func (d *Dao) TagID(appID int64, build string) (tagID int64, err error) {
row := d.DB.Select("tag_id").Where("app_id =? and name= ?", appID, build).Model(&model.Build{}).Row()
if err = row.Scan(&tagID); err != nil {
log.Error("TagID(%v) error(%v)", build, err)
if err == sql.ErrNoRows {
err = ecode.NothingFound
}
}
return
}
// BuildID get build by ID.
func (d *Dao) BuildID(appID int64, build string) (buildID int64, err error) {
row := d.DB.Select("id").Where("app_id =? and name= ?", appID, build).Model(&model.Build{}).Row()
if err = row.Scan(&buildID); err != nil {
log.Error("buildID(%v) error(%v)", buildID, err)
if err == sql.ErrNoRows {
err = ecode.NothingFound
}
}
return
}

View File

@@ -0,0 +1,47 @@
package v2
import (
"testing"
"github.com/smartystreets/goconvey/convey"
)
func TestV2BuildsByAppID(t *testing.T) {
var (
appID = int64(24)
)
convey.Convey("BuildsByAppID", t, func(ctx convey.C) {
builds, err := d.BuildsByAppID(appID)
ctx.Convey("Then err should be nil.builds should not be nil.", func(ctx convey.C) {
ctx.So(err, convey.ShouldBeNil)
ctx.So(builds, convey.ShouldNotBeNil)
})
})
}
func TestV2BuildsByAppIDs(t *testing.T) {
var (
appIDs = []int64{24}
)
convey.Convey("BuildsByAppIDs", t, func(ctx convey.C) {
builds, err := d.BuildsByAppIDs(appIDs)
ctx.Convey("Then err should be nil.builds should not be nil.", func(ctx convey.C) {
ctx.So(err, convey.ShouldBeNil)
ctx.So(builds, convey.ShouldNotBeNil)
})
})
}
func TestV2TagID(t *testing.T) {
var (
appID = int64(24)
build = "docker-1"
)
convey.Convey("TagID", t, func(ctx convey.C) {
tagID, err := d.TagID(appID, build)
ctx.Convey("Then err should be nil.tagID should not be nil.", func(ctx convey.C) {
ctx.So(err, convey.ShouldBeNil)
ctx.So(tagID, convey.ShouldNotBeNil)
})
})
}

View File

@@ -0,0 +1,43 @@
package v2
import (
"database/sql"
"go-common/app/infra/config/model"
"go-common/library/ecode"
"go-common/library/log"
)
// UpdateConfValue update config state/
func (d *Dao) UpdateConfValue(ID int64, value string) (err error) {
err = d.DB.Model(&model.Config{ID: ID}).Where("state=?", model.ConfigIng).Update("comment", value).Error
return
}
// UpdateConfState update config state/
func (d *Dao) UpdateConfState(ID int64, state int8) (err error) {
err = d.DB.Model(&model.Config{ID: ID}).Update("state", state).Error
return
}
// ConfigsByIDs get Config by IDs.
func (d *Dao) ConfigsByIDs(ids []int64) (confs []*model.Value, err error) {
var rows *sql.Rows
if rows, err = d.DB.Where(ids).Select("id,name,comment").Where("state = ?", model.ConfigEnd).Model(&model.Config{}).Rows(); err != nil {
log.Error("ConfigsByIDs(%v) error(%v)", ids, err)
return
}
defer rows.Close()
for rows.Next() {
v := new(model.Value)
if err = rows.Scan(&v.ConfigID, &v.Name, &v.Config); err != nil {
log.Error("ConfigsByIDs(%v) error(%v)", ids, err)
return
}
confs = append(confs, v)
}
if len(confs) == 0 {
err = ecode.NothingFound
}
return
}

View File

@@ -0,0 +1,53 @@
package v2
import (
"testing"
"github.com/smartystreets/goconvey/convey"
)
func TestV2UpdateConfValue(t *testing.T) {
var (
ID = int64(0)
value = ""
)
convey.Convey("UpdateConfValue", t, func(ctx convey.C) {
err := d.UpdateConfValue(ID, value)
ctx.Convey("Then err should be nil.", func(ctx convey.C) {
ctx.So(err, convey.ShouldBeNil)
})
})
}
func TestV2UpdateConfState(t *testing.T) {
var (
ID = int64(855)
state = int8(0)
state2 = int8(2)
)
convey.Convey("UpdateConfState", t, func(ctx convey.C) {
err := d.UpdateConfState(ID, state)
ctx.Convey("Then err should be nil.", func(ctx convey.C) {
ctx.So(err, convey.ShouldBeNil)
})
})
convey.Convey("UpdateConfState restoration", t, func(ctx convey.C) {
err := d.UpdateConfState(ID, state2)
ctx.Convey("Then err should be nil.", func(ctx convey.C) {
ctx.So(err, convey.ShouldBeNil)
})
})
}
func TestV2ConfigsByIDs(t *testing.T) {
var (
ids = []int64{855, 788}
)
convey.Convey("ConfigsByIDs", t, func(ctx convey.C) {
confs, err := d.ConfigsByIDs(ids)
ctx.Convey("Then err should be nil.confs should not be nil.", func(ctx convey.C) {
ctx.So(err, convey.ShouldBeNil)
ctx.So(confs, convey.ShouldNotBeNil)
})
})
}

View File

@@ -0,0 +1,55 @@
package v2
import (
"context"
"time"
"go-common/app/infra/config/conf"
"go-common/library/cache/redis"
"go-common/library/database/orm"
"github.com/jinzhu/gorm"
)
// Dao dao.
type Dao struct {
// redis
redis *redis.Pool
expire time.Duration
// cache
pathCache string
//DB
DB *gorm.DB
}
// New new a dao.
func New(c *conf.Config) *Dao {
d := &Dao{
// redis
redis: redis.NewPool(c.Redis),
expire: time.Duration(c.PollTimeout),
// cache
pathCache: c.PathCache,
// orm
DB: orm.NewMySQL(c.ORM),
}
return d
}
// Ping ping is ok.
func (d *Dao) Ping(c context.Context) (err error) {
if err = d.pingRedis(c); err != nil {
return
}
return d.DB.DB().PingContext(c)
}
// Close close resuouces.
func (d *Dao) Close() {
if d.DB != nil {
d.DB.Close()
}
if d.redis != nil {
d.redis.Close()
}
}

View File

@@ -0,0 +1,36 @@
package v2
import (
"flag"
"go-common/app/infra/config/conf"
"os"
"testing"
)
var (
d *Dao
)
func TestMain(m *testing.M) {
// if os.Getenv("DEPLOY_ENV") != "" {
// // flag.Set("app_id", "")
// // flag.Set("conf_token", "")
// // flag.Set("tree_id", "")
// // flag.Set("conf_version", "docker-1")
// // flag.Set("deploy_env", "fat1")
// // 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/config-service-example.toml")
// }
flag.Set("conf", "../../cmd/config-service-example.toml")
flag.Parse()
if err := conf.Init(); err != nil {
panic(err)
}
d = New(conf.Conf)
m.Run()
os.Exit(0)
}

View File

@@ -0,0 +1,70 @@
package v2
import (
"encoding/json"
"io/ioutil"
"os"
"path"
"go-common/app/infra/config/model"
"go-common/library/log"
)
// SetFile set config file.
func (d *Dao) SetFile(name string, conf *model.Content) (err error) {
b, err := json.Marshal(conf)
if err != nil {
log.Error("json.Marshal(%v) error(%v)", conf, err)
return
}
p := path.Join(d.pathCache, name)
if err = ioutil.WriteFile(p, b, 0644); err != nil {
log.Error("ioutil.WriteFile(%s) error(%v)", p, err)
}
return
}
// File return config file.
func (d *Dao) File(name string) (res *model.Content, err error) {
p := path.Join(d.pathCache, name)
b, err := ioutil.ReadFile(p)
if err != nil {
log.Error("ioutil.ReadFile(%s) error(%v)", p, err)
return
}
res = &model.Content{}
if err = json.Unmarshal(b, &res); err != nil {
log.Error("json.Unmarshal(%s) error(%v)", b, err)
}
return
}
// DelFile delete file cache.
func (d *Dao) DelFile(name string) (err error) {
p := path.Join(d.pathCache, name)
if err = os.Remove(p); err != nil {
log.Error("os.Remove(%s) error(%v)", p, err)
}
return
}
// SetFileStr save string file.
func (d *Dao) SetFileStr(name string, val string) (err error) {
p := path.Join(d.pathCache, name)
if err = ioutil.WriteFile(p, []byte(val), 0644); err != nil {
log.Error("ioutil.WriteFile(%s) error(%v)", p, err)
}
return
}
// FileStr get string file.
func (d *Dao) FileStr(name string) (file string, err error) {
p := path.Join(d.pathCache, name)
b, err := ioutil.ReadFile(p)
if err != nil {
log.Error("ioutil.ReadFile(%s) error(%v)", p, err)
return
}
file = string(b)
return
}

View File

@@ -0,0 +1,72 @@
package v2
import (
"go-common/app/infra/config/model"
"testing"
"github.com/smartystreets/goconvey/convey"
)
func TestV2SetFile(t *testing.T) {
var (
name = "main.common-arch.apm-admin_460"
conf = &model.Content{}
)
convey.Convey("SetFile", t, func(ctx convey.C) {
err := d.SetFile(name, conf)
ctx.Convey("Then err should be nil.", func(ctx convey.C) {
ctx.So(err, convey.ShouldBeNil)
})
})
}
func TestV2File(t *testing.T) {
var (
name = "main.common-arch.apm-admin_460"
)
convey.Convey("File", t, func(ctx convey.C) {
res, err := d.File(name)
ctx.Convey("Then err should be nil.res should not be nil.", func(ctx convey.C) {
ctx.So(err, convey.ShouldBeNil)
ctx.So(res, convey.ShouldNotBeNil)
})
})
}
func TestV2DelFile(t *testing.T) {
var (
name = "main.common-arch.apm-admin_460"
)
convey.Convey("DelFile", t, func(ctx convey.C) {
err := d.DelFile(name)
ctx.Convey("Then err should be nil.", func(ctx convey.C) {
ctx.So(err, convey.ShouldBeNil)
})
})
}
func TestV2SetFileStr(t *testing.T) {
var (
name = "main.common-arch.apm-admin_460"
val = "test"
)
convey.Convey("SetFileStr", t, func(ctx convey.C) {
err := d.SetFileStr(name, val)
ctx.Convey("Then err should be nil.", func(ctx convey.C) {
ctx.So(err, convey.ShouldBeNil)
})
})
}
func TestV2FileStr(t *testing.T) {
var (
name = "main.common-arch.apm-admin_460"
)
convey.Convey("FileStr", t, func(ctx convey.C) {
file, err := d.FileStr(name)
ctx.Convey("Then err should be nil.file should not be nil.", func(ctx convey.C) {
ctx.So(err, convey.ShouldBeNil)
ctx.So(file, convey.ShouldNotBeNil)
})
})
}

View File

@@ -0,0 +1,22 @@
package v2
import (
"database/sql"
"go-common/app/infra/config/model"
"go-common/library/log"
)
// Force get force by ID.
func (d *Dao) Force(appID int64, hostname string) (version int64, err error) {
row := d.DB.Select("version").Where("app_id = ? and hostname = ?", appID, hostname).Model(&model.Force{}).Row()
if err = row.Scan(&version); err != nil {
if err == sql.ErrNoRows {
err = nil
version = 0
} else {
log.Error("version(%v) error(%v)", version, err)
}
}
return
}

View File

@@ -0,0 +1,17 @@
package v2
// func TestV2Force(t *testing.T) {
// convey.SkipConvey("Force", t, func(ctx convey.C) {
// var (
// appID = int64(24)
// hostname = "apm-admin-31733-7c776f4d86-rxv6z"
// )
// ctx.Convey("When everything goes positive", func(ctx convey.C) {
// version, err := d.Force(appID, hostname)
// ctx.Convey("Then err should be nil.version should not be nil.", func(ctx convey.C) {
// ctx.So(err, convey.ShouldBeNil)
// ctx.So(version, convey.ShouldNotBeNil)
// })
// })
// })
// }

View File

@@ -0,0 +1,90 @@
package v2
import (
"context"
"encoding/json"
"time"
"go-common/app/infra/config/model"
"go-common/library/cache/redis"
"go-common/library/log"
)
const (
expireDuration = 3 * time.Hour
)
// Hosts return service hosts from redis.
func (d *Dao) Hosts(c context.Context, svr string) (hosts []*model.Host, err error) {
var (
dels []string
now = time.Now()
conn = d.redis.Get(c)
)
defer conn.Close()
res, err := redis.Strings(conn.Do("HGETALL", svr))
if err != nil {
log.Error("conn.Do(HGETALL, %s) error(%v)", svr, err)
return
}
for i, r := range res {
if i%2 == 0 {
continue
}
h := &model.Host{}
if err = json.Unmarshal([]byte(r), h); err != nil {
log.Error("json.Unmarshal(%s) error(%v)", r, err)
return
}
if now.Sub(h.HeartbeatTime.Time()) <= d.expire+5 {
h.State = model.HostOnline
hosts = append(hosts, h)
} else if now.Sub(h.HeartbeatTime.Time()) >= expireDuration {
dels = append(dels, h.Name)
} else {
h.State = model.HostOffline
hosts = append(hosts, h)
}
}
if len(dels) > 0 {
if _, err1 := conn.Do("HDEL", svr, dels); err1 != nil {
log.Error("conn.Do(HDEL, %s, %v) error(%v)", svr, dels, err1)
}
}
return
}
// SetHost add service host to redis.
func (d *Dao) SetHost(c context.Context, host *model.Host, svr string) (err error) {
b, err := json.Marshal(host)
if err != nil {
log.Error("json.Marshal(%s) error(%v)", host, err)
return
}
conn := d.redis.Get(c)
defer conn.Close()
if _, err = conn.Do("HSET", svr, host.Name, string(b)); err != nil {
log.Error("conn.Do(SET, %s, %s, %v) error(%v)", svr, host.Name, host, err)
}
return
}
// ClearHost clear all hosts.
func (d *Dao) ClearHost(c context.Context, svr string) (err error) {
var (
conn = d.redis.Get(c)
)
defer conn.Close()
if _, err = conn.Do("DEL", svr); err != nil {
log.Error("conn.Do(DEL, %s) error(%v)", svr, err)
}
return
}
// Ping check Redis connection
func (d *Dao) pingRedis(c context.Context) (err error) {
conn := d.redis.Get(c)
_, err = conn.Do("SET", "PING", "PONG")
conn.Close()
return
}

View File

@@ -0,0 +1,62 @@
package v2
import (
"context"
"go-common/app/infra/config/model"
"testing"
"github.com/smartystreets/goconvey/convey"
)
func TestV2Hosts(t *testing.T) {
var (
c = context.TODO()
svr = "11133_fat1_sh001"
)
convey.Convey("Hosts", t, func(ctx convey.C) {
hosts, err := d.Hosts(c, svr)
ctx.Convey("Then err should be nil.hosts should not be nil.", func(ctx convey.C) {
ctx.So(err, convey.ShouldBeNil)
ctx.So(hosts, convey.ShouldNotBeNil)
})
})
}
func TestV2SetHost(t *testing.T) {
var (
c = context.TODO()
host = &model.Host{}
svr = "11133_fat1_sh001"
)
convey.Convey("SetHost", t, func(ctx convey.C) {
err := d.SetHost(c, host, svr)
ctx.Convey("Then err should be nil.", func(ctx convey.C) {
ctx.So(err, convey.ShouldBeNil)
})
})
}
func TestV2ClearHost(t *testing.T) {
var (
c = context.TODO()
svr = "11133_fat1_sh001"
)
convey.Convey("ClearHost", t, func(ctx convey.C) {
err := d.ClearHost(c, svr)
ctx.Convey("Then err should be nil.", func(ctx convey.C) {
ctx.So(err, convey.ShouldBeNil)
})
})
}
func TestV2pingRedis(t *testing.T) {
var (
c = context.TODO()
)
convey.Convey("pingRedis", t, func(ctx convey.C) {
err := d.pingRedis(c)
ctx.Convey("Then err should be nil.", func(ctx convey.C) {
ctx.So(err, convey.ShouldBeNil)
})
})
}

View File

@@ -0,0 +1,79 @@
package v2
import (
"database/sql"
"go-common/app/infra/config/model"
"go-common/library/ecode"
"go-common/library/log"
"go-common/library/xstr"
)
//Tags get builds by app id.
func (d *Dao) Tags(appID int64) (tags []*model.ReVer, err error) {
rows, err := d.DB.Select("id,mark").Where("app_id = ? ", appID).Model(&model.DBTag{}).Rows()
if err != nil {
log.Error("Tags(%v) error(%v)", appID, err)
return
}
defer rows.Close()
for rows.Next() {
reVer := &model.ReVer{}
if err = rows.Scan(&reVer.Version, &reVer.Remark); err != nil {
log.Error("Tags(%v) error(%v)", appID, err)
return
}
tags = append(tags, reVer)
}
if len(tags) == 0 {
err = ecode.NothingFound
}
return
}
//ConfIDs get tag by id.
func (d *Dao) ConfIDs(ID int64) (ids []int64, err error) {
tag := &model.DBTag{}
if err = d.DB.First(tag, ID).Error; err != nil {
log.Error("ConfIDs(%v) error(%v)", ID, err)
return
}
ids, _ = xstr.SplitInts(tag.ConfigIDs)
if len(ids) == 0 {
err = ecode.NothingFound
}
return
}
// TagForce get force by tag.
func (d *Dao) TagForce(ID int64) (force int8, err error) {
row := d.DB.Select("`force`").Where("id = ?", ID).Model(&model.DBTag{}).Row()
if err = row.Scan(&force); err != nil {
log.Error("tagID(%v) error(%v)", ID, err)
if err == sql.ErrNoRows {
err = ecode.NothingFound
}
}
return
}
// LastForce ...
func (d *Dao) LastForce(appID, buildID int64) (lastForce int64, err error) {
row := d.DB.Select("id").Where("`app_id` = ? and `build_id` = ? and `force` = 1", appID, buildID).Model(&model.DBTag{}).Row()
if err = row.Scan(&lastForce); err != nil {
log.Error("lastForce(%v) error(%v)", lastForce, err)
if err == sql.ErrNoRows {
err = nil
lastForce = 0
}
}
return
}
//TagAll ...
func (d *Dao) TagAll(tagID int64) (tag *model.DBTag, err error) {
tag = &model.DBTag{}
if err = d.DB.First(tag, tagID).Error; err != nil {
log.Error("tagID(%v) error(%v)", tagID, err)
}
return
}

View File

@@ -0,0 +1,33 @@
package v2
import (
"testing"
"github.com/smartystreets/goconvey/convey"
)
func TestV2Tags(t *testing.T) {
var (
appID = int64(24)
)
convey.Convey("Tags", t, func(ctx convey.C) {
tags, err := d.Tags(appID)
ctx.Convey("Then err should be nil.tags should not be nil.", func(ctx convey.C) {
ctx.So(err, convey.ShouldBeNil)
ctx.So(tags, convey.ShouldNotBeNil)
})
})
}
func TestV2ConfIDs(t *testing.T) {
var (
ID = int64(460)
)
convey.Convey("ConfIDs", t, func(ctx convey.C) {
ids, err := d.ConfIDs(ID)
ctx.Convey("Then err should be nil.ids should not be nil.", func(ctx convey.C) {
ctx.So(err, convey.ShouldBeNil)
ctx.So(ids, convey.ShouldNotBeNil)
})
})
}

View File

@@ -0,0 +1,45 @@
package(default_visibility = ["//visibility:public"])
load(
"@io_bazel_rules_go//go:def.bzl",
"go_library",
)
go_library(
name = "go_default_library",
srcs = [
"config.go",
"config2.go",
"http.go",
"local.go",
],
importpath = "go-common/app/infra/config/http",
tags = ["automanaged"],
visibility = ["//visibility:public"],
deps = [
"//app/infra/config/conf:go_default_library",
"//app/infra/config/model:go_default_library",
"//app/infra/config/service/v1:go_default_library",
"//app/infra/config/service/v2:go_default_library",
"//library/ecode:go_default_library",
"//library/log:go_default_library",
"//library/net/http/blademaster:go_default_library",
"//library/net/http/blademaster/middleware/antispam:go_default_library",
"//library/net/http/blademaster/middleware/verify:go_default_library",
"//vendor/github.com/dgryski/go-farm: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,500 @@
package http
import (
"encoding/json"
"net/http"
"strconv"
"strings"
"time"
"go-common/app/infra/config/model"
"go-common/library/ecode"
"go-common/library/log"
bm "go-common/library/net/http/blademaster"
)
// push config update
func push(c *bm.Context) {
var (
err error
svr string
buildVer string
ver int64
env string
)
query := c.Request.Form
verStr := query.Get("version")
// params
if svr = query.Get("service"); svr == "" {
c.JSON(nil, ecode.RequestErr)
return
}
if buildVer = query.Get("build_ver"); buildVer == "" {
c.JSON(nil, ecode.RequestErr)
return
}
if ver, err = strconv.ParseInt(verStr, 10, 64); err != nil {
c.JSON(nil, ecode.RequestErr)
return
}
if env = query.Get("environment"); err != nil {
c.JSON(nil, ecode.RequestErr)
return
}
service := &model.Service{Name: svr, BuildVersion: buildVer, Version: ver, Env: env}
// update & write cache
c.JSON(nil, confSvc.Push(c, service))
}
// hosts client hosts
func hosts(c *bm.Context) {
var (
err error
svr string
data []*model.Host
env string
)
query := c.Request.URL.Query()
// params
if svr = query.Get("service"); svr == "" {
c.JSON(nil, ecode.RequestErr)
return
}
if env = query.Get("environment"); env == "" {
c.JSON(nil, ecode.RequestErr)
return
}
if data, err = confSvc.Hosts(c, svr, env); err != nil {
c.JSON(nil, err)
return
}
c.JSON(data, nil)
}
// versions client versions which the configuration is complete
func versions(c *bm.Context) {
var (
err error
svr string
data *model.Versions
env string
bver string
)
query := c.Request.URL.Query()
// params
if svr = query.Get("service"); svr == "" {
c.JSON(nil, ecode.RequestErr)
return
}
if env = query.Get("environment"); env == "" {
c.JSON(nil, ecode.RequestErr)
return
}
if bver = query.Get("build"); env == "" {
c.JSON(nil, ecode.RequestErr)
return
}
if data, err = confSvc.VersionSuccess(c, svr, env, bver); err != nil {
c.JSON(nil, err)
return
}
c.JSON(data, nil)
}
// versions client versions which the configuration is complete
func versionIng(c *bm.Context) {
var (
err error
svr string
data []int64
env string
)
query := c.Request.URL.Query()
// params
if svr = query.Get("service"); svr == "" {
c.JSON(nil, ecode.RequestErr)
return
}
if env = query.Get("environment"); env == "" {
c.JSON(nil, ecode.RequestErr)
return
}
if data, err = confSvc.VersionIng(c, svr, env); err != nil {
c.JSON(nil, err)
return
}
c.JSON(data, nil)
}
// config get config file
func config(c *bm.Context) {
var (
err error
svr string
host string
buildVer string
version int64
env string
token string
)
query := c.Request.URL.Query()
verStr := query.Get("version")
// params
if svr = query.Get("service"); svr == "" {
c.JSON(nil, ecode.RequestErr)
return
}
if env = query.Get("environment"); env == "" {
c.JSON(nil, ecode.RequestErr)
return
}
if token = query.Get("token"); token == "" {
c.JSON(nil, ecode.RequestErr)
return
}
if host = query.Get("hostname"); host == "" {
c.JSON(nil, ecode.RequestErr)
return
}
if buildVer = query.Get("build"); buildVer == "" {
c.JSON(nil, ecode.RequestErr)
return
}
if version, err = strconv.ParseInt(verStr, 10, 64); err != nil {
c.JSON(nil, ecode.RequestErr)
return
}
service := &model.Service{Name: svr, BuildVersion: buildVer, Env: env, Token: token, Version: version, Host: host}
data, err := confSvc.Config(c, service)
if err != nil {
c.JSON(nil, err)
return
}
c.JSON(data, nil)
}
// file get one file value
func file(c *bm.Context) {
var (
err error
svr string
buildVer string
env string
token string
file string
ver int64
data string
)
query := c.Request.URL.Query()
// params
if buildVer = query.Get("build"); buildVer == "" {
data = "build is null"
}
if !strings.HasPrefix(buildVer, "shsb") && !strings.HasPrefix(buildVer, "shylf") &&
query.Get("zone") != "" && query.Get("env") != "" && query.Get("treeid") != "" {
file2(c)
return
}
if verStr := query.Get("version"); verStr == "" {
ver = model.UnknownVersion
} else {
if ver, err = strconv.ParseInt(verStr, 10, 64); err != nil {
data = "version must be num"
}
}
if svr = query.Get("service"); svr == "" {
data = "service is null"
}
if env = query.Get("environment"); env == "" {
data = "environment is null"
}
if token = query.Get("token"); token == "" {
data = "token is null"
}
if file = query.Get("fileName"); file == "" {
data = "fileName is null"
}
service := &model.Service{Name: svr, BuildVersion: buildVer, Env: env, File: file, Token: token, Version: ver}
if data == "" {
if data, err = confSvc.File(c, service); err != nil {
data = err.Error()
c.AbortWithStatus(http.StatusInternalServerError)
}
} else {
c.AbortWithStatus(http.StatusBadRequest)
}
if _, err = c.Writer.Write([]byte(data)); err != nil {
log.Error("Response().Write(%v) error(%v)", data, err)
}
}
// check check config version
func check(c *bm.Context) {
var (
err error
svr string
host string
buildVer string
ip string
ver int64
env string
token string
appoint int64
query = c.Request.URL.Query()
)
// params
if svr = query.Get("service"); svr == "" {
c.JSON(nil, ecode.RequestErr)
return
}
if env = query.Get("environment"); env == "" {
c.JSON(nil, ecode.RequestErr)
return
}
if token = query.Get("token"); token == "" {
c.JSON(nil, ecode.RequestErr)
return
}
if ip = query.Get("ip"); ip == "" {
c.JSON(nil, ecode.RequestErr)
return
}
if host = query.Get("hostname"); host == "" {
c.JSON(nil, ecode.RequestErr)
return
}
if buildVer = query.Get("build"); buildVer == "" {
c.JSON(nil, ecode.RequestErr)
return
}
if ver, err = strconv.ParseInt(query.Get("version"), 10, 64); err != nil {
c.JSON(nil, ecode.RequestErr)
return
}
appoint, _ = strconv.ParseInt(query.Get("appoint"), 10, 64)
// check config version
rhost := &model.Host{Service: svr, Name: host, BuildVersion: buildVer, IP: ip, ConfigVersion: ver, Appoint: appoint, Customize: query.Get("customize")}
evt, err := confSvc.CheckVersion(c, rhost, env, token)
if err != nil {
c.JSON(nil, err)
return
}
// wait for version change
select {
case e := <-evt:
c.JSON(e, nil)
case <-time.After(time.Duration(cnf.PollTimeout)):
c.JSON(nil, ecode.NotModified)
case <-c.Writer.(http.CloseNotifier).CloseNotify():
c.JSON(nil, ecode.NotModified)
}
confSvc.Unsub(svr, host, env)
}
//clear host in redis
func clearhost(c *bm.Context) {
var (
svr string
env string
)
query := c.Request.Form
if svr = query.Get("service"); svr == "" {
c.JSON(nil, ecode.RequestErr)
return
}
if env = query.Get("environment"); env == "" {
c.JSON(nil, ecode.RequestErr)
return
}
c.JSON(nil, confSvc.ClearHost(c, svr, env))
}
// versions client versions which the configuration is complete
func builds(c *bm.Context) {
var (
svr string
bs, bs2 []string
env string
)
query := c.Request.URL.Query()
// params
if svr = query.Get("service"); svr == "" {
c.JSON(nil, ecode.RequestErr)
return
}
if env = query.Get("environment"); env == "" {
c.JSON(nil, ecode.RequestErr)
return
}
bs, _ = confSvc.Builds(c, svr, env)
bs2, _ = confSvc2.TmpBuilds(svr, env)
bs = append(bs, bs2...)
if len(bs) == 0 {
c.JSON(nil, ecode.NothingFound)
return
}
c.JSON(bs, nil)
}
func addConfigs(c *bm.Context) {
var (
svr string
env string
token string
user string
data map[string]string
err error
values = c.Request.PostForm
)
// params
if svr = values.Get("service"); svr == "" {
c.JSON(nil, ecode.RequestErr)
return
}
if env = values.Get("environment"); env == "" {
c.JSON(nil, ecode.RequestErr)
return
}
if token = values.Get("token"); token == "" {
c.JSON(nil, ecode.RequestErr)
return
}
if user = values.Get("user"); user == "" {
c.JSON(nil, ecode.RequestErr)
return
}
if err = json.Unmarshal([]byte(values.Get("data")), &data); err != nil {
c.JSON(nil, ecode.RequestErr)
return
}
c.JSON(nil, confSvc.AddConfigs(c, svr, env, token, user, data))
}
func copyConfigs(c *bm.Context) {
var (
svr string
env string
token string
build string
user string
err error
ver int64
values = c.Request.PostForm
)
// params
if svr = values.Get("service"); svr == "" {
c.JSON(nil, ecode.RequestErr)
return
}
if env = values.Get("environment"); env == "" {
c.JSON(nil, ecode.RequestErr)
return
}
if token = values.Get("token"); token == "" {
c.JSON(nil, ecode.RequestErr)
return
}
if user = values.Get("user"); user == "" {
c.JSON(nil, ecode.RequestErr)
return
}
if build = values.Get("build"); build == "" {
c.JSON(nil, ecode.RequestErr)
return
}
if ver, err = confSvc.CopyConfigs(c, svr, env, token, user, build); err != nil {
c.JSON(nil, err)
return
}
c.JSON(ver, nil)
}
func updateConfigs(c *bm.Context) {
var (
svr string
env string
token string
ver int64
user string
data map[string]string
err error
values = c.Request.PostForm
)
// params
if svr = values.Get("service"); svr == "" {
c.JSON(nil, ecode.RequestErr)
return
}
if env = values.Get("environment"); env == "" {
c.JSON(nil, ecode.RequestErr)
return
}
if ver, err = strconv.ParseInt(values.Get("version"), 10, 64); err != nil {
c.JSON(nil, ecode.RequestErr)
return
}
if token = values.Get("token"); token == "" {
c.JSON(nil, ecode.RequestErr)
return
}
if user = values.Get("user"); user == "" {
c.JSON(nil, ecode.RequestErr)
return
}
if err = json.Unmarshal([]byte(values.Get("data")), &data); err != nil {
c.JSON(nil, ecode.RequestErr)
return
}
c.JSON(nil, confSvc.UpdateConfigs(c, svr, env, token, user, ver, data))
}
// configN get config namespace file
func configN(c *bm.Context) {
var (
err error
svr string
host string
buildVer string
version int64
env string
token string
)
query := c.Request.URL.Query()
verStr := query.Get("version")
// params
if svr = query.Get("service"); svr == "" {
c.JSON(nil, ecode.RequestErr)
return
}
if env = query.Get("environment"); env == "" {
c.JSON(nil, ecode.RequestErr)
return
}
if token = query.Get("token"); token == "" {
c.JSON(nil, ecode.RequestErr)
return
}
if host = query.Get("hostname"); host == "" {
c.JSON(nil, ecode.RequestErr)
return
}
if buildVer = query.Get("build"); buildVer == "" {
c.JSON(nil, ecode.RequestErr)
return
}
if version, err = strconv.ParseInt(verStr, 10, 64); err != nil {
c.JSON(nil, ecode.RequestErr)
return
}
service := &model.Service{Name: svr, BuildVersion: buildVer, Env: env, Token: token, Version: version, Host: host}
data, err := confSvc.Config2(c, service)
if err != nil {
c.JSON(nil, err)
return
}
c.JSON(data, nil)
}

View File

@@ -0,0 +1,352 @@
package http
import (
"encoding/json"
"fmt"
"net/http"
"strconv"
"time"
"go-common/app/infra/config/model"
"go-common/library/ecode"
"go-common/library/log"
bm "go-common/library/net/http/blademaster"
)
// versions client versions which the configuration is complete
func versions2(c *bm.Context) {
var (
err error
svr string
data *model.Versions
bver string
token string
env string
zone string
)
query := c.Request.URL.Query()
// params
svr = query.Get("service")
if svr == "" {
token = query.Get("token")
zone = query.Get("zone")
env = query.Get("env")
if zone == "" || env == "" || token == "" {
c.JSON(nil, ecode.RequestErr)
return
}
if svr, err = confSvc2.AppService(zone, env, token); err != nil {
c.JSON(nil, ecode.RequestErr)
return
}
}
if bver = query.Get("build"); bver == "" {
c.JSON(nil, ecode.RequestErr)
return
}
if data, err = confSvc2.VersionSuccess(c, svr, bver); err != nil {
c.JSON(nil, err)
return
}
c.JSON(data, nil)
}
// config get config file
func config2(c *bm.Context) {
var (
err error
svr string
buildVer string
version int64
token string
ids []int64
zone string
env string
)
query := c.Request.URL.Query()
// params
if token = query.Get("token"); token == "" {
c.JSON(nil, ecode.RequestErr)
return
}
svr = query.Get("service")
if svr == "" {
zone = query.Get("zone")
env = query.Get("env")
if zone == "" || env == "" || token == "" {
c.JSON(nil, ecode.RequestErr)
return
}
if svr, err = confSvc2.AppService(zone, env, token); err != nil {
c.JSON(nil, ecode.RequestErr)
return
}
}
if buildVer = query.Get("build"); buildVer == "" {
c.JSON(nil, ecode.RequestErr)
return
}
if version, err = strconv.ParseInt(query.Get("version"), 10, 64); err != nil {
c.JSON(nil, ecode.RequestErr)
return
}
if idsStr := query.Get("ids"); len(idsStr) != 0 {
if err = json.Unmarshal([]byte(query.Get("ids")), &ids); err != nil {
c.JSON(nil, ecode.RequestErr)
return
}
}
data, err := confSvc2.Config(c, svr, token, version, ids)
if err != nil {
c.JSON(nil, err)
return
}
c.JSON(data, nil)
}
// file get one file value
func file2(c *bm.Context) {
var (
err error
svr string
buildVer string
token string
file string
ver int64
treeID string
env string
zone string
data string
)
query := c.Request.URL.Query()
// params
if verStr := query.Get("version"); verStr == "" {
ver = model.UnknownVersion
} else {
if ver, err = strconv.ParseInt(verStr, 10, 64); err != nil {
data = "version must be num"
}
}
if env = query.Get("env"); env == "" {
data = "env is null"
}
if zone = query.Get("zone"); zone == "" {
data = "zone is null"
}
if token = query.Get("token"); token == "" {
data = "token is null"
}
if treeID = query.Get("treeid"); treeID == "" {
if svr, err = confSvc2.AppService(zone, env, token); err != nil {
data = "appid is null"
}
} else {
svr = fmt.Sprintf("%s_%s_%s", treeID, env, zone)
}
if buildVer = query.Get("build"); buildVer == "" {
data = "build is null"
}
if file = query.Get("fileName"); file == "" {
data = "fileName is null"
}
service := &model.Service{Name: svr, BuildVersion: buildVer, File: file, Token: token, Version: ver}
if data == "" {
if data, err = confSvc2.File(c, service); err != nil {
data = err.Error()
c.AbortWithStatus(http.StatusInternalServerError)
}
} else {
c.AbortWithStatus(http.StatusBadRequest)
}
if _, err = c.Writer.Write([]byte(data)); err != nil {
log.Error("Response().Write(%v) error(%v)", data, err)
}
}
// check check config version
func check2(c *bm.Context) {
var (
err error
svr string
host string
buildVer string
ip string
ver int64
token string
appoint int64
zone string
env string
query = c.Request.URL.Query()
)
// params
if token = query.Get("token"); token == "" {
c.JSON(nil, ecode.RequestErr)
return
}
svr = query.Get("service")
if svr == "" {
zone = query.Get("zone")
env = query.Get("env")
if zone == "" || env == "" || token == "" {
c.JSON(nil, ecode.RequestErr)
return
}
if svr, err = confSvc2.AppService(zone, env, token); err != nil {
c.JSON(nil, ecode.RequestErr)
return
}
}
if ip = query.Get("ip"); ip == "" {
c.JSON(nil, ecode.RequestErr)
return
}
if host = query.Get("hostname"); host == "" {
c.JSON(nil, ecode.RequestErr)
return
}
if buildVer = query.Get("build"); buildVer == "" {
c.JSON(nil, ecode.RequestErr)
return
}
if ver, err = strconv.ParseInt(query.Get("version"), 10, 64); err != nil {
c.JSON(nil, ecode.RequestErr)
return
}
appoint, _ = strconv.ParseInt(query.Get("appoint"), 10, 64)
// check config version
rhost := &model.Host{Service: svr, Name: host, BuildVersion: buildVer, IP: ip, ConfigVersion: ver, Appoint: appoint, Customize: query.Get("customize")}
evt, err := confSvc2.CheckVersion(c, rhost, token)
if err != nil {
c.JSON(nil, err)
return
}
// wait for version change
select {
case e := <-evt:
c.JSON(e, nil)
case <-time.After(time.Duration(cnf.PollTimeout)):
c.JSON(nil, ecode.NotModified)
case <-c.Writer.(http.CloseNotifier).CloseNotify():
c.JSON(nil, ecode.NotModified)
}
confSvc2.Unsub(svr, host)
}
//clear host in redis
func clearhost2(c *bm.Context) {
var (
svr string
zone string
env string
token string
err error
)
query := c.Request.Form
svr = query.Get("service")
if svr == "" {
token = query.Get("token")
zone = query.Get("zone")
env = query.Get("env")
if zone == "" || env == "" || token == "" {
c.JSON(nil, ecode.RequestErr)
return
}
if svr, err = confSvc2.AppService(zone, env, token); err != nil {
c.JSON(nil, ecode.RequestErr)
return
}
}
c.JSON(nil, confSvc2.ClearHost(c, svr))
}
// versions client versions which the configuration is complete
func builds2(c *bm.Context) {
var (
err error
svr string
data []string
token string
env string
zone string
)
query := c.Request.URL.Query()
// params
svr = query.Get("service")
if svr == "" {
token = query.Get("token")
zone = query.Get("zone")
env = query.Get("env")
if zone == "" || env == "" || token == "" {
c.JSON(nil, ecode.RequestErr)
return
}
if svr, err = confSvc2.AppService(zone, env, token); err != nil {
c.JSON(nil, ecode.RequestErr)
return
}
}
if data, err = confSvc2.Builds(c, svr); err != nil {
c.JSON(nil, err)
return
}
c.JSON(data, nil)
}
// config get config file
func latest(c *bm.Context) {
var (
err error
svr string
buildVer string
version int64
token string
ids []int64
zone string
env string
verStr string
)
query := c.Request.URL.Query()
// params
if token = query.Get("token"); token == "" {
c.JSON(nil, ecode.RequestErr)
return
}
svr = query.Get("service")
if svr == "" {
zone = query.Get("zone")
env = query.Get("env")
if zone == "" || env == "" || token == "" {
c.JSON(nil, ecode.RequestErr)
return
}
if svr, err = confSvc2.AppService(zone, env, token); err != nil {
c.JSON(nil, ecode.RequestErr)
return
}
}
if buildVer = query.Get("build"); buildVer == "" {
c.JSON(nil, ecode.RequestErr)
return
}
verStr = query.Get("version")
if len(verStr) > 0 {
if version, err = strconv.ParseInt(verStr, 10, 64); err != nil {
c.JSON(nil, ecode.RequestErr)
return
}
} else {
rhost := &model.Host{Service: svr, BuildVersion: buildVer}
version, err = confSvc2.CheckLatest(c, rhost, token)
if err != nil {
c.JSON(nil, err)
return
}
}
data, err := confSvc2.ConfigCheck(c, svr, token, version, ids)
if err != nil {
c.JSON(nil, err)
return
}
c.JSON(data, nil)
}

View File

@@ -0,0 +1,117 @@
package http
import (
"io"
"strconv"
"strings"
"go-common/app/infra/config/conf"
"go-common/app/infra/config/service/v1"
"go-common/app/infra/config/service/v2"
"go-common/library/ecode"
"go-common/library/log"
bm "go-common/library/net/http/blademaster"
"go-common/library/net/http/blademaster/middleware/antispam"
v "go-common/library/net/http/blademaster/middleware/verify"
"github.com/dgryski/go-farm"
)
var (
cnf *conf.Config
verify *v.Verify
confSvc *v1.Service
confSvc2 *v2.Service
anti *antispam.Antispam
)
// Init init.
func Init(c *conf.Config, s *v1.Service, s2 *v2.Service, rpcCloser io.Closer) {
initService(c)
verify = v.New(c.Verify)
cnf = c
confSvc = s
confSvc2 = s2
engine := bm.DefaultServer(c.BM)
innerRouter(engine)
if err := engine.Start(); err != nil {
log.Error("engine.Start() error(%v)", err)
panic(err)
}
}
// innerRouter init inner router.
func innerRouter(e *bm.Engine) {
e.Ping(ping)
e.Register(register)
b := e.Group("/", verify.Verify)
noAuth := e.Group("/")
{
v1 := b.Group("v1/config/")
{
v1.GET("host/infos", hosts)
v1.POST("host/clear", clearhost)
v1.POST("push", push)
}
{
noAuth.GET("v1/config/versions", versions)
noAuth.GET("v1/config/builds", builds)
noAuth.GET("v1/config/check", check)
noAuth.GET("v1/config/get", config)
noAuth.GET("v1/config/get2", configN)
noAuth.GET("v1/config/file.so", file)
noAuth.GET("v1/config/version/ing", versionIng)
noAuth.POST("v1/config/config/add", addConfigs)
noAuth.POST("v1/config/config/copy", copyConfigs)
noAuth.POST("v1/config/config/update", updateConfigs)
noAuth.GET("config/v2/versions", versions2)
noAuth.GET("config/v2/builds", builds2)
noAuth.GET("config/v2/check", check2)
noAuth.GET("config/v2/get", setMid, anti.ServeHTTP, config2)
noAuth.GET("config/v2/file.so", file2)
noAuth.GET("config/v2/latest", latest)
}
v2 := b.Group("config/v2/")
{
v2.POST("host/clear", clearhost2)
}
}
}
func setMid(c *bm.Context) {
var (
token string
service string
query = c.Request.URL.Query()
hash uint64
)
service = query.Get("service")
if service == "" {
token = query.Get("token")
if token == "" {
c.JSON(nil, ecode.RequestErr)
c.Abort()
return
}
hash = farm.Hash64([]byte(token))
} else {
arrs := strings.Split(service, "_")
if len(arrs) != 3 {
c.JSON(nil, ecode.RequestErr)
c.Abort()
return
}
_, err := strconv.ParseInt(arrs[0], 10, 64)
if err != nil {
c.JSON(nil, ecode.RequestErr)
c.Abort()
return
}
hash = farm.Hash64([]byte(service))
}
c.Set("mid", int64(hash))
}
func initService(c *conf.Config) {
anti = antispam.New(c.Antispam)
}

View File

@@ -0,0 +1,25 @@
package http
import (
"net/http"
"go-common/library/log"
bm "go-common/library/net/http/blademaster"
)
// ping check server ok.
func ping(c *bm.Context) {
var (
err error
)
if err = confSvc.Ping(c); err != nil {
log.Error("config service ping error(%v)", err)
c.JSON(nil, err)
http.Error(c.Writer, "", http.StatusServiceUnavailable)
}
}
// register check server ok.
func register(c *bm.Context) {
c.JSON(map[string]struct{}{}, nil)
}

View File

@@ -0,0 +1,38 @@
package(default_visibility = ["//visibility:public"])
load(
"@io_bazel_rules_go//go:def.bzl",
"go_library",
)
go_library(
name = "go_default_library",
srcs = [
"app.go",
"build.go",
"client.go",
"common_config.go",
"config.go",
"force.go",
"rpc.go",
"tag.go",
],
importpath = "go-common/app/infra/config/model",
tags = ["automanaged"],
visibility = ["//visibility:public"],
deps = ["//library/time: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,30 @@
package model
import "go-common/library/time"
// DBApp mysql app DB.
type DBApp struct {
ID int64 `json:"id" gorm:"primary_key"`
Name string `json:"name"`
Token string `json:"token"`
Env string `json:"env"`
Zone string `json:"zone"`
TreeID int `json:"tree_id"`
Ctime time.Time `json:"ctime"`
Mtime time.Time `json:"mtime"`
}
// TableName app
func (DBApp) TableName() string {
return "app"
}
// App app local cache.
type App struct {
ID int64 `json:"id"`
Name string `json:"name"`
Token string `json:"token"`
Env string `json:"env"`
Zone string `json:"zone"`
TreeID int `json:"tree_id"`
}

View File

@@ -0,0 +1,19 @@
package model
import "go-common/library/time"
// Build build.
type Build struct {
ID int64 `json:"id"`
AppID int64 `json:"app_id"`
Name string `json:"name"`
TagID int64 `json:"tag_id"`
Operator string `json:"operator"`
Ctime time.Time `json:"ctime"`
Mtime time.Time `json:"mtime"`
}
// TableName build.
func (Build) TableName() string {
return "build"
}

View File

@@ -0,0 +1,95 @@
package model
import (
"go-common/library/time"
)
const (
// HostOffline host offline state.
HostOffline = 0
// HostOnline host online state.
HostOnline = 1
// HostStateOK host state ok.
HostStateOK = 2
// UnknownVersion unknown version.
UnknownVersion = -1
)
// Diff return to client.
type Diff struct {
Version int64 `json:"version"`
Diffs []int64 `json:"diffs"`
}
// Version return to client.
type Version struct {
Version int64 `json:"version"`
}
// ReVer reVer
type ReVer struct {
Version int64 `json:"version"`
Remark string `json:"remark"`
}
// Versions versions
type Versions struct {
Version []*ReVer `json:"version"`
DefVer int64 `json:"defver"`
}
// Content return to client.
type Content struct {
Version int64 `json:"version"`
Md5 string `json:"md5"`
Content string `json:"content"`
}
// Namespace the key-value config object.
type Namespace struct {
Name string `json:"name"`
Data map[string]string `json:"data"`
}
// Service service
type Service struct {
Name string
BuildVersion string
Env string
Token string
File string
Version int64
Host string
IP string
Appoint int64
}
// NSValue config value.
type NSValue struct {
ConfigID int64 `json:"cid"`
NamespaceID int64 `json:"nsid"`
Name string `json:"name"`
Config string `json:"config"`
}
// Value config value.
type Value struct {
ConfigID int64 `json:"cid"`
Name string `json:"name"`
Config string `json:"config"`
}
// Host host.
type Host struct {
Name string `json:"hostname"`
Service string `json:"service"`
BuildVersion string `json:"build"`
IP string `json:"ip"`
ConfigVersion int64 `json:"version"`
HeartbeatTime time.Time `json:"heartbeat_time"`
State int `json:"state"`
Appoint int64 `json:"appoint"`
Customize string `json:"customize"`
Force int8 `json:"force"`
ForceVersion int64 `json:"force_version"`
}

View File

@@ -0,0 +1,21 @@
package model
import "go-common/library/time"
// CommonConf common config.
type CommonConf struct {
ID int64 `json:"id" gorm:"primary_key"`
TeamID int64 `json:"team_id"`
Name string `json:"name"`
Comment string `json:"comment"`
State int8 `json:"state"`
Mark string `json:"mark"`
Operator string `json:"operator"`
Ctime time.Time `json:"ctime"`
Mtime time.Time `json:"mtime"`
}
// TableName commonConfig.
func (CommonConf) TableName() string {
return "common_config"
}

View File

@@ -0,0 +1,29 @@
package model
import "go-common/library/time"
var (
//ConfigIng config ing.
ConfigIng = int8(1)
//ConfigEnd config ing.
ConfigEnd = int8(2)
)
// Config config.
type Config struct {
ID int64 `json:"id" gorm:"primary_key"`
AppID int64 `json:"app_id"`
Name string `json:"name"`
Comment string `json:"comment"`
From int64 `json:"from"`
State int8 `json:"state"`
Mark string `json:"mark"`
Operator string `json:"operator"`
Ctime time.Time `json:"ctime"`
Mtime time.Time `json:"mtime"`
}
// TableName config
func (Config) TableName() string {
return "config"
}

View File

@@ -0,0 +1,20 @@
package model
import "go-common/library/time"
// Force ...
type Force struct {
ID int64 `json:"id"`
AppID int64 `json:"app_id"`
HostName string `json:"hostname"`
IP string `json:"ip"`
Version int64 `json:"version"`
Operator string `json:"operator"`
Ctime time.Time `json:"ctime"`
Mtime time.Time `json:"mtime"`
}
// TableName build.
func (Force) TableName() string {
return "force"
}

View File

@@ -0,0 +1,18 @@
package model
// ArgConf config param.
type ArgConf struct {
App string
BuildVer string
Ver int64
Env string
Hosts map[string]string
SType int8
}
// ArgToken token param.
type ArgToken struct {
App string
Token string
Env string
}

View File

@@ -0,0 +1,20 @@
package model
import "go-common/library/time"
// DBTag tag table in mysql.
type DBTag struct {
ID int64 `json:"id" gorm:"primary_key"`
AppID int64 `json:"app_id"`
ConfigIDs string `json:"config_ids"`
Mark string `json:"mark"`
Force int8 `json:"force"`
Operator string `json:"operator"`
Ctime time.Time `json:"ctime"`
Mtime time.Time `json:"mtime"`
}
// TableName tag.
func (DBTag) TableName() string {
return "tag"
}

View File

@@ -0,0 +1,41 @@
package(default_visibility = ["//visibility:public"])
load(
"@io_bazel_rules_go//go:def.bzl",
"go_test",
"go_library",
)
go_test(
name = "go_default_test",
srcs = ["config_test.go"],
embed = [":go_default_library"],
tags = ["automanaged"],
deps = ["//app/infra/config/model:go_default_library"],
)
go_library(
name = "go_default_library",
srcs = ["config.go"],
importpath = "go-common/app/infra/config/rpc/client",
tags = ["automanaged"],
visibility = ["//visibility:public"],
deps = [
"//app/infra/config/model:go_default_library",
"//library/net/rpc: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,78 @@
package config
import (
"context"
"go-common/app/infra/config/model"
"go-common/library/net/rpc"
)
const (
_appid = "config.service"
_push = "RPC.Push"
_setToken = "RPC.SetToken"
_pushV4 = "RPC.PushV4"
_force = "RPC.Force"
_setTokenV4 = "RPC.SetTokenV4"
_hosts = "RPC.Hosts"
_clearHost = "RPC.ClearHost"
)
var (
_noArg = &struct{}{}
)
//Service2 service.
type Service2 struct {
client *rpc.Client2
}
// New2 new a config service.
func New2(c *rpc.ClientConfig) (s *Service2) {
s = &Service2{}
s.client = rpc.NewDiscoveryCli(_appid, c)
return
}
// Push push new ver to config-service
func (s *Service2) Push(c context.Context, arg *model.ArgConf) (err error) {
err = s.client.Boardcast(c, _push, arg, _noArg)
return
}
// SetToken update token in config-service
func (s *Service2) SetToken(c context.Context, arg *model.ArgToken) (err error) {
err = s.client.Boardcast(c, _setToken, arg, _noArg)
return
}
// PushV4 push new ver to config-service
func (s *Service2) PushV4(c context.Context, arg *model.ArgConf) (err error) {
err = s.client.Boardcast(c, _pushV4, arg, _noArg)
return
}
// SetTokenV4 update token in config-service
func (s *Service2) SetTokenV4(c context.Context, arg *model.ArgToken) (err error) {
err = s.client.Boardcast(c, _setTokenV4, arg, _noArg)
return
}
//Hosts get host list.
func (s *Service2) Hosts(c context.Context, svr string) (hosts []*model.Host, err error) {
err = s.client.Call(c, _hosts, svr, &hosts)
return
}
// ClearHost update token in config-service
func (s *Service2) ClearHost(c context.Context, svr string) (err error) {
err = s.client.Call(c, _clearHost, svr, _noArg)
return
}
// Force push new host ver to config-service
func (s *Service2) Force(c context.Context, arg *model.ArgConf) (err error) {
err = s.client.Boardcast(c, _force, arg, _noArg)
return
}

View File

@@ -0,0 +1,61 @@
package config
import (
"context"
"fmt"
"testing"
"time"
"go-common/app/infra/config/model"
)
func TestConf(t *testing.T) {
s := New2(nil)
time.Sleep(1 * time.Second)
// coin
testPush(t, s)
testSetToken(t, s)
testHosts(t, s)
testClearHost(t, s)
}
func testPush(t *testing.T, s *Service2) {
arg := &model.ArgConf{
App: "zjx_test",
BuildVer: "1_0_0_0",
Ver: 113,
Env: "2",
}
if err := s.Push(context.TODO(), arg); err != nil {
fmt.Println(err)
t.FailNow()
}
}
func testSetToken(t *testing.T, s *Service2) {
arg := &model.ArgToken{
App: "zjx_test",
Token: "123",
Env: "2",
}
if err := s.SetToken(context.TODO(), arg); err != nil {
fmt.Println(err)
t.FailNow()
}
}
func testHosts(t *testing.T, s *Service2) {
if hosts, err := s.Hosts(context.TODO(), "testApp4890934756659"); err != nil {
t.Log(err)
t.FailNow()
} else {
t.Log(len(hosts))
}
}
func testClearHost(t *testing.T, s *Service2) {
if err := s.ClearHost(context.TODO(), "testApp4890934756659"); err != nil {
t.Log(err)
t.FailNow()
}
}

View File

@@ -0,0 +1,36 @@
package(default_visibility = ["//visibility:public"])
load(
"@io_bazel_rules_go//go:def.bzl",
"go_library",
)
go_library(
name = "go_default_library",
srcs = ["config.go"],
importpath = "go-common/app/infra/config/rpc/server",
tags = ["automanaged"],
visibility = ["//visibility:public"],
deps = [
"//app/infra/config/conf:go_default_library",
"//app/infra/config/model:go_default_library",
"//app/infra/config/service/v1:go_default_library",
"//app/infra/config/service/v2:go_default_library",
"//library/net/rpc:go_default_library",
"//library/net/rpc/context: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,76 @@
package rpc
import (
"go-common/app/infra/config/conf"
"go-common/app/infra/config/service/v1"
"go-common/app/infra/config/service/v2"
"go-common/library/net/rpc"
"go-common/library/net/rpc/context"
"go-common/app/infra/config/model"
)
// RPC export rpc service
type RPC struct {
s *v1.Service
s2 *v2.Service
}
// New new rpc server.
func New(c *conf.Config, s *v1.Service, s2 *v2.Service) (svr *rpc.Server) {
r := &RPC{s: s, s2: s2}
svr = rpc.NewServer(c.RPCServer)
if err := svr.Register(r); err != nil {
panic(err)
}
return
}
// Ping check connection success.
func (r *RPC) Ping(c context.Context, arg *struct{}, res *struct{}) (err error) {
return
}
// Push push new config change to config-service
func (r *RPC) Push(c context.Context, a *model.ArgConf, res *struct{}) (err error) {
service := &model.Service{Name: a.App, BuildVersion: a.BuildVer, Version: a.Ver, Env: a.Env}
err = r.s.Push(c, service)
return
}
//SetToken update Token
func (r *RPC) SetToken(c context.Context, a *model.ArgToken, res *struct{}) (err error) {
r.s.SetToken(c, a.App, a.Env, a.Token)
return
}
// PushV4 push new config change to config-service
func (r *RPC) PushV4(c context.Context, a *model.ArgConf, res *struct{}) (err error) {
service := &model.Service{Name: a.App, BuildVersion: a.BuildVer, Version: a.Ver}
err = r.s2.Push(c, service)
return
}
//SetTokenV4 update Token
func (r *RPC) SetTokenV4(c context.Context, a *model.ArgToken, res *struct{}) (err error) {
r.s2.SetToken(a.App, a.Token)
return
}
//Hosts get host list.
func (r *RPC) Hosts(c context.Context, svr string, res *[]*model.Host) (err error) {
*res, err = r.s2.Hosts(c, svr)
return
}
//ClearHost clear host.
func (r *RPC) ClearHost(c context.Context, svr string, res *struct{}) error {
return r.s2.ClearHost(c, svr)
}
// Force push new host config change to config-service
func (r *RPC) Force(c context.Context, a *model.ArgConf, res *struct{}) (err error) {
service := &model.Service{Name: a.App, BuildVersion: a.BuildVer, Version: a.Ver}
err = r.s2.Force(c, service, a.Hosts, a.SType)
return
}

View File

@@ -0,0 +1,54 @@
package(default_visibility = ["//visibility:public"])
load(
"@io_bazel_rules_go//go:def.bzl",
"go_test",
"go_library",
)
go_test(
name = "go_default_test",
srcs = ["client_test.go"],
embed = [":go_default_library"],
tags = ["automanaged"],
deps = [
"//app/infra/config/conf:go_default_library",
"//app/infra/config/model:go_default_library",
"//vendor/github.com/BurntSushi/toml:go_default_library",
"//vendor/github.com/smartystreets/goconvey/convey:go_default_library",
],
)
go_library(
name = "go_default_library",
srcs = [
"client.go",
"service.go",
],
importpath = "go-common/app/infra/config/service/v1",
tags = ["automanaged"],
visibility = ["//visibility:public"],
deps = [
"//app/infra/config/conf:go_default_library",
"//app/infra/config/dao/v1:go_default_library",
"//app/infra/config/model:go_default_library",
"//library/database/sql:go_default_library",
"//library/ecode:go_default_library",
"//library/log:go_default_library",
"//library/time: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,570 @@
package v1
import (
"context"
"crypto/md5"
"encoding/hex"
"encoding/json"
"fmt"
"time"
"go-common/app/infra/config/model"
"go-common/library/database/sql"
"go-common/library/ecode"
"go-common/library/log"
xtime "go-common/library/time"
)
const (
_buildVerKey = "%s_%s_%s"
_pushKey = "%s_%s_%s"
_cacheKey = "%s_%s_%d_%s"
_cacheKey2 = "%s_%s_%d_%s_2"
_fileKey = "%s_%d"
)
var (
addBusiness = "前端接口api添加配置版本"
addInfo = "添加版本:%d"
copyBusiness = "前端接口api拷贝配置版本"
copyInfo = "拷贝版本:%d新的版本:%d"
updateBusiness = "前端接口api更新配置"
updateInfo = "更新版本:%d"
)
// PushKey push sub id
func pushKey(svr, host, env string) string {
return fmt.Sprintf(_pushKey, svr, host, env)
}
// buildVerKey version mapping key
func buildVerKey(svr, bver, env string) string {
return fmt.Sprintf(_buildVerKey, svr, bver, env)
}
// cacheKey config cache key
func cacheKey(svr, bver, env string, ver int64) string {
return fmt.Sprintf(_cacheKey, svr, bver, ver, env)
}
// cacheKey config cache key
func cacheKey2(svr, bver, env string, ver int64) string {
return fmt.Sprintf(_cacheKey2, svr, bver, ver, env)
}
// fileKey
func fileKey(filename string, ver int64) string {
return fmt.Sprintf(_fileKey, filename, ver)
}
// tokenKey
func tokenKey(svr, env string) string {
return fmt.Sprintf("%s_%s", svr, env)
}
// genConfig generate config
func genConfig(ver int64, cs []*model.NSValue) (conf *model.Content, err error) {
var b []byte
data := make(map[string]string)
for _, c := range cs {
data[c.Name] = c.Config
}
if b, err = json.Marshal(data); err != nil {
return
}
mb := md5.Sum(b)
conf = &model.Content{
Version: ver,
Md5: hex.EncodeToString(mb[:]),
Content: string(b),
}
return
}
// genConfig2 generate config
func genConfig2(ver int64, cs []*model.NSValue, ns map[int64]string) (conf *model.Content, err error) {
var (
b []byte
v string
ok bool
s *model.Namespace
)
nsc := make(map[string]*model.Namespace)
for _, c := range cs {
if v, ok = ns[c.NamespaceID]; !ok && c.NamespaceID != 0 {
continue
}
if s, ok = nsc[v]; !ok {
s = &model.Namespace{Name: v, Data: map[string]string{}}
nsc[v] = s
}
s.Data[c.Name] = c.Config
}
if b, err = json.Marshal(nsc); err != nil {
return
}
mb := md5.Sum(b)
conf = &model.Content{
Version: ver,
Md5: hex.EncodeToString(mb[:]),
Content: string(b),
}
return
}
// Push version to clients & generate config caches
func (s *Service) Push(c context.Context, svr *model.Service) (err error) {
var (
hosts []*model.Host
values []*model.NSValue
conf *model.Content
conf2 *model.Content
namespaces map[int64]string
)
if values, err = s.dao.Values(c, svr.Version); err != nil {
return
}
if namespaces, err = s.dao.Namespaces(c, svr.Version); err != nil {
return
}
if len(values) == 0 {
err = fmt.Errorf("config values is empty. svr:%s, host:%s, buildVer:%s, ver:%d", svr.Name, svr.Host, svr.BuildVersion, svr.Version)
log.Error("%v", err)
return
}
// compatible old version sdk
if conf, err = genConfig(svr.Version, values); err != nil {
log.Error("get config value:%s error(%v) ", values, err)
return
}
cacheKey := cacheKey(svr.Name, svr.BuildVersion, svr.Env, svr.Version)
if err = s.dao.SetFile(cacheKey, conf); err != nil {
log.Error("set confCashe error. svr:%s, buildVer:%s, ver:%d", svr.Name, svr.BuildVersion, svr.Env)
err = nil
}
if conf2, err = genConfig2(svr.Version, values, namespaces); err != nil {
log.Error("get config2 value:%s error(%v) ", values, err)
return
}
cacheKey2 := cacheKey2(svr.Name, svr.BuildVersion, svr.Env, svr.Version)
if err = s.dao.SetFile(cacheKey2, conf2); err != nil {
log.Error("set confCashe2 error. svr:%s, buildVer:%s, ver:%d", svr.Name, svr.BuildVersion, svr.Env)
err = nil
}
s.setVersion(svr.Name, svr.BuildVersion, svr.Env, svr.Version)
// push hosts
if hosts, err = s.dao.Hosts(c, svr.Name, svr.Env); err != nil {
log.Error("get hosts error. svr:%s, buildVer:%s, ver:%d", svr.Name, svr.BuildVersion, svr.Version)
err = nil
return
}
for _, h := range hosts {
if h.State == model.HostOnline {
pushKey := pushKey(h.Service, h.Name, svr.Env)
if ok := s.pubEvent(pushKey, &model.Version{Version: conf.Version}); ok {
log.Info("s.events.Pub(%s, %d) ok: %t", pushKey, conf.Version, ok)
}
}
}
return
}
// Config return config content.
func (s *Service) Config(c context.Context, svr *model.Service) (conf *model.Content, err error) {
var values []*model.NSValue
if err = s.appAuth(c, svr.Name, svr.Env, svr.Token); err != nil {
return
}
cacheName := cacheKey(svr.Name, svr.BuildVersion, svr.Env, svr.Version)
if conf, err = s.dao.File(cacheName); err == nil {
return
}
if values, err = s.dao.Values(c, svr.Version); err != nil {
return
}
if len(values) == 0 {
err = fmt.Errorf("config values is empty. svr:%s, host:%s, buildVer:%s, ver:%d", svr.Name, svr.Host, svr.BuildVersion, svr.Version)
log.Error("%v", err)
return
}
if conf, err = genConfig(svr.Version, values); err != nil {
log.Error("get config value:%s error(%v) ", values, err)
return
}
if err = s.dao.SetFile(cacheName, conf); err != nil {
err = nil
}
return
}
// Config2 return config content.
func (s *Service) Config2(c context.Context, svr *model.Service) (conf *model.Content, err error) {
var (
values []*model.NSValue
namespaces map[int64]string
)
if err = s.appAuth(c, svr.Name, svr.Env, svr.Token); err != nil {
return
}
cacheName := cacheKey2(svr.Name, svr.BuildVersion, svr.Env, svr.Version)
if conf, err = s.dao.File(cacheName); err == nil {
return
}
if namespaces, err = s.dao.Namespaces(c, svr.Version); err != nil {
return
}
if values, err = s.dao.Values(c, svr.Version); err != nil {
return
}
if len(values) == 0 {
err = fmt.Errorf("config values is empty. svr:%s, host:%s, buildVer:%s, ver:%d", svr.Name, svr.Host, svr.BuildVersion, svr.Version)
log.Error("%v", err)
return
}
if conf, err = genConfig2(svr.Version, values, namespaces); err != nil {
log.Error("get config value:(%s) error(%v) ", values, err)
return
}
if err = s.dao.SetFile(cacheName, conf); err != nil {
err = nil
}
return
}
// File get one file content.
func (s *Service) File(c context.Context, svr *model.Service) (val string, err error) {
var (
curVer int64
ok bool
)
if err = s.appAuth(c, svr.Name, svr.Env, svr.Token); err != nil {
return
}
if svr.Version != model.UnknownVersion {
curVer = svr.Version
} else {
curVer, ok = s.version(svr.Name, svr.BuildVersion, svr.Env)
if !ok {
if curVer, err = s.dao.BuildVersion(c, svr.Name, svr.BuildVersion, svr.Env); err != nil {
log.Error("BuildVersion(%v) error(%v)", svr, err)
return
}
s.setVersion(svr.Name, svr.BuildVersion, svr.Env, curVer)
}
}
fKey := fileKey(svr.File, curVer)
if val, err = s.dao.FileStr(fKey); err == nil {
return
}
if val, err = s.dao.Value(c, svr.File, curVer); err != nil {
log.Error("Value(%v) error(%v)", svr.File, err)
return
}
s.dao.SetFileStr(fKey, val)
return
}
// CheckVersion check client version.
func (s *Service) CheckVersion(c context.Context, rhost *model.Host, env, token string) (evt chan *model.Version, err error) {
var (
curVer int64
)
if err = s.appAuth(c, rhost.Service, env, token); err != nil {
return
}
// set heartbeat
rhost.HeartbeatTime = xtime.Time(time.Now().Unix())
if err = s.dao.SetHost(c, rhost, rhost.Service, env); err != nil {
err = nil
}
evt = make(chan *model.Version, 1)
if rhost.Appoint > 0 {
if rhost.Appoint != rhost.ConfigVersion {
evt <- &model.Version{Version: rhost.Appoint}
}
return
}
// get current version, return if has new config version
if curVer, err = s.curVer(c, rhost.Service, rhost.BuildVersion, env); err != nil {
return
}
if curVer == model.UnknownVersion {
err = ecode.NothingFound
return
}
if curVer != rhost.ConfigVersion {
evt <- &model.Version{Version: curVer}
return
}
pushKey := pushKey(rhost.Service, rhost.Name, env)
s.eLock.Lock()
s.events[pushKey] = evt
s.eLock.Unlock()
return
}
// AppAuth check app is auth
func (s *Service) appAuth(c context.Context, svr, env, token string) (err error) {
var (
dbToken string
ok bool
tokenKey = tokenKey(svr, env)
)
s.eLock.RLock()
dbToken, ok = s.token[tokenKey]
s.eLock.RUnlock()
if !ok {
if dbToken, err = s.dao.Token(c, svr, env); err != nil {
log.Error("Token(%v,%v) error(%v)", svr, env, err)
return
}
s.SetToken(c, svr, env, dbToken)
}
if dbToken != token {
err = ecode.AccessDenied
}
return
}
// SetToken update Token
func (s *Service) SetToken(c context.Context, svr, env, token string) {
tokenKey := tokenKey(svr, env)
s.eLock.Lock()
s.token[tokenKey] = token
s.eLock.Unlock()
}
// Hosts return client hosts.
func (s *Service) Hosts(c context.Context, svr, env string) (hosts []*model.Host, err error) {
return s.dao.Hosts(c, svr, env)
}
// VersionSuccess return client versions which configuration is complete
func (s *Service) VersionSuccess(c context.Context, svr, env, bver string) (versions *model.Versions, err error) {
var (
vers []*model.ReVer
ver int64
)
if vers, err = s.dao.Versions(c, svr, env, model.ConfigEnd); err != nil {
log.Error("Versions(%v,%v,%v) error(%v)", svr, env, bver, err)
return
}
if ver, err = s.dao.BuildVersion(c, svr, bver, env); err != nil {
log.Error("BuildVersion(%v) error(%v)", svr, err)
return
}
versions = &model.Versions{
Version: vers,
DefVer: ver,
}
return
}
// VersionIng return client versions which configuration is creating
func (s *Service) VersionIng(c context.Context, svr, env string) (vers []int64, err error) {
var (
res []*model.ReVer
)
if res, err = s.dao.Versions(c, svr, env, model.ConfigIng); err != nil {
log.Error("Versions(%v,%v) error(%v)", svr, env, err)
return
}
vers = make([]int64, 0)
for _, reVer := range res {
vers = append(vers, reVer.Version)
}
return
}
// Builds all builds
func (s *Service) Builds(c context.Context, svr, env string) (builds []string, err error) {
return s.dao.Builds(c, svr, env)
}
// AddConfigs insert config into db.
func (s *Service) AddConfigs(c context.Context, svr, env, token, user string, data map[string]string) (err error) {
var (
svrID int64
ver int64
)
if err = s.appAuth(c, svr, env, token); err != nil {
return
}
if svrID, err = s.dao.ServiceID(c, svr, env); err != nil {
return
}
var tx *sql.Tx
if tx, err = s.dao.BeginTran(c); err != nil {
log.Error("begin tran error(%v)", err)
return
}
if ver, err = s.dao.TxInsertVer(tx, svrID, user); err != nil {
tx.Rollback()
return
}
if len(data) != 0 {
if err = s.dao.TxInsertValues(c, tx, ver, user, data); err != nil {
tx.Rollback()
return
}
}
if err = tx.Commit(); err != nil {
log.Error("tx.Commit error(%v)", err)
return
}
s.dao.InsertLog(c, user, addBusiness, fmt.Sprintf(addInfo, ver))
return
}
// CopyConfigs copy config in newVer.
func (s *Service) CopyConfigs(c context.Context, svr, env, token, user string, build string) (ver int64, err error) {
var (
svrID int64
curVer int64
values []*model.NSValue
)
if err = s.appAuth(c, svr, env, token); err != nil {
return
}
if curVer, err = s.curVer(c, svr, build, env); err != nil {
return
}
if values, err = s.dao.Values(c, curVer); err != nil {
return
}
data := make(map[string]string)
for _, c := range values {
data[c.Name] = c.Config
}
if svrID, err = s.dao.ServiceID(c, svr, env); err != nil {
return
}
var tx *sql.Tx
if tx, err = s.dao.BeginTran(c); err != nil {
log.Error("begin tran error(%v)", err)
return
}
if ver, err = s.dao.TxInsertVer(tx, svrID, user); err != nil {
tx.Rollback()
return
}
if err = s.dao.TxInsertValues(c, tx, ver, user, data); err != nil {
tx.Rollback()
return
}
if err = tx.Commit(); err != nil {
log.Error("tx.Commit error(%v)", err)
return
}
s.dao.InsertLog(c, user, copyBusiness, fmt.Sprintf(copyInfo, curVer, ver))
return
}
// UpdateConfigs update config.
func (s *Service) UpdateConfigs(c context.Context, svr, env, token, user string, ver int64, data map[string]string) (err error) {
var (
values []*model.NSValue
addData = make(map[string]string)
udata = make(map[string]string)
)
if err = s.appAuth(c, svr, env, token); err != nil {
return
}
if len(data) == 0 {
return
}
if values, err = s.dao.Values(c, ver); err != nil {
return
}
if len(values) == 0 {
return ecode.NothingFound
}
oldData := make(map[string]string)
for _, c := range values {
oldData[c.Name] = c.Config
}
for k, v := range data {
if _, ok := oldData[k]; ok {
udata[k] = v
} else {
addData[k] = v
}
}
var tx *sql.Tx
if tx, err = s.dao.BeginTran(c); err != nil {
log.Error("begin tran error(%v)", err)
return
}
if len(addData) != 0 {
if err = s.dao.TxInsertValues(c, tx, ver, user, addData); err != nil {
tx.Rollback()
return
}
}
if len(udata) != 0 {
if err = s.dao.TxUpdateValues(tx, ver, user, udata); err != nil {
tx.Rollback()
return
}
}
if err = tx.Commit(); err != nil {
log.Error("tx.Commit error(%v)", err)
return
}
s.dao.InsertLog(c, user, updateBusiness, fmt.Sprintf(updateInfo, ver))
return
}
func (s *Service) version(svr, bver, env string) (ver int64, ok bool) {
verKey := buildVerKey(svr, bver, env)
s.vLock.RLock()
ver, ok = s.versions[verKey]
s.vLock.RUnlock()
return
}
func (s *Service) setVersion(svr, bver, env string, ver int64) {
verKey := buildVerKey(svr, bver, env)
s.vLock.Lock()
s.versions[verKey] = ver
s.vLock.Unlock()
}
// ClearHost clear service hosts.
func (s *Service) ClearHost(c context.Context, svr, env string) (err error) {
return s.dao.ClearHost(c, svr, env)
}
// pubEvent publish a event to chan.
func (s *Service) pubEvent(key string, evt *model.Version) (ok bool) {
s.eLock.RLock()
c, ok := s.events[key]
s.eLock.RUnlock()
if ok {
c <- evt
}
return
}
// Unsub unsub a event.
func (s *Service) Unsub(svr, host, env string) {
key := pushKey(svr, host, env)
s.eLock.Lock()
delete(s.events, key)
s.eLock.Unlock()
}
func (s *Service) curVer(c context.Context, svr, build, env string) (ver int64, err error) {
var ok bool
// get current version, return if has new config version
ver, ok = s.version(svr, build, env)
if !ok {
if ver, err = s.dao.BuildVersion(c, svr, build, env); err != nil {
return
}
s.setVersion(svr, build, env, ver)
}
return
}

View File

@@ -0,0 +1,216 @@
package v1
import (
"context"
"testing"
"go-common/app/infra/config/conf"
"go-common/app/infra/config/model"
"github.com/BurntSushi/toml"
. "github.com/smartystreets/goconvey/convey"
)
func svr(t *testing.T) *Service {
var (
confPath = "../cmd/config-service-example.toml"
conf *conf.Config
)
Convey("get apply", t, func() {
_, err := toml.DecodeFile(confPath, &conf)
So(err, ShouldBeNil)
})
return New(conf)
}
func TestService_CheckVersionest(t *testing.T) {
var (
c = context.TODO()
svrName = "zjx_test"
hostname = "test_host"
bver = "v1.0.0"
ip = "123"
version = int64(-1)
env = "10"
token = "AXiLBa3Bww3inhfm6qx7g0zLY6WkLSZc"
appoint = int64(97)
)
svr := svr(t)
rhost := &model.Host{Service: svrName, Name: hostname, BuildVersion: bver, IP: ip, ConfigVersion: version, Appoint: appoint, Customize: "test"}
Convey("get tag id by name", t, func() {
event, err := svr.CheckVersion(c, rhost, env, token)
So(err, ShouldBeNil)
So(event, ShouldNotBeEmpty)
Convey("get tag id by name", func() {
e := <-event
So(e, ShouldNotBeEmpty)
})
})
}
func TestService_Hosts(t *testing.T) {
svr := svr(t)
Convey("should get hosts", t, func() {
_, err := svr.Hosts(context.TODO(), "zjx_test", "10")
So(err, ShouldBeNil)
})
}
func TestService_Config(t *testing.T) {
var (
c = context.TODO()
svrName = "zjx_test"
hostname = "test_host"
bver = "v1.0.0"
version = int64(78)
env = "10"
token = "AXiLBa3Bww3inhfm6qx7g0zLY6WkLSZc"
)
svr := svr(t)
service := &model.Service{Name: svrName, BuildVersion: bver, Env: env, Token: token, Version: version, Host: hostname}
Convey("should get hosts", t, func() {
conf, err := svr.Config(c, service)
So(err, ShouldBeNil)
So(conf, ShouldNotBeEmpty)
})
}
func TestService_Config2(t *testing.T) {
var (
c = context.TODO()
svrName = "config_test"
hostname = "test_host"
bver = "shsb-docker-1"
version = int64(199)
env = "10"
token = "qmVUPwNXnNfcSpuyqbiIBb0H4GcbSZFV"
)
service := &model.Service{Name: svrName, BuildVersion: bver, Env: env, Token: token, Version: version, Host: hostname}
svr := svr(t)
Convey("should get Config2", t, func() {
conf, err := svr.Config2(c, service)
So(err, ShouldBeNil)
So(conf, ShouldNotBeEmpty)
})
}
func TestService_Push(t *testing.T) {
var (
c = context.TODO()
svrName = "zjx_test"
bver = "v1.0.0"
version = int64(113)
env = "10"
)
service := &model.Service{Name: svrName, BuildVersion: bver, Version: version, Env: env}
svr := svr(t)
Convey("should get Config2", t, func() {
err := svr.Push(c, service)
So(err, ShouldBeNil)
})
}
func TestService_SetToken(t *testing.T) {
var (
c = context.TODO()
svrName = "zjx_test"
env = "10"
token = "AXiLBa3Bww3inhfm6qx7g0zLY6WkLSZc"
)
svr := svr(t)
Convey("should get Config2", t, func() {
svr.SetToken(c, svrName, env, token)
})
}
func TestService_ClearHost(t *testing.T) {
var (
c = context.TODO()
svrName = "zjx_test"
env = "10"
)
svr := svr(t)
Convey("should clear host", t, func() {
err := svr.ClearHost(c, svrName, env)
So(err, ShouldBeNil)
})
}
func TestService_VersionSuccess(t *testing.T) {
var (
c = context.TODO()
svrName = "zjx_test"
bver = "v1.0.0"
env = "10"
)
svr := svr(t)
Convey("should clear host", t, func() {
vers, err := svr.VersionSuccess(c, svrName, env, bver)
So(err, ShouldBeNil)
So(vers, ShouldNotBeEmpty)
})
}
func TestService_Builds(t *testing.T) {
var (
c = context.TODO()
svrName = "zjx_test"
env = "10"
err error
builds []string
)
svr := svr(t)
Convey("should clear host", t, func() {
builds, err = svr.Builds(c, svrName, env)
So(err, ShouldBeNil)
So(builds, ShouldNotBeEmpty)
})
}
func TestService_File(t *testing.T) {
var (
c = context.TODO()
svrName = "zjx_test"
bver = "v1.0.0"
env = "10"
fileName = "test.toml"
token = "AXiLBa3Bww3inhfm6qx7g0zLY6WkLSZc"
ver = int64(74)
err error
)
service := &model.Service{Name: svrName, BuildVersion: bver, Env: env, File: fileName, Token: token, Version: ver}
svr := svr(t)
Convey("should clear host", t, func() {
_, err = svr.File(c, service)
So(err, ShouldBeNil)
})
}
func TestService_AddConfigs(t *testing.T) {
svr := svr(t)
Convey("should clear host", t, func() {
err := svr.AddConfigs(context.TODO(), "zjx_test", "10", "AXiLBa3Bww3inhfm6qx7g0zLY6WkLSZc", "zjx", map[string]string{"aa": "bb"})
So(err, ShouldBeNil)
})
}
func TestService_UpdateConfigs(t *testing.T) {
svr := svr(t)
Convey("should clear host", t, func() {
err := svr.UpdateConfigs(context.TODO(), "zjx_test", "10", "AXiLBa3Bww3inhfm6qx7g0zLY6WkLSZc", "zjx", 491, map[string]string{"test": "test123"})
So(err, ShouldBeNil)
})
}
func TestService_CopyConfigs(t *testing.T) {
svr := svr(t)
Convey("should clear host", t, func() {
_, err := svr.CopyConfigs(context.TODO(), "zjx_test", "10", "AXiLBa3Bww3inhfm6qx7g0zLY6WkLSZc", "zjx", "shsb-docker-1")
So(err, ShouldBeNil)
})
}
func TestService_VersionIng(t *testing.T) {
svr := svr(t)
Convey("should clear host", t, func() {
_, err := svr.VersionIng(context.TODO(), "zjx_test1", "10")
So(err, ShouldBeNil)
})
}

View File

@@ -0,0 +1,43 @@
package v1
import (
"context"
"sync"
"go-common/app/infra/config/conf"
"go-common/app/infra/config/dao/v1"
"go-common/app/infra/config/model"
xtime "go-common/library/time"
)
// Service service.
type Service struct {
dao *v1.Dao
vLock sync.RWMutex
versions map[string]int64 // serviceName_buildVersion > configVersion
eLock sync.RWMutex
events map[string]chan *model.Version
PollTimeout xtime.Duration
token map[string]string
}
// New new a service.
func New(c *conf.Config) (s *Service) {
s = new(Service)
s.dao = v1.New(c)
s.versions = make(map[string]int64)
s.events = make(map[string]chan *model.Version)
s.PollTimeout = c.PollTimeout
s.token = make(map[string]string)
return
}
// Ping check is ok.
func (s *Service) Ping(c context.Context) (err error) {
return s.dao.Ping(c)
}
// Close close resources.
func (s *Service) Close() {
s.dao.Close()
}

View File

@@ -0,0 +1,54 @@
package(default_visibility = ["//visibility:public"])
load(
"@io_bazel_rules_go//go:def.bzl",
"go_test",
"go_library",
)
go_test(
name = "go_default_test",
srcs = ["client_test.go"],
embed = [":go_default_library"],
tags = ["automanaged"],
deps = [
"//app/infra/config/conf:go_default_library",
"//app/infra/config/model:go_default_library",
"//vendor/github.com/BurntSushi/toml:go_default_library",
"//vendor/github.com/smartystreets/goconvey/convey:go_default_library",
],
)
go_library(
name = "go_default_library",
srcs = [
"cache_tag.go",
"client.go",
"service.go",
],
importpath = "go-common/app/infra/config/service/v2",
tags = ["automanaged"],
visibility = ["//visibility:public"],
deps = [
"//app/infra/config/conf:go_default_library",
"//app/infra/config/dao/v2:go_default_library",
"//app/infra/config/model:go_default_library",
"//library/ecode:go_default_library",
"//library/log:go_default_library",
"//library/time: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,45 @@
package v2
// cacheTag tag.
type cacheTag struct {
Tag int64
ConfIDs []int64
Force int8
}
// curTag current tag version.
type curTag struct {
O *cacheTag
C *cacheTag
}
func (tag *curTag) diff() (diffs []int64) {
if tag.O == nil || tag.C == nil {
return nil
}
oIDs := tag.O.ConfIDs
tmp := make(map[int64]struct{}, len(oIDs))
for _, oID := range tag.O.ConfIDs {
tmp[oID] = struct{}{}
}
for _, ID := range tag.C.ConfIDs {
if _, ok := tmp[ID]; !ok {
diffs = append(diffs, ID)
}
}
return
}
func (tag *curTag) old() int64 {
if tag.O == nil {
return 0
}
return tag.O.Tag
}
func (tag *curTag) cur() int64 {
if tag.C == nil {
return 0
}
return tag.C.Tag
}

View File

@@ -0,0 +1,769 @@
package v2
import (
"context"
"crypto/md5"
"encoding/hex"
"encoding/json"
"fmt"
"strconv"
"strings"
"time"
"go-common/app/infra/config/model"
"go-common/library/ecode"
"go-common/library/log"
xtime "go-common/library/time"
)
const (
_buildVerKey = "%d_%s"
_pushKey = "%s_%s"
_cacheKey = "%s_%d"
_pushForceKey = "%s_%s_%s"
_tagForceKey = "%d_%s_%d_tagforce"
_tagIDkey = "%d_%s_tagID"
_lastForceKey = "%d_%s_lastForce"
)
func lastForceKey(svr int64, bver string) string {
return fmt.Sprintf(_tagIDkey, svr, bver)
}
func tagIDkey(svr int64, bver string) string {
return fmt.Sprintf(_tagIDkey, svr, bver)
}
// tagForceKey...
func tagForceKey(svr int64, bver string, tagID int64) string {
return fmt.Sprintf(_tagForceKey, svr, bver, tagID)
}
// pushForceKey push
func pushForceKey(svr, host, ip string) string {
return fmt.Sprintf(_pushForceKey, svr, host, ip)
}
// PushKey push sub id
func pushKey(svr, host string) string {
return fmt.Sprintf(_pushKey, svr, host)
}
// buildVerKey version mapping key
func buildVerKey(svr int64, bver string) string {
return fmt.Sprintf(_buildVerKey, svr, bver)
}
// cacheKey config cache key
func cacheKey(svr string, ver int64) string {
return fmt.Sprintf(_cacheKey, svr, ver)
}
// genConfig generate config
func genConfig(ver int64, values []*model.Value) (conf *model.Content, err error) {
var b []byte
if b, err = json.Marshal(values); err != nil {
return
}
mb := md5.Sum(b)
conf = &model.Content{
Version: ver,
Md5: hex.EncodeToString(mb[:]),
Content: string(b),
}
return
}
// Push version to clients & generate config caches
func (s *Service) Push(c context.Context, svr *model.Service) (err error) {
var (
hosts []*model.Host
values []*model.Value
conf *model.Content
app *model.App
ids []int64
force int8
)
if ids, err = s.dao.ConfIDs(svr.Version); err != nil {
return
}
if values, err = s.dao.ConfigsByIDs(ids); err != nil {
return
}
if len(values) == 0 {
err = fmt.Errorf("config values is empty. svr:%s, host:%s, buildVer:%s, ver:%d", svr.Name, svr.Host, svr.BuildVersion, svr.Version)
log.Error("%v", err)
return
}
// compatible old version sdk
if conf, err = genConfig(svr.Version, values); err != nil {
log.Error("get config value:%s error(%v) ", values, err)
return
}
cacheName := cacheKey(svr.Name, svr.Version)
if err = s.dao.SetFile(cacheName, conf); err != nil {
return
}
if app, err = s.app(svr.Name); err != nil {
return
}
tag := s.setTag(app.ID, svr.BuildVersion, &cacheTag{Tag: svr.Version, ConfIDs: ids})
s.setTagID(app.ID, tag.C.Tag, svr.BuildVersion)
if force, err = s.dao.TagForce(svr.Version); err != nil {
return
}
if force == 1 {
s.setLFVForces(svr.Version, app.ID, svr.BuildVersion)
}
// push hosts
if hosts, err = s.dao.Hosts(c, svr.Name); err != nil {
log.Error("get hosts error. svr:%s, buildVer:%s, ver:%d", svr.Name, svr.BuildVersion, svr.Version)
err = nil
return
}
for _, h := range hosts {
if h.State == model.HostOnline {
pushKey := pushKey(h.Service, h.Name)
if ok := s.pubEvent(pushKey, &model.Diff{Version: svr.Version, Diffs: tag.diff()}); ok {
log.Info("s.events.Pub(%s, %d) ok: %t", pushKey, conf.Version, ok)
}
}
}
return
}
// Config return config content.
func (s *Service) Config(c context.Context, svrName, token string, ver int64, ids []int64) (conf *model.Content, err error) {
var (
values, all []*model.Value
)
if _, err = s.appAuth(c, svrName, token); err != nil {
return
}
cacheName := cacheKey(svrName, ver)
if conf, err = s.dao.File(cacheName); err == nil {
if len(ids) == 0 {
return
}
if err = json.Unmarshal([]byte(conf.Content), &all); err != nil {
return
}
} else {
if all, err = s.values(ver); err != nil {
return
}
if conf, err = genConfig(ver, all); err != nil {
return
}
cacheName := cacheKey(svrName, ver)
if err = s.dao.SetFile(cacheName, conf); err != nil {
return
}
if len(ids) == 0 {
return
}
}
if len(all) == 0 {
err = ecode.NothingFound
return
}
for _, v := range all {
for _, id := range ids {
if v.ConfigID == id {
values = append(values, v)
}
}
}
if len(values) == 0 {
log.Error("Config(%s,%v) error", svrName, ids)
err = ecode.NothingFound
return
}
if conf, err = genConfig(ver, values); err != nil {
log.Error("get config value:%s error(%v) ", values, err)
return
}
return
}
// File get one file content.
func (s *Service) File(c context.Context, svr *model.Service) (val string, err error) {
var (
curVer, appID int64
all []*model.Value
conf *model.Content
tag *curTag
)
if appID, err = s.appAuth(c, svr.Name, svr.Token); err != nil {
return
}
if svr.Version != model.UnknownVersion {
curVer = svr.Version
} else {
// get current version, return if has new config version
if tag, err = s.curTag(appID, svr.BuildVersion); err != nil {
return
}
curVer = tag.cur()
}
cacheName := cacheKey(svr.Name, svr.Version)
if conf, err = s.dao.File(cacheName); err == nil {
if err = json.Unmarshal([]byte(conf.Content), &all); err != nil {
return
}
} else {
if all, err = s.values(curVer); err != nil {
return
}
if conf, err = genConfig(curVer, all); err != nil {
return
}
cacheName := cacheKey(svr.Name, curVer)
if err = s.dao.SetFile(cacheName, conf); err != nil {
return
}
}
for _, v := range all {
if v.Name == svr.File {
val = v.Config
break
}
}
return
}
// CheckVersion check client version.
func (s *Service) CheckVersion(c context.Context, rhost *model.Host, token string) (evt chan *model.Diff, err error) {
var (
appID, tagID int64
tag *curTag
ForceVersion int64
tagForce int8
lastForce int64
)
if appID, err = s.appAuth(c, rhost.Service, token); err != nil {
return
}
// set heartbeat
rhost.HeartbeatTime = xtime.Time(time.Now().Unix())
s.dao.SetHost(c, rhost, rhost.Service)
evt = make(chan *model.Diff, 1)
//请开始你的表演
tagForce, err = s.tagForce(appID, rhost.BuildVersion)
if err != nil {
return
}
rhost.Force = tagForce
s.dao.SetHost(c, rhost, rhost.Service)
if rhost.ConfigVersion > 0 {
// force has a higher priority than appoint
if ForceVersion, err = s.curForce(rhost, appID); err != nil {
return
}
if ForceVersion > 0 {
rhost.ForceVersion = ForceVersion
s.dao.SetHost(c, rhost, rhost.Service)
if ForceVersion != rhost.ConfigVersion {
evt <- &model.Diff{Version: ForceVersion}
}
return
}
if lastForce, err = s.lastForce(appID, rhost.ConfigVersion, rhost.BuildVersion); err != nil {
return
}
if rhost.ConfigVersion <= lastForce {
if rhost.ConfigVersion != lastForce {
evt <- &model.Diff{Version: lastForce}
}
return
}
}
if rhost.Appoint > 0 {
if rhost.Appoint != rhost.ConfigVersion {
evt <- &model.Diff{Version: rhost.Appoint}
}
return
}
//结束表演
// get current version, return if has new config version
if tag, err = s.curTag(appID, rhost.BuildVersion); err != nil {
return
}
if tagID = tag.cur(); tagID == 0 {
err = ecode.NothingFound
return
}
if tagID != rhost.ConfigVersion {
ver := &model.Diff{Version: tagID}
if rhost.ConfigVersion == tag.old() {
ver.Diffs = tag.diff()
}
evt <- ver
return
}
pushKey := pushKey(rhost.Service, rhost.Name)
s.eLock.Lock()
s.events[pushKey] = evt
s.eLock.Unlock()
return
}
// values get configs by tag id.
func (s *Service) values(tagID int64) (values []*model.Value, err error) {
var (
ids []int64
)
if ids, err = s.dao.ConfIDs(tagID); err != nil {
return
}
values, err = s.dao.ConfigsByIDs(ids)
return
}
// appAuth check app is auth
func (s *Service) appAuth(c context.Context, svr, token string) (appID int64, err error) {
app, err := s.app(svr)
if err != nil {
return
}
if app.Token != token {
err = ecode.AccessDenied
return
}
appID = app.ID
return
}
// Hosts return client hosts.
func (s *Service) Hosts(c context.Context, svr string) (hosts []*model.Host, err error) {
return s.dao.Hosts(c, svr)
}
// Builds all builds
func (s *Service) Builds(c context.Context, svr string) (builds []string, err error) {
var (
app *model.App
)
if app, err = s.app(svr); err != nil {
return
}
return s.dao.BuildsByAppID(app.ID)
}
// VersionSuccess return client versions which configuration is complete
func (s *Service) VersionSuccess(c context.Context, svr, bver string) (versions *model.Versions, err error) {
var (
vers []*model.ReVer
ver int64
app *model.App
)
if app, err = s.app(svr); err != nil {
return
}
if ver, err = s.dao.TagID(app.ID, bver); err != nil {
log.Error("BuildVersion(%v) error(%v)", svr, err)
return
}
if vers, err = s.dao.Tags(app.ID); err != nil {
return
}
versions = &model.Versions{
Version: vers,
DefVer: ver,
}
return
}
// ClearHost clear service hosts.
func (s *Service) ClearHost(c context.Context, svr string) (err error) {
return s.dao.ClearHost(c, svr)
}
// pubEvent publish a event to chan.
func (s *Service) pubEvent(key string, evt *model.Diff) (ok bool) {
s.eLock.RLock()
c, ok := s.events[key]
s.eLock.RUnlock()
if ok {
c <- evt
}
return
}
// Unsub unsub a event.
func (s *Service) Unsub(svr, host string) {
key := pushKey(svr, host)
s.eLock.Lock()
delete(s.events, key)
s.eLock.Unlock()
}
func (s *Service) curTag(appID int64, build string) (tag *curTag, err error) {
var (
ok bool
tagID int64
ids []int64
tagForce int8
)
// get current version, return if has new config version
verKey := buildVerKey(appID, build)
s.tLock.RLock()
tag, ok = s.tags[verKey]
s.tLock.RUnlock()
if !ok {
if tagID, err = s.dao.TagID(appID, build); err != nil {
return
}
if ids, err = s.dao.ConfIDs(tagID); err != nil {
return
}
if tagForce, err = s.dao.TagForce(tagID); err != nil {
return
}
tag = s.setTag(appID, build, &cacheTag{Tag: tagID, ConfIDs: ids, Force: tagForce})
}
return
}
func (s *Service) setTag(appID int64, bver string, cTag *cacheTag) (nTag *curTag) {
var (
oTag *curTag
ok bool
)
verKey := buildVerKey(appID, bver)
nTag = &curTag{C: cTag}
s.tLock.Lock()
oTag, ok = s.tags[verKey]
if ok && oTag.C != nil {
nTag.O = oTag.C
}
s.tags[verKey] = nTag
s.tLock.Unlock()
return
}
func (s *Service) app(svr string) (app *model.App, err error) {
var (
ok bool
treeID int64
)
s.aLock.RLock()
app, ok = s.apps[svr]
s.aLock.RUnlock()
if !ok {
arrs := strings.Split(svr, "_")
if len(arrs) != 3 {
err = ecode.RequestErr
return
}
if treeID, err = strconv.ParseInt(arrs[0], 10, 64); err != nil {
return
}
if app, err = s.dao.AppByTree(arrs[2], arrs[1], treeID); err != nil {
log.Error("Token(%v) error(%v)", svr, err)
return
}
s.aLock.Lock()
s.apps[svr] = app
s.aLock.Unlock()
}
return app, nil
}
//SetToken set token.
func (s *Service) SetToken(svr, token string) (err error) {
var (
ok bool
treeID int64
app, nApp *model.App
)
s.aLock.RLock()
app, ok = s.apps[svr]
s.aLock.RUnlock()
if ok {
nApp = &model.App{ID: app.ID, Name: app.Name, Token: token}
} else {
arrs := strings.Split(svr, "_")
if len(arrs) != 3 {
err = ecode.RequestErr
return
}
if treeID, err = strconv.ParseInt(arrs[0], 10, 64); err != nil {
return
}
if nApp, err = s.dao.AppByTree(arrs[2], arrs[1], treeID); err != nil {
log.Error("Token(%v) error(%v)", svr, err)
return
}
}
s.aLock.Lock()
s.apps[svr] = nApp
s.aLock.Unlock()
return
}
//TmpBuilds get builds.
func (s *Service) TmpBuilds(svr, env string) (builds []string, err error) {
var (
apps []*model.DBApp
appIDs []int64
)
switch env {
case "10":
env = "dev"
case "11":
env = "fat1"
case "13":
env = "uat"
case "14":
env = "pre"
case "3":
env = "prod"
default:
}
if apps, err = s.dao.AppsByNameEnv(svr, env); err != nil {
return
}
if len(apps) == 0 {
err = ecode.NothingFound
return
}
for _, app := range apps {
appIDs = append(appIDs, app.ID)
}
return s.dao.BuildsByAppIDs(appIDs)
}
// AppService ...
func (s *Service) AppService(zone, env, token string) (service string, err error) {
var (
ok bool
key string
app *model.App
)
key = fmt.Sprintf("%s_%s_%s", zone, env, token)
s.bLock.RLock()
app, ok = s.services[key]
s.bLock.RUnlock()
if !ok {
app, err = s.dao.AppGet(zone, env, token)
if err != nil {
log.Error("AppService error(%v)", err)
return
}
s.bLock.Lock()
s.services[key] = app
s.bLock.Unlock()
}
service = fmt.Sprintf("%d_%s_%s", app.TreeID, app.Env, app.Zone)
return
}
// Force version to clients & generate config caches
func (s *Service) Force(c context.Context, svr *model.Service, hostnames map[string]string, sType int8) (err error) {
var (
values []*model.Value
ids []int64
)
if sType == 1 { // push 1 clear other
if ids, err = s.dao.ConfIDs(svr.Version); err != nil {
return
}
if values, err = s.dao.ConfigsByIDs(ids); err != nil {
return
}
if len(values) == 0 {
err = fmt.Errorf("config values is empty. svr:%s, host:%s, buildVer:%s, ver:%d", svr.Name, svr.Host, svr.BuildVersion, svr.Version)
log.Error("%v", err)
return
}
}
for key, val := range hostnames {
pushForceKey := pushForceKey(svr.Name, key, val)
s.pubForce(pushForceKey, svr.Version)
log.Info("s.events.Pub(%s, %d)", pushForceKey, svr.Version)
}
return
}
func (s *Service) curForce(rhost *model.Host, appID int64) (version int64, err error) {
var (
ok bool
)
// get force version
pushForceKey := pushForceKey(rhost.Service, rhost.Name, rhost.IP)
s.fLock.RLock()
version, ok = s.forces[pushForceKey]
s.fLock.RUnlock()
if !ok {
if version, err = s.dao.Force(appID, rhost.Name); err != nil {
return
}
s.fLock.Lock()
s.forces[pushForceKey] = version
s.fLock.Unlock()
}
return
}
// pubEvent publish a forces.
func (s *Service) pubForce(key string, version int64) {
s.fLock.Lock()
s.forces[key] = version
s.fLock.Unlock()
}
func (s *Service) tagForce(appID int64, build string) (force int8, err error) {
var (
ok bool
tagID int64
)
key := tagIDkey(appID, build)
s.tagIDLock.RLock()
tagID, ok = s.tagID[key]
s.tagIDLock.RUnlock()
if !ok {
if tagID, err = s.dao.TagID(appID, build); err != nil {
return
}
s.setTagID(appID, tagID, build)
}
verKey := tagForceKey(appID, build, tagID)
s.tfLock.RLock()
force, ok = s.forceType[verKey]
s.tfLock.RUnlock()
if !ok {
if force, err = s.dao.TagForce(tagID); err != nil {
return
}
s.tfLock.Lock()
s.forceType[verKey] = force
s.tfLock.Unlock()
}
return
}
func (s *Service) setTagID(appID, tagID int64, build string) {
key := tagIDkey(appID, build)
s.tagIDLock.Lock()
s.tagID[key] = tagID
s.tagIDLock.Unlock()
}
func (s *Service) lastForce(appID, version int64, build string) (lastForce int64, err error) {
var ok bool
var buildID int64
key := lastForceKey(appID, build)
s.lfvLock.RLock()
lastForce, ok = s.lfvforces[key]
s.lfvLock.RUnlock()
if !ok {
if buildID, err = s.dao.BuildID(appID, build); err != nil {
return
}
if lastForce, err = s.dao.LastForce(appID, buildID); err != nil {
return
}
s.lfvLock.Lock()
s.lfvforces[key] = lastForce
s.lfvLock.Unlock()
}
return
}
func (s *Service) setLFVForces(lastForce, appID int64, build string) {
key := lastForceKey(appID, build)
s.lfvLock.Lock()
s.lfvforces[key] = lastForce
s.lfvLock.Unlock()
}
//CheckLatest ...
func (s *Service) CheckLatest(c context.Context, rhost *model.Host, token string) (ver int64, err error) {
var (
appID, tagID int64
tag *curTag
)
if appID, err = s.appAuth(c, rhost.Service, token); err != nil {
return
}
// get current version, return if has new config version
if tag, err = s.curTag(appID, rhost.BuildVersion); err != nil {
return
}
if tagID = tag.cur(); tagID == 0 {
err = ecode.NothingFound
return
}
ver = tagID
return
}
// ConfigCheck return config content.
func (s *Service) ConfigCheck(c context.Context, svrName, token string, ver int64, ids []int64) (conf *model.Content, err error) {
var (
values, all []*model.Value
appID int64
tagAll *model.DBTag
)
if appID, err = s.appAuth(c, svrName, token); err != nil {
return
}
if tagAll, err = s.dao.TagAll(ver); err != nil {
return
}
if appID != tagAll.AppID {
err = ecode.RequestErr
return
}
cacheName := cacheKey(svrName, ver)
if conf, err = s.dao.File(cacheName); err == nil {
if len(ids) == 0 {
return
}
if err = json.Unmarshal([]byte(conf.Content), &all); err != nil {
return
}
} else {
if all, err = s.values(ver); err != nil {
return
}
if conf, err = genConfig(ver, all); err != nil {
return
}
cacheName := cacheKey(svrName, ver)
if err = s.dao.SetFile(cacheName, conf); err != nil {
return
}
if len(ids) == 0 {
return
}
}
if len(all) == 0 {
err = ecode.NothingFound
return
}
for _, v := range all {
for _, id := range ids {
if v.ConfigID == id {
values = append(values, v)
}
}
}
if len(values) == 0 {
log.Error("Config(%s,%v) error", svrName, ids)
err = ecode.NothingFound
return
}
if conf, err = genConfig(ver, values); err != nil {
log.Error("get config value:%s error(%v) ", values, err)
return
}
return
}

View File

@@ -0,0 +1,204 @@
package v2
import (
"context"
"testing"
"go-common/app/infra/config/conf"
"go-common/app/infra/config/model"
"github.com/BurntSushi/toml"
. "github.com/smartystreets/goconvey/convey"
)
func svr(t *testing.T) *Service {
var (
confPath = "../../cmd/config-service-example.toml"
conf *conf.Config
)
Convey("get apply", t, func() {
_, err := toml.DecodeFile(confPath, &conf)
So(err, ShouldBeNil)
})
return New(conf)
}
func TestService_CheckVersion(t *testing.T) {
var (
c = context.TODO()
svrName = "2888_dev_sh001"
hostname = "test_host"
bver = "server-1"
ip = "123"
version = int64(-1)
token = "47fbbf237b7f11e8992154e1ad15006a"
appoint = int64(-1)
)
svr := svr(t)
Convey("AppService", t, func() {
tmp, err := svr.AppService("sh001", "dev", "47fbbf237b7f11e8992154e1ad15006a")
So(err, ShouldBeNil)
So(tmp, ShouldEqual, svrName)
})
rhost := &model.Host{Service: svrName, Name: hostname, BuildVersion: bver, IP: ip, ConfigVersion: version, Appoint: appoint, Customize: "test"}
Convey("get tag id by name", t, func() {
event, err := svr.CheckVersion(c, rhost, token)
So(err, ShouldBeNil)
So(event, ShouldNotBeEmpty)
Convey("get tag id by name", func() {
e := <-event
So(e, ShouldNotBeEmpty)
})
})
}
func TestService_Hosts(t *testing.T) {
var (
svrName = "2888_dev_sh001"
)
svr := svr(t)
Convey("AppService", t, func() {
tmp, err := svr.AppService("sh001", "dev", "47fbbf237b7f11e8992154e1ad15006a")
So(err, ShouldBeNil)
So(tmp, ShouldEqual, svrName)
})
Convey("should get hosts", t, func() {
_, err := svr.Hosts(context.TODO(), svrName)
So(err, ShouldBeNil)
})
}
func TestService_Config(t *testing.T) {
var (
c = context.TODO()
svrName = "2888_dev_sh001"
version = int64(49)
token = "47fbbf237b7f11e8992154e1ad15006a"
)
svr := svr(t)
Convey("AppService", t, func() {
tmp, err := svr.AppService("sh001", "dev", "47fbbf237b7f11e8992154e1ad15006a")
So(err, ShouldBeNil)
So(tmp, ShouldEqual, svrName)
})
Convey("should get hosts", t, func() {
conf, err := svr.Config(c, svrName, token, version, nil)
So(err, ShouldBeNil)
So(conf, ShouldNotBeEmpty)
})
}
func TestService_Push(t *testing.T) {
var (
c = context.TODO()
svrName = "2888_dev_sh001"
bver = "server-1"
version = int64(49)
)
svr := svr(t)
Convey("AppService", t, func() {
tmp, err := svr.AppService("sh001", "dev", "47fbbf237b7f11e8992154e1ad15006a")
So(err, ShouldBeNil)
So(tmp, ShouldEqual, svrName)
})
service := &model.Service{Name: svrName, BuildVersion: bver, Version: version}
Convey("should get Config2", t, func() {
err := svr.Push(c, service)
So(err, ShouldBeNil)
})
}
func TestService_SetToken(t *testing.T) {
var (
svrName = "2888_dev_sh001"
token = "47fbbf237b7f11e8992154e1ad15006a"
)
svr := svr(t)
Convey("AppService", t, func() {
tmp, err := svr.AppService("sh001", "dev", "47fbbf237b7f11e8992154e1ad15006a")
So(err, ShouldBeNil)
So(tmp, ShouldEqual, svrName)
})
Convey("should get Config2", t, func() {
svr.SetToken(svrName, token)
})
}
func TestService_ClearHost(t *testing.T) {
var (
c = context.TODO()
svrName = "2888_dev_sh001"
)
svr := svr(t)
Convey("AppService", t, func() {
tmp, err := svr.AppService("sh001", "dev", "47fbbf237b7f11e8992154e1ad15006a")
So(err, ShouldBeNil)
So(tmp, ShouldEqual, svrName)
})
Convey("should clear host", t, func() {
err := svr.ClearHost(c, svrName)
So(err, ShouldBeNil)
})
}
func TestService_VersionSuccess(t *testing.T) {
var (
c = context.TODO()
svrName = "2888_dev_sh001"
bver = "server-1"
)
svr := svr(t)
Convey("AppService", t, func() {
tmp, err := svr.AppService("sh001", "dev", "47fbbf237b7f11e8992154e1ad15006a")
So(err, ShouldBeNil)
So(tmp, ShouldEqual, svrName)
})
Convey("should clear host", t, func() {
vers, err := svr.VersionSuccess(c, svrName, bver)
So(err, ShouldBeNil)
So(vers, ShouldNotBeEmpty)
})
}
func TestService_Builds(t *testing.T) {
var (
c = context.TODO()
svrName = "2888_dev_sh001"
err error
builds []string
tmp string
)
svr := svr(t)
Convey("AppService", t, func() {
tmp, err = svr.AppService("sh001", "dev", "47fbbf237b7f11e8992154e1ad15006a")
So(err, ShouldBeNil)
So(tmp, ShouldEqual, svrName)
})
Convey("should clear host", t, func() {
builds, err = svr.Builds(c, svrName)
So(err, ShouldBeNil)
So(builds, ShouldNotBeEmpty)
})
}
func TestService_File(t *testing.T) {
var (
c = context.TODO()
svrName = "2888_dev_sh001"
bver = "server-1"
fileName = "test.toml"
token = "47fbbf237b7f11e8992154e1ad15006a"
ver = int64(49)
err error
tmp string
)
svr := svr(t)
Convey("AppService", t, func() {
tmp, err = svr.AppService("sh001", "dev", "47fbbf237b7f11e8992154e1ad15006a")
So(err, ShouldBeNil)
So(tmp, ShouldEqual, svrName)
})
service := &model.Service{Name: svrName, BuildVersion: bver, File: fileName, Token: token, Version: ver}
Convey("should clear host", t, func() {
_, err = svr.File(c, service)
So(err, ShouldBeNil)
})
}

View File

@@ -0,0 +1,68 @@
package v2
import (
"context"
"sync"
"go-common/app/infra/config/conf"
"go-common/app/infra/config/dao/v2"
"go-common/app/infra/config/model"
xtime "go-common/library/time"
)
// Service service.
type Service struct {
dao *v2.Dao
PollTimeout xtime.Duration
//config2 app
aLock sync.RWMutex
bLock sync.RWMutex
apps map[string]*model.App
services map[string]*model.App
//config2 tags
tLock sync.RWMutex
tags map[string]*curTag
eLock sync.RWMutex
events map[string]chan *model.Diff
//force version
fLock sync.RWMutex
forces map[string]int64
//config2 tag force
tfLock sync.RWMutex
forceType map[string]int8
// config2 tagID
tagIDLock sync.RWMutex
tagID map[string]int64
//config2 last force version
lfvLock sync.RWMutex
lfvforces map[string]int64
}
// New new a service.
func New(c *conf.Config) (s *Service) {
s = new(Service)
s.dao = v2.New(c)
s.PollTimeout = c.PollTimeout
s.tags = make(map[string]*curTag)
s.apps = make(map[string]*model.App)
s.services = make(map[string]*model.App)
s.events = make(map[string]chan *model.Diff)
s.forces = make(map[string]int64)
s.forceType = make(map[string]int8)
s.tagID = make(map[string]int64)
s.lfvforces = make(map[string]int64)
return
}
// Ping check is ok.
func (s *Service) Ping(c context.Context) (err error) {
return s.dao.Ping(c)
}
// Close close resources.
func (s *Service) Close() {
s.dao.Close()
}