├── .github └── workflows │ └── unittest.yml ├── .gitignore ├── LICENSE ├── Makefile ├── README.md ├── cache ├── cache.go ├── cache_test.go ├── config.go ├── middleware.go ├── option.go ├── persist │ ├── cache.go │ ├── codec.go │ ├── codec_test.go │ ├── memory.go │ ├── memory_test.go │ └── redis.go ├── reply.go └── wrapper.go ├── comctx ├── context.go ├── error.go └── message.go ├── context ├── config.go ├── context.go ├── context_test.go ├── env.go ├── env_test.go ├── platform.go ├── platform_test.go ├── run.go └── run_test.go ├── dmcontext ├── access.go ├── blink.go ├── config.go ├── context.go ├── device.go ├── expression.go ├── expression_test.go ├── format.go ├── format_test.go ├── northlink.go └── run.go ├── errors ├── error.go └── errors_test.go ├── example ├── etc │ └── baetyl │ │ └── service.yml └── var │ └── lib │ └── baetyl │ └── testcert │ ├── ca.crt │ ├── client.crt │ ├── client.key │ ├── multiCA.pem │ ├── server.crt │ └── server.key ├── faas ├── function.pb.go ├── function.proto └── functionpb_test.go ├── go.mod ├── go.sum ├── http ├── client.go ├── client_test.go ├── options.go ├── responses.go ├── server.go └── server_test.go ├── json └── jsoniter.go ├── log ├── config.go ├── logger.go ├── logger_test.go └── zap.go ├── mock ├── http_server.go ├── http_server_test.go ├── msg_flow.go ├── msg_flow_test.go └── testcert │ ├── ca.pem │ ├── client.key │ ├── client.pem │ ├── server.key │ └── server.pem ├── mqtt ├── client.go ├── client_test.go ├── counter.go ├── counter_test.go ├── gomqtt.go ├── mock_test.go ├── mqtt.pb.go ├── mqtt.proto ├── mqttpb_test.go ├── observer.go ├── options.go ├── stream.go ├── topic.go └── topic_test.go ├── native ├── mapping.go ├── mapping_test.go ├── port.go └── port_test.go ├── pki ├── pki.go ├── pki_test.go ├── utils.go └── utils_test.go ├── plugin └── register.go ├── pubsub ├── handler.go ├── processor.go ├── processor_test.go ├── pubsub.go └── pubsub_test.go ├── spec └── v1 │ ├── active.go │ ├── application.go │ ├── configuration.go │ ├── lazy_value.go │ ├── lazy_value_test.go │ ├── message.go │ ├── module.go │ ├── node.go │ ├── node_test.go │ ├── object.go │ ├── ota.go │ ├── resource.go │ ├── rpc.go │ ├── secret.go │ ├── sts.go │ ├── sync_desire.go │ ├── sync_desire_test.go │ └── sync_report.go ├── task ├── channel_broker.go ├── map_backend.go ├── message.go ├── producer.go ├── task.go ├── task_test.go └── worker.go ├── tools └── issue_certificate.go ├── trigger ├── trigger.go └── trigger_test.go ├── utils ├── cert.go ├── cert_test.go ├── config.go ├── config_test.go ├── defaults.go ├── defaults_test.go ├── fingerprint.go ├── fingerprint_test.go ├── flock.go ├── flock_windows.go ├── matcher.go ├── matcher_test.go ├── path.go ├── path_test.go ├── port.go ├── port_test.go ├── string.go ├── string_test.go ├── tar.go ├── tar_test.go ├── tgz.go ├── tgz_test.go ├── tomb.go ├── tomb_test.go ├── trace.go ├── url.go ├── url_test.go ├── validate.go ├── validate_test.go ├── version.go ├── version_test.go ├── zip.go └── zip_test.go └── websocket ├── client.go ├── client_test.go └── options.go /.github/workflows/unittest.yml: -------------------------------------------------------------------------------- 1 | name: unittest 2 | on: pull_request 3 | 4 | jobs: 5 | test: 6 | name: unittest 7 | runs-on: ubuntu-latest 8 | steps: 9 | - name: Setup Go 10 | uses: actions/setup-go@v1 11 | with: 12 | go-version: 1.18 13 | - name: Checkout code 14 | uses: actions/checkout@v1 15 | - name: Run unittest 16 | run: make test 17 | - name: Upload coverage to Codecov 18 | uses: codecov/codecov-action@v1 19 | with: 20 | token: ${{ secrets.CODECOV_TOKEN }} 21 | file: ./coverage.out 22 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Binaries for programs and plugins 2 | *.exe 3 | *.exe~ 4 | *.dll 5 | *.so 6 | *.dylib 7 | /.idea 8 | /.vscode 9 | /**/coverage.* 10 | /**/.DS_Store 11 | /**/*.log 12 | /vendor 13 | # Test binary, build with `go test -c` 14 | *.test 15 | 16 | # Output of the go coverage tool, specifically when used with LiteIDE 17 | *.out 18 | 19 | # Other 20 | *~ 21 | *.swp 22 | *.a 23 | *.jar 24 | *.iml 25 | /.idea 26 | /.vscode 27 | /output* 28 | /**/*.zip 29 | /**/.DS_Store 30 | /**/*.db 31 | /**/*.db.lock 32 | /**/*debug.test 33 | /**/debug 34 | /**/*.log 35 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | HOMEDIR := $(shell pwd) 2 | OUTDIR := $(HOMEDIR)/output 3 | 4 | GIT_TAG:=$(shell git tag --contains HEAD) 5 | GIT_REV:=git-$(shell git rev-parse --short HEAD) 6 | VERSION:=$(if $(GIT_TAG),$(GIT_TAG),$(GIT_REV)) 7 | 8 | GO = go 9 | GO_MOD = $(GO) mod 10 | GO_ENV = env CGO_ENABLED=0 11 | GO_BUILD = $(GO_ENV) $(GO) build 12 | GOTEST = $(GO) test 13 | GOPKGS = $$($(GO) list ./...) 14 | 15 | all: test 16 | 17 | prepare: prepare-dep 18 | prepare-dep: 19 | git config --global http.sslVerify false 20 | 21 | set-env: 22 | $(GO) env -w GOPROXY=https://goproxy.cn 23 | $(GO) env -w GONOSUMDB=\* 24 | 25 | compile:build 26 | build: set-env 27 | $(GO_MOD) tidy 28 | $(GO_BUILD) ./... 29 | 30 | test: fmt test-case 31 | test-case: set-env 32 | $(GOTEST) -race -cover -coverprofile=coverage.out $(GOPKGS) 33 | 34 | fmt: 35 | go fmt ./... 36 | 37 | .PHONY: all prepare compile test build 38 | -------------------------------------------------------------------------------- /cache/cache.go: -------------------------------------------------------------------------------- 1 | package cache 2 | 3 | import ( 4 | "time" 5 | 6 | "github.com/gin-gonic/gin" 7 | "golang.org/x/sync/singleflight" 8 | 9 | "github.com/baetyl/baetyl-go/v2/cache/persist" 10 | ) 11 | 12 | // Strategy the cache strategy 13 | type Strategy struct { 14 | CacheKey string 15 | 16 | // CacheStore if nil, use default cache store instead 17 | CacheStore persist.CacheStore 18 | 19 | // CacheDuration 20 | CacheDuration time.Duration 21 | } 22 | 23 | // GetCacheStrategyByRequest User can this function to design custom cache strategy by request. 24 | // The second return value bool means whether this request should be cached. 25 | // The first return value Strategy determine the special strategy by this request. 26 | type GetCacheStrategyByRequest func(c *gin.Context) (Strategy, bool) 27 | 28 | func _cache(defaultCacheStore persist.CacheStore, defaultExpire time.Duration, cfg *Config, isMiddleware bool, handle gin.HandlerFunc) gin.HandlerFunc { 29 | if cfg.getCacheStrategyByRequest == nil { 30 | panic("cache strategy is nil") 31 | } 32 | 33 | sfGroup := singleflight.Group{} 34 | 35 | return func(c *gin.Context) { 36 | cacheStrategy, shouldCache := cfg.getCacheStrategyByRequest(c) 37 | if !shouldCache { 38 | if isMiddleware { 39 | c.Next() 40 | } else { 41 | handle(c) 42 | } 43 | return 44 | } 45 | 46 | cacheKey := cacheStrategy.CacheKey 47 | 48 | if cfg.prefixKey != "" { 49 | cacheKey = cfg.prefixKey + cacheKey 50 | } 51 | 52 | if cfg.keyWithGinContext != nil && len(cfg.keyWithGinContext) > 0 { 53 | for _, k := range cfg.keyWithGinContext { 54 | cacheKey = c.GetString(k) + cacheKey 55 | } 56 | } 57 | 58 | // merge cfg 59 | cacheStore := defaultCacheStore 60 | if cacheStrategy.CacheStore != nil { 61 | cacheStore = cacheStrategy.CacheStore 62 | } 63 | 64 | cacheDuration := defaultExpire 65 | if cacheStrategy.CacheDuration > 0 { 66 | cacheDuration = cacheStrategy.CacheDuration 67 | } 68 | 69 | // read cache first 70 | { 71 | respCache := &ResponseCache{} 72 | err := cacheStore.Get(cacheKey, &respCache) 73 | if err == nil { 74 | replyWithCache(c, cfg, respCache) 75 | cfg.hitCacheCallback(c) 76 | return 77 | } 78 | 79 | if err != persist.ErrCacheMiss { 80 | cfg.logger.Errorf("get cache error: %s, cache key: %s", err, cacheKey) 81 | } 82 | cfg.missCacheCallback(c) 83 | } 84 | 85 | // cache miss, then call the backend 86 | 87 | // use responseCacheWriter in order to record the response 88 | cacheWriter := &responseCacheWriter{ 89 | ResponseWriter: c.Writer, 90 | } 91 | c.Writer = cacheWriter 92 | 93 | inFlight := false 94 | rawRespCache, _, _ := sfGroup.Do(cacheKey, func() (interface{}, error) { 95 | if cfg.singleFlightForgetTimeout > 0 { 96 | forgetTimer := time.AfterFunc(cfg.singleFlightForgetTimeout, func() { 97 | sfGroup.Forget(cacheKey) 98 | }) 99 | defer forgetTimer.Stop() 100 | } 101 | 102 | if isMiddleware { 103 | c.Next() 104 | } else { 105 | handle(c) 106 | } 107 | 108 | inFlight = true 109 | 110 | respCache := &ResponseCache{} 111 | respCache.fillWithCacheWriter(cacheWriter, cfg.withoutHeader, cfg.withoutHeaderIgnore) 112 | 113 | // only cache 2xx response 114 | if !c.IsAborted() && cacheWriter.Status() < 300 && cacheWriter.Status() >= 200 { 115 | if err := cacheStore.Set(cacheKey, respCache, cacheDuration); err != nil { 116 | cfg.logger.Errorf("set cache key error: %s, cache key: %s", err, cacheKey) 117 | } 118 | } 119 | 120 | return respCache, nil 121 | }) 122 | 123 | if !inFlight { 124 | replyWithCache(c, cfg, rawRespCache.(*ResponseCache)) 125 | cfg.shareSingleFlightCallback(c) 126 | } 127 | } 128 | } 129 | -------------------------------------------------------------------------------- /cache/config.go: -------------------------------------------------------------------------------- 1 | package cache 2 | 3 | import ( 4 | "net/url" 5 | "sort" 6 | "strings" 7 | "time" 8 | 9 | "github.com/gin-gonic/gin" 10 | ) 11 | 12 | // Config contains all options 13 | type Config struct { 14 | logger Logger 15 | 16 | getCacheStrategyByRequest GetCacheStrategyByRequest 17 | 18 | hitCacheCallback OnHitCacheCallback 19 | missCacheCallback OnMissCacheCallback 20 | 21 | beforeReplyWithCacheCallback BeforeReplyWithCacheCallback 22 | 23 | singleFlightForgetTimeout time.Duration 24 | shareSingleFlightCallback OnShareSingleFlightCallback 25 | 26 | ignoreQueryOrder bool 27 | 28 | withoutHeader bool 29 | // Only effective when without is true, including header fields that will not be ignored 30 | // Keys that are not in the array will still be ignored 31 | withoutHeaderIgnore []string 32 | 33 | prefixKey string 34 | keyWithGinContext []string 35 | } 36 | 37 | func newConfigByOpts(opts ...Option) *Config { 38 | cfg := &Config{ 39 | logger: Discard{}, 40 | hitCacheCallback: defaultHitCacheCallback, 41 | missCacheCallback: defaultMissCacheCallback, 42 | beforeReplyWithCacheCallback: defaultBeforeReplyWithCacheCallback, 43 | shareSingleFlightCallback: defaultShareSingleFlightCallback, 44 | } 45 | 46 | for _, opt := range opts { 47 | opt(cfg) 48 | } 49 | 50 | return cfg 51 | } 52 | 53 | func (cfg *Config) setRequestURI() { 54 | var cacheStrategy GetCacheStrategyByRequest 55 | if cfg.ignoreQueryOrder { 56 | cacheStrategy = func(c *gin.Context) (Strategy, bool) { 57 | newUri, err := getRequestUriIgnoreQueryOrder(c.Request.RequestURI) 58 | if err != nil { 59 | cfg.logger.Errorf("getRequestUriIgnoreQueryOrder error: %s", err) 60 | newUri = c.Request.RequestURI 61 | } 62 | 63 | return Strategy{ 64 | CacheKey: newUri, 65 | }, true 66 | } 67 | 68 | } else { 69 | cacheStrategy = func(c *gin.Context) (Strategy, bool) { 70 | return Strategy{ 71 | CacheKey: c.Request.RequestURI, 72 | }, true 73 | } 74 | } 75 | cfg.getCacheStrategyByRequest = cacheStrategy 76 | } 77 | 78 | func getRequestUriIgnoreQueryOrder(requestURI string) (string, error) { 79 | parsedUrl, err := url.ParseRequestURI(requestURI) 80 | if err != nil { 81 | return "", err 82 | } 83 | 84 | values := parsedUrl.Query() 85 | 86 | if len(values) == 0 { 87 | return requestURI, nil 88 | } 89 | 90 | queryKeys := make([]string, 0, len(values)) 91 | for queryKey := range values { 92 | queryKeys = append(queryKeys, queryKey) 93 | } 94 | sort.Strings(queryKeys) 95 | 96 | queryVals := make([]string, 0, len(values)) 97 | for _, queryKey := range queryKeys { 98 | sort.Strings(values[queryKey]) 99 | for _, val := range values[queryKey] { 100 | queryVals = append(queryVals, queryKey+"="+val) 101 | } 102 | } 103 | 104 | return parsedUrl.Path + "?" + strings.Join(queryVals, "&"), nil 105 | } 106 | -------------------------------------------------------------------------------- /cache/middleware.go: -------------------------------------------------------------------------------- 1 | package cache 2 | 3 | import ( 4 | "time" 5 | 6 | "github.com/gin-gonic/gin" 7 | 8 | "github.com/baetyl/baetyl-go/v2/cache/persist" 9 | ) 10 | 11 | // MCache user must pass getCacheKey to describe the way to generate cache key 12 | func MCache(defaultCacheStore persist.CacheStore, defaultExpire time.Duration, opts ...Option) gin.HandlerFunc { 13 | cfg := newConfigByOpts(opts...) 14 | return mCache(defaultCacheStore, defaultExpire, cfg) 15 | } 16 | 17 | func mCache(defaultCacheStore persist.CacheStore, defaultExpire time.Duration, cfg *Config) gin.HandlerFunc { 18 | return _cache(defaultCacheStore, defaultExpire, cfg, true, nil) 19 | } 20 | 21 | // MCacheByRequestURI a shortcut function for caching response by uri 22 | func MCacheByRequestURI(defaultCacheStore persist.CacheStore, defaultExpire time.Duration, opts ...Option) gin.HandlerFunc { 23 | cfg := newConfigByOpts(opts...) 24 | cfg.setRequestURI() 25 | return mCache(defaultCacheStore, defaultExpire, cfg) 26 | } 27 | 28 | // MCacheByRequestPath a shortcut function for caching response by url path, means will discard the query params 29 | func MCacheByRequestPath(defaultCacheStore persist.CacheStore, defaultExpire time.Duration, opts ...Option) gin.HandlerFunc { 30 | opts = append(opts, WithCacheStrategyByRequest(func(c *gin.Context) (Strategy, bool) { 31 | return Strategy{ 32 | CacheKey: c.Request.URL.Path, 33 | }, true 34 | })) 35 | 36 | return MCache(defaultCacheStore, defaultExpire, opts...) 37 | } 38 | -------------------------------------------------------------------------------- /cache/option.go: -------------------------------------------------------------------------------- 1 | package cache 2 | 3 | import ( 4 | "time" 5 | 6 | "github.com/gin-gonic/gin" 7 | ) 8 | 9 | // Option represents the optional function. 10 | type Option func(c *Config) 11 | 12 | // WithLogger set the custom logger 13 | func WithLogger(l Logger) Option { 14 | return func(c *Config) { 15 | if l != nil { 16 | c.logger = l 17 | } 18 | } 19 | } 20 | 21 | // Logger define the logger interface 22 | type Logger interface { 23 | Errorf(string, ...interface{}) 24 | } 25 | 26 | // Discard the default logger that will discard all logs of gin-cache 27 | type Discard struct { 28 | } 29 | 30 | // Errorf will output the log at error level 31 | func (l Discard) Errorf(string, ...interface{}) { 32 | } 33 | 34 | // WithCacheStrategyByRequest set up the custom strategy by per request 35 | func WithCacheStrategyByRequest(getGetCacheStrategyByRequest GetCacheStrategyByRequest) Option { 36 | return func(c *Config) { 37 | if getGetCacheStrategyByRequest != nil { 38 | c.getCacheStrategyByRequest = getGetCacheStrategyByRequest 39 | } 40 | } 41 | } 42 | 43 | // OnHitCacheCallback define the callback when use cache 44 | type OnHitCacheCallback func(c *gin.Context) 45 | 46 | var defaultHitCacheCallback = func(c *gin.Context) {} 47 | 48 | // WithOnHitCache will be called when cache hit. 49 | func WithOnHitCache(cb OnHitCacheCallback) Option { 50 | return func(c *Config) { 51 | if cb != nil { 52 | c.hitCacheCallback = cb 53 | } 54 | } 55 | } 56 | 57 | // OnMissCacheCallback define the callback when use cache 58 | type OnMissCacheCallback func(c *gin.Context) 59 | 60 | var defaultMissCacheCallback = func(c *gin.Context) {} 61 | 62 | // WithOnMissCache will be called when cache miss. 63 | func WithOnMissCache(cb OnMissCacheCallback) Option { 64 | return func(c *Config) { 65 | if cb != nil { 66 | c.missCacheCallback = cb 67 | } 68 | } 69 | } 70 | 71 | type BeforeReplyWithCacheCallback func(c *gin.Context, cache *ResponseCache) 72 | 73 | var defaultBeforeReplyWithCacheCallback = func(c *gin.Context, cache *ResponseCache) {} 74 | 75 | // WithBeforeReplyWithCache will be called before replying with cache. 76 | func WithBeforeReplyWithCache(cb BeforeReplyWithCacheCallback) Option { 77 | return func(c *Config) { 78 | if cb != nil { 79 | c.beforeReplyWithCacheCallback = cb 80 | } 81 | } 82 | } 83 | 84 | // OnShareSingleFlightCallback define the callback when share the singleflight result 85 | type OnShareSingleFlightCallback func(c *gin.Context) 86 | 87 | var defaultShareSingleFlightCallback = func(c *gin.Context) {} 88 | 89 | // WithOnShareSingleFlight will be called when share the singleflight result 90 | func WithOnShareSingleFlight(cb OnShareSingleFlightCallback) Option { 91 | return func(c *Config) { 92 | if cb != nil { 93 | c.shareSingleFlightCallback = cb 94 | } 95 | } 96 | } 97 | 98 | // WithSingleFlightForgetTimeout to reduce the impact of long tail requests. 99 | // singleflight.Forget will be called after the timeout has reached for each backend request when timeout is greater than zero. 100 | func WithSingleFlightForgetTimeout(forgetTimeout time.Duration) Option { 101 | return func(c *Config) { 102 | if forgetTimeout > 0 { 103 | c.singleFlightForgetTimeout = forgetTimeout 104 | } 105 | } 106 | } 107 | 108 | // IgnoreQueryOrder will ignore the queries order in url when generate cache key . This option only takes effect in CacheByRequestURI function 109 | func IgnoreQueryOrder() Option { 110 | return func(c *Config) { 111 | c.ignoreQueryOrder = true 112 | } 113 | } 114 | 115 | // WithPrefixKey will prefix the key 116 | func WithPrefixKey(prefix string) Option { 117 | return func(c *Config) { 118 | c.prefixKey = prefix 119 | } 120 | } 121 | 122 | func WithoutHeader() Option { 123 | return func(c *Config) { 124 | c.withoutHeader = true 125 | } 126 | } 127 | 128 | // WithoutHeaderIgnore Only effective when without is true, including header fields that will not be ignored 129 | // Keys that are not in the array will still be ignored 130 | func WithoutHeaderIgnore(ks []string) Option { 131 | return func(c *Config) { 132 | c.withoutHeaderIgnore = ks 133 | } 134 | } 135 | 136 | func KeyWithGinContext(ks []string) Option { 137 | return func(c *Config) { 138 | c.keyWithGinContext = ks 139 | } 140 | } 141 | -------------------------------------------------------------------------------- /cache/persist/cache.go: -------------------------------------------------------------------------------- 1 | package persist 2 | 3 | import ( 4 | "errors" 5 | "time" 6 | ) 7 | 8 | // ErrCacheMiss represent the cache key does not exist in the store 9 | var ( 10 | ErrCacheMiss = errors.New("cache: key not found") 11 | ErrNotStored = errors.New("cache: not stored") 12 | ) 13 | 14 | // CacheStore is the interface of a Cache backend 15 | type CacheStore interface { 16 | // Get retrieves an item from the Cache. if key does not exist in the store, return ErrCacheMiss 17 | Get(key string, value interface{}) error 18 | 19 | // Set sets an item to the Cache, replacing any existing item. 20 | Set(key string, value interface{}, expire time.Duration) error 21 | 22 | // Delete removes an item from the Cache. Does nothing if the key is not in the Cache. 23 | Delete(key string) error 24 | } 25 | -------------------------------------------------------------------------------- /cache/persist/codec.go: -------------------------------------------------------------------------------- 1 | package persist 2 | 3 | import ( 4 | "bytes" 5 | "encoding/gob" 6 | ) 7 | 8 | // Serialize returns a []byte representing the passed value 9 | func Serialize(value interface{}) ([]byte, error) { 10 | var b bytes.Buffer 11 | encoder := gob.NewEncoder(&b) 12 | if err := encoder.Encode(value); err != nil { 13 | return nil, err 14 | } 15 | return b.Bytes(), nil 16 | } 17 | 18 | // Deserialize will deserialize the passed []byte into the passed ptr interface{} 19 | func Deserialize(payload []byte, ptr interface{}) (err error) { 20 | return gob.NewDecoder(bytes.NewBuffer(payload)).Decode(ptr) 21 | } 22 | -------------------------------------------------------------------------------- /cache/persist/codec_test.go: -------------------------------------------------------------------------------- 1 | package persist 2 | 3 | import ( 4 | "testing" 5 | 6 | "github.com/stretchr/testify/assert" 7 | "github.com/stretchr/testify/require" 8 | ) 9 | 10 | type testStruct struct { 11 | A int 12 | B string 13 | C *int 14 | } 15 | 16 | func TestCodec(t *testing.T) { 17 | src := &testStruct{ 18 | A: 1, 19 | B: "2", 20 | } 21 | 22 | payload, err := Serialize(src) 23 | require.Nil(t, err) 24 | require.True(t, len(payload) > 0) 25 | 26 | var dest testStruct 27 | err = Deserialize(payload, &dest) 28 | require.Nil(t, err) 29 | 30 | assert.Equal(t, src.A, dest.A) 31 | assert.Equal(t, src.B, dest.B) 32 | assert.Equal(t, src.C, dest.C) 33 | } 34 | -------------------------------------------------------------------------------- /cache/persist/memory.go: -------------------------------------------------------------------------------- 1 | package persist 2 | 3 | import ( 4 | "reflect" 5 | "time" 6 | 7 | "github.com/robfig/go-cache" 8 | ) 9 | 10 | // InMemoryStore represents the cache with memory persistence 11 | type InMemoryStore struct { 12 | cache.Cache 13 | } 14 | 15 | // NewInMemoryStore returns a InMemoryStore 16 | func NewInMemoryStore(defaultExpiration time.Duration) *InMemoryStore { 17 | return &InMemoryStore{*cache.New(defaultExpiration, time.Minute)} 18 | } 19 | 20 | // Get (see CacheStore interface) 21 | func (c *InMemoryStore) Get(key string, value interface{}) error { 22 | val, found := c.Cache.Get(key) 23 | if !found { 24 | return ErrCacheMiss 25 | } 26 | 27 | v := reflect.ValueOf(value) 28 | if v.Type().Kind() == reflect.Ptr && v.Elem().CanSet() { 29 | v.Elem().Set(reflect.ValueOf(val)) 30 | return nil 31 | } 32 | return ErrNotStored 33 | } 34 | 35 | // Set (see CacheStore interface) 36 | func (c *InMemoryStore) Set(key string, value interface{}, expires time.Duration) error { 37 | // NOTE: go-cache understands the values of DEFAULT and FOREVER 38 | c.Cache.Set(key, value, expires) 39 | return nil 40 | } 41 | 42 | // Delete (see CacheStore interface) 43 | func (c *InMemoryStore) Delete(key string) error { 44 | if found := c.Cache.Delete(key); !found { 45 | return ErrCacheMiss 46 | } 47 | return nil 48 | } 49 | -------------------------------------------------------------------------------- /cache/persist/memory_test.go: -------------------------------------------------------------------------------- 1 | package persist 2 | 3 | import ( 4 | "testing" 5 | "time" 6 | 7 | "github.com/stretchr/testify/assert" 8 | "github.com/stretchr/testify/require" 9 | ) 10 | 11 | func TestMemoryStore(t *testing.T) { 12 | memoryStore := NewInMemoryStore(1 * time.Minute) 13 | 14 | expectVal := "123" 15 | require.Nil(t, memoryStore.Set("test", expectVal, 1*time.Second)) 16 | 17 | value := "" 18 | assert.Nil(t, memoryStore.Get("test", &value)) 19 | assert.Equal(t, expectVal, value) 20 | 21 | time.Sleep(1 * time.Second) 22 | assert.Equal(t, ErrCacheMiss, memoryStore.Get("test", &value)) 23 | } 24 | -------------------------------------------------------------------------------- /cache/persist/redis.go: -------------------------------------------------------------------------------- 1 | package persist 2 | 3 | import ( 4 | "context" 5 | "errors" 6 | "time" 7 | 8 | "github.com/go-redis/redis/v8" 9 | ) 10 | 11 | // RedisStore store http response in redis 12 | type RedisStore struct { 13 | RedisClient *redis.Client 14 | } 15 | 16 | // NewRedisStore create a redis memory store with redis client 17 | func NewRedisStore(redisClient *redis.Client) *RedisStore { 18 | return &RedisStore{ 19 | RedisClient: redisClient, 20 | } 21 | } 22 | 23 | // Set put key value pair to redis, and expire after expireDuration 24 | func (store *RedisStore) Set(key string, value interface{}, expire time.Duration) error { 25 | payload, err := Serialize(value) 26 | if err != nil { 27 | return err 28 | } 29 | 30 | ctx := context.TODO() 31 | return store.RedisClient.Set(ctx, key, payload, expire).Err() 32 | } 33 | 34 | // Delete remove key in redis, do nothing if key doesn't exist 35 | func (store *RedisStore) Delete(key string) error { 36 | ctx := context.TODO() 37 | return store.RedisClient.Del(ctx, key).Err() 38 | } 39 | 40 | // Get retrieves an item from redis, if key doesn't exist, return ErrCacheMiss 41 | func (store *RedisStore) Get(key string, value interface{}) error { 42 | ctx := context.TODO() 43 | payload, err := store.RedisClient.Get(ctx, key).Bytes() 44 | 45 | if errors.Is(err, redis.Nil) { 46 | return ErrCacheMiss 47 | } 48 | 49 | if err != nil { 50 | return err 51 | } 52 | return Deserialize(payload, value) 53 | } 54 | -------------------------------------------------------------------------------- /cache/reply.go: -------------------------------------------------------------------------------- 1 | package cache 2 | 3 | import ( 4 | "bytes" 5 | "encoding/gob" 6 | "net/http" 7 | 8 | "github.com/gin-gonic/gin" 9 | ) 10 | 11 | func init() { 12 | gob.Register(&ResponseCache{}) 13 | } 14 | 15 | // ResponseCache record the http response cache 16 | type ResponseCache struct { 17 | Status int 18 | Header http.Header 19 | Data []byte 20 | } 21 | 22 | func (c *ResponseCache) fillWithCacheWriter(cacheWriter *responseCacheWriter, withoutHeader bool, withoutHeaderIgnore []string) { 23 | c.Status = cacheWriter.Status() 24 | c.Data = cacheWriter.body.Bytes() 25 | if !withoutHeader { 26 | c.Header = cacheWriter.Header().Clone() 27 | } else { 28 | if c.Header == nil { 29 | c.Header = http.Header{} 30 | } 31 | for _, k := range withoutHeaderIgnore { 32 | c.Header.Set(k, cacheWriter.Header().Get(k)) 33 | } 34 | } 35 | } 36 | 37 | // responseCacheWriter 38 | type responseCacheWriter struct { 39 | gin.ResponseWriter 40 | 41 | body bytes.Buffer 42 | } 43 | 44 | func (w *responseCacheWriter) Write(b []byte) (int, error) { 45 | w.body.Write(b) 46 | return w.ResponseWriter.Write(b) 47 | } 48 | 49 | func (w *responseCacheWriter) WriteString(s string) (int, error) { 50 | w.body.WriteString(s) 51 | return w.ResponseWriter.WriteString(s) 52 | } 53 | 54 | func replyWithCache(c *gin.Context, cfg *Config, respCache *ResponseCache) { 55 | cfg.beforeReplyWithCacheCallback(c, respCache) 56 | 57 | c.Writer.WriteHeader(respCache.Status) 58 | 59 | if !cfg.withoutHeader { 60 | for key, values := range respCache.Header { 61 | for _, val := range values { 62 | c.Writer.Header().Set(key, val) 63 | } 64 | } 65 | } else { 66 | for _, key := range cfg.withoutHeaderIgnore { 67 | c.Writer.Header().Set(key, respCache.Header.Get(key)) 68 | } 69 | } 70 | 71 | if _, err := c.Writer.Write(respCache.Data); err != nil { 72 | cfg.logger.Errorf("write response error: %s", err) 73 | } 74 | 75 | // abort handler chain and return directly 76 | c.Abort() 77 | } 78 | -------------------------------------------------------------------------------- /cache/wrapper.go: -------------------------------------------------------------------------------- 1 | package cache 2 | 3 | import ( 4 | "time" 5 | 6 | "github.com/gin-gonic/gin" 7 | 8 | "github.com/baetyl/baetyl-go/v2/cache/persist" 9 | ) 10 | 11 | // WCache user must pass getCacheKey to describe the way to generate cache key 12 | func WCache(defaultCacheStore persist.CacheStore, defaultExpire time.Duration, handle gin.HandlerFunc, opts ...Option) gin.HandlerFunc { 13 | cfg := newConfigByOpts(opts...) 14 | return wCache(defaultCacheStore, defaultExpire, cfg, handle) 15 | } 16 | 17 | func wCache(defaultCacheStore persist.CacheStore, defaultExpire time.Duration, cfg *Config, handle gin.HandlerFunc) gin.HandlerFunc { 18 | return _cache(defaultCacheStore, defaultExpire, cfg, false, handle) 19 | } 20 | 21 | // WCacheByRequestURI a shortcut function for caching response by uri 22 | func WCacheByRequestURI(defaultCacheStore persist.CacheStore, defaultExpire time.Duration, handle gin.HandlerFunc, opts ...Option) gin.HandlerFunc { 23 | cfg := newConfigByOpts(opts...) 24 | cfg.setRequestURI() 25 | return wCache(defaultCacheStore, defaultExpire, cfg, handle) 26 | } 27 | 28 | // WCacheByRequestPath a shortcut function for caching response by url path, means will discard the query params 29 | func WCacheByRequestPath(defaultCacheStore persist.CacheStore, defaultExpire time.Duration, handle gin.HandlerFunc, opts ...Option) gin.HandlerFunc { 30 | opts = append(opts, WithCacheStrategyByRequest(func(c *gin.Context) (Strategy, bool) { 31 | return Strategy{ 32 | CacheKey: c.Request.URL.Path, 33 | }, true 34 | })) 35 | 36 | return WCache(defaultCacheStore, defaultExpire, handle, opts...) 37 | } 38 | -------------------------------------------------------------------------------- /comctx/error.go: -------------------------------------------------------------------------------- 1 | package comctx 2 | 3 | import ( 4 | "bytes" 5 | "strings" 6 | "text/template" 7 | 8 | "github.com/baetyl/baetyl-go/v2/errors" 9 | ) 10 | 11 | // Field returns a field 12 | func Field(k string, v interface{}) *F { 13 | return &F{k, v} 14 | } 15 | 16 | // Error returns an error with code and fields 17 | func Error(c Code, fs ...*F) error { 18 | m := c.String() 19 | if strings.Contains(m, "{{") { 20 | vs := map[string]interface{}{} 21 | for _, f := range fs { 22 | vs[f.k] = f.v 23 | } 24 | t, err := template.New(string(c)).Option("missingkey=zero").Parse(m) 25 | if err != nil { 26 | panic(err) 27 | } 28 | b := bytes.NewBuffer(nil) 29 | err = t.Execute(b, vs) 30 | if err != nil { 31 | panic(err) 32 | } 33 | m = b.String() 34 | } 35 | return errors.CodeError(string(c), m) 36 | } 37 | 38 | // Field field 39 | type F struct { 40 | k string 41 | v interface{} 42 | } 43 | -------------------------------------------------------------------------------- /context/config.go: -------------------------------------------------------------------------------- 1 | package context 2 | 3 | import ( 4 | "github.com/baetyl/baetyl-go/v2/http" 5 | "github.com/baetyl/baetyl-go/v2/log" 6 | "github.com/baetyl/baetyl-go/v2/mqtt" 7 | "github.com/baetyl/baetyl-go/v2/utils" 8 | ) 9 | 10 | const ( 11 | SystemCertCA = "ca.pem" 12 | SystemCertCrt = "crt.pem" 13 | SystemCertKey = "key.pem" 14 | SystemCertPath = "var/lib/baetyl/system/certs" 15 | ) 16 | 17 | // SystemConfig config of baetyl system 18 | type SystemConfig struct { 19 | Certificate utils.Certificate `yaml:"cert,omitempty" json:"cert,omitempty" default:"{\"ca\":\"var/lib/baetyl/system/certs/ca.pem\",\"key\":\"var/lib/baetyl/system/certs/key.pem\",\"cert\":\"var/lib/baetyl/system/certs/crt.pem\"}"` 20 | Function http.ClientConfig `yaml:"function,omitempty" json:"function,omitempty"` 21 | Core http.ClientConfig `yaml:"core,omitempty" json:"core,omitempty"` 22 | Broker mqtt.ClientConfig `yaml:"broker,omitempty" json:"broker,omitempty"` 23 | Logger log.Config `yaml:"logger,omitempty" json:"logger,omitempty"` 24 | } 25 | -------------------------------------------------------------------------------- /context/env.go: -------------------------------------------------------------------------------- 1 | package context 2 | 3 | import ( 4 | "fmt" 5 | "os" 6 | "runtime" 7 | 8 | "github.com/baetyl/baetyl-go/v2/errors" 9 | ) 10 | 11 | // All keys 12 | const ( 13 | KeyBaetyl = "BAETYL" 14 | KeyConfFile = "BAETYL_CONF_FILE" 15 | KeyNodeName = "BAETYL_NODE_NAME" 16 | KeyAppName = "BAETYL_APP_NAME" 17 | KeyAppVersion = "BAETYL_APP_VERSION" 18 | KeySvcName = "BAETYL_SERVICE_NAME" 19 | KeySysConf = "BAETYL_SYSTEM_CONF" 20 | KeyRunMode = "BAETYL_RUN_MODE" 21 | KeyServiceDynamicPort = "BAETYL_SERVICE_DYNAMIC_PORT" 22 | KeyBaetylHostPathLib = "BAETYL_HOST_PATH_LIB" 23 | ) 24 | 25 | const ( 26 | RunModeKube = "kube" 27 | RunModeNative = "native" 28 | RunModeAndroid = "android" 29 | ) 30 | 31 | const ( 32 | localHost = "127.0.0.1" 33 | baetylEdgeNamespace = "baetyl-edge" 34 | baetylEdgeSystemNamespace = "baetyl-edge-system" 35 | baetylCoreNativeSystemPort = "8443" 36 | baetylCoreKubeSystemPort = "443" 37 | baetylBrokerSystemPort = "50010" 38 | baetylFunctionSystemHttpPort = "50011" 39 | baetylFunctionSystemGrpcPort = "50012" 40 | DefaultHostPathLib = "/var/lib/baetyl" 41 | DefaultWindowsHostPathLib = "C:/baetyl" 42 | ) 43 | 44 | // HostPathLib return HostPathLib 45 | func HostPathLib() (string, error) { 46 | var hostPathLib string 47 | if val := os.Getenv(KeyBaetylHostPathLib); val == "" { 48 | val = DefaultHostPathLib 49 | if runtime.GOOS == "windows" { 50 | val = DefaultWindowsHostPathLib 51 | } 52 | err := os.Setenv(KeyBaetylHostPathLib, val) 53 | if err != nil { 54 | return "", errors.Trace(err) 55 | } 56 | hostPathLib = val 57 | } else { 58 | hostPathLib = val 59 | } 60 | return hostPathLib, nil 61 | } 62 | 63 | // RunMode return run mode of edge. 64 | func RunMode() string { 65 | mode := os.Getenv(KeyRunMode) 66 | if mode != RunModeNative { 67 | mode = RunModeKube 68 | } 69 | return mode 70 | } 71 | 72 | // EdgeNamespace return namespace of edge. 73 | func EdgeNamespace() string { 74 | return baetylEdgeNamespace 75 | } 76 | 77 | // EdgeSystemNamespace return system namespace of edge. 78 | func EdgeSystemNamespace() string { 79 | return baetylEdgeSystemNamespace 80 | } 81 | 82 | // BrokerPort return broker port. 83 | func BrokerPort() string { 84 | return baetylBrokerSystemPort 85 | } 86 | 87 | // FunctionHttpPort return http port of function. 88 | func FunctionHttpPort() string { 89 | return baetylFunctionSystemHttpPort 90 | } 91 | 92 | func CoreHttpPort() string { 93 | if RunMode() == RunModeNative { 94 | return baetylCoreNativeSystemPort 95 | } 96 | return baetylCoreKubeSystemPort 97 | } 98 | 99 | // BrokerHost return broker host. 100 | func BrokerHost() string { 101 | if RunMode() == RunModeNative { 102 | return localHost 103 | } 104 | return fmt.Sprintf("%s.%s", "baetyl-broker", baetylEdgeSystemNamespace) 105 | } 106 | 107 | // CoreHost return cpre host. 108 | func CoreHost() string { 109 | if RunMode() == RunModeNative { 110 | return localHost 111 | } 112 | return fmt.Sprintf("%s.%s", "baetyl-core", baetylEdgeSystemNamespace) 113 | } 114 | 115 | // FunctionHost return function host. 116 | func FunctionHost() string { 117 | if RunMode() == RunModeNative { 118 | return localHost 119 | } 120 | return fmt.Sprintf("%s.%s", "baetyl-function", baetylEdgeSystemNamespace) 121 | } 122 | 123 | func GatewayHost() string { 124 | if RunMode() == RunModeNative { 125 | return localHost 126 | } 127 | return fmt.Sprintf("%s.%s", "baetyl-gateway", baetylEdgeSystemNamespace) 128 | } 129 | 130 | func getBrokerAddress() string { 131 | return fmt.Sprintf("%s://%s:%s", "ssl", BrokerHost(), BrokerPort()) 132 | } 133 | 134 | func getFunctionAddress() string { 135 | return fmt.Sprintf("%s://%s:%s", "https", FunctionHost(), FunctionHttpPort()) 136 | } 137 | 138 | func getCoreAddress() string { 139 | return fmt.Sprintf("%s://%s:%s", "https", CoreHost(), CoreHttpPort()) 140 | } 141 | 142 | func getCoreInscureAdddress() string { 143 | return fmt.Sprintf("%s://%s:%s", "http", CoreHost(), CoreHttpPort()) 144 | } 145 | -------------------------------------------------------------------------------- /context/env_test.go: -------------------------------------------------------------------------------- 1 | package context 2 | 3 | import ( 4 | "fmt" 5 | "os" 6 | "testing" 7 | 8 | "github.com/stretchr/testify/assert" 9 | ) 10 | 11 | func TestGetHostPathLib(t *testing.T) { 12 | hostPathLib, err := HostPathLib() 13 | assert.NoError(t, err) 14 | assert.Equal(t, DefaultHostPathLib, hostPathLib) 15 | os.Setenv(KeyBaetylHostPathLib, "/var/data") 16 | hostPathLib, err = HostPathLib() 17 | assert.NoError(t, err) 18 | assert.Equal(t, "/var/data", hostPathLib) 19 | } 20 | 21 | func TestDetectRunMode(t *testing.T) { 22 | os.Setenv(KeyRunMode, "native") 23 | assert.Equal(t, "native", RunMode()) 24 | os.Setenv(KeyRunMode, "xxx") 25 | assert.Equal(t, "kube", RunMode()) 26 | } 27 | 28 | func TestConst(t *testing.T) { 29 | assert.Equal(t, baetylEdgeNamespace, EdgeNamespace()) 30 | assert.Equal(t, baetylEdgeSystemNamespace, EdgeSystemNamespace()) 31 | assert.Equal(t, baetylBrokerSystemPort, BrokerPort()) 32 | assert.Equal(t, baetylFunctionSystemHttpPort, FunctionHttpPort()) 33 | assert.Equal(t, fmt.Sprintf("%s.%s", "baetyl-broker", baetylEdgeSystemNamespace), BrokerHost()) 34 | assert.Equal(t, fmt.Sprintf("%s.%s", "baetyl-function", baetylEdgeSystemNamespace), FunctionHost()) 35 | } 36 | -------------------------------------------------------------------------------- /context/platform.go: -------------------------------------------------------------------------------- 1 | package context 2 | 3 | import ( 4 | "fmt" 5 | 6 | "github.com/containerd/containerd/platforms" 7 | specs "github.com/opencontainers/image-spec/specs-go/v1" 8 | ) 9 | 10 | type PlatformInfo = specs.Platform 11 | 12 | func Platform() PlatformInfo { 13 | return platforms.DefaultSpec() 14 | } 15 | 16 | func PlatformString() string { 17 | pl := platforms.DefaultSpec() 18 | if pl.OS == "" { 19 | return "unknown" 20 | } 21 | if pl.Variant == "" { 22 | return fmt.Sprintf("%s-%s", pl.OS, pl.Architecture) 23 | } 24 | return fmt.Sprintf("%s-%s-%s", pl.OS, pl.Architecture, pl.Variant) 25 | } 26 | -------------------------------------------------------------------------------- /context/platform_test.go: -------------------------------------------------------------------------------- 1 | package context 2 | 3 | import ( 4 | "runtime" 5 | "testing" 6 | 7 | "github.com/stretchr/testify/assert" 8 | ) 9 | 10 | func TestDetectPlatform(t *testing.T) { 11 | assert.Equal(t, Platform().OS, runtime.GOOS) 12 | assert.Contains(t, PlatformString(), runtime.GOOS) 13 | } 14 | -------------------------------------------------------------------------------- /context/run.go: -------------------------------------------------------------------------------- 1 | package context 2 | 3 | import ( 4 | "flag" 5 | "os" 6 | "runtime/debug" 7 | 8 | "github.com/baetyl/baetyl-go/v2/log" 9 | "github.com/baetyl/baetyl-go/v2/utils" 10 | ) 11 | 12 | // Run service 13 | func Run(handle func(Context) error) { 14 | utils.PrintVersion() 15 | 16 | var h bool 17 | var c string 18 | flag.BoolVar(&h, "h", false, "this help") 19 | flag.StringVar(&c, "c", "etc/baetyl/conf.yml", "the configuration file") 20 | flag.Parse() 21 | if h { 22 | flag.Usage() 23 | return 24 | } 25 | 26 | ctx := NewContext(c) 27 | defer func() { 28 | if r := recover(); r != nil { 29 | ctx.Log().Error("service is stopped with panic", log.Any("panic", r), log.Any("stack", string(debug.Stack()))) 30 | } 31 | }() 32 | 33 | pwd, _ := os.Getwd() 34 | ctx.Log().Info("service starting", log.Any("args", os.Args), log.Any("pwd", pwd)) 35 | err := handle(ctx) 36 | if err != nil { 37 | ctx.Log().Error("service has stopped with error", log.Error(err)) 38 | } else { 39 | ctx.Log().Info("service has stopped") 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /context/run_test.go: -------------------------------------------------------------------------------- 1 | package context 2 | 3 | import ( 4 | "os" 5 | "testing" 6 | 7 | "github.com/stretchr/testify/assert" 8 | 9 | "github.com/baetyl/baetyl-go/v2/http" 10 | "github.com/baetyl/baetyl-go/v2/log" 11 | "github.com/baetyl/baetyl-go/v2/mqtt" 12 | "github.com/baetyl/baetyl-go/v2/utils" 13 | ) 14 | 15 | func TestContext_Run(t *testing.T) { 16 | os.Setenv(KeySvcName, "service") 17 | os.Setenv(KeyRunMode, "kube") 18 | Run(func(ctx Context) error { 19 | assert.Equal(t, "etc/baetyl/conf.yml", ctx.ConfFile()) 20 | assert.Equal(t, &SystemConfig{ 21 | Certificate: utils.Certificate{CA: "var/lib/baetyl/system/certs/ca.pem", Key: "var/lib/baetyl/system/certs/key.pem", Cert: "var/lib/baetyl/system/certs/crt.pem", Name: "", InsecureSkipVerify: false, ClientAuthType: 0}, 22 | Function: http.ClientConfig{ByteUnit: "KB", Address: "https://baetyl-function.baetyl-edge-system:" + baetylFunctionSystemHttpPort, Timeout: 30000000000, KeepAlive: 30000000000, MaxIdleConns: 100, IdleConnTimeout: 90000000000, TLSHandshakeTimeout: 10000000000, ExpectContinueTimeout: 1000000000, Certificate: utils.Certificate{CA: "var/lib/baetyl/system/certs/ca.pem", Key: "var/lib/baetyl/system/certs/key.pem", Cert: "var/lib/baetyl/system/certs/crt.pem", Name: "", InsecureSkipVerify: false, ClientAuthType: 0}}, 23 | Core: http.ClientConfig{ByteUnit: "KB", Address: "https://baetyl-core.baetyl-edge-system:" + baetylCoreKubeSystemPort, Timeout: 30000000000, KeepAlive: 30000000000, MaxIdleConns: 100, IdleConnTimeout: 90000000000, TLSHandshakeTimeout: 10000000000, ExpectContinueTimeout: 1000000000, Certificate: utils.Certificate{CA: "var/lib/baetyl/system/certs/ca.pem", Key: "var/lib/baetyl/system/certs/key.pem", Cert: "var/lib/baetyl/system/certs/crt.pem", Name: "", InsecureSkipVerify: false, ClientAuthType: 0}}, 24 | Broker: mqtt.ClientConfig{Address: "ssl://baetyl-broker.baetyl-edge-system:" + baetylBrokerSystemPort, Username: "", Password: "", ClientID: "baetyl-link-app", CleanSession: false, Timeout: 30000000000, KeepAlive: 30000000000, MaxReconnectInterval: 180000000000, MaxCacheMessages: 10, DisableAutoAck: false, Subscriptions: []mqtt.QOSTopic{{1, "$link/service"}}, Certificate: utils.Certificate{CA: "var/lib/baetyl/system/certs/ca.pem", Key: "var/lib/baetyl/system/certs/key.pem", Cert: "var/lib/baetyl/system/certs/crt.pem", Name: "", InsecureSkipVerify: false, ClientAuthType: 0}}, 25 | Logger: log.Config{Level: "info", Encoding: "json", Filename: "", Compress: false, MaxAge: 15, MaxSize: 50, MaxBackups: 15, EncodeTime: "", EncodeLevel: ""}, 26 | }, ctx.SystemConfig()) 27 | panic("it is a panic") 28 | return nil 29 | }) 30 | } 31 | -------------------------------------------------------------------------------- /dmcontext/blink.go: -------------------------------------------------------------------------------- 1 | package dmcontext 2 | 3 | import ( 4 | "time" 5 | 6 | "github.com/google/uuid" 7 | 8 | v1 "github.com/baetyl/baetyl-go/v2/spec/v1" 9 | ) 10 | 11 | const ( 12 | MethodPropertyInvoke = "thing.property.invoke" 13 | MethodPropertyReport = "thing.property.post" 14 | MethodEventReport = "thing.event.post" 15 | MethodPropertyGet = "thing.property.get" 16 | MethodLifecyclePost = "thing.lifecycle.post" 17 | DefaultVersion = "1.0" 18 | KeyOnlineState = "online_state" 19 | ) 20 | 21 | type MsgBlink struct { 22 | } 23 | 24 | type ContentBlink struct { 25 | Blink DataBlink `yaml:"blink,omitempty" json:"blink,omitempty"` 26 | } 27 | 28 | type DataBlink struct { 29 | ReqID string `yaml:"reqId,omitempty" json:"reqId,omitempty"` 30 | Method string `yaml:"method,omitempty" json:"method,omitempty"` 31 | Version string `yaml:"version,omitempty" json:"version,omitempty"` 32 | Timestamp int64 `yaml:"timestamp,omitempty" json:"timestamp,omitempty"` 33 | Properties any `yaml:"properties,omitempty" json:"properties,omitempty"` 34 | Events map[string]any `yaml:"events,omitempty" json:"events,omitempty"` 35 | Params map[string]any `yaml:"params,omitempty" json:"params,omitempty"` 36 | } 37 | 38 | func (b *MsgBlink) GenDeltaBlinkData(properties map[string]interface{}) v1.LazyValue { 39 | return v1.LazyValue{ 40 | Value: ContentBlink{ 41 | Blink: DataBlink{ 42 | ReqID: uuid.New().String(), 43 | Method: MethodPropertyInvoke, 44 | Version: DefaultVersion, 45 | Timestamp: getCurrentTimestamp(), 46 | Properties: properties, 47 | }, 48 | }, 49 | } 50 | } 51 | 52 | func (b *MsgBlink) GenPropertyReportData(properties map[string]any) v1.LazyValue { 53 | return v1.LazyValue{ 54 | Value: ContentBlink{ 55 | Blink: DataBlink{ 56 | ReqID: uuid.New().String(), 57 | Method: MethodPropertyReport, 58 | Version: DefaultVersion, 59 | Timestamp: getCurrentTimestamp(), 60 | Properties: properties, 61 | }, 62 | }, 63 | } 64 | } 65 | 66 | func (b *MsgBlink) GenEventReportData(events map[string]any) v1.LazyValue { 67 | return v1.LazyValue{ 68 | Value: ContentBlink{ 69 | Blink: DataBlink{ 70 | ReqID: uuid.New().String(), 71 | Method: MethodEventReport, 72 | Version: DefaultVersion, 73 | Timestamp: getCurrentTimestamp(), 74 | Events: events, 75 | }, 76 | }, 77 | } 78 | } 79 | 80 | func (b *MsgBlink) GenPropertyGetBlinkData(properties []string) v1.LazyValue { 81 | return v1.LazyValue{ 82 | Value: ContentBlink{ 83 | Blink: DataBlink{ 84 | ReqID: uuid.New().String(), 85 | Method: MethodPropertyGet, 86 | Version: DefaultVersion, 87 | Timestamp: getCurrentTimestamp(), 88 | Properties: properties, 89 | }, 90 | }, 91 | } 92 | } 93 | 94 | func (b *MsgBlink) GenLifecycleReportData(online bool) v1.LazyValue { 95 | return v1.LazyValue{ 96 | Value: ContentBlink{ 97 | Blink: DataBlink{ 98 | ReqID: uuid.New().String(), 99 | Method: MethodLifecyclePost, 100 | Version: DefaultVersion, 101 | Timestamp: getCurrentTimestamp(), 102 | Params: map[string]any{KeyOnlineState: online}, 103 | }, 104 | }, 105 | } 106 | } 107 | 108 | func getCurrentTimestamp() int64 { 109 | return time.Now().UnixNano() / 1e6 110 | } 111 | -------------------------------------------------------------------------------- /dmcontext/expression_test.go: -------------------------------------------------------------------------------- 1 | package dmcontext 2 | 3 | import ( 4 | "fmt" 5 | "os/exec" 6 | "strings" 7 | "testing" 8 | 9 | "github.com/stretchr/testify/assert" 10 | ) 11 | 12 | func TestParseExpression(t *testing.T) { 13 | args, err := ParseExpression("") 14 | assert.NoError(t, err) 15 | assert.Nil(t, args) 16 | 17 | args, err = ParseExpression("&***)") 18 | assert.Error(t, err) 19 | assert.Nil(t, args) 20 | 21 | args, err = ParseExpression("x1--x2") 22 | assert.Error(t, err) 23 | assert.Nil(t, args) 24 | 25 | args, err = ParseExpression("4/(1+2+1*3*10)") 26 | assert.NoError(t, err) 27 | assert.NotNil(t, args) 28 | assert.Equal(t, 0, len(args)) 29 | 30 | args, err = ParseExpression("x1") 31 | assert.NoError(t, err) 32 | assert.NotNil(t, args) 33 | assert.Equal(t, 1, len(args)) 34 | assert.Equal(t, "x1", args[0]) 35 | 36 | args, err = ParseExpression("x4/(x1+x2+x1*x3*10)") 37 | assert.NoError(t, err) 38 | assert.NotNil(t, args) 39 | assert.Equal(t, 5, len(args)) 40 | assert.Equal(t, "x1", args[1]) 41 | } 42 | 43 | func TestExecExpression(t *testing.T) { 44 | res, err := ExecExpression("", map[string]any{}, "test") 45 | assert.Error(t, err) 46 | assert.Nil(t, res) 47 | 48 | res, err = ExecExpression("", map[string]any{}, MappingNone) 49 | assert.NoError(t, err) 50 | assert.Nil(t, res) 51 | 52 | res, err = ExecExpression("", map[string]any{}, MappingValue) 53 | assert.Error(t, err) 54 | assert.Nil(t, res) 55 | res, err = ExecExpression("x1++x2", map[string]any{}, MappingValue) 56 | assert.Error(t, err) 57 | assert.Nil(t, res) 58 | res, err = ExecExpression("x1+x2", map[string]any{}, MappingValue) 59 | assert.Error(t, err) 60 | assert.Nil(t, res) 61 | res, err = ExecExpression("x1", map[string]any{"x2": 1}, MappingValue) 62 | assert.Error(t, err) 63 | assert.Nil(t, res) 64 | res, err = ExecExpression("x1", map[string]any{"x1": 1}, MappingValue) 65 | assert.NoError(t, err) 66 | assert.Equal(t, 1, res) 67 | res, err = ExecExpression("x1", map[string]any{"x1": "1"}, MappingValue) 68 | assert.NoError(t, err) 69 | assert.Equal(t, "1", res) 70 | res, err = processValueMappingWithPrecision("x1", map[string]any{"x1": "2"}, 2) 71 | assert.NoError(t, err) 72 | assert.Equal(t, "2", res) 73 | res, err = processValueMappingWithPrecision("x1", map[string]any{"x1": 2}, 2) 74 | assert.NoError(t, err) 75 | assert.Equal(t, float64(2), res) 76 | 77 | res, err = ExecExpression("", map[string]any{}, MappingCalculate) 78 | assert.Error(t, err) 79 | assert.Nil(t, res) 80 | res, err = ExecExpression("x1++x2", map[string]any{}, MappingCalculate) 81 | assert.Error(t, err) 82 | assert.Nil(t, res) 83 | res, err = ExecExpression("x4/(x1+x2+x1*x3*10)", map[string]any{"x1": 1}, MappingCalculate) 84 | assert.Error(t, err) 85 | assert.Nil(t, res) 86 | res, err = ExecExpression("x4/(x1+x2+x1*x3*10)", map[string]any{"x1": "asasd"}, MappingCalculate) 87 | assert.Error(t, err) 88 | assert.Nil(t, res) 89 | args1 := map[string]any{"x1": int16(1), "x2": float32(1.1), "x3": 0.99, "x4": int64(15)} 90 | res, err = ExecExpression("x4/(x1+x2+x1*x3*10)", args1, MappingCalculate) 91 | assert.NoError(t, err) 92 | assert.Equal(t, 1.249999997516473, res) 93 | args2 := map[string]any{"x1": int16(1), "x2": float32(1.1), "x3": 0.99, "x4": int64(15)} 94 | res, err = processCalcMappingWithPrecision("x4/(x1+x2+x1*x3*10)", args2, 2) 95 | assert.NoError(t, err) 96 | assert.Equal(t, 1.25, res) 97 | } 98 | 99 | func TestSolveExpression(t *testing.T) { 100 | zero := float64(0) 101 | 102 | res, err := SolveExpression("", 1) 103 | assert.Error(t, err) 104 | assert.Equal(t, zero, res) 105 | 106 | res, err = SolveExpression("x1*2+x2*3", 1) 107 | assert.Error(t, err) 108 | assert.Equal(t, zero, res) 109 | 110 | res, err = SolveExpression("(x1+2)*x1", 1) 111 | assert.Error(t, err) 112 | assert.Equal(t, zero, res) 113 | 114 | res, err = SolveExpression("1/(x1+2)", 1) 115 | assert.Error(t, err) 116 | assert.Equal(t, zero, res) 117 | 118 | res, err = SolveExpression("(x1+2)&x1", 1) 119 | assert.Error(t, err) 120 | assert.Equal(t, zero, res) 121 | 122 | res, err = SolveExpression("x1*2-x1*2+1", 1) 123 | assert.Error(t, err) 124 | assert.Equal(t, zero, res) 125 | 126 | res, err = SolveExpression("x1*2-11", 9) 127 | assert.NoError(t, err) 128 | assert.Equal(t, float64(10), res) 129 | 130 | res, err = SolveExpression("(x1+1)*3+x1*2+1", 9) 131 | assert.NoError(t, err) 132 | assert.Equal(t, float64(1), res) 133 | } 134 | 135 | func TestDmCtx_GetAccessTemplatesxxx(t *testing.T) { 136 | xx := "ls" 137 | ss := strings.Fields(xx) 138 | cm := xx 139 | args := []string{} 140 | if len(ss) > 1 { 141 | cm = ss[0] 142 | args = ss[1:] 143 | } 144 | fmt.Println(cm) 145 | fmt.Println(args) 146 | 147 | c := exec.Command(cm, args...) 148 | err := c.Run() 149 | fmt.Println(err) 150 | } 151 | -------------------------------------------------------------------------------- /dmcontext/format_test.go: -------------------------------------------------------------------------------- 1 | package dmcontext 2 | 3 | import ( 4 | "fmt" 5 | "reflect" 6 | "testing" 7 | "time" 8 | 9 | "github.com/stretchr/testify/assert" 10 | ) 11 | 12 | func TestParseValue(t *testing.T) { 13 | _, err := ParseValue("xx", nil, nil) 14 | assert.Error(t, err) 15 | 16 | args, err := ParseValue("int16", 1, nil) 17 | assert.NoError(t, err) 18 | assert.NotNil(t, args) 19 | assert.Equal(t, "int16", reflect.TypeOf(args).Name()) 20 | 21 | args, err = ParseValue("int32", 1, nil) 22 | assert.NoError(t, err) 23 | assert.NotNil(t, args) 24 | assert.Equal(t, "int32", reflect.TypeOf(args).Name()) 25 | 26 | args, err = ParseValue("int64", 1, nil) 27 | assert.NoError(t, err) 28 | assert.NotNil(t, args) 29 | assert.Equal(t, "int64", reflect.TypeOf(args).Name()) 30 | 31 | args, err = ParseValue("float32", 1.2, nil) 32 | assert.NoError(t, err) 33 | assert.NotNil(t, args) 34 | assert.Equal(t, "float32", reflect.TypeOf(args).Name()) 35 | 36 | args, err = ParseValue("float64", 1.2, nil) 37 | assert.NoError(t, err) 38 | assert.NotNil(t, args) 39 | assert.Equal(t, "float64", reflect.TypeOf(args).Name()) 40 | 41 | args, err = ParseValue("string", "1.2", nil) 42 | assert.NoError(t, err) 43 | assert.NotNil(t, args) 44 | assert.Equal(t, "string", reflect.TypeOf(args).Name()) 45 | 46 | args, err = ParseValue("time", "17.04.05", "hh:mm:ss") 47 | assert.NoError(t, err) 48 | assert.NotNil(t, args) 49 | assert.Equal(t, "string", reflect.TypeOf(args).Name()) 50 | 51 | args, err = ParseValue("time", "17.04.05", "HH:mm:ss") 52 | assert.NoError(t, err) 53 | assert.NotNil(t, args) 54 | assert.Equal(t, "string", reflect.TypeOf(args).Name()) 55 | 56 | args, err = ParseValue("time", "17.04.05", "SS:mm:ss") 57 | assert.Error(t, err) 58 | assert.Equal(t, "", args) 59 | 60 | ti, _ := time.Parse("2006-01-02", "2022-10-19") 61 | args, err = ParseValue("time", ti, "yyyy/mm/dd") 62 | assert.NoError(t, err) 63 | assert.NotNil(t, args) 64 | assert.Equal(t, "string", reflect.TypeOf(args).Name()) 65 | assert.Equal(t, "2022/10/19", args) 66 | 67 | arrayType := ArrayType{ 68 | Type: TypeString, 69 | Min: 2, 70 | Max: 4, 71 | } 72 | testArray := [3]int{1, 2} 73 | args, err = ParseValue("array", testArray, arrayType) 74 | assert.NoError(t, err) 75 | assert.NotNil(t, args) 76 | arg := args.([]any) 77 | fmt.Println(arg[0]) 78 | assert.Equal(t, "1", arg[0]) 79 | 80 | testArray2 := [1]int{1} 81 | args, err = ParseValue("array", testArray2, arrayType) 82 | assert.Error(t, err) 83 | assert.Nil(t, args) 84 | 85 | enum := EnumType{ 86 | Type: "string", 87 | Values: []EnumValue{{"1", "Fire", "火灾"}}, 88 | } 89 | args, err = ParseValue("enum", "Fire", enum) 90 | assert.NoError(t, err) 91 | assert.NotNil(t, args) 92 | assert.Equal(t, "1", args) 93 | 94 | enum1 := EnumType{ 95 | Type: "xx", 96 | Values: []EnumValue{{"1", "Fire", "火灾"}}, 97 | } 98 | args, err = ParseValue("enum", "Fire", enum1) 99 | assert.Error(t, err) 100 | assert.Equal(t, "", args) 101 | 102 | ob := map[string]ObjectType{ 103 | "name": {DisplayName: "demo", Type: "string"}, 104 | "age": {DisplayName: "demo", Type: "int"}, 105 | "class": {DisplayName: "demo", Type: "string"}, 106 | } 107 | test := map[string]any{ 108 | "name": "te", 109 | "age": 12, 110 | } 111 | args, err = ParseValue("object", test, ob) 112 | assert.NoError(t, err) 113 | assert.NotNil(t, args) 114 | testMap := args.(map[string]any) 115 | assert.Equal(t, 12, testMap["age"]) 116 | } 117 | -------------------------------------------------------------------------------- /dmcontext/northlink.go: -------------------------------------------------------------------------------- 1 | package dmcontext 2 | 3 | import v1 "github.com/baetyl/baetyl-go/v2/spec/v1" 4 | 5 | const ( 6 | Blink = "blink" 7 | ) 8 | 9 | type Msg interface { 10 | GenPropertyReportData(properties map[string]any) v1.LazyValue 11 | GenEventReportData(properties map[string]any) v1.LazyValue 12 | GenLifecycleReportData(online bool) v1.LazyValue 13 | GenDeltaBlinkData(properties map[string]interface{}) v1.LazyValue 14 | GenPropertyGetBlinkData(properties []string) v1.LazyValue 15 | } 16 | 17 | func InitMsg(msg string) Msg { 18 | switch msg { 19 | case Blink: 20 | return &MsgBlink{} 21 | } 22 | return &MsgBlink{} 23 | } 24 | -------------------------------------------------------------------------------- /dmcontext/run.go: -------------------------------------------------------------------------------- 1 | package dmcontext 2 | 3 | import ( 4 | "flag" 5 | "os" 6 | "runtime/debug" 7 | 8 | "github.com/baetyl/baetyl-go/v2/log" 9 | "github.com/baetyl/baetyl-go/v2/utils" 10 | ) 11 | 12 | // Run service 13 | func Run(handle func(Context) error) { 14 | utils.PrintVersion() 15 | 16 | var h bool 17 | var c string 18 | flag.BoolVar(&h, "h", false, "this help") 19 | flag.StringVar(&c, "c", "etc/baetyl/conf.yml", "the configuration file") 20 | flag.Parse() 21 | if h { 22 | flag.Usage() 23 | return 24 | } 25 | 26 | ctx := NewContext(c) 27 | defer func() { 28 | if r := recover(); r != nil { 29 | ctx.Log().Error("service is stopped with panic", log.Any("panic", r), log.Any("stack", string(debug.Stack()))) 30 | } 31 | }() 32 | 33 | pwd, _ := os.Getwd() 34 | ctx.Log().Info("service starting", log.Any("args", os.Args), log.Any("pwd", pwd)) 35 | err := handle(ctx) 36 | if err != nil { 37 | ctx.Log().Error("service has stopped with error", log.Error(err)) 38 | } else { 39 | ctx.Log().Info("service has stopped") 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /errors/error.go: -------------------------------------------------------------------------------- 1 | package errors 2 | 3 | import ( 4 | "fmt" 5 | 6 | "github.com/pkg/errors" 7 | ) 8 | 9 | type Coder interface { 10 | Code() string 11 | } 12 | 13 | func New(message string) error { 14 | return errors.New(message) 15 | } 16 | 17 | func Errorf(format string, args ...interface{}) error { 18 | return errors.Errorf(format, args...) 19 | } 20 | 21 | func Cause(err error) error { 22 | return errors.Cause(err) 23 | } 24 | 25 | func Trace(err error) error { 26 | if err == nil { 27 | return nil 28 | } 29 | switch err.(type) { 30 | case fmt.Formatter: 31 | return err 32 | default: 33 | return errors.WithStack(err) 34 | } 35 | } 36 | 37 | func CodeError(code, message string) error { 38 | return &codeError{errors.New(message), code} 39 | } 40 | 41 | type codeError struct { 42 | e error 43 | c string 44 | } 45 | 46 | func (e *codeError) Code() string { 47 | return e.c 48 | } 49 | 50 | func (e *codeError) Error() string { 51 | return e.e.Error() 52 | } 53 | 54 | func (e *codeError) Format(s fmt.State, verb rune) { 55 | e.e.(fmt.Formatter).Format(s, verb) 56 | } 57 | -------------------------------------------------------------------------------- /errors/errors_test.go: -------------------------------------------------------------------------------- 1 | package errors 2 | 3 | import ( 4 | goerrors "errors" 5 | "fmt" 6 | "testing" 7 | 8 | "github.com/stretchr/testify/assert" 9 | ) 10 | 11 | func TestErrors(t *testing.T) { 12 | assert.NotEqual(t, CodeError("c", "abc"), CodeError("c", "abc")) 13 | assert.NotEqual(t, CodeError("c", "abc"), CodeError("c", "xyc")) 14 | assert.NotEqual(t, CodeError("c", "abc"), CodeError("b", "abc")) 15 | 16 | e := CodeError("c", "abc") 17 | assert.Equal(t, e, Trace(e)) 18 | assert.Equal(t, e, Cause(e)) 19 | assert.EqualError(t, e, "abc") 20 | assert.Equal(t, e.(Coder).Code(), "c") 21 | assert.Contains(t, fmt.Sprintf("%+v", e), "baetyl-go/errors/error.go") 22 | _, ok := e.(fmt.Formatter) 23 | assert.True(t, ok) 24 | 25 | e2 := goerrors.New("ddd") 26 | w2 := Trace(e2) 27 | assert.NotEqual(t, e2, w2) 28 | assert.Equal(t, e2, Cause(w2)) 29 | assert.EqualError(t, e2, "ddd") 30 | assert.EqualError(t, w2, "ddd") 31 | assert.Equal(t, "ddd", fmt.Sprintf("%+v", e2)) 32 | assert.Contains(t, fmt.Sprintf("%+v", w2), "baetyl-go/errors/error.go") 33 | _, ok = e2.(Coder) 34 | assert.False(t, ok) 35 | _, ok = w2.(Coder) 36 | assert.False(t, ok) 37 | _, ok = e2.(fmt.Formatter) 38 | assert.False(t, ok) 39 | _, ok = w2.(fmt.Formatter) 40 | assert.True(t, ok) 41 | 42 | e3 := New("edc") 43 | e4 := Errorf("rfv%s", "1") 44 | assert.Equal(t, e3, Trace(e3)) 45 | assert.Equal(t, e4, Trace(e4)) 46 | assert.Equal(t, e3, Cause(e3)) 47 | assert.Equal(t, e4, Cause(e4)) 48 | assert.Contains(t, fmt.Sprintf("%+v", e3), "baetyl-go/errors/error.go") 49 | assert.Contains(t, fmt.Sprintf("%+v", e4), "baetyl-go/errors/error.go") 50 | 51 | assert.Nil(t, Trace(nil)) 52 | assert.Nil(t, Cause(nil)) 53 | } 54 | -------------------------------------------------------------------------------- /example/etc/baetyl/service.yml: -------------------------------------------------------------------------------- 1 | function: 2 | address: https://baetyl-function:8880 3 | broker: 4 | address: ssl://baetyl-broker:8883 5 | cert: 6 | ca: example/var/lib/baetyl/testcert/ca.pem 7 | key: example/var/lib/baetyl/testcert/client.key 8 | cert: example/var/lib/baetyl/testcert/client.pem 9 | logger: 10 | filename: "var/log/service.log" 11 | level: "debug" 12 | encoding: "console" 13 | -------------------------------------------------------------------------------- /example/var/lib/baetyl/testcert/ca.crt: -------------------------------------------------------------------------------- 1 | -----BEGIN CERTIFICATE----- 2 | MIICfzCCAiSgAwIBAgIIFizowlvYkxgwCgYIKoZIzj0EAwIwgaUxCzAJBgNVBAYT 3 | AkNOMRAwDgYDVQQIEwdCZWlqaW5nMRkwFwYDVQQHExBIYWlkaWFuIERpc3RyaWN0 4 | MRUwEwYDVQQJEwxCYWlkdSBDYW1wdXMxDzANBgNVBBETBjEwMDA5MzEeMBwGA1UE 5 | ChMVTGludXggRm91bmRhdGlvbiBFZGdlMQ8wDQYDVQQLEwZCQUVUWUwxEDAOBgNV 6 | BAMTB3Jvb3QuY2EwIBcNMjAwODIwMDcxODA5WhgPMjA3MDA4MDgwNzE4MDlaMIGl 7 | MQswCQYDVQQGEwJDTjEQMA4GA1UECBMHQmVpamluZzEZMBcGA1UEBxMQSGFpZGlh 8 | biBEaXN0cmljdDEVMBMGA1UECRMMQmFpZHUgQ2FtcHVzMQ8wDQYDVQQREwYxMDAw 9 | OTMxHjAcBgNVBAoTFUxpbnV4IEZvdW5kYXRpb24gRWRnZTEPMA0GA1UECxMGQkFF 10 | VFlMMRAwDgYDVQQDEwdyb290LmNhMFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAE 11 | 3GSIw55wTQIaVWSD2fePbIts9pToj9OtiyG0/1zlvkht1Go2yCGc0xwaoR0YdW1H 12 | Fi1jpzMfmvJhppQaz5F6F6M6MDgwDgYDVR0PAQH/BAQDAgGGMA8GA1UdEwEB/wQF 13 | MAMBAf8wFQYDVR0RBA4wDIcEAAAAAIcEfwAAATAKBggqhkjOPQQDAgNJADBGAiEA 14 | qaeTS1oKts1XiC6eWkuK0n6TH45yWJvC3/NU6PqpBSYCIQDIHGDb3OL+4OsUitvb 15 | svDCT14MNf0cgIeg7gO+D0Xvqg== 16 | -----END CERTIFICATE----- 17 | -------------------------------------------------------------------------------- /example/var/lib/baetyl/testcert/client.crt: -------------------------------------------------------------------------------- 1 | -----BEGIN CERTIFICATE----- 2 | MIIClzCCAj2gAwIBAgIIFizowlwFTFAwCgYIKoZIzj0EAwIwgaUxCzAJBgNVBAYT 3 | AkNOMRAwDgYDVQQIEwdCZWlqaW5nMRkwFwYDVQQHExBIYWlkaWFuIERpc3RyaWN0 4 | MRUwEwYDVQQJEwxCYWlkdSBDYW1wdXMxDzANBgNVBBETBjEwMDA5MzEeMBwGA1UE 5 | ChMVTGludXggRm91bmRhdGlvbiBFZGdlMQ8wDQYDVQQLEwZCQUVUWUwxEDAOBgNV 6 | BAMTB3Jvb3QuY2EwHhcNMjAwODIwMDcxODA5WhcNNDAwODE1MDcxODA5WjCBpDEL 7 | MAkGA1UEBhMCQ04xEDAOBgNVBAgTB0JlaWppbmcxGTAXBgNVBAcTEEhhaWRpYW4g 8 | RGlzdHJpY3QxFTATBgNVBAkTDEJhaWR1IENhbXB1czEPMA0GA1UEERMGMTAwMDkz 9 | MR4wHAYDVQQKExVMaW51eCBGb3VuZGF0aW9uIEVkZ2UxDzANBgNVBAsTBkJBRVRZ 10 | TDEPMA0GA1UEAxMGY2xpZW50MFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAErp2K 11 | LVIYfqeCzJlR/gteIZyN7i1/ckXuuXNO1i2GGu/bFdkoj1ST1ypj1FuY/WpdmwSQ 12 | HBIVm42s1vf0Gnc7yKNWMFQwDgYDVR0PAQH/BAQDAgWgMB0GA1UdJQQWMBQGCCsG 13 | AQUFBwMCBggrBgEFBQcDATAMBgNVHRMBAf8EAjAAMBUGA1UdEQQOMAyHBAAAAACH 14 | BH8AAAEwCgYIKoZIzj0EAwIDSAAwRQIgOVxan95heTLe3c20iUvPJmX1EPMvfg6J 15 | 5GeWeK2cA8QCIQCfO6xOoQj386u+7XD4K4srGdFj77f9tfWt/M6ryIscdA== 16 | -----END CERTIFICATE----- 17 | -------------------------------------------------------------------------------- /example/var/lib/baetyl/testcert/client.key: -------------------------------------------------------------------------------- 1 | -----BEGIN EC PRIVATE KEY----- 2 | MHcCAQEEIAwqz3MtAG2O5xaKy2RXWLxcOpKYKWilCs89L27+6it1oAoGCCqGSM49 3 | AwEHoUQDQgAErp2KLVIYfqeCzJlR/gteIZyN7i1/ckXuuXNO1i2GGu/bFdkoj1ST 4 | 1ypj1FuY/WpdmwSQHBIVm42s1vf0Gnc7yA== 5 | -----END EC PRIVATE KEY----- 6 | -------------------------------------------------------------------------------- /example/var/lib/baetyl/testcert/server.crt: -------------------------------------------------------------------------------- 1 | -----BEGIN CERTIFICATE----- 2 | MIICojCCAkigAwIBAgIIFizowlwTTkAwCgYIKoZIzj0EAwIwgaUxCzAJBgNVBAYT 3 | AkNOMRAwDgYDVQQIEwdCZWlqaW5nMRkwFwYDVQQHExBIYWlkaWFuIERpc3RyaWN0 4 | MRUwEwYDVQQJEwxCYWlkdSBDYW1wdXMxDzANBgNVBBETBjEwMDA5MzEeMBwGA1UE 5 | ChMVTGludXggRm91bmRhdGlvbiBFZGdlMQ8wDQYDVQQLEwZCQUVUWUwxEDAOBgNV 6 | BAMTB3Jvb3QuY2EwHhcNMjAwODIwMDcxODA5WhcNNDAwODE1MDcxODA5WjCBpDEL 7 | MAkGA1UEBhMCQ04xEDAOBgNVBAgTB0JlaWppbmcxGTAXBgNVBAcTEEhhaWRpYW4g 8 | RGlzdHJpY3QxFTATBgNVBAkTDEJhaWR1IENhbXB1czEPMA0GA1UEERMGMTAwMDkz 9 | MR4wHAYDVQQKExVMaW51eCBGb3VuZGF0aW9uIEVkZ2UxDzANBgNVBAsTBkJBRVRZ 10 | TDEPMA0GA1UEAxMGc2VydmVyMFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEQXCQ 11 | TGn4+frJYOumFk8gs8BIbgduEuiHonhYdJTFGIPLiOqPQoIvDmICod7W0oIzYYXw 12 | TF4NfadliSryoXx9IaNhMF8wDgYDVR0PAQH/BAQDAgWgMB0GA1UdJQQWMBQGCCsG 13 | AQUFBwMCBggrBgEFBQcDATAMBgNVHRMBAf8EAjAAMCAGA1UdEQQZMBeCCWxvY2Fs 14 | aG9zdIcEAAAAAIcEfwAAATAKBggqhkjOPQQDAgNIADBFAiB5vz8+oob7SkN54uf7 15 | RErbE4tWT5AHtgBgIs3A+TjnyQIhAPvnL8W1dq4qdkVr0eiH5He0xNHdsQc6eWxS 16 | RcKyjhh1 17 | -----END CERTIFICATE----- 18 | -------------------------------------------------------------------------------- /example/var/lib/baetyl/testcert/server.key: -------------------------------------------------------------------------------- 1 | -----BEGIN EC PRIVATE KEY----- 2 | MHcCAQEEIPcuJ/l+/s8PXvAN5M6VNBZrKD4HDW6n6Y4rQCYinF5doAoGCCqGSM49 3 | AwEHoUQDQgAEQXCQTGn4+frJYOumFk8gs8BIbgduEuiHonhYdJTFGIPLiOqPQoIv 4 | DmICod7W0oIzYYXwTF4NfadliSryoXx9IQ== 5 | -----END EC PRIVATE KEY----- 6 | -------------------------------------------------------------------------------- /faas/function.proto: -------------------------------------------------------------------------------- 1 | syntax = "proto3"; 2 | 3 | package faas; 4 | 5 | option (gogoproto.sizer_all) = true; 6 | option (gogoproto.marshaler_all) = true; 7 | option (gogoproto.unmarshaler_all) = true; 8 | option (gogoproto.goproto_getters_all) = false; 9 | option (gogoproto.testgen_all) = true; 10 | option (gogoproto.benchgen_all) = true; 11 | option (gogoproto.populate_all) = true; 12 | option (gogoproto.equal_all) = true; 13 | option (gogoproto.gostring_all) = true; 14 | // option (gogoproto.verbose_equal_all) = true; 15 | // option (gogoproto.goproto_stringer_all) = false; 16 | // option (gogoproto.stringer_all) = true; 17 | 18 | import "github.com/gogo/protobuf/gogoproto/gogo.proto"; 19 | 20 | // The function server definition. 21 | service Function { 22 | rpc Call(Message) returns (Message) {} 23 | } 24 | 25 | // Message message 26 | message Message { 27 | uint64 ID = 1; // message id 28 | map Metadata = 2; // Metadata 29 | bytes Payload = 3; // Payload 30 | } 31 | 32 | // protoc -I=. -I=$GOPATH/src -I=$GOPATH/src/github.com/gogo/protobuf/protobuf --gogofaster_out=plugins=grpc:. function.proto -------------------------------------------------------------------------------- /go.mod: -------------------------------------------------------------------------------- 1 | module github.com/baetyl/baetyl-go/v2 2 | 3 | go 1.18 4 | 5 | require ( 6 | github.com/256dpi/gomqtt v0.14.3 7 | github.com/conduitio/bwlimit v0.1.0 8 | github.com/containerd/containerd v1.5.18 9 | github.com/creasty/defaults v1.4.0 10 | github.com/crsmithdev/goexpr v0.0.0-20150309021426-69a8c42346f1 11 | github.com/denisbrodbeck/machineid v1.0.1 12 | github.com/docker/go-connections v0.4.0 13 | github.com/docker/go-units v0.4.0 14 | github.com/evanphx/json-patch v4.9.0+incompatible 15 | github.com/fsnotify/fsnotify v1.4.9 16 | github.com/gin-gonic/gin v1.9.0 17 | github.com/go-playground/validator/v10 v10.11.2 18 | github.com/go-redis/redis/v8 v8.11.5 19 | github.com/gogo/protobuf v1.3.2 20 | github.com/google/uuid v1.2.0 21 | github.com/gorilla/websocket v1.4.1 22 | github.com/jinzhu/copier v0.1.0 23 | github.com/jpillora/backoff v1.0.0 24 | github.com/json-iterator/go v1.1.12 25 | github.com/mholt/archiver v3.1.1+incompatible 26 | github.com/mitchellh/mapstructure v1.1.2 27 | github.com/opencontainers/image-spec v1.0.2 28 | github.com/panjf2000/ants/v2 v2.8.1 29 | github.com/pkg/errors v0.9.1 30 | github.com/qiangxue/fasthttp-routing v0.0.0-20160225050629-6ccdc2a18d87 31 | github.com/robfig/go-cache v0.0.0-20130306151617-9fc39e0dbf62 32 | github.com/satori/go.uuid v1.2.0 33 | github.com/spf13/cast v1.3.0 34 | github.com/stretchr/testify v1.8.1 35 | github.com/super-l/machine-code v0.0.0-20210720085303-62525d58dab0 36 | github.com/valyala/fasthttp v1.34.0 37 | go.uber.org/zap v1.16.0 38 | golang.org/x/sync v0.1.0 39 | google.golang.org/grpc v1.33.2 40 | gopkg.in/natefinch/lumberjack.v2 v2.0.0 41 | gopkg.in/tomb.v2 v2.0.0-20161208151619-d5d1b5820637 42 | gopkg.in/yaml.v2 v2.4.0 43 | gotest.tools v2.2.0+incompatible 44 | k8s.io/api v0.20.6 45 | k8s.io/apimachinery v0.20.6 46 | ) 47 | 48 | require ( 49 | github.com/256dpi/mercury v0.2.0 // indirect 50 | github.com/andybalholm/brotli v1.0.4 // indirect 51 | github.com/bytedance/sonic v1.8.0 // indirect 52 | github.com/cespare/xxhash/v2 v2.1.2 // indirect 53 | github.com/chenzhuoyu/base64x v0.0.0-20221115062448-fe3a3abad311 // indirect 54 | github.com/davecgh/go-spew v1.1.1 // indirect 55 | github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f // indirect 56 | github.com/dsnet/compress v0.0.1 // indirect 57 | github.com/frankban/quicktest v1.11.3 // indirect 58 | github.com/gin-contrib/sse v0.1.0 // indirect 59 | github.com/go-logr/logr v0.2.0 // indirect 60 | github.com/go-ozzo/ozzo-routing v2.1.4+incompatible // indirect 61 | github.com/go-playground/locales v0.14.1 // indirect 62 | github.com/go-playground/universal-translator v0.18.1 // indirect 63 | github.com/goccy/go-json v0.10.0 // indirect 64 | github.com/golang/gddo v0.0.0-20200611223618-a4829ef13274 // indirect 65 | github.com/golang/protobuf v1.5.2 // indirect 66 | github.com/golang/snappy v0.0.1 // indirect 67 | github.com/google/go-cmp v0.5.6 // indirect 68 | github.com/google/gofuzz v1.1.0 // indirect 69 | github.com/klauspost/compress v1.15.0 // indirect 70 | github.com/klauspost/cpuid/v2 v2.0.9 // indirect 71 | github.com/leodido/go-urn v1.2.1 // indirect 72 | github.com/mattn/go-isatty v0.0.17 // indirect 73 | github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect 74 | github.com/modern-go/reflect2 v1.0.2 // indirect 75 | github.com/nwaples/rardecode v1.1.0 // indirect 76 | github.com/opencontainers/go-digest v1.0.0 // indirect 77 | github.com/pelletier/go-toml/v2 v2.0.6 // indirect 78 | github.com/pierrec/lz4 v2.5.2+incompatible // indirect 79 | github.com/pmezard/go-difflib v1.0.0 // indirect 80 | github.com/sirupsen/logrus v1.8.1 // indirect 81 | github.com/twitchyliquid64/golang-asm v0.15.1 // indirect 82 | github.com/ugorji/go/codec v1.2.9 // indirect 83 | github.com/ulikunitz/xz v0.5.8 // indirect 84 | github.com/valyala/bytebufferpool v1.0.0 // indirect 85 | github.com/xi2/xz v0.0.0-20171230120015-48954b6210f8 // indirect 86 | go.uber.org/atomic v1.6.0 // indirect 87 | go.uber.org/multierr v1.5.0 // indirect 88 | golang.org/x/arch v0.0.0-20210923205945-b76863e36670 // indirect 89 | golang.org/x/crypto v0.5.0 // indirect 90 | golang.org/x/lint v0.0.0-20201208152925-83fdc39ff7b5 // indirect 91 | golang.org/x/net v0.7.0 // indirect 92 | golang.org/x/sys v0.5.0 // indirect 93 | golang.org/x/text v0.7.0 // indirect 94 | golang.org/x/time v0.3.0 // indirect 95 | google.golang.org/genproto v0.0.0-20201110150050-8816d57aaa9a // indirect 96 | google.golang.org/protobuf v1.28.1 // indirect 97 | gopkg.in/inf.v0 v0.9.1 // indirect 98 | gopkg.in/yaml.v3 v3.0.1 // indirect 99 | k8s.io/klog/v2 v2.4.0 // indirect 100 | sigs.k8s.io/structured-merge-diff/v4 v4.0.3 // indirect 101 | ) 102 | -------------------------------------------------------------------------------- /http/client_test.go: -------------------------------------------------------------------------------- 1 | package http 2 | 3 | import ( 4 | "bytes" 5 | "testing" 6 | "time" 7 | 8 | "github.com/stretchr/testify/assert" 9 | 10 | "github.com/baetyl/baetyl-go/v2/mock" 11 | "github.com/baetyl/baetyl-go/v2/utils" 12 | ) 13 | 14 | func TestClientRequests(t *testing.T) { 15 | tlssvr, err := utils.NewTLSConfigServer(utils.Certificate{CA: "../mock/testcert/ca.pem", Key: "../mock/testcert/server.key", Cert: "../mock/testcert/server.pem"}) 16 | assert.NoError(t, err) 17 | assert.NotNil(t, tlssvr) 18 | 19 | response := mock.NewResponse(200, []byte("abc")) 20 | ms := mock.NewServer(tlssvr, response, response, response) 21 | defer ms.Close() 22 | 23 | var cfg ClientConfig 24 | utils.UnmarshalYAML(nil, &cfg) 25 | cfg.CA = "../mock/testcert/ca.pem" 26 | cfg.Key = "../mock/testcert/client.key" 27 | cfg.Cert = "../mock/testcert/client.pem" 28 | cfg.InsecureSkipVerify = true 29 | cfg.Address = ms.URL 30 | ops, err := cfg.ToClientOptions() 31 | assert.NoError(t, err) 32 | assert.NotNil(t, ops) 33 | c := NewClient(ops) 34 | resp, err := c.Call("service/function", []byte("{}")) 35 | assert.NoError(t, err) 36 | assert.Equal(t, "abc", string(resp)) 37 | 38 | data, err := c.PostJSON("v1", []byte("{}")) 39 | assert.NoError(t, err) 40 | assert.Equal(t, "abc", string(data)) 41 | 42 | data, err = c.GetJSON("v1") 43 | assert.NoError(t, err) 44 | assert.Equal(t, "abc", string(data)) 45 | } 46 | 47 | func TestClientBadRequests(t *testing.T) { 48 | response := mock.NewResponse(400, []byte("abc")) 49 | ms := mock.NewServer(nil, response, response, response) 50 | defer ms.Close() 51 | 52 | ops := NewClientOptions() 53 | ops.Address = ms.URL 54 | c := NewClient(ops) 55 | 56 | data, err := c.Call("service/function", []byte("{}")) 57 | assert.EqualError(t, err, "[400] abc") 58 | assert.Equal(t, "abc", string(data)) 59 | 60 | data, err = c.GetJSON(ms.URL) 61 | assert.EqualError(t, err, "[400] abc") 62 | assert.Equal(t, "abc", string(data)) 63 | 64 | data, err = c.PostJSON(ms.URL, []byte("abc")) 65 | assert.EqualError(t, err, "[400] abc") 66 | assert.Equal(t, "abc", string(data)) 67 | 68 | ops.SyncMaxConcurrency = 10 69 | c = NewClient(ops) 70 | result := make(chan *SyncResults, 1000) 71 | for i := 0; i < 100; i++ { 72 | c.SyncSendUrl("GET", ms.URL, nil, result, map[string]interface{}{}) 73 | } 74 | time.Sleep(time.Second * 2) 75 | assert.Equal(t, len(result), 100) 76 | 77 | } 78 | 79 | func TestSendURL(t *testing.T) { 80 | resp := []*mock.Response{ 81 | mock.NewResponse(200, []byte("Get")), 82 | mock.NewResponse(200, []byte("Post")), 83 | mock.NewResponse(200, []byte("Put")), 84 | mock.NewResponse(200, []byte("Put")), 85 | mock.NewResponse(200, []byte("Delete")), 86 | } 87 | ms := mock.NewServer(nil, resp...) 88 | defer ms.Close() 89 | 90 | header := map[string]string{ 91 | "a": "b", 92 | } 93 | 94 | cli := NewClient(NewClientOptions()) 95 | res, err := cli.GetURL(ms.URL, header) 96 | assert.NoError(t, err) 97 | data, err := HandleResponse(res) 98 | assert.NoError(t, err) 99 | assert.Equal(t, []byte("Get"), data) 100 | 101 | res, err = cli.PostURL(ms.URL, bytes.NewReader([]byte("body")), header) 102 | assert.NoError(t, err) 103 | data, err = HandleResponse(res) 104 | assert.NoError(t, err) 105 | assert.Equal(t, []byte("Post"), data) 106 | 107 | res, err = cli.GetURL(ms.URL, header) 108 | assert.NoError(t, err) 109 | data, err = HandleResponse(res) 110 | assert.NoError(t, err) 111 | assert.Equal(t, []byte("Put"), data) 112 | 113 | res, err = cli.PutURL(ms.URL, bytes.NewReader([]byte("body")), header) 114 | assert.NoError(t, err) 115 | data, err = HandleResponse(res) 116 | assert.NoError(t, err) 117 | assert.Equal(t, []byte("Put"), data) 118 | 119 | res, err = cli.DeleteURL(ms.URL, header) 120 | assert.NoError(t, err) 121 | data, err = HandleResponse(res) 122 | assert.NoError(t, err) 123 | assert.Equal(t, []byte("Delete"), data) 124 | } 125 | -------------------------------------------------------------------------------- /http/options.go: -------------------------------------------------------------------------------- 1 | package http 2 | 3 | import ( 4 | "crypto/tls" 5 | "net/http" 6 | "time" 7 | 8 | "github.com/baetyl/baetyl-go/v2/errors" 9 | "github.com/baetyl/baetyl-go/v2/utils" 10 | ) 11 | 12 | const ( 13 | ByteUnitKB = "KB" 14 | ByteUnitMB = "MB" 15 | ) 16 | 17 | type SyncResults struct { 18 | Url string 19 | Body []byte 20 | Err error 21 | Response *http.Response 22 | SendCost time.Duration 23 | SyncCost time.Duration 24 | Extra map[string]interface{} 25 | } 26 | 27 | // ServerConfig server config 28 | type ServerConfig struct { 29 | Address string `yaml:"address" json:"address" default:":80"` 30 | Concurrency int `yaml:"concurrency" json:"concurrency"` 31 | DisableKeepalive bool `yaml:"disableKeepalive" json:"disableKeepalive"` 32 | TCPKeepalive bool `yaml:"tcpKeepalive" json:"tcpKeepalive"` 33 | MaxRequestBodySize int `yaml:"maxRequestBodySize" json:"maxRequestBodySize"` 34 | ReadTimeout time.Duration `yaml:"readTimeout" json:"readTimeout"` 35 | WriteTimeout time.Duration `yaml:"writeTimeout" json:"writeTimeout"` 36 | IdleTimeout time.Duration `yaml:"idleTimeout" json:"idleTimeout"` 37 | utils.Certificate `yaml:",inline" json:",inline"` 38 | } 39 | 40 | // ClientOptions client options 41 | type ClientOptions struct { 42 | Address string 43 | TLSConfig *tls.Config 44 | Timeout time.Duration 45 | KeepAlive time.Duration 46 | MaxIdleConns int 47 | IdleConnTimeout time.Duration 48 | TLSHandshakeTimeout time.Duration 49 | ExpectContinueTimeout time.Duration 50 | SpeedLimit int 51 | ByteUnit string 52 | SyncMaxConcurrency int 53 | } 54 | 55 | // NewClientOptions creates client options with default values 56 | func NewClientOptions() *ClientOptions { 57 | return &ClientOptions{ 58 | Timeout: 30 * time.Second, 59 | KeepAlive: 30 * time.Second, 60 | MaxIdleConns: 100, 61 | IdleConnTimeout: 90 * time.Second, 62 | TLSHandshakeTimeout: 10 * time.Second, 63 | ExpectContinueTimeout: 1 * time.Second, 64 | } 65 | } 66 | 67 | // ClientConfig client config 68 | type ClientConfig struct { 69 | Address string `yaml:"address" json:"address"` 70 | Timeout time.Duration `yaml:"timeout" json:"timeout" default:"30s"` 71 | KeepAlive time.Duration `yaml:"keepalive" json:"keepalive" default:"30s"` 72 | MaxIdleConns int `yaml:"maxIdleConns" json:"maxIdleConns" default:"100"` 73 | IdleConnTimeout time.Duration `yaml:"idleConnTimeout" json:"idleConnTimeout" default:"90s"` 74 | TLSHandshakeTimeout time.Duration `yaml:"tlsHandshakeTimeout" json:"tlsHandshakeTimeout" default:"10s"` 75 | ExpectContinueTimeout time.Duration `yaml:"expectContinueTimeout" json:"expectContinueTimeout" default:"1s"` 76 | ByteUnit string `yaml:"byteUnit" json:"byteUnit" default:"KB"` 77 | SpeedLimit int `yaml:"speedLimit" json:"speedLimit" default:"0"` 78 | SyncMaxConcurrency int `yaml:"syncMaxConcurrency" json:"syncMaxConcurrency" default:"0"` 79 | utils.Certificate `yaml:",inline" json:",inline"` 80 | } 81 | 82 | // ToClientOptions converts client config to client options 83 | func (cc ClientConfig) ToClientOptions() (*ClientOptions, error) { 84 | tlsConfig, err := utils.NewTLSConfigClient(cc.Certificate) 85 | if err != nil { 86 | return nil, errors.Trace(err) 87 | } 88 | return &ClientOptions{ 89 | Address: cc.Address, 90 | Timeout: cc.Timeout, 91 | TLSConfig: tlsConfig, 92 | KeepAlive: cc.KeepAlive, 93 | MaxIdleConns: cc.MaxIdleConns, 94 | IdleConnTimeout: cc.IdleConnTimeout, 95 | TLSHandshakeTimeout: cc.TLSHandshakeTimeout, 96 | ExpectContinueTimeout: cc.ExpectContinueTimeout, 97 | SpeedLimit: cc.SpeedLimit, 98 | ByteUnit: cc.ByteUnit, 99 | SyncMaxConcurrency: cc.SyncMaxConcurrency, 100 | }, nil 101 | } 102 | 103 | // ToClientOptionsWithPassphrase converts client config to client options with passphrase 104 | func (cc ClientConfig) ToClientOptionsWithPassphrase() (*ClientOptions, error) { 105 | tlsConfig, err := utils.NewTLSConfigClientWithPassphrase(cc.Certificate) 106 | if err != nil { 107 | return nil, errors.Trace(err) 108 | } 109 | return &ClientOptions{ 110 | Address: cc.Address, 111 | Timeout: cc.Timeout, 112 | TLSConfig: tlsConfig, 113 | KeepAlive: cc.KeepAlive, 114 | MaxIdleConns: cc.MaxIdleConns, 115 | IdleConnTimeout: cc.IdleConnTimeout, 116 | TLSHandshakeTimeout: cc.TLSHandshakeTimeout, 117 | ExpectContinueTimeout: cc.ExpectContinueTimeout, 118 | SpeedLimit: cc.SpeedLimit, 119 | ByteUnit: cc.ByteUnit, 120 | SyncMaxConcurrency: cc.SyncMaxConcurrency, 121 | }, nil 122 | } 123 | -------------------------------------------------------------------------------- /http/responses.go: -------------------------------------------------------------------------------- 1 | package http 2 | 3 | import ( 4 | "encoding/json" 5 | "io" 6 | 7 | routing "github.com/qiangxue/fasthttp-routing" 8 | ) 9 | 10 | const ( 11 | jsonContentTypeHeader = "application/json" 12 | ) 13 | 14 | // Response Response 15 | type Response struct { 16 | Code string `json:"code"` 17 | Message string `json:"message"` 18 | } 19 | 20 | // NewResponse NewResponse 21 | func NewResponse(code, msg string) Response { 22 | return Response{ 23 | Code: code, 24 | Message: msg, 25 | } 26 | } 27 | 28 | // RespondMsg RespondMsg 29 | func RespondMsg(c *routing.Context, httpCode int, code, msg string) { 30 | resp := NewResponse(code, msg) 31 | b, _ := json.Marshal(&resp) 32 | Respond(c, httpCode, b) 33 | } 34 | 35 | // Respond Respond 36 | func Respond(c *routing.Context, httpCode int, obj []byte) { 37 | c.RequestCtx.Response.SetStatusCode(httpCode) 38 | c.RequestCtx.Response.SetBody(obj) 39 | if json.Valid(obj) { 40 | c.RequestCtx.Response.Header.SetContentType(jsonContentTypeHeader) 41 | } 42 | } 43 | 44 | // RespondStream RespondStream 45 | // If bodySize < 0, then bodyStream is read until io.EOF. 46 | func RespondStream(c *routing.Context, httpCode int, bodyStream io.Reader, bodySize int) { 47 | c.RequestCtx.Response.SetStatusCode(httpCode) 48 | c.RequestCtx.Response.SetBodyStream(bodyStream, bodySize) 49 | } 50 | -------------------------------------------------------------------------------- /http/server.go: -------------------------------------------------------------------------------- 1 | package http 2 | 3 | import ( 4 | "crypto/tls" 5 | "crypto/x509" 6 | "errors" 7 | "fmt" 8 | "io/ioutil" 9 | "net" 10 | "time" 11 | 12 | "github.com/valyala/fasthttp" 13 | 14 | "github.com/baetyl/baetyl-go/v2/log" 15 | ) 16 | 17 | var errNoCertOrKeyProvided = errors.New("cert or key has not provided") 18 | 19 | type Server struct { 20 | conf ServerConfig 21 | *fasthttp.Server 22 | } 23 | 24 | // NewServer new server 25 | func NewServer(cfg ServerConfig, handler fasthttp.RequestHandler) *Server { 26 | return &Server{ 27 | conf: cfg, 28 | Server: &fasthttp.Server{ 29 | Handler: handler, 30 | Concurrency: cfg.Concurrency, 31 | DisableKeepalive: cfg.DisableKeepalive, 32 | TCPKeepalive: cfg.TCPKeepalive, 33 | MaxRequestBodySize: cfg.MaxRequestBodySize, 34 | ReadTimeout: cfg.ReadTimeout, 35 | WriteTimeout: cfg.WriteTimeout, 36 | IdleTimeout: cfg.IdleTimeout, 37 | }, 38 | } 39 | } 40 | 41 | func (s *Server) Start() { 42 | go func() { 43 | logger := log.With(log.Any("http", "server")) 44 | logger.Info("server is running", log.Any("address", s.conf.Address)) 45 | if s.conf.Cert != "" || s.conf.Key != "" { 46 | if len(s.conf.CA) != 0 { 47 | if err := s.ListenAndServeMTLS(s.conf.Address, s.conf.Cert, s.conf.Key); err != nil { 48 | logger.Error("https server shutdown", log.Error(err)) 49 | } 50 | } else { 51 | if err := s.ListenAndServeTLS(s.conf.Address, s.conf.Cert, s.conf.Key); err != nil { 52 | logger.Error("https server shutdown", log.Error(err)) 53 | } 54 | } 55 | } else { 56 | if err := s.ListenAndServe(s.conf.Address); err != nil { 57 | logger.Error("http server shutdown", log.Error(err)) 58 | } 59 | } 60 | }() 61 | } 62 | 63 | func (s *Server) ListenAndServeMTLS(addr, certFile, keyFile string) error { 64 | ln, err := net.Listen("tcp4", addr) 65 | if err != nil { 66 | return err 67 | } 68 | cert, err := tls.LoadX509KeyPair(certFile, keyFile) 69 | if err != nil { 70 | return fmt.Errorf("cannot load TLS key pair from certFile=%q and keyFile=%q: %s", certFile, keyFile, err) 71 | } 72 | tlsConfig := &tls.Config{ 73 | Certificates: []tls.Certificate{cert}, 74 | PreferServerCipherSuites: true, 75 | } 76 | if len(s.conf.CA) != 0 { 77 | pool := x509.NewCertPool() 78 | caCrt, err := ioutil.ReadFile(s.conf.CA) 79 | if err != nil { 80 | return fmt.Errorf("cannot load TLS ca from caFile=%q: %s", s.conf.CA, err) 81 | } 82 | pool.AppendCertsFromPEM(caCrt) 83 | tlsConfig.ClientAuth = s.conf.ClientAuthType 84 | tlsConfig.ClientCAs = pool 85 | } 86 | tlsConfig.BuildNameToCertificate() 87 | if s.TCPKeepalive { 88 | if tcpln, ok := ln.(*net.TCPListener); ok { 89 | return s.Serve(tls.NewListener(tcpKeepaliveListener{ 90 | TCPListener: tcpln, 91 | keepalivePeriod: s.TCPKeepalivePeriod, 92 | }, tlsConfig)) 93 | } 94 | } 95 | return s.Serve(tls.NewListener(ln, tlsConfig)) 96 | } 97 | 98 | func (s *Server) Close() { 99 | if s.Server != nil { 100 | s.Server.Shutdown() 101 | } 102 | } 103 | 104 | type tcpKeepaliveListener struct { 105 | *net.TCPListener 106 | keepalivePeriod time.Duration 107 | } 108 | 109 | func (ln tcpKeepaliveListener) Accept() (net.Conn, error) { 110 | tc, err := ln.AcceptTCP() 111 | if err != nil { 112 | return nil, err 113 | } 114 | if err := tc.SetKeepAlive(true); err != nil { 115 | tc.Close() //nolint:errcheck 116 | return nil, err 117 | } 118 | if ln.keepalivePeriod > 0 { 119 | if err := tc.SetKeepAlivePeriod(ln.keepalivePeriod); err != nil { 120 | tc.Close() //nolint:errcheck 121 | return nil, err 122 | } 123 | } 124 | return tc, nil 125 | } 126 | -------------------------------------------------------------------------------- /json/jsoniter.go: -------------------------------------------------------------------------------- 1 | package json 2 | 3 | import ( 4 | "io" 5 | 6 | jsoniter "github.com/json-iterator/go" 7 | ) 8 | 9 | func Marshal(v interface{}) ([]byte, error) { 10 | json := jsoniter.ConfigCompatibleWithStandardLibrary 11 | return json.Marshal(v) 12 | } 13 | 14 | func Unmarshal(data []byte, v interface{}) error { 15 | json := jsoniter.ConfigCompatibleWithStandardLibrary 16 | return json.Unmarshal(data, v) 17 | } 18 | 19 | func NewDecoder(reader io.Reader) *jsoniter.Decoder { 20 | json := jsoniter.ConfigCompatibleWithStandardLibrary 21 | return json.NewDecoder(reader) 22 | } 23 | 24 | func NewEncoder(writer io.Writer) *jsoniter.Encoder { 25 | json := jsoniter.ConfigCompatibleWithStandardLibrary 26 | return json.NewEncoder(writer) 27 | } 28 | -------------------------------------------------------------------------------- /log/config.go: -------------------------------------------------------------------------------- 1 | package log 2 | 3 | import ( 4 | "encoding/base64" 5 | "fmt" 6 | "net/url" 7 | "strconv" 8 | 9 | "github.com/baetyl/baetyl-go/v2/errors" 10 | ) 11 | 12 | // Config for logging 13 | type Config struct { 14 | Level string `yaml:"level" json:"level" default:"info" binding:"oneof=fatal panic error warn info debug"` 15 | Encoding string `yaml:"encoding" json:"encoding" default:"json" binding:"oneof=json console"` 16 | Filename string `yaml:"filename" json:"filename"` 17 | Compress bool `yaml:"compress" json:"compress"` 18 | MaxAge int `yaml:"maxAge" json:"maxAge" default:"15" binding:"min=1"` // days 19 | MaxSize int `yaml:"maxSize" json:"maxSize" default:"50" binding:"min=1"` // MB 20 | MaxBackups int `yaml:"maxBackups" json:"maxBackups" default:"15" binding:"min=1"` 21 | EncodeTime string `yaml:"encodeTime" json:"encodeTime"` // time format, like [2006/01/02 15:04:05 UTC] 22 | EncodeLevel string `yaml:"encodeLevel" json:"encodeLevel"` // symbols surround level, like [level] 23 | } 24 | 25 | func (c *Config) String() string { 26 | return fmt.Sprintf("level=%s&encoding=%s&filename=%s&compress=%t&maxAge=%d&maxSize=%d&maxBackups=%d", 27 | c.Level, 28 | c.Encoding, 29 | base64.URLEncoding.EncodeToString([]byte(c.Filename)), 30 | c.Compress, 31 | c.MaxAge, 32 | c.MaxSize, 33 | c.MaxBackups) 34 | } 35 | 36 | // FromURL creates config from url 37 | func FromURL(u *url.URL) (*Config, error) { 38 | args := u.Query() 39 | c := new(Config) 40 | c.Level = args.Get("level") 41 | c.Encoding = args.Get("encoding") 42 | filename, err := base64.URLEncoding.DecodeString(args.Get("filename")) 43 | if err != nil { 44 | return nil, errors.Trace(err) 45 | } 46 | c.Filename = string(filename) 47 | c.Compress, err = strconv.ParseBool(args.Get("compress")) 48 | if err != nil { 49 | return nil, errors.Trace(err) 50 | } 51 | c.MaxAge, err = strconv.Atoi(args.Get("maxAge")) 52 | if err != nil { 53 | return nil, errors.Trace(err) 54 | } 55 | c.MaxSize, err = strconv.Atoi(args.Get("maxSize")) 56 | if err != nil { 57 | return nil, errors.Trace(err) 58 | } 59 | c.MaxBackups, err = strconv.Atoi(args.Get("maxBackups")) 60 | if err != nil { 61 | return nil, errors.Trace(err) 62 | } 63 | return c, nil 64 | } 65 | -------------------------------------------------------------------------------- /log/logger.go: -------------------------------------------------------------------------------- 1 | package log 2 | 3 | import ( 4 | "fmt" 5 | "net/url" 6 | "os" 7 | "path/filepath" 8 | "strings" 9 | "time" 10 | 11 | "go.uber.org/zap" 12 | "go.uber.org/zap/zapcore" 13 | "gopkg.in/natefinch/lumberjack.v2" 14 | 15 | "github.com/baetyl/baetyl-go/v2/errors" 16 | ) 17 | 18 | func init() { 19 | // Config{ 20 | // Level: NewAtomicLevelAt(InfoLevel), 21 | // Development: false, 22 | // Sampling: &SamplingConfig{ 23 | // Initial: 100, 24 | // Thereafter: 100, 25 | // }, 26 | // Encoding: "json", 27 | // EncoderConfig: NewProductionEncoderConfig(), 28 | // OutputPaths: []string{"stderr"}, 29 | // ErrorOutputPaths: []string{"stderr"}, 30 | // } 31 | c := zap.NewProductionConfig() 32 | c.Sampling = nil 33 | c.OutputPaths = []string{"stdout"} 34 | l, err := c.Build() 35 | if err != nil { 36 | panic(fmt.Sprintf("failed to create default logger: %s", err.Error())) 37 | } 38 | err = zap.RegisterSink("lumberjack", newFileHook) 39 | if err != nil { 40 | l.Error("failed to register lumberjack", Error(err)) 41 | } 42 | zap.ReplaceGlobals(l) 43 | } 44 | 45 | // Init init and return logger 46 | func Init(cfg Config, fields ...Field) (*Logger, error) { 47 | c := zap.NewProductionConfig() 48 | c.Sampling = nil 49 | if cfg.Filename != "" { 50 | c.OutputPaths = append(c.OutputPaths, "lumberjack:?"+cfg.String()) 51 | } 52 | if cfg.Encoding == "console" { 53 | c.Encoding = "console" 54 | c.EncoderConfig.EncodeTime = zapcore.ISO8601TimeEncoder 55 | } 56 | if cfg.EncodeTime != "" { 57 | c.EncoderConfig.EncodeTime = func(t time.Time, enc zapcore.PrimitiveArrayEncoder) { 58 | enc.AppendString(t.Format(cfg.EncodeTime)) 59 | } 60 | } 61 | if cfg.EncodeLevel != "" { 62 | c.EncoderConfig.EncodeLevel = func(lvl zapcore.Level, enc zapcore.PrimitiveArrayEncoder) { 63 | ft := strings.ReplaceAll(cfg.EncodeLevel, "level", "%s") 64 | enc.AppendString(fmt.Sprintf(ft, lvl.String())) 65 | } 66 | } 67 | c.Level = zap.NewAtomicLevelAt(parseLevel(cfg.Level)) 68 | l, err := c.Build(zap.Fields(fields...)) 69 | if err != nil { 70 | return nil, errors.Trace(err) 71 | } 72 | zap.ReplaceGlobals(l) 73 | return L(), nil 74 | } 75 | 76 | type lumberjackSink struct { 77 | *lumberjack.Logger 78 | } 79 | 80 | func (*lumberjackSink) Sync() error { 81 | return nil 82 | } 83 | 84 | func newFileHook(u *url.URL) (zap.Sink, error) { 85 | cfg, err := FromURL(u) 86 | if err != nil { 87 | return nil, errors.Trace(err) 88 | } 89 | err = os.MkdirAll(filepath.Dir(cfg.Filename), 0755) 90 | if err != nil { 91 | return nil, errors.Trace(err) 92 | } 93 | return &lumberjackSink{&lumberjack.Logger{ 94 | Compress: cfg.Compress, 95 | Filename: cfg.Filename, 96 | MaxAge: cfg.MaxAge, 97 | MaxSize: cfg.MaxSize, 98 | MaxBackups: cfg.MaxBackups, 99 | }}, nil 100 | } 101 | 102 | func parseLevel(lvl string) Level { 103 | switch strings.ToLower(lvl) { 104 | case "fatal": 105 | return FatalLevel 106 | case "panic": 107 | return PanicLevel 108 | case "error": 109 | return ErrorLevel 110 | case "warn", "warning": 111 | return WarnLevel 112 | case "info": 113 | return InfoLevel 114 | case "debug": 115 | return DebugLevel 116 | default: 117 | L().Warn("failed to parse log level, use default level (info)", Any("level", lvl)) 118 | return InfoLevel 119 | } 120 | } 121 | -------------------------------------------------------------------------------- /log/zap.go: -------------------------------------------------------------------------------- 1 | package log 2 | 3 | import ( 4 | "go.uber.org/zap" 5 | "go.uber.org/zap/zapcore" 6 | 7 | "github.com/baetyl/baetyl-go/v2/errors" 8 | ) 9 | 10 | // Field log field 11 | type Field = zap.Field 12 | 13 | // Option log Option 14 | type Option = zap.Option 15 | 16 | // Logger logger 17 | type Logger = zap.Logger 18 | 19 | // Level log level 20 | type Level = zapcore.Level 21 | 22 | // all log level 23 | const ( 24 | DebugLevel Level = iota - 1 25 | InfoLevel 26 | WarnLevel 27 | ErrorLevel 28 | PanicLevel 29 | FatalLevel 30 | ) 31 | 32 | // Any constructs a field with the given key and value 33 | func Any(key string, val interface{}) Field { 34 | return zap.Any(key, val) 35 | } 36 | 37 | // Code constructs a field with the given value and code key 38 | func Code(err error) Field { 39 | switch e := err.(type) { 40 | case errors.Coder: 41 | return zap.Any("errorCode", e.Code()) 42 | default: 43 | return zap.Skip() 44 | } 45 | } 46 | 47 | // Error constructs a field with the given value and error key 48 | func Error(err error) Field { 49 | return zap.Error(err) 50 | } 51 | 52 | // L returns the global Logger, which can be reconfigured with ReplaceGlobals. 53 | // It's safe for concurrent use. 54 | func L() *Logger { 55 | return zap.L() 56 | } 57 | 58 | // With creates a child logger and adds structured context to it. Fields added 59 | // to the child don't affect the parent, and vice versa. 60 | func With(fields ...Field) *Logger { 61 | return zap.L().With(fields...) 62 | } 63 | -------------------------------------------------------------------------------- /mock/http_server.go: -------------------------------------------------------------------------------- 1 | package mock 2 | 3 | import ( 4 | "crypto/tls" 5 | "net/http" 6 | "net/http/httptest" 7 | ) 8 | 9 | // Response the mocked http repsonse 10 | type Response struct { 11 | status int 12 | body []byte 13 | } 14 | 15 | // NewResponse create a new repsonse 16 | func NewResponse(status int, body []byte) *Response { 17 | return &Response{status, body} 18 | } 19 | 20 | // Server the mocked http server 21 | type Server struct { 22 | *httptest.Server 23 | tlsConfig *tls.Config 24 | responses []*Response 25 | } 26 | 27 | // NewServer create a new mocked server 28 | func NewServer(tlsConfig *tls.Config, responses ...*Response) *Server { 29 | ms := &Server{tlsConfig: tlsConfig, responses: responses} 30 | ms.Server = httptest.NewUnstartedServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { 31 | if len(ms.responses) == 0 { 32 | return 33 | } 34 | w.WriteHeader(ms.responses[0].status) 35 | w.Write(ms.responses[0].body) 36 | ms.responses = ms.responses[1:] 37 | })) 38 | if tlsConfig == nil { 39 | ms.Server.Start() 40 | } else { 41 | ms.Server.Config.TLSConfig = tlsConfig 42 | ms.Server.StartTLS() 43 | } 44 | return ms 45 | } 46 | -------------------------------------------------------------------------------- /mock/http_server_test.go: -------------------------------------------------------------------------------- 1 | package mock 2 | 3 | import ( 4 | "io/ioutil" 5 | "net" 6 | "net/http" 7 | "sync" 8 | "testing" 9 | "time" 10 | 11 | "github.com/stretchr/testify/assert" 12 | 13 | "github.com/baetyl/baetyl-go/v2/utils" 14 | ) 15 | 16 | func TestHttpServer(t *testing.T) { 17 | tlssvr, err := utils.NewTLSConfigServer(utils.Certificate{CA: "./testcert/ca.pem", Key: "./testcert/server.key", Cert: "./testcert/server.pem"}) 18 | assert.NoError(t, err) 19 | assert.NotNil(t, tlssvr) 20 | tlscli, err := utils.NewTLSConfigClient(utils.Certificate{CA: "./testcert/ca.pem", Key: "./testcert/client.key", Cert: "./testcert/client.pem", InsecureSkipVerify: true}) 21 | assert.NoError(t, err) 22 | assert.NotNil(t, tlscli) 23 | 24 | svr := NewServer(tlssvr, NewResponse(200, []byte("hi")), NewResponse(400, nil)) 25 | defer svr.Close() 26 | 27 | cli := &http.Client{ 28 | Transport: &http.Transport{ 29 | TLSClientConfig: tlscli, 30 | Proxy: http.ProxyFromEnvironment, 31 | DialContext: (&net.Dialer{ 32 | Timeout: 30 * time.Second, 33 | KeepAlive: 30 * time.Second, 34 | DualStack: true, 35 | }).DialContext, 36 | ForceAttemptHTTP2: true, 37 | MaxIdleConns: 100, 38 | IdleConnTimeout: 90 * time.Second, 39 | TLSHandshakeTimeout: 10 * time.Second, 40 | ExpectContinueTimeout: 1 * time.Second, 41 | }, 42 | } 43 | 44 | res, err := cli.Post(svr.URL, "application/json", nil) 45 | assert.NoError(t, err) 46 | assert.NotNil(t, res) 47 | assert.Equal(t, 200, res.StatusCode) 48 | data, err := ioutil.ReadAll(res.Body) 49 | assert.NoError(t, err) 50 | assert.Equal(t, "hi", string(data)) 51 | 52 | var wg sync.WaitGroup 53 | wg.Add(1) 54 | go func() { 55 | defer wg.Done() 56 | res, err := cli.Post(svr.URL, "application/json", nil) 57 | assert.NoError(t, err) 58 | assert.NotNil(t, res) 59 | assert.Equal(t, 400, res.StatusCode) 60 | data, err := ioutil.ReadAll(res.Body) 61 | assert.NoError(t, err) 62 | assert.Equal(t, "", string(data)) 63 | }() 64 | wg.Wait() 65 | 66 | res, err = cli.Post(svr.URL, "application/json", nil) 67 | assert.NoError(t, err) 68 | assert.NotNil(t, res) 69 | assert.Equal(t, 200, res.StatusCode) 70 | data, err = ioutil.ReadAll(res.Body) 71 | assert.NoError(t, err) 72 | assert.Equal(t, "", string(data)) 73 | } 74 | -------------------------------------------------------------------------------- /mock/msg_flow_test.go: -------------------------------------------------------------------------------- 1 | package mock 2 | 3 | import ( 4 | "testing" 5 | "time" 6 | 7 | "github.com/stretchr/testify/assert" 8 | 9 | "github.com/baetyl/baetyl-go/v2/mqtt" 10 | ) 11 | 12 | func TestFlow(t *testing.T) { 13 | connect := mqtt.NewConnect() 14 | connack := mqtt.NewConnack() 15 | 16 | subscribe := mqtt.NewSubscribe() 17 | subscribe.Subscriptions = []mqtt.Subscription{ 18 | {Topic: "test"}, 19 | } 20 | subscribe.ID = 1 21 | 22 | publish1 := mqtt.NewPublish() 23 | publish1.ID = 2 24 | publish1.Message.Topic = "test" 25 | publish1.Message.QOS = 1 26 | 27 | publish2 := mqtt.NewPublish() 28 | publish2.ID = 3 29 | publish2.Message.Topic = "test" 30 | publish2.Message.QOS = 1 31 | 32 | wait := make(chan struct{}) 33 | 34 | server := NewFlow(). 35 | Receive(connect). 36 | Send(connack). 37 | Run(func() { 38 | close(wait) 39 | }). 40 | Skip(&mqtt.Subscribe{}). 41 | Receive(publish1, publish2). 42 | Close() 43 | 44 | client := NewFlow(). 45 | Send(connect). 46 | Receive(connack). 47 | Run(func() { 48 | <-wait 49 | }). 50 | Send(subscribe). 51 | Send(publish2, publish1). 52 | End() 53 | 54 | pipe := NewPipe() 55 | 56 | errCh := server.TestAsync(pipe, 100*time.Millisecond) 57 | 58 | err := client.Test(pipe) 59 | assert.NoError(t, err) 60 | 61 | err = <-errCh 62 | assert.NoError(t, err) 63 | } 64 | 65 | func TestAlreadyClosedError(t *testing.T) { 66 | pipe := NewPipe() 67 | pipe.Close() 68 | 69 | err := pipe.Send(nil) 70 | assert.Error(t, err) 71 | } 72 | -------------------------------------------------------------------------------- /mock/testcert/ca.pem: -------------------------------------------------------------------------------- 1 | -----BEGIN CERTIFICATE----- 2 | MIICfzCCAiSgAwIBAgIIFizowlvYkxgwCgYIKoZIzj0EAwIwgaUxCzAJBgNVBAYT 3 | AkNOMRAwDgYDVQQIEwdCZWlqaW5nMRkwFwYDVQQHExBIYWlkaWFuIERpc3RyaWN0 4 | MRUwEwYDVQQJEwxCYWlkdSBDYW1wdXMxDzANBgNVBBETBjEwMDA5MzEeMBwGA1UE 5 | ChMVTGludXggRm91bmRhdGlvbiBFZGdlMQ8wDQYDVQQLEwZCQUVUWUwxEDAOBgNV 6 | BAMTB3Jvb3QuY2EwIBcNMjAwODIwMDcxODA5WhgPMjA3MDA4MDgwNzE4MDlaMIGl 7 | MQswCQYDVQQGEwJDTjEQMA4GA1UECBMHQmVpamluZzEZMBcGA1UEBxMQSGFpZGlh 8 | biBEaXN0cmljdDEVMBMGA1UECRMMQmFpZHUgQ2FtcHVzMQ8wDQYDVQQREwYxMDAw 9 | OTMxHjAcBgNVBAoTFUxpbnV4IEZvdW5kYXRpb24gRWRnZTEPMA0GA1UECxMGQkFF 10 | VFlMMRAwDgYDVQQDEwdyb290LmNhMFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAE 11 | 3GSIw55wTQIaVWSD2fePbIts9pToj9OtiyG0/1zlvkht1Go2yCGc0xwaoR0YdW1H 12 | Fi1jpzMfmvJhppQaz5F6F6M6MDgwDgYDVR0PAQH/BAQDAgGGMA8GA1UdEwEB/wQF 13 | MAMBAf8wFQYDVR0RBA4wDIcEAAAAAIcEfwAAATAKBggqhkjOPQQDAgNJADBGAiEA 14 | qaeTS1oKts1XiC6eWkuK0n6TH45yWJvC3/NU6PqpBSYCIQDIHGDb3OL+4OsUitvb 15 | svDCT14MNf0cgIeg7gO+D0Xvqg== 16 | -----END CERTIFICATE----- 17 | -------------------------------------------------------------------------------- /mock/testcert/client.key: -------------------------------------------------------------------------------- 1 | -----BEGIN EC PRIVATE KEY----- 2 | MHcCAQEEIAwqz3MtAG2O5xaKy2RXWLxcOpKYKWilCs89L27+6it1oAoGCCqGSM49 3 | AwEHoUQDQgAErp2KLVIYfqeCzJlR/gteIZyN7i1/ckXuuXNO1i2GGu/bFdkoj1ST 4 | 1ypj1FuY/WpdmwSQHBIVm42s1vf0Gnc7yA== 5 | -----END EC PRIVATE KEY----- 6 | -------------------------------------------------------------------------------- /mock/testcert/client.pem: -------------------------------------------------------------------------------- 1 | -----BEGIN CERTIFICATE----- 2 | MIIClzCCAj2gAwIBAgIIFizowlwFTFAwCgYIKoZIzj0EAwIwgaUxCzAJBgNVBAYT 3 | AkNOMRAwDgYDVQQIEwdCZWlqaW5nMRkwFwYDVQQHExBIYWlkaWFuIERpc3RyaWN0 4 | MRUwEwYDVQQJEwxCYWlkdSBDYW1wdXMxDzANBgNVBBETBjEwMDA5MzEeMBwGA1UE 5 | ChMVTGludXggRm91bmRhdGlvbiBFZGdlMQ8wDQYDVQQLEwZCQUVUWUwxEDAOBgNV 6 | BAMTB3Jvb3QuY2EwHhcNMjAwODIwMDcxODA5WhcNNDAwODE1MDcxODA5WjCBpDEL 7 | MAkGA1UEBhMCQ04xEDAOBgNVBAgTB0JlaWppbmcxGTAXBgNVBAcTEEhhaWRpYW4g 8 | RGlzdHJpY3QxFTATBgNVBAkTDEJhaWR1IENhbXB1czEPMA0GA1UEERMGMTAwMDkz 9 | MR4wHAYDVQQKExVMaW51eCBGb3VuZGF0aW9uIEVkZ2UxDzANBgNVBAsTBkJBRVRZ 10 | TDEPMA0GA1UEAxMGY2xpZW50MFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAErp2K 11 | LVIYfqeCzJlR/gteIZyN7i1/ckXuuXNO1i2GGu/bFdkoj1ST1ypj1FuY/WpdmwSQ 12 | HBIVm42s1vf0Gnc7yKNWMFQwDgYDVR0PAQH/BAQDAgWgMB0GA1UdJQQWMBQGCCsG 13 | AQUFBwMCBggrBgEFBQcDATAMBgNVHRMBAf8EAjAAMBUGA1UdEQQOMAyHBAAAAACH 14 | BH8AAAEwCgYIKoZIzj0EAwIDSAAwRQIgOVxan95heTLe3c20iUvPJmX1EPMvfg6J 15 | 5GeWeK2cA8QCIQCfO6xOoQj386u+7XD4K4srGdFj77f9tfWt/M6ryIscdA== 16 | -----END CERTIFICATE----- 17 | -------------------------------------------------------------------------------- /mock/testcert/server.key: -------------------------------------------------------------------------------- 1 | -----BEGIN EC PRIVATE KEY----- 2 | MHcCAQEEIPcuJ/l+/s8PXvAN5M6VNBZrKD4HDW6n6Y4rQCYinF5doAoGCCqGSM49 3 | AwEHoUQDQgAEQXCQTGn4+frJYOumFk8gs8BIbgduEuiHonhYdJTFGIPLiOqPQoIv 4 | DmICod7W0oIzYYXwTF4NfadliSryoXx9IQ== 5 | -----END EC PRIVATE KEY----- 6 | -------------------------------------------------------------------------------- /mock/testcert/server.pem: -------------------------------------------------------------------------------- 1 | -----BEGIN CERTIFICATE----- 2 | MIICojCCAkigAwIBAgIIFizowlwTTkAwCgYIKoZIzj0EAwIwgaUxCzAJBgNVBAYT 3 | AkNOMRAwDgYDVQQIEwdCZWlqaW5nMRkwFwYDVQQHExBIYWlkaWFuIERpc3RyaWN0 4 | MRUwEwYDVQQJEwxCYWlkdSBDYW1wdXMxDzANBgNVBBETBjEwMDA5MzEeMBwGA1UE 5 | ChMVTGludXggRm91bmRhdGlvbiBFZGdlMQ8wDQYDVQQLEwZCQUVUWUwxEDAOBgNV 6 | BAMTB3Jvb3QuY2EwHhcNMjAwODIwMDcxODA5WhcNNDAwODE1MDcxODA5WjCBpDEL 7 | MAkGA1UEBhMCQ04xEDAOBgNVBAgTB0JlaWppbmcxGTAXBgNVBAcTEEhhaWRpYW4g 8 | RGlzdHJpY3QxFTATBgNVBAkTDEJhaWR1IENhbXB1czEPMA0GA1UEERMGMTAwMDkz 9 | MR4wHAYDVQQKExVMaW51eCBGb3VuZGF0aW9uIEVkZ2UxDzANBgNVBAsTBkJBRVRZ 10 | TDEPMA0GA1UEAxMGc2VydmVyMFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEQXCQ 11 | TGn4+frJYOumFk8gs8BIbgduEuiHonhYdJTFGIPLiOqPQoIvDmICod7W0oIzYYXw 12 | TF4NfadliSryoXx9IaNhMF8wDgYDVR0PAQH/BAQDAgWgMB0GA1UdJQQWMBQGCCsG 13 | AQUFBwMCBggrBgEFBQcDATAMBgNVHRMBAf8EAjAAMCAGA1UdEQQZMBeCCWxvY2Fs 14 | aG9zdIcEAAAAAIcEfwAAATAKBggqhkjOPQQDAgNIADBFAiB5vz8+oob7SkN54uf7 15 | RErbE4tWT5AHtgBgIs3A+TjnyQIhAPvnL8W1dq4qdkVr0eiH5He0xNHdsQc6eWxS 16 | RcKyjhh1 17 | -----END CERTIFICATE----- 18 | -------------------------------------------------------------------------------- /mqtt/counter.go: -------------------------------------------------------------------------------- 1 | package mqtt 2 | 3 | import ( 4 | "sync" 5 | ) 6 | 7 | type Counter struct { 8 | next ID 9 | mutex sync.Mutex 10 | } 11 | 12 | // NewCounter creates a new counter 13 | func NewCounter() *Counter { 14 | return NewCounterWithNext(1) 15 | } 16 | 17 | // NewIDCounterWithNext returns a new counter that will emit the specified if 18 | // id as the next id. 19 | func NewCounterWithNext(next ID) *Counter { 20 | return &Counter{ 21 | next: next, 22 | } 23 | } 24 | 25 | // NextID will return the next id and increase id 26 | func (c *Counter) NextID() ID { 27 | c.mutex.Lock() 28 | defer c.mutex.Unlock() 29 | 30 | // cache next id 31 | id := c.next 32 | 33 | // increment id 34 | c.next = NextCounterID(id) 35 | 36 | return id 37 | } 38 | 39 | // GetNextID will return the next id without increment 40 | func (c *Counter) GetNextID() ID { 41 | c.mutex.Lock() 42 | defer c.mutex.Unlock() 43 | 44 | return c.next 45 | } 46 | 47 | // Reset will reset the counter. 48 | func (c *Counter) Reset() { 49 | c.mutex.Lock() 50 | defer c.mutex.Unlock() 51 | 52 | // reset counter 53 | c.next = 1 54 | } 55 | 56 | func NextCounterID(id ID) ID { 57 | id++ 58 | if id == 0 { 59 | id++ 60 | } 61 | return id 62 | } 63 | -------------------------------------------------------------------------------- /mqtt/counter_test.go: -------------------------------------------------------------------------------- 1 | package mqtt 2 | 3 | import ( 4 | "math" 5 | "testing" 6 | 7 | "github.com/stretchr/testify/assert" 8 | ) 9 | 10 | func TestCounter(t *testing.T) { 11 | counter := NewCounter() 12 | 13 | assert.Equal(t, ID(1), counter.NextID()) 14 | assert.Equal(t, ID(2), counter.NextID()) 15 | 16 | for i := 0; i < math.MaxUint16-3; i++ { 17 | counter.NextID() 18 | } 19 | 20 | assert.Equal(t, ID(math.MaxUint16), counter.NextID()) 21 | assert.Equal(t, ID(1), counter.NextID()) 22 | 23 | counter.Reset() 24 | 25 | assert.Equal(t, ID(1), counter.NextID()) 26 | assert.Equal(t, counter.GetNextID(), counter.NextID()) 27 | assert.Equal(t, ID(3), counter.NextID()) 28 | 29 | i := ID(1) 30 | assert.Equal(t, ID(2), NextCounterID(i)) 31 | 32 | i = ID(math.MaxUint16) 33 | assert.Equal(t, ID(1), NextCounterID(i)) 34 | } 35 | -------------------------------------------------------------------------------- /mqtt/mock_test.go: -------------------------------------------------------------------------------- 1 | package mqtt 2 | 3 | import ( 4 | "fmt" 5 | "net" 6 | "sync" 7 | "testing" 8 | "time" 9 | 10 | "github.com/256dpi/gomqtt/packet" 11 | "github.com/256dpi/gomqtt/transport" 12 | "github.com/stretchr/testify/assert" 13 | 14 | "github.com/baetyl/baetyl-go/v2/mock" 15 | ) 16 | 17 | type mockObserver struct { 18 | t *testing.T 19 | pkts chan Packet 20 | errs chan error 21 | errOnPublish error 22 | sync.Mutex 23 | } 24 | 25 | func newMockObserver(t *testing.T) *mockObserver { 26 | return &mockObserver{ 27 | t: t, 28 | pkts: make(chan Packet, 10), 29 | errs: make(chan error, 10), 30 | } 31 | } 32 | 33 | func (o *mockObserver) OnPublish(pkt *packet.Publish) error { 34 | fmt.Println("--> OnPublish:", pkt) 35 | o.pkts <- pkt 36 | o.Lock() 37 | defer o.Unlock() 38 | return o.errOnPublish 39 | } 40 | 41 | func (o *mockObserver) OnPuback(pkt *packet.Puback) error { 42 | fmt.Println("--> OnPuback:", pkt) 43 | o.pkts <- pkt 44 | return nil 45 | } 46 | 47 | func (o *mockObserver) OnError(err error) { 48 | fmt.Println("--> OnError:", err) 49 | o.errs <- err 50 | } 51 | 52 | func (o *mockObserver) setErrOnPublish(err error) { 53 | o.Lock() 54 | o.errOnPublish = err 55 | o.Unlock() 56 | } 57 | 58 | func (o *mockObserver) assertPkts(pkts ...Packet) { 59 | for _, pkt := range pkts { 60 | select { 61 | case <-time.After(6 * time.Minute): 62 | panic("nothing received") 63 | case p := <-o.pkts: 64 | assert.Equal(o.t, pkt, p) 65 | } 66 | } 67 | } 68 | 69 | func (o *mockObserver) assertErrs(errs ...error) { 70 | for _, err := range errs { 71 | select { 72 | case <-time.After(6 * time.Second): 73 | panic("nothing received") 74 | case e := <-o.errs: 75 | assert.Equal(o.t, err.Error(), e.Error()) 76 | } 77 | } 78 | } 79 | 80 | func safeReceive(ch chan struct{}) { 81 | select { 82 | case <-time.After(1 * time.Second): 83 | panic("nothing received") 84 | case <-ch: 85 | } 86 | } 87 | 88 | func newClientOptions(t *testing.T, port string, Subscriptions []Subscription) *ClientOptions { 89 | c := NewClientOptions() 90 | c.Address = "tcp://localhost:" + port 91 | c.KeepAlive = 0 92 | c.CleanSession = true 93 | c.DisableAutoAck = true 94 | c.Subscriptions = Subscriptions 95 | return c 96 | } 97 | 98 | func initMockBroker(t *testing.T, testFlows ...*mock.Flow) (chan struct{}, string) { 99 | done := make(chan struct{}) 100 | 101 | server, err := transport.Launch("tcp://localhost:0") 102 | assert.NoError(t, err) 103 | 104 | go func() { 105 | for _, f := range testFlows { 106 | conn, err := server.Accept() 107 | assert.NoError(t, err) 108 | 109 | err = f.Test(newWrapper(conn)) 110 | assert.NoError(t, err) 111 | } 112 | 113 | err = server.Close() 114 | assert.NoError(t, err) 115 | 116 | close(done) 117 | }() 118 | 119 | _, port, _ := net.SplitHostPort(server.Addr().String()) 120 | return done, port 121 | } 122 | 123 | type wrapper struct { 124 | Connection 125 | } 126 | 127 | func newWrapper(conn Connection) mock.Conn { 128 | return &wrapper{Connection: conn} 129 | } 130 | 131 | func (c *wrapper) Send(pkt interface{}) error { 132 | return c.Connection.Send(pkt.(Packet), false) 133 | } 134 | 135 | func (c *wrapper) Receive() (interface{}, error) { 136 | pkt, err := c.Connection.Receive() 137 | if err != nil { 138 | return nil, err 139 | } 140 | return pkt.(Packet), nil 141 | } 142 | 143 | func connectPacket() *packet.Connect { 144 | pkt := packet.NewConnect() 145 | pkt.CleanSession = true 146 | return pkt 147 | } 148 | 149 | func connackPacket() *packet.Connack { 150 | pkt := packet.NewConnack() 151 | pkt.ReturnCode = packet.ConnectionAccepted 152 | pkt.SessionPresent = false 153 | return pkt 154 | } 155 | 156 | func disconnectPacket() *packet.Disconnect { 157 | return packet.NewDisconnect() 158 | } 159 | -------------------------------------------------------------------------------- /mqtt/mqtt.proto: -------------------------------------------------------------------------------- 1 | syntax = "proto3"; 2 | 3 | package mqtt; 4 | 5 | option (gogoproto.sizer_all) = true; 6 | option (gogoproto.marshaler_all) = true; 7 | option (gogoproto.unmarshaler_all) = true; 8 | option (gogoproto.goproto_getters_all) = false; 9 | option (gogoproto.testgen_all) = true; 10 | option (gogoproto.benchgen_all) = true; 11 | option (gogoproto.populate_all) = true; 12 | option (gogoproto.equal_all) = true; 13 | option (gogoproto.gostring_all) = true; 14 | // option (gogoproto.verbose_equal_all) = true; 15 | // option (gogoproto.goproto_stringer_all) = false; 16 | // option (gogoproto.stringer_all) = true; 17 | 18 | import "github.com/gogo/protobuf/gogoproto/gogo.proto"; 19 | 20 | message Context { 21 | uint64 ID = 1; 22 | uint64 TS = 2; 23 | uint32 QOS = 3; 24 | uint32 Flags = 4; 25 | string Topic = 5; 26 | } 27 | 28 | message Message { 29 | Context Context = 1 [(gogoproto.nullable) = false]; 30 | bytes Content = 2; 31 | } 32 | 33 | // protoc -I=. -I=$GOPATH/src -I=$GOPATH/src/github.com/gogo/protobuf/protobuf --gogofaster_out=plugins=grpc:. mqtt.proto -------------------------------------------------------------------------------- /mqtt/observer.go: -------------------------------------------------------------------------------- 1 | package mqtt 2 | 3 | import "github.com/256dpi/gomqtt/packet" 4 | 5 | // OnPublish handles publish packet 6 | type OnPublish func(*packet.Publish) error 7 | 8 | // OnPuback handles puback packet 9 | type OnPuback func(*packet.Puback) error 10 | 11 | // OnError handles error 12 | type OnError func(error) 13 | 14 | // Observer message observer interface 15 | type Observer interface { 16 | OnPublish(*packet.Publish) error 17 | OnPuback(*packet.Puback) error 18 | // Do not invoke client.Close() in OnError, otherwise a deadlock will occur. 19 | OnError(error) 20 | } 21 | 22 | // ObserverWrapper MQTT message handler wrapper 23 | type ObserverWrapper struct { 24 | onPublish OnPublish 25 | onPuback OnPuback 26 | onError OnError 27 | } 28 | 29 | // NewObserverWrapper creates a new handler wrapper 30 | func NewObserverWrapper(onPublish OnPublish, onPuback OnPuback, onError OnError) *ObserverWrapper { 31 | return &ObserverWrapper{ 32 | onPublish: onPublish, 33 | onPuback: onPuback, 34 | onError: onError, 35 | } 36 | } 37 | 38 | // OnPublish handles publish packet 39 | func (h *ObserverWrapper) OnPublish(pkt *packet.Publish) error { 40 | if h.onPublish == nil { 41 | return nil 42 | } 43 | return h.onPublish(pkt) 44 | } 45 | 46 | // OnPuback handles puback packet 47 | func (h *ObserverWrapper) OnPuback(pkt *packet.Puback) error { 48 | if h.onPuback == nil { 49 | return nil 50 | } 51 | return h.onPuback(pkt) 52 | } 53 | 54 | // OnError handles error 55 | func (h *ObserverWrapper) OnError(err error) { 56 | if h.onError == nil { 57 | return 58 | } 59 | h.onError(err) 60 | } 61 | -------------------------------------------------------------------------------- /mqtt/options.go: -------------------------------------------------------------------------------- 1 | package mqtt 2 | 3 | import ( 4 | "crypto/tls" 5 | "time" 6 | 7 | "github.com/baetyl/baetyl-go/v2/errors" 8 | "github.com/baetyl/baetyl-go/v2/utils" 9 | ) 10 | 11 | // ClientOptions client options 12 | type ClientOptions struct { 13 | Address string 14 | Username string 15 | Password string 16 | TLSConfig *tls.Config 17 | ClientID string 18 | CleanSession bool 19 | Timeout time.Duration 20 | KeepAlive time.Duration 21 | MaxReconnectInterval time.Duration 22 | MaxMessageSize utils.Size 23 | MaxCacheMessages int 24 | Subscriptions []Subscription 25 | DisableAutoAck bool 26 | } 27 | 28 | // NewClientOptions creates client options with default values 29 | func NewClientOptions() *ClientOptions { 30 | return &ClientOptions{ 31 | Timeout: 30 * time.Second, 32 | KeepAlive: 3 * time.Minute, 33 | MaxReconnectInterval: 3 * time.Minute, 34 | MaxMessageSize: 4 * 1024 * 1024, 35 | MaxCacheMessages: 10, 36 | } 37 | } 38 | 39 | // QOSTopic topic and qos 40 | type QOSTopic struct { 41 | QOS uint32 `yaml:"qos" json:"qos" binding:"min=0,max=1"` 42 | Topic string `yaml:"topic" json:"topic" binding:"nonzero"` 43 | } 44 | 45 | // ClientConfig client config 46 | type ClientConfig struct { 47 | Address string `yaml:"address" json:"address"` 48 | Username string `yaml:"username" json:"username"` 49 | Password string `yaml:"password" json:"password"` 50 | ClientID string `yaml:"clientid" json:"clientid"` 51 | CleanSession bool `yaml:"cleansession" json:"cleansession"` 52 | Timeout time.Duration `yaml:"timeout" json:"timeout" default:"30s"` 53 | KeepAlive time.Duration `yaml:"keepalive" json:"keepalive" default:"30s"` 54 | MaxReconnectInterval time.Duration `yaml:"maxReconnectInterval" json:"maxReconnectInterval" default:"3m"` 55 | MaxCacheMessages int `yaml:"maxCacheMessages" json:"maxCacheMessages" default:"10"` 56 | DisableAutoAck bool `yaml:"disableAutoAck" json:"disableAutoAck"` 57 | Subscriptions []QOSTopic `yaml:"subscriptions" json:"subscriptions" default:"[]"` 58 | utils.Certificate `yaml:",inline" json:",inline"` 59 | } 60 | 61 | // ToClientOptions converts client config to client options 62 | func (cc ClientConfig) ToClientOptions() (*ClientOptions, error) { 63 | ops := &ClientOptions{ 64 | Address: cc.Address, 65 | Username: cc.Username, 66 | Password: cc.Password, 67 | ClientID: cc.ClientID, 68 | CleanSession: cc.CleanSession, 69 | Timeout: cc.Timeout, 70 | KeepAlive: cc.KeepAlive, 71 | MaxReconnectInterval: cc.MaxReconnectInterval, 72 | MaxCacheMessages: cc.MaxCacheMessages, 73 | DisableAutoAck: cc.DisableAutoAck, 74 | } 75 | if cc.Certificate.Key != "" || cc.Certificate.Cert != "" { 76 | tlsconfig, err := utils.NewTLSConfigClient(cc.Certificate) 77 | if err != nil { 78 | return nil, errors.Trace(err) 79 | } 80 | ops.TLSConfig = tlsconfig 81 | } 82 | for _, topic := range cc.Subscriptions { 83 | ops.Subscriptions = append(ops.Subscriptions, Subscription{Topic: topic.Topic, QOS: QOS(topic.QOS)}) 84 | } 85 | return ops, nil 86 | } 87 | 88 | // ToClientOptionsWithPassphrase converts client config to client options with passphrase 89 | func (cc ClientConfig) ToClientOptionsWithPassphrase() (*ClientOptions, error) { 90 | ops := &ClientOptions{ 91 | Address: cc.Address, 92 | Username: cc.Username, 93 | Password: cc.Password, 94 | ClientID: cc.ClientID, 95 | CleanSession: cc.CleanSession, 96 | Timeout: cc.Timeout, 97 | KeepAlive: cc.KeepAlive, 98 | MaxReconnectInterval: cc.MaxReconnectInterval, 99 | MaxCacheMessages: cc.MaxCacheMessages, 100 | DisableAutoAck: cc.DisableAutoAck, 101 | } 102 | if cc.Certificate.Key != "" || cc.Certificate.Cert != "" { 103 | tlsconfig, err := utils.NewTLSConfigClientWithPassphrase(cc.Certificate) 104 | if err != nil { 105 | return nil, errors.Trace(err) 106 | } 107 | ops.TLSConfig = tlsconfig 108 | } 109 | for _, topic := range cc.Subscriptions { 110 | ops.Subscriptions = append(ops.Subscriptions, Subscription{Topic: topic.Topic, QOS: QOS(topic.QOS)}) 111 | } 112 | return ops, nil 113 | } 114 | -------------------------------------------------------------------------------- /mqtt/topic.go: -------------------------------------------------------------------------------- 1 | package mqtt 2 | 3 | import ( 4 | "bytes" 5 | "strings" 6 | "text/template" 7 | ) 8 | 9 | const ( 10 | maxTopicLevels = 9 11 | maxTopicLength = 255 12 | 13 | TopicNamespace = "(.Namespace)" 14 | TopicNodeName = "(.NodeName)" 15 | TopicReport = "report" 16 | TopicDesire = "desire" 17 | TopicDelta = "delta" 18 | TopicDesireResponse = "desireResponse" 19 | TopicWildcard = "+" 20 | ) 21 | 22 | func ProcessTopic(topic string, vars interface{}) (string, error) { 23 | tmpl, err := template.New("mqttTopic").Parse(topic) 24 | if err != nil { 25 | return "", err 26 | } 27 | var tmplBytes bytes.Buffer 28 | err = tmpl.Execute(&tmplBytes, vars) 29 | if err != nil { 30 | return "", err 31 | } 32 | return tmplBytes.String(), nil 33 | } 34 | 35 | // CheckTopic check topic 36 | func CheckTopic(topic string, wildcard bool) bool { 37 | return checkTopic(topic, wildcard, nil) 38 | } 39 | 40 | func checkTopic(topic string, wildcard bool, sysTopics map[string]struct{}) bool { 41 | if topic == "" { 42 | return false 43 | } 44 | if len(topic) > maxTopicLength || strings.Contains(topic, "\u0000") { 45 | return false 46 | } 47 | segments := strings.Split(topic, "/") 48 | if strings.HasPrefix(segments[0], "$") { 49 | if len(segments) < 2 { 50 | return false 51 | } 52 | if len(sysTopics) == 0 { 53 | if strings.Contains(segments[0], "+") || strings.Contains(segments[0], "#") { 54 | return false 55 | } 56 | } else { 57 | if _, ok := sysTopics[segments[0]]; !ok { 58 | return false 59 | } 60 | } 61 | segments = segments[1:] 62 | } 63 | levels := len(segments) 64 | if levels > maxTopicLevels { 65 | return false 66 | } 67 | for index := 0; index < levels; index++ { 68 | segment := segments[index] 69 | // check use of wildcards 70 | if len(segment) > 1 && (strings.Contains(segment, "+") || strings.Contains(segment, "#")) { 71 | return false 72 | } 73 | // check if wildcards are allowed 74 | if !wildcard && (segment == "#" || segment == "+") { 75 | return false 76 | } 77 | // check if # is the last level 78 | if segment == "#" && index != levels-1 { 79 | return false 80 | } 81 | } 82 | return true 83 | } 84 | 85 | // TopicChecker checks topic 86 | type TopicChecker struct { 87 | sysTopics map[string]struct{} 88 | } 89 | 90 | // NewTopicChecker create topicChecker 91 | func NewTopicChecker(sysTopics []string) *TopicChecker { 92 | tc := &TopicChecker{ 93 | sysTopics: make(map[string]struct{}), 94 | } 95 | for _, t := range sysTopics { 96 | tc.sysTopics[t] = struct{}{} 97 | } 98 | return tc 99 | } 100 | 101 | // CheckTopic checks the topic 102 | func (tc *TopicChecker) CheckTopic(topic string, wildcard bool) bool { 103 | return checkTopic(topic, wildcard, tc.sysTopics) 104 | } 105 | -------------------------------------------------------------------------------- /native/port.go: -------------------------------------------------------------------------------- 1 | package native 2 | 3 | import ( 4 | "github.com/baetyl/baetyl-go/v2/errors" 5 | "github.com/baetyl/baetyl-go/v2/utils" 6 | ) 7 | 8 | type PortAllocator struct { 9 | base int 10 | size int 11 | offset int 12 | } 13 | 14 | func NewPortAllocator(start, end int) (*PortAllocator, error) { 15 | if start < 1024 || end > 65535 || start >= end { 16 | return nil, errors.Errorf("port range (%d) - (%d) is not valid", start, end) 17 | } 18 | return &PortAllocator{ 19 | base: start, 20 | size: end - start + 1, 21 | }, nil 22 | } 23 | 24 | func (p *PortAllocator) Allocate() (int, error) { 25 | var times int 26 | for { 27 | if times == p.size { 28 | return 0, errors.Errorf("no available ports in range %d-%d", p.base, p.base+p.size-1) 29 | } 30 | port := p.base + p.offset 31 | p.offset++ 32 | p.offset = p.offset % p.size 33 | if utils.CheckPortAvailable("127.0.0.1", port) { 34 | return port, nil 35 | } 36 | times++ 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /native/port_test.go: -------------------------------------------------------------------------------- 1 | package native 2 | 3 | import ( 4 | "fmt" 5 | "net" 6 | "testing" 7 | 8 | "github.com/stretchr/testify/assert" 9 | ) 10 | 11 | func TestNewPortAllocator(t *testing.T) { 12 | _, err := NewPortAllocator(-1, 2) 13 | assert.Error(t, err) 14 | _, err = NewPortAllocator(30000, 1000000) 15 | assert.Error(t, err) 16 | _, err = NewPortAllocator(1024, 1024) 17 | assert.Error(t, err) 18 | 19 | alloc, err := NewPortAllocator(50020, 50021) 20 | assert.NoError(t, err) 21 | port1, err := alloc.Allocate() 22 | assert.NoError(t, err) 23 | assert.Equal(t, port1, 50020) 24 | port2, err := alloc.Allocate() 25 | assert.NoError(t, err) 26 | assert.Equal(t, port2, 50021) 27 | 28 | listener1, err := net.Listen("tcp", fmt.Sprintf("127.0.0.1:%d", port1)) 29 | assert.NoError(t, err) 30 | 31 | listener2, err := net.Listen("tcp", fmt.Sprintf("127.0.0.1:%d", port2)) 32 | assert.NoError(t, err) 33 | 34 | _, err = alloc.Allocate() 35 | assert.Error(t, err) 36 | assert.Equal(t, err.Error(), "no available ports in range 50020-50021") 37 | 38 | listener1.Close() 39 | listener2.Close() 40 | 41 | port4, err := alloc.Allocate() 42 | assert.NoError(t, err) 43 | assert.Equal(t, port4, 50020) 44 | } 45 | -------------------------------------------------------------------------------- /plugin/register.go: -------------------------------------------------------------------------------- 1 | package plugin 2 | 3 | import ( 4 | "fmt" 5 | "io" 6 | "strings" 7 | "sync" 8 | 9 | "github.com/baetyl/baetyl-go/v2/log" 10 | ) 11 | 12 | // Plugin interfaces 13 | type Plugin interface { 14 | io.Closer 15 | } 16 | 17 | // Factory create engine by given config 18 | type Factory func() (Plugin, error) 19 | 20 | // PluginFactory contains all supported plugin factory 21 | var pluginFactory sync.Map 22 | var plugins sync.Map 23 | 24 | // RegisterFactory adds a supported plugin 25 | func RegisterFactory(name string, f Factory) { 26 | if _, ok := pluginFactory.Load(name); ok { 27 | log.L().Info("plugin already exists, skip", log.Any("plugin", name)) 28 | return 29 | } 30 | pluginFactory.Store(name, f) 31 | log.L().Info("plugin is registered", log.Any("plugin", name)) 32 | } 33 | 34 | // GetPlugin GetPlugin 35 | func GetPlugin(name string) (Plugin, error) { 36 | name = strings.ToLower(name) 37 | if p, ok := plugins.Load(name); ok { 38 | return p.(Plugin), nil 39 | } 40 | f, ok := pluginFactory.Load(name) 41 | if !ok { 42 | return nil, fmt.Errorf("plugin [%s] not found", name) 43 | } 44 | p, err := f.(Factory)() 45 | if err != nil { 46 | log.L().Error("failed to create plugin", log.Error(err)) 47 | return nil, err 48 | } 49 | act, ok := plugins.LoadOrStore(name, p) 50 | if ok { 51 | err := p.Close() 52 | if err != nil { 53 | log.L().Warn("failed to close plugin", log.Error(err)) 54 | } 55 | return act.(Plugin), nil 56 | } 57 | return p, nil 58 | } 59 | 60 | // ClosePlugins ClosePlugins 61 | func ClosePlugins() { 62 | plugins.Range(func(key, value interface{}) bool { 63 | value.(Plugin).Close() 64 | return true 65 | }) 66 | } 67 | -------------------------------------------------------------------------------- /pubsub/handler.go: -------------------------------------------------------------------------------- 1 | package pubsub 2 | 3 | type Handler interface { 4 | OnMessage(interface{}) error 5 | OnTimeout() error 6 | } 7 | -------------------------------------------------------------------------------- /pubsub/processor.go: -------------------------------------------------------------------------------- 1 | package pubsub 2 | 3 | import ( 4 | "time" 5 | 6 | "github.com/baetyl/baetyl-go/v2/errors" 7 | "github.com/baetyl/baetyl-go/v2/log" 8 | "github.com/baetyl/baetyl-go/v2/utils" 9 | ) 10 | 11 | var ( 12 | ErrProcessorTimeout = errors.New("failed to send message because of timeout") 13 | ) 14 | 15 | type Processor interface { 16 | Start() 17 | Close() 18 | } 19 | 20 | type processor struct { 21 | channel <-chan interface{} 22 | timeout time.Duration 23 | handler Handler 24 | tomb utils.Tomb 25 | log *log.Logger 26 | } 27 | 28 | func NewProcessor(ch <-chan interface{}, timeout time.Duration, handler Handler) Processor { 29 | return &processor{ 30 | channel: ch, 31 | timeout: timeout, 32 | handler: handler, 33 | tomb: utils.Tomb{}, 34 | log: log.L().With(log.Any("pubsub", "processor")), 35 | } 36 | } 37 | 38 | func (p *processor) Start() { 39 | if p.timeout > 0 { 40 | p.tomb.Go(p.timerProcessing) 41 | } else { 42 | p.tomb.Go(p.processing) 43 | } 44 | } 45 | 46 | func (p *processor) Close() { 47 | p.tomb.Kill(nil) 48 | p.tomb.Wait() 49 | } 50 | 51 | func (p *processor) timerProcessing() error { 52 | timer := time.NewTimer(p.timeout) 53 | defer timer.Stop() 54 | for { 55 | select { 56 | case msg := <-p.channel: 57 | if p.handler != nil { 58 | if err := p.handler.OnMessage(msg); err != nil { 59 | p.log.Error("failed to handle message", log.Error(err)) 60 | } 61 | } 62 | timer.Reset(p.timeout) 63 | case <-timer.C: 64 | p.log.Warn("pubsub timeout") 65 | if p.handler != nil { 66 | if err := p.handler.OnTimeout(); err != nil { 67 | p.log.Error("failed to handle message because of timeout", log.Error(err)) 68 | } 69 | } 70 | p.tomb.Kill(ErrProcessorTimeout) 71 | case <-p.tomb.Dying(): 72 | return nil 73 | } 74 | } 75 | } 76 | 77 | func (p *processor) processing() error { 78 | for { 79 | select { 80 | case msg := <-p.channel: 81 | if p.handler != nil { 82 | if err := p.handler.OnMessage(msg); err != nil { 83 | p.log.Error("failed to handle message", log.Error(err)) 84 | } 85 | } 86 | case <-p.tomb.Dying(): 87 | return nil 88 | } 89 | } 90 | } 91 | -------------------------------------------------------------------------------- /pubsub/processor_test.go: -------------------------------------------------------------------------------- 1 | package pubsub 2 | 3 | import ( 4 | "sync" 5 | "testing" 6 | "time" 7 | 8 | "github.com/stretchr/testify/assert" 9 | ) 10 | 11 | const ( 12 | topicDown = "test.down" 13 | topicUp = "test.up" 14 | ) 15 | 16 | var ( 17 | syncWG = sync.WaitGroup{} 18 | ) 19 | 20 | func TestNewTimerProcessor(t *testing.T) { 21 | pb, err := NewPubsub(1) 22 | assert.NoError(t, err) 23 | assert.NotNil(t, pb) 24 | 25 | chDown, err := pb.Subscribe(topicDown) 26 | assert.NoError(t, err) 27 | hpDown := NewProcessor(chDown, time.Second*2, &hdDown{pb: pb, t: t}) 28 | hpDown.Start() 29 | 30 | chUp, err := pb.Subscribe(topicUp) 31 | assert.NoError(t, err) 32 | hphUp := NewProcessor(chUp, time.Second*2, &hdUp{pb: pb, t: t}) 33 | hphUp.Start() 34 | 35 | err = pb.Publish(topicDown, "down") 36 | assert.NoError(t, err) 37 | syncWG.Add(1) 38 | syncWG.Wait() 39 | hpDown.Close() 40 | hphUp.Close() 41 | } 42 | 43 | func TestNewProcessor(t *testing.T) { 44 | pb, err := NewPubsub(1) 45 | assert.NoError(t, err) 46 | assert.NotNil(t, pb) 47 | 48 | chDown, err := pb.Subscribe(topicDown) 49 | assert.NoError(t, err) 50 | hpDown := NewProcessor(chDown, 0, &hdDown{pb: pb, t: t}) 51 | hpDown.Start() 52 | 53 | chUp, err := pb.Subscribe(topicUp) 54 | assert.NoError(t, err) 55 | hphUp := NewProcessor(chUp, 0, &hdUp{pb: pb, t: t}) 56 | hphUp.Start() 57 | 58 | err = pb.Publish(topicDown, "down") 59 | assert.NoError(t, err) 60 | syncWG.Add(1) 61 | syncWG.Wait() 62 | hpDown.Close() 63 | hphUp.Close() 64 | } 65 | 66 | type hdDown struct { 67 | pb Pubsub 68 | t *testing.T 69 | } 70 | 71 | func (h *hdDown) OnMessage(msg interface{}) error { 72 | m, ok := msg.(string) 73 | assert.True(h.t, ok) 74 | assert.Equal(h.t, "down", m) 75 | return h.pb.Publish(topicUp, "up") 76 | } 77 | 78 | func (h *hdDown) OnTimeout() error { 79 | return h.pb.Publish(topicUp, "timeout") 80 | } 81 | 82 | type hdUp struct { 83 | pb Pubsub 84 | t *testing.T 85 | } 86 | 87 | func (h *hdUp) OnMessage(msg interface{}) error { 88 | m, ok := msg.(string) 89 | assert.True(h.t, ok) 90 | assert.Equal(h.t, "up", m) 91 | syncWG.Done() 92 | return nil 93 | } 94 | 95 | func (h *hdUp) OnTimeout() error { 96 | return nil 97 | } 98 | -------------------------------------------------------------------------------- /pubsub/pubsub.go: -------------------------------------------------------------------------------- 1 | package pubsub 2 | 3 | import ( 4 | "io" 5 | "strings" 6 | "sync" 7 | "time" 8 | 9 | "github.com/baetyl/baetyl-go/v2/errors" 10 | "github.com/baetyl/baetyl-go/v2/log" 11 | ) 12 | 13 | const ( 14 | pubTimeout = time.Millisecond * 10 15 | ) 16 | 17 | var ( 18 | ErrPubsubTimeout = errors.New("failed to send message to topic because of timeout") 19 | ) 20 | 21 | type Pubsub interface { 22 | Publish(topic string, msg interface{}) error 23 | Subscribe(topic string) (<-chan interface{}, error) 24 | Unsubscribe(topic string, ch <-chan interface{}) error 25 | io.Closer 26 | } 27 | 28 | type pubsub struct { 29 | size int 30 | channels map[string]map[<-chan interface{}]chan interface{} 31 | chanLock sync.RWMutex 32 | log *log.Logger 33 | } 34 | 35 | func NewPubsub(size int) (Pubsub, error) { 36 | return &pubsub{ 37 | size: size, 38 | channels: make(map[string]map[<-chan interface{}]chan interface{}), 39 | log: log.With(log.Any("pubsub", "memory")), 40 | }, nil 41 | } 42 | 43 | func (m *pubsub) Publish(topic string, msg interface{}) error { 44 | var errs []string 45 | if chs := m.getChannel(topic); chs != nil { 46 | for _, ch := range chs { 47 | err := m.publish(ch, msg) 48 | if err != nil { 49 | errs = append(errs, err.Error()) 50 | } 51 | } 52 | } 53 | if len(errs) > 0 { 54 | return errors.New(strings.Join(errs, "\n")) 55 | } 56 | return nil 57 | } 58 | 59 | func (m *pubsub) Subscribe(topic string) (<-chan interface{}, error) { 60 | m.chanLock.Lock() 61 | defer m.chanLock.Unlock() 62 | 63 | chs, ok := m.channels[topic] 64 | if !ok { 65 | chs = map[<-chan interface{}]chan interface{}{} 66 | m.channels[topic] = chs 67 | } 68 | ch := make(chan interface{}, m.size) 69 | chs[ch] = ch 70 | return ch, nil 71 | } 72 | 73 | func (m *pubsub) Unsubscribe(topic string, ch <-chan interface{}) error { 74 | m.chanLock.Lock() 75 | defer m.chanLock.Unlock() 76 | if chs, ok := m.channels[topic]; ok { 77 | if _, exist := chs[ch]; exist { 78 | delete(chs, ch) 79 | } 80 | } 81 | return nil 82 | } 83 | 84 | func (m *pubsub) Close() error { 85 | m.chanLock.Lock() 86 | defer m.chanLock.Unlock() 87 | for topic, chs := range m.channels { 88 | for k := range chs { 89 | delete(chs, k) 90 | } 91 | delete(m.channels, topic) 92 | } 93 | return nil 94 | } 95 | 96 | func (m *pubsub) publish(ch chan interface{}, msg interface{}) error { 97 | timer := time.NewTimer(pubTimeout) 98 | defer timer.Stop() 99 | 100 | select { 101 | case ch <- msg: 102 | case <-timer.C: 103 | m.log.Warn("publish message timeout") 104 | return ErrPubsubTimeout 105 | } 106 | return nil 107 | } 108 | 109 | func (m *pubsub) getChannel(topic string) map[<-chan interface{}]chan interface{} { 110 | m.chanLock.RLock() 111 | defer m.chanLock.RUnlock() 112 | if chs, ok := m.channels[topic]; ok { 113 | return chs 114 | } 115 | return nil 116 | } 117 | -------------------------------------------------------------------------------- /pubsub/pubsub_test.go: -------------------------------------------------------------------------------- 1 | package pubsub 2 | 3 | import ( 4 | "sync" 5 | "testing" 6 | 7 | "github.com/stretchr/testify/assert" 8 | ) 9 | 10 | const ( 11 | expMsg = "exp" 12 | ) 13 | 14 | var ( 15 | wg = sync.WaitGroup{} 16 | ) 17 | 18 | func TestPubsub(t *testing.T) { 19 | pb, err := NewPubsub(1) 20 | assert.NoError(t, err) 21 | assert.NotNil(t, pb) 22 | 23 | topicTest1 := "1" 24 | topicTest2 := "2" 25 | 26 | ch1, err := pb.Subscribe(topicTest1) 27 | assert.NoError(t, err) 28 | go Reading(t, ch1) 29 | wg.Add(1) 30 | pb.Publish(topicTest1, expMsg) 31 | 32 | ch2, err := pb.Subscribe(topicTest1) 33 | assert.NoError(t, err) 34 | go Reading(t, ch2) 35 | wg.Add(2) 36 | pb.Publish(topicTest1, expMsg) 37 | 38 | ch3, err := pb.Subscribe(topicTest2) 39 | assert.NoError(t, err) 40 | go Reading(t, ch3) 41 | wg.Add(1) 42 | pb.Publish(topicTest2, expMsg) 43 | 44 | wg.Wait() 45 | 46 | err = pb.Close() 47 | assert.NoError(t, err) 48 | } 49 | 50 | func Reading(t *testing.T, ch <-chan interface{}) { 51 | for { 52 | msg := <-ch 53 | switch msg.(type) { 54 | case string: 55 | assert.Equal(t, expMsg, msg) 56 | wg.Done() 57 | default: 58 | return 59 | } 60 | } 61 | } 62 | -------------------------------------------------------------------------------- /spec/v1/active.go: -------------------------------------------------------------------------------- 1 | package v1 2 | 3 | import "github.com/baetyl/baetyl-go/v2/utils" 4 | 5 | // ActiveRequest body of active request 6 | type ActiveRequest struct { 7 | BatchName string `yaml:"batchName,omitempty" json:"batchName,omitempty"` 8 | Namespace string `yaml:"namespace,omitempty" json:"namespace,omitempty"` 9 | FingerprintValue string `yaml:"fingerprintValue,omitempty" json:"fingerprintValue,omitempty"` 10 | SecurityType string `yaml:"securityType,omitempty" json:"securityType,omitempty"` 11 | SecurityValue string `yaml:"securityValue,omitempty" json:"securityValue,omitempty"` 12 | Mode string `yaml:"mode,omitempty" json:"mode,omitempty" default:"kube"` 13 | PenetrateData map[string]string `yaml:"penetrateData,omitempty" json:"penetrateData,omitempty"` 14 | } 15 | 16 | // ActiveResponse body of active responce 17 | type ActiveResponse struct { 18 | NodeName string `yaml:"nodeName,omitempty" json:"nodeName,omitempty"` 19 | Namespace string `yaml:"namespace,omitempty" json:"namespace,omitempty"` 20 | Certificate utils.Certificate `yaml:"certificate,omitempty" json:"certificate,omitempty"` 21 | MqttCert utils.Certificate `yaml:"mqttCert,omitempty" json:"mqttCert,omitempty"` 22 | } 23 | -------------------------------------------------------------------------------- /spec/v1/configuration.go: -------------------------------------------------------------------------------- 1 | package v1 2 | 3 | import ( 4 | "strings" 5 | "time" 6 | ) 7 | 8 | const ( 9 | PrefixConfigObject = "_object_" 10 | ConfigType = "baetyl-config-type" 11 | ConfigHelmTar = "baetyl-helm-tar" 12 | ConfigHelmValue = "baetyl-helm-value" 13 | ) 14 | 15 | // Configuration config info 16 | type Configuration struct { 17 | Name string `json:"name,omitempty" yaml:"name,omitempty" binding:"res_name"` 18 | Namespace string `json:"namespace,omitempty" yaml:"namespace,omitempty"` 19 | Labels map[string]string `json:"labels,omitempty" yaml:"labels,omitempty"` 20 | Data map[string]string `json:"data,omitempty" yaml:"data,omitempty" default:"{}" binding:"required"` 21 | CreationTimestamp time.Time `json:"createTime,omitempty" yaml:"createTime,omitempty"` 22 | UpdateTimestamp time.Time `json:"updateTime,omitempty" yaml:"updateTime,omitempty"` 23 | Description string `json:"description,omitempty" yaml:"description,omitempty"` 24 | Version string `json:"version,omitempty" yaml:"version,omitempty"` 25 | System bool `json:"system,omitempty" yaml:"system,omitempty"` 26 | } 27 | 28 | // ConfigurationObject extended feature for object configuration 29 | type ConfigurationObject struct { 30 | // hex format 31 | MD5 string `json:"md5,omitempty" yaml:"md5,omitempty"` 32 | Sha256 string `json:"sha256,omitempty" yaml:"sha256,omitempty"` 33 | URL string `json:"url,omitempty" yaml:"url,omitempty"` 34 | Token string `json:"token,omitempty" yaml:"token,omitempty"` 35 | Unpack string `json:"unpack,omitempty" yaml:"unpack,omitempty"` 36 | Metadata map[string]string `json:"metadata,omitempty" yaml:"metadata,omitempty"` 37 | } 38 | 39 | func IsConfigObject(key string) bool { 40 | return strings.HasPrefix(key, PrefixConfigObject) 41 | } 42 | -------------------------------------------------------------------------------- /spec/v1/lazy_value.go: -------------------------------------------------------------------------------- 1 | package v1 2 | 3 | import ( 4 | "bytes" 5 | "encoding/json" 6 | ) 7 | 8 | // VariableValue variable value which can be app, config or secret 9 | type LazyValue struct { 10 | Value interface{} 11 | doc []byte 12 | } 13 | 14 | // UnmarshalJSON unmarshal from json data 15 | func (v *LazyValue) UnmarshalJSON(b []byte) error { 16 | v.doc = b 17 | return nil 18 | } 19 | 20 | // SetJSON set the json doc 21 | func (v *LazyValue) SetJSON(doc []byte) { 22 | v.doc = doc 23 | } 24 | 25 | // GetJSON get the json doc 26 | func (v *LazyValue) GetJSON() []byte { 27 | return v.doc 28 | } 29 | 30 | // MarshalJSON marshal to json data 31 | func (v LazyValue) MarshalJSON() ([]byte, error) { 32 | if v.doc != nil { 33 | return v.doc, nil 34 | } 35 | return json.Marshal(v.Value) 36 | } 37 | 38 | // Unmarshal unmarshal from json data to obj 39 | func (v *LazyValue) Unmarshal(obj interface{}) error { 40 | if v.doc != nil { 41 | return json.Unmarshal(v.doc, obj) 42 | } 43 | if v.Value != nil { 44 | bs, err := json.Marshal(v.Value) 45 | if err != nil { 46 | return err 47 | } 48 | return json.Unmarshal(bs, obj) 49 | } 50 | return nil 51 | } 52 | 53 | func (v *LazyValue) ExactUnmarshal(obj interface{}) error { 54 | if v.doc != nil { 55 | decoder := json.NewDecoder(bytes.NewReader(v.doc)) 56 | decoder.UseNumber() 57 | return decoder.Decode(obj) 58 | } 59 | if v.Value != nil { 60 | bs, err := json.Marshal(v.Value) 61 | if err != nil { 62 | return err 63 | } 64 | decoder := json.NewDecoder(bytes.NewReader(bs)) 65 | decoder.UseNumber() 66 | return decoder.Decode(obj) 67 | } 68 | return nil 69 | } 70 | -------------------------------------------------------------------------------- /spec/v1/lazy_value_test.go: -------------------------------------------------------------------------------- 1 | package v1 2 | 3 | import ( 4 | "encoding/json" 5 | "testing" 6 | 7 | "github.com/stretchr/testify/assert" 8 | ) 9 | 10 | func TestSpecV1_LazyValue(t *testing.T) { 11 | dr := &DesireRequest{ 12 | Infos: []ResourceInfo{ 13 | { 14 | Kind: "config", 15 | Name: "c082001", 16 | Version: "599944", 17 | }, 18 | }, 19 | } 20 | v := &Message{ 21 | Kind: MessageReport, 22 | Metadata: map[string]string{"1": "2"}, 23 | Content: LazyValue{ 24 | Value: dr, 25 | }, 26 | } 27 | 28 | expData := "{\"kind\":\"report\",\"meta\":{\"1\":\"2\"},\"content\":{\"infos\":[{\"kind\":\"config\",\"name\":\"c082001\",\"version\":\"599944\"}]}}" 29 | 30 | desire := &DesireRequest{} 31 | err := v.Content.Unmarshal(desire) 32 | assert.NoError(t, err) 33 | assert.EqualValues(t, dr, desire) 34 | 35 | data, err := json.Marshal(v) 36 | assert.NoError(t, err) 37 | assert.Equal(t, expData, string(data)) 38 | data, err = json.Marshal(v) 39 | assert.NoError(t, err) 40 | assert.Equal(t, expData, string(data)) 41 | 42 | expContentData := "{\"infos\":[{\"kind\":\"config\",\"name\":\"c082001\",\"version\":\"599944\"}]}" 43 | 44 | msg := &Message{} 45 | err = json.Unmarshal(data, msg) 46 | assert.NoError(t, err) 47 | assert.Nil(t, msg.Content.Value) 48 | assert.Equal(t, expContentData, string(msg.Content.doc)) 49 | err = json.Unmarshal(data, msg) 50 | assert.NoError(t, err) 51 | assert.Nil(t, msg.Content.Value) 52 | assert.Equal(t, expContentData, string(msg.Content.doc)) 53 | 54 | desire1 := &DesireRequest{} 55 | err = msg.Content.Unmarshal(desire1) 56 | assert.NoError(t, err) 57 | assert.EqualValues(t, dr, desire1) 58 | desire2 := &DesireRequest{} 59 | err = msg.Content.Unmarshal(desire2) 60 | assert.NoError(t, err) 61 | assert.EqualValues(t, dr, desire2) 62 | 63 | data2, err := json.Marshal(msg) 64 | assert.NoError(t, err) 65 | assert.Equal(t, expData, string(data2)) 66 | msg2 := &Message{} 67 | err = json.Unmarshal(data2, msg2) 68 | assert.NoError(t, err) 69 | assert.Nil(t, msg2.Content.Value) 70 | assert.Equal(t, expContentData, string(msg2.Content.doc)) 71 | 72 | msg3 := &Message{ 73 | Kind: MessageReport, 74 | Metadata: map[string]string{"1": "2"}, 75 | } 76 | msg3.Content.SetJSON([]byte(expContentData)) 77 | data4, err := json.Marshal(msg3) 78 | assert.NoError(t, err) 79 | assert.Equal(t, expData, string(data4)) 80 | 81 | i64 := int64(-1958835689816845425) 82 | s := "test" 83 | b := true 84 | msg4 := &Message{ 85 | Kind: MessageReport, 86 | Content: LazyValue{Value: map[string]interface{}{"int64": i64, "string": s, "bool": b}}, 87 | } 88 | data5, err := json.Marshal(msg4) 89 | assert.NoError(t, err) 90 | var res Message 91 | err = json.Unmarshal(data5, &res) 92 | assert.NoError(t, err) 93 | var im map[string]interface{} 94 | err = res.Content.Unmarshal(&im) 95 | assert.NoError(t, err) 96 | resi := int64(im["int64"].(float64)) 97 | resb := im["bool"].(bool) 98 | ress := im["string"].(string) 99 | assert.NotEqual(t, resi, i64) 100 | assert.Equal(t, resb, b) 101 | assert.Equal(t, ress, s) 102 | 103 | err = res.Content.ExactUnmarshal(&im) 104 | assert.NoError(t, err) 105 | resi, err = im["int64"].(json.Number).Int64() 106 | resb = im["bool"].(bool) 107 | ress = im["string"].(string) 108 | assert.NoError(t, err) 109 | assert.Equal(t, resi, i64) 110 | assert.Equal(t, resb, b) 111 | assert.Equal(t, ress, s) 112 | } 113 | -------------------------------------------------------------------------------- /spec/v1/message.go: -------------------------------------------------------------------------------- 1 | package v1 2 | 3 | // MessageKind message kind 4 | type MessageKind string 5 | 6 | const ( 7 | // Message response message kind 8 | MessageWebsocketRead MessageKind = "wsReadMsg" 9 | // MessageReport report message kind 10 | MessageReport MessageKind = "report" 11 | // MessageDesire desire message kind 12 | MessageDesire MessageKind = "desire" 13 | // MessageKeep keep alive message kind 14 | MessageKeep MessageKind = "keepalive" 15 | // MessageCMD command message kind 16 | MessageCMD MessageKind = "cmd" 17 | // MessageData data message kind 18 | MessageData MessageKind = "data" 19 | // MessageError error message kind 20 | MessageError MessageKind = "error" 21 | // Message response message kind 22 | MessageResponse MessageKind = "response" 23 | // MessageDelta delta message kind 24 | MessageDelta MessageKind = "delta" 25 | // MessageEvent event message = "event" 26 | MessageEvent MessageKind = "event" 27 | // MessageNodeProps node props message kind 28 | MessageNodeProps MessageKind = "nodeProps" 29 | // MessageDevices devices message kind 30 | MessageDevices MessageKind = "devices" 31 | // MessageDeviceEvent device event message kind 32 | MessageDeviceEvent MessageKind = "deviceEvent" 33 | // MessageReport device report message kind 34 | MessageDeviceReport MessageKind = "deviceReport" 35 | // MessageDesire device desire message kind 36 | MessageDeviceDesire MessageKind = "deviceDesire" 37 | //new version MessageDesire device desire message kind 38 | MessageMultipleDeviceDesire MessageKind = "multipleDeviceDesire" 39 | // MessageDesire device delta message kind 40 | MessageDeviceDelta MessageKind = "deviceDelta" 41 | // MessageDeviceLatestProperty device get property message kind 42 | MessageDevicePropertyGet MessageKind = "thing.property.get" 43 | // MessageReport device event report message kind 44 | MessageDeviceEventReport MessageKind = "thing.event.post" 45 | // MessageReport device lifecycle report message kind 46 | MessageDeviceLifecycleReport MessageKind = "thing.lifecycle.post" 47 | // MessageMC get/send mc info 48 | MessageMC MessageKind = "mc" 49 | // MessageSTS get/send s3 sts info 50 | MessageSTS MessageKind = "sts" 51 | 52 | // MessageCommandConnect start remote debug command 53 | MessageCommandConnect = "connect" 54 | // MessageCommandDisconnect stop remote debug command 55 | MessageCommandDisconnect = "disconnect" 56 | // MessageCommandLogs logs 57 | MessageCommandLogs = "logs" 58 | // MessageCommandNodeLabel label the edge cluster nodes 59 | MessageCommandNodeLabel = "nodeLabel" 60 | // MessageCommandMultiNodeLabels label multiple nodes 61 | MessageCommandMultiNodeLabels = "multiNodeLabels" 62 | // MessageCommandDescribe describe 63 | MessageCommandDescribe = "describe" 64 | // MessageRPC call edge app 65 | MessageRPC = "rpc" 66 | // MessageAgent get or set agent stat 67 | MessageAgent = "agent" 68 | // MessageRPCMqtt push message to broker 69 | MessageRPCMqtt = "rpcMqtt" 70 | // MessageMCAppList get mc app list 71 | MessageMCAppList = "mcAppList" 72 | // MessageMCAppInfo get mc app info 73 | MessageMCAppInfo = "mcAppInfo" 74 | // MessageSTSTypeMinio get minio sts 75 | MessageSTSTypeMinio = "minio" 76 | ) 77 | 78 | // Message general structure for http and ws sync 79 | type Message struct { 80 | Kind MessageKind `yaml:"kind" json:"kind"` 81 | Metadata map[string]string `yaml:"meta" json:"meta"` 82 | Content LazyValue `yaml:"content" json:"content"` 83 | } 84 | -------------------------------------------------------------------------------- /spec/v1/module.go: -------------------------------------------------------------------------------- 1 | package v1 2 | 3 | const ( 4 | BaetylInit = "baetyl-init" 5 | BaetylCore = "baetyl-core" 6 | BaetylCoreAndroid = "com.baidubce.baetyl.core" 7 | BaetylBroker = "baetyl-broker" 8 | BaetylFunction = "baetyl-function" 9 | BaetylRule = "baetyl-rule" 10 | BaetylAgent = "baetyl-agent" 11 | BaetylLog = "baetyl-log" 12 | BaetylGPUMetrics = "baetyl-accelerator-metrics" 13 | BaetylEkuiper = "baetyl-ekuiper" 14 | BaetylOpcuaSimulator = "baetyl-opcua-simulator" 15 | BaetylModbusTcpSimulator = "baetyl-modbus-tcp-simulator" 16 | BaetylModbusRtuSimulator = "baetyl-modbus-rtu-simulator" 17 | ) 18 | -------------------------------------------------------------------------------- /spec/v1/object.go: -------------------------------------------------------------------------------- 1 | package v1 2 | 3 | const ( 4 | HeaderKeyObjectUnpack = "baetyl-object-unpack" 5 | HeaderKeyObjectMD5 = "baetyl-object-md5" 6 | HeaderKeyObjectDir = "baetyl-object-dir" 7 | ) 8 | -------------------------------------------------------------------------------- /spec/v1/ota.go: -------------------------------------------------------------------------------- 1 | package v1 2 | 3 | type OtaInfo struct { 4 | ApkInfo *ApkInfo `json:"apkInfo,omitempty"` 5 | DeviceGroup *OtaDeviceGroup `json:"deviceGroup,omitempty"` 6 | Task *OtaTask `json:"task,omitempty"` 7 | } 8 | 9 | type ApkInfo struct { 10 | Key string `json:"key,omitempty"` 11 | Url string `json:"url,omitempty"` 12 | Md5 string `json:"md5,omitempty"` 13 | Sha1 string `json:"sha1,omitempty"` 14 | PackName string `json:"packName,omitempty"` 15 | Size int64 `json:"size,omitempty"` 16 | VersionCode int `json:"versionCode,omitempty"` 17 | Version string `json:"version"` 18 | } 19 | 20 | type OtaDeviceGroup struct { 21 | AddTestDeviceGroupName string `json:"addTestDeviceGroupName,omitempty"` 22 | AddTestDeviceGroupId int `json:"addTestDeviceGroupId,omitempty"` 23 | AddTestDeviceGroupKey string `json:"addTestDeviceGroupKey,omitempty"` 24 | AddDeviceGroupName string `json:"addDeviceGroupName,omitempty"` 25 | AddDeviceGroupId int `json:"addDeviceGroupId,omitempty"` 26 | AddDeviceGroupKey string `json:"addDeviceGroupKey,omitempty"` 27 | DelTestDeviceGroupName string `json:"delTestDeviceGroupName,omitempty"` 28 | DelTestDeviceGroupId int `json:"delTestDeviceGroupId,omitempty"` 29 | DelTestDeviceGroupKey string `json:"delTestDeviceGroupKey,omitempty"` 30 | DelDeviceGroupName string `json:"delDeviceGroupName,omitempty"` 31 | DelDeviceGroupId int `json:"delDeviceGroupId,omitempty"` 32 | DelDeviceGroupKey string `json:"delDeviceGroupKey,omitempty"` 33 | } 34 | 35 | type OtaTask struct { 36 | AddTaskName string `json:"addTaskName,omitempty"` 37 | AddTaskId int `json:"addTaskId,omitempty"` 38 | DelTaskName string `json:"delTaskName,omitempty"` 39 | DelTaskId int `json:"delTaskId,omitempty"` 40 | } 41 | -------------------------------------------------------------------------------- /spec/v1/resource.go: -------------------------------------------------------------------------------- 1 | package v1 2 | 3 | // Kind app model kind, crd on k8s 4 | type Kind string 5 | 6 | // All kinds 7 | const ( 8 | KindNode Kind = "node" 9 | KindApp Kind = "app" 10 | KindApplication Kind = "application" 11 | KindConfig Kind = "config" 12 | KindConfiguration Kind = "configuration" 13 | KindSecret Kind = "secret" 14 | ) 15 | 16 | const ( 17 | SecretLabel = "secret-type" 18 | // speical secret of the the registry 19 | SecretRegistry = "registry" 20 | // speical secret of the the config 21 | SecretConfig = "config" 22 | // speical secret of the the certificate 23 | SecretCertificate = "certificate" 24 | // speical custom secret of the the certificate 25 | SecretCustomCertificate = "custom-certificate" 26 | ) 27 | -------------------------------------------------------------------------------- /spec/v1/rpc.go: -------------------------------------------------------------------------------- 1 | package v1 2 | 3 | type RPCRequest struct { 4 | App string `json:"app"` 5 | Method string `json:"method"` 6 | System bool `json:"system,omitempty" default:"false"` 7 | Params string `json:"params,omitempty"` 8 | Header map[string]string `json:"header,omitempty"` 9 | Body interface{} `json:"body,omitempty"` 10 | } 11 | 12 | type RPCResponse struct { 13 | StatusCode int `json:"statusCode"` 14 | Header map[string][]string `json:"header,omitempty"` 15 | Body []byte `json:"body,omitempty"` 16 | } 17 | 18 | type RPCMqttMessage struct { 19 | QoS uint32 `json:"qos,omitempty"` 20 | Topic string `json:"topic,omitempty"` 21 | Content interface{} `json:"content,omitempty"` 22 | } 23 | -------------------------------------------------------------------------------- /spec/v1/secret.go: -------------------------------------------------------------------------------- 1 | package v1 2 | 3 | import "time" 4 | 5 | // Secret secret info 6 | type Secret struct { 7 | Name string `json:"name,omitempty" yaml:"name,omitempty" binding:"res_name"` 8 | Namespace string `json:"namespace,omitempty" yaml:"namespace,omitempty"` 9 | Labels map[string]string `json:"labels,omitempty" yaml:"labels,omitempty"` 10 | Annotations map[string]string `json:"annotations,omitempty" yaml:"annotations,omitempty"` 11 | Data map[string][]byte `json:"data,omitempty" yaml:"data,omitempty"` 12 | CreationTimestamp time.Time `json:"createTime,omitempty" yaml:"createTime,omitempty"` 13 | UpdateTimestamp time.Time `json:"updateTime,omitempty" yaml:"updateTime,omitempty"` 14 | Description string `json:"description,omitempty" yaml:"description,omitempty"` 15 | Version string `json:"version,omitempty" yaml:"version,omitempty"` 16 | System bool `json:"system,omitempty" yaml:"system,omitempty"` 17 | } 18 | -------------------------------------------------------------------------------- /spec/v1/sts.go: -------------------------------------------------------------------------------- 1 | package v1 2 | 3 | import "time" 4 | 5 | type STSRequest struct { 6 | STSType string `json:"stsType,omitempty" default:"minio"` 7 | ExpiredTime time.Duration `json:"expiredTime,omitempty" default:"12h"` 8 | } 9 | 10 | type STSResponse struct { 11 | AK string `json:"ak"` 12 | SK string `json:"sk"` 13 | Token string `json:"token"` 14 | Endpoint string `json:"endpoint"` 15 | Bucket string `json:"bucket"` 16 | Namespace string `json:"namespace"` 17 | NodeName string `json:"nodeName"` 18 | Expiration time.Time `json:"expiration"` 19 | } 20 | -------------------------------------------------------------------------------- /spec/v1/sync_desire.go: -------------------------------------------------------------------------------- 1 | package v1 2 | 3 | // DesireRequest body of request to sync desired data 4 | type DesireRequest struct { 5 | Infos []ResourceInfo `yaml:"infos" json:"infos"` 6 | } 7 | 8 | // DesireResponse body of response to sync desired data 9 | type DesireResponse struct { 10 | Values []ResourceValue `yaml:"values" json:"values"` 11 | } 12 | 13 | // ResourceInfo desired info 14 | type ResourceInfo struct { 15 | Kind Kind `yaml:"kind,omitempty" json:"kind,omitempty"` 16 | Name string `yaml:"name,omitempty" json:"name,omitempty"` 17 | Version string `yaml:"version,omitempty" json:"version,omitempty"` 18 | } 19 | 20 | // ResourceValue desired value 21 | type ResourceValue struct { 22 | ResourceInfo `yaml:",inline" json:",inline"` 23 | Value LazyValue `yaml:"value,omitempty" json:"value,omitempty"` 24 | } 25 | 26 | // App return app data if its kind is app 27 | func (v *ResourceValue) App() *Application { 28 | if v.Kind == KindApplication || v.Kind == KindApp { 29 | var app Application 30 | v.Value.Unmarshal(&app) 31 | return &app 32 | } 33 | return nil 34 | } 35 | 36 | // Config return config data if its kind is config 37 | func (v *ResourceValue) Config() *Configuration { 38 | if v.Kind == KindConfiguration || v.Kind == KindConfig { 39 | var cfg Configuration 40 | v.Value.Unmarshal(&cfg) 41 | return &cfg 42 | } 43 | return nil 44 | } 45 | 46 | // Secret return secret data if its kind is secret 47 | func (v *ResourceValue) Secret() *Secret { 48 | if v.Kind == KindSecret { 49 | var sec Secret 50 | v.Value.Unmarshal(&sec) 51 | return &sec 52 | } 53 | return nil 54 | } 55 | -------------------------------------------------------------------------------- /spec/v1/sync_desire_test.go: -------------------------------------------------------------------------------- 1 | package v1 2 | 3 | import ( 4 | "encoding/json" 5 | "fmt" 6 | "testing" 7 | 8 | "github.com/stretchr/testify/assert" 9 | ) 10 | 11 | func TestCRDData(t *testing.T) { 12 | { 13 | // --- app 14 | desireddata := &ResourceValue{} 15 | desireddata.Name = "app" 16 | desireddata.Version = "123" 17 | desireddata.Kind = KindApplication 18 | desireddata.Value.Value = &Application{Name: "c"} 19 | 20 | appdata, err := json.Marshal(desireddata) 21 | assert.NoError(t, err) 22 | fmt.Printf(string(appdata)) 23 | 24 | desireddata2 := &ResourceValue{} 25 | err = json.Unmarshal(appdata, desireddata2) 26 | assert.NoError(t, err) 27 | assert.Nil(t, desireddata2.Value.Value) 28 | 29 | // success 30 | app := desireddata2.App() 31 | assert.Equal(t, desireddata.Value.Value, app) 32 | 33 | desireddata.Kind = KindApp 34 | app = desireddata2.App() 35 | assert.Equal(t, desireddata.Value.Value, app) 36 | 37 | // failure 38 | cfg := desireddata2.Config() 39 | assert.Nil(t, cfg) 40 | 41 | // failure 42 | scr := desireddata2.Secret() 43 | assert.Nil(t, scr) 44 | } 45 | { 46 | // --- config 47 | desireddata := &ResourceValue{} 48 | desireddata.Name = "cfg" 49 | desireddata.Version = "123" 50 | desireddata.Kind = KindConfiguration 51 | desireddata.Value.Value = &Configuration{Name: "c"} 52 | 53 | appdata, err := json.Marshal(desireddata) 54 | assert.NoError(t, err) 55 | fmt.Printf(string(appdata)) 56 | 57 | desireddata2 := &ResourceValue{} 58 | err = json.Unmarshal(appdata, desireddata2) 59 | assert.NoError(t, err) 60 | assert.Nil(t, desireddata2.Value.Value) 61 | 62 | // failure 63 | app := desireddata2.App() 64 | assert.Nil(t, app) 65 | assert.Nil(t, desireddata2.Value.Value) 66 | 67 | // sucees 68 | cfg := desireddata2.Config() 69 | assert.Equal(t, desireddata.Value.Value, cfg) 70 | 71 | desireddata.Kind = KindConfig 72 | cfg = desireddata2.Config() 73 | assert.Equal(t, desireddata.Value.Value, cfg) 74 | 75 | // failure 76 | scr := desireddata2.Secret() 77 | assert.Nil(t, scr) 78 | } 79 | { 80 | // --- secret 81 | desireddata := &ResourceValue{} 82 | desireddata.Name = "scr" 83 | desireddata.Version = "123" 84 | desireddata.Kind = KindSecret 85 | desireddata.Value.Value = &Secret{Name: "c"} 86 | 87 | appdata, err := json.Marshal(desireddata) 88 | assert.NoError(t, err) 89 | fmt.Printf(string(appdata)) 90 | 91 | desireddata2 := &ResourceValue{} 92 | err = json.Unmarshal(appdata, desireddata2) 93 | assert.NoError(t, err) 94 | assert.Nil(t, desireddata2.Value.Value) 95 | 96 | // failure 97 | app := desireddata2.App() 98 | assert.Nil(t, app) 99 | assert.Nil(t, desireddata2.Value.Value) 100 | 101 | // failure 102 | cfg := desireddata2.Config() 103 | assert.Nil(t, cfg) 104 | assert.Nil(t, desireddata2.Value.Value) 105 | 106 | // failure 107 | scr := desireddata2.Secret() 108 | assert.Equal(t, desireddata.Value.Value, scr) 109 | } 110 | } 111 | -------------------------------------------------------------------------------- /task/channel_broker.go: -------------------------------------------------------------------------------- 1 | package task 2 | 3 | import ( 4 | "time" 5 | 6 | "github.com/baetyl/baetyl-go/v2/errors" 7 | ) 8 | 9 | var ( 10 | SendMsgTimeout = errors.New("failed to send message") 11 | GetMsgTimeout = errors.New("failed to get message") 12 | ) 13 | 14 | type channelBroker struct { 15 | broker chan *BrokerMessage 16 | } 17 | 18 | func NewChannelBroker(cache int) TaskBroker { 19 | return &channelBroker{ 20 | broker: make(chan *BrokerMessage, cache), 21 | } 22 | } 23 | 24 | func (b *channelBroker) SendMessage(msg *BrokerMessage) error { 25 | select { 26 | case b.broker <- msg: 27 | return nil 28 | case <-time.After(time.Millisecond): 29 | return SendMsgTimeout 30 | } 31 | } 32 | 33 | func (b *channelBroker) GetMessage() (*BrokerMessage, error) { 34 | select { 35 | case msg := <-b.broker: 36 | return msg, nil 37 | case <-time.After(time.Millisecond): 38 | return nil, GetMsgTimeout 39 | } 40 | } 41 | 42 | func (b *channelBroker) Close() error { 43 | close(b.broker) 44 | return nil 45 | } 46 | -------------------------------------------------------------------------------- /task/map_backend.go: -------------------------------------------------------------------------------- 1 | package task 2 | 3 | import ( 4 | "sync" 5 | 6 | "github.com/baetyl/baetyl-go/v2/errors" 7 | ) 8 | 9 | var ( 10 | ErrResultNotFound = errors.New("failed to find result") 11 | ) 12 | 13 | type mapBackend struct { 14 | mapLock sync.RWMutex 15 | cache map[string]*ResultMessage 16 | } 17 | 18 | func NewMapBackend() TaskBackend { 19 | return &mapBackend{ 20 | cache: map[string]*ResultMessage{}, 21 | } 22 | } 23 | 24 | func (m *mapBackend) GetResult(taskId string) (*ResultMessage, error) { 25 | m.mapLock.Lock() 26 | defer m.mapLock.Unlock() 27 | result, ok := m.cache[taskId] 28 | if !ok { 29 | return nil, ErrResultNotFound 30 | } 31 | delete(m.cache, taskId) 32 | return result, nil 33 | } 34 | 35 | func (m *mapBackend) SetResult(taskID string, result *ResultMessage) error { 36 | m.mapLock.Lock() 37 | defer m.mapLock.Unlock() 38 | m.cache[taskID] = result 39 | return nil 40 | } 41 | -------------------------------------------------------------------------------- /task/message.go: -------------------------------------------------------------------------------- 1 | package task 2 | 3 | import ( 4 | "encoding/base64" 5 | "encoding/json" 6 | "fmt" 7 | "time" 8 | ) 9 | 10 | const ( 11 | TaskSuccess = "success" 12 | TaskFail = "fail" 13 | 14 | RetryGap = 10 * time.Millisecond 15 | ) 16 | 17 | type TaskMessage struct { 18 | ID string `json:"id"` 19 | Name string `json:"task"` 20 | Args []interface{} `json:"args"` 21 | Kwargs map[string]interface{} `json:"kwargs"` 22 | Retries int `json:"retries"` 23 | Expires *time.Time `json:"expires"` 24 | } 25 | 26 | type ResultMessage struct { 27 | ID string `json:"id"` 28 | Status string `json:"status"` 29 | Traceback string `json:"traceback"` 30 | Result interface{} `json:"result"` 31 | } 32 | 33 | type BrokerMessage struct { 34 | ID string `json:"id"` 35 | Value string `json:"value"` 36 | } 37 | 38 | type TaskResult struct { 39 | ID string 40 | backend TaskBackend 41 | result *ResultMessage 42 | } 43 | 44 | // Encode returns base64 json encoded string 45 | func (tm *TaskMessage) Encode() (string, error) { 46 | jsonData, err := json.Marshal(tm) 47 | if err != nil { 48 | return "", err 49 | } 50 | encodedData := base64.StdEncoding.EncodeToString(jsonData) 51 | return encodedData, err 52 | } 53 | 54 | // Decode return taskMessage 55 | func (bm *BrokerMessage) Decode() (*TaskMessage, error) { 56 | body, err := base64.StdEncoding.DecodeString(bm.Value) 57 | if err != nil { 58 | return nil, err 59 | } 60 | msg := &TaskMessage{} 61 | err = json.Unmarshal(body, msg) 62 | if err != nil { 63 | return nil, err 64 | } 65 | return msg, nil 66 | } 67 | 68 | // Get result synchronize 69 | func (tr *TaskResult) Get(timeout time.Duration) (*ResultMessage, error) { 70 | ticker := time.NewTicker(RetryGap) 71 | timeoutChan := time.After(timeout) 72 | defer ticker.Stop() 73 | for { 74 | select { 75 | case <-timeoutChan: 76 | return nil, fmt.Errorf("timeout result for %s", tr.ID) 77 | case <-ticker.C: 78 | result, err := tr.AsyncGet() 79 | if err == ErrResultNotFound { 80 | continue 81 | } 82 | return result, nil 83 | } 84 | } 85 | } 86 | 87 | // AsyncGet result 88 | func (tr *TaskResult) AsyncGet() (*ResultMessage, error) { 89 | if tr.result != nil { 90 | return tr.result, nil 91 | } 92 | return tr.backend.GetResult(tr.ID) 93 | } 94 | -------------------------------------------------------------------------------- /task/producer.go: -------------------------------------------------------------------------------- 1 | package task 2 | 3 | import ( 4 | "github.com/google/uuid" 5 | ) 6 | 7 | type taskProducer struct { 8 | broker TaskBroker 9 | backend TaskBackend 10 | } 11 | 12 | func NewTaskProducer(broker TaskBroker, backend TaskBackend) TaskProducer { 13 | return &taskProducer{ 14 | broker, 15 | backend, 16 | } 17 | } 18 | 19 | func (p *taskProducer) AddTask(name string, args ...interface{}) (*TaskResult, error) { 20 | id, _ := uuid.NewUUID() 21 | task := &TaskMessage{ 22 | ID: id.String(), 23 | Name: name, 24 | Args: args, 25 | Kwargs: make(map[string]interface{}), 26 | } 27 | encodedMsg, err := task.Encode() 28 | if err != nil { 29 | return nil, err 30 | } 31 | return &TaskResult{ID: id.String(), backend: p.backend}, 32 | p.broker.SendMessage(&BrokerMessage{ 33 | ID: id.String(), 34 | Value: encodedMsg, 35 | }) 36 | } 37 | 38 | func (p *taskProducer) AddTaskWithKey(name string, args map[string]interface{}) (*TaskResult, error) { 39 | id, _ := uuid.NewUUID() 40 | task := &TaskMessage{ 41 | ID: id.String(), 42 | Name: name, 43 | Args: make([]interface{}, 0), 44 | Kwargs: args, 45 | } 46 | encodedMsg, err := task.Encode() 47 | if err != nil { 48 | return nil, err 49 | } 50 | return &TaskResult{ID: id.String(), backend: p.backend}, p.broker.SendMessage(&BrokerMessage{ 51 | ID: id.String(), 52 | Value: encodedMsg, 53 | }) 54 | } 55 | -------------------------------------------------------------------------------- /task/task.go: -------------------------------------------------------------------------------- 1 | package task 2 | 3 | import ( 4 | "context" 5 | "io" 6 | "reflect" 7 | ) 8 | 9 | type TaskProducer interface { 10 | AddTask(name string, args ...interface{}) (*TaskResult, error) 11 | AddTaskWithKey(name string, args map[string]interface{}) (*TaskResult, error) 12 | } 13 | 14 | type TaskBroker interface { 15 | SendMessage(*BrokerMessage) error 16 | GetMessage() (*BrokerMessage, error) 17 | io.Closer 18 | } 19 | 20 | type TaskWorker interface { 21 | StartWorker(ctx context.Context) 22 | StopWorker() 23 | Register(name string, task interface{}) 24 | } 25 | 26 | type TaskBackend interface { 27 | GetResult(taskId string) (*ResultMessage, error) 28 | SetResult(taskID string, result *ResultMessage) error 29 | } 30 | 31 | type AsyncTask interface { 32 | // ParseKwargs - define a method to parse kwargs 33 | ParseKwargs(map[string]interface{}) error 34 | 35 | // RunTask - define a method for execution 36 | RunTask() (interface{}, error) 37 | } 38 | 39 | func GetRealValue(val *reflect.Value) interface{} { 40 | if val == nil { 41 | return nil 42 | } 43 | switch val.Kind() { 44 | case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64: 45 | return val.Int() 46 | case reflect.String: 47 | return val.String() 48 | case reflect.Bool: 49 | return val.Bool() 50 | case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64: 51 | return val.Uint() 52 | case reflect.Float32, reflect.Float64: 53 | return val.Float() 54 | case reflect.Slice, reflect.Map: 55 | return val.Interface() 56 | default: 57 | return nil 58 | } 59 | } 60 | -------------------------------------------------------------------------------- /task/task_test.go: -------------------------------------------------------------------------------- 1 | package task 2 | 3 | import ( 4 | "context" 5 | "strconv" 6 | "testing" 7 | "time" 8 | 9 | "github.com/stretchr/testify/assert" 10 | 11 | "github.com/baetyl/baetyl-go/v2/errors" 12 | ) 13 | 14 | var ( 15 | ErrArgs = errors.New("failed to parse args, due to unknown args") 16 | ErrFormat = errors.New("failed to parse args, due to err format") 17 | ) 18 | 19 | func TestTaskBasic(t *testing.T) { 20 | broker := NewChannelBroker(10) 21 | backend := NewMapBackend() 22 | producer := NewTaskProducer(broker, backend) 23 | worker := NewTaskWorker(broker, backend) 24 | 25 | addTask := "Add" 26 | worker.Register(addTask, Add) 27 | 28 | addWithKey := "AddKey" 29 | worker.Register(addWithKey, &addInt{}) 30 | 31 | worker.StartWorker(context.Background()) 32 | defer worker.StopWorker() 33 | 34 | asyncResult1, err := producer.AddTask(addTask, 1, 2) 35 | assert.NoError(t, err) 36 | 37 | asyncResult2, err := producer.AddTaskWithKey(addWithKey, map[string]interface{}{ 38 | "a": 1, 39 | "b": "2", 40 | }) 41 | assert.NoError(t, err) 42 | 43 | result1, err := asyncResult1.Get(time.Second) 44 | assert.NoError(t, err) 45 | assert.Equal(t, result1.Status, TaskSuccess) 46 | assert.Equal(t, result1.Result, int64(3)) 47 | 48 | result2, err := asyncResult2.Get(time.Second) 49 | assert.NoError(t, err) 50 | assert.Equal(t, result2.Status, TaskSuccess) 51 | assert.Equal(t, result2.Result, 3) 52 | } 53 | 54 | func Add(a, b int) (int, error) { 55 | return a + b, nil 56 | } 57 | 58 | type addInt struct { 59 | a int 60 | b string 61 | } 62 | 63 | func (a *addInt) ParseKwargs(kwargs map[string]interface{}) error { 64 | argA, ok := kwargs["a"] 65 | if !ok { 66 | return ErrArgs 67 | } 68 | argAFloat, ok := argA.(float64) 69 | if !ok { 70 | return ErrFormat 71 | } 72 | a.a = int(argAFloat) 73 | argB, ok := kwargs["b"] 74 | if !ok { 75 | return ErrArgs 76 | } 77 | a.b, ok = argB.(string) 78 | if !ok { 79 | return ErrFormat 80 | } 81 | return nil 82 | } 83 | 84 | func (a *addInt) RunTask() (interface{}, error) { 85 | b, err := strconv.Atoi(a.b) 86 | if err != nil { 87 | return nil, err 88 | } 89 | return a.a + b, nil 90 | } 91 | 92 | func DoNothing() {} 93 | 94 | func ValueBool(a bool) (bool, error) { 95 | return a, nil 96 | } 97 | 98 | func ValueFloat(a float32) (float32, error) { 99 | return a, nil 100 | } 101 | 102 | func ValueString(a string) (string, error) { 103 | return a, nil 104 | } 105 | 106 | func ValueMap(a string) (map[string]string, error) { 107 | return map[string]string{"result": a}, nil 108 | } 109 | 110 | func TestTaskMultiValues(t *testing.T) { 111 | broker := NewChannelBroker(10) 112 | backend := NewMapBackend() 113 | producer := NewTaskProducer(broker, backend) 114 | worker := NewTaskWorker(broker, backend) 115 | 116 | blankTask := "blank" 117 | worker.Register(blankTask, DoNothing) 118 | boolTask := "bool" 119 | worker.Register(boolTask, ValueBool) 120 | floatTask := "float" 121 | worker.Register(floatTask, ValueFloat) 122 | stringTask := "string" 123 | worker.Register(stringTask, ValueString) 124 | mapTask := "map" 125 | worker.Register(mapTask, ValueMap) 126 | 127 | worker.StartWorker(context.Background()) 128 | defer worker.StopWorker() 129 | 130 | asyncBlank, err := producer.AddTask(blankTask) 131 | assert.NoError(t, err) 132 | asyncBool, err := producer.AddTask(boolTask, true) 133 | assert.NoError(t, err) 134 | asyncFloat, err := producer.AddTask(floatTask, float32(1)) 135 | assert.NoError(t, err) 136 | asyncString, err := producer.AddTask(stringTask, "test") 137 | assert.NoError(t, err) 138 | asyncMap, err := producer.AddTask(mapTask, "test") 139 | assert.NoError(t, err) 140 | 141 | resultBool, err := asyncBool.Get(time.Second) 142 | assert.NoError(t, err) 143 | assert.Equal(t, resultBool.Result, true) 144 | resultFloat, err := asyncFloat.Get(time.Second) 145 | assert.NoError(t, err) 146 | assert.Equal(t, resultFloat.Result, float64(1)) 147 | resultString, err := asyncString.Get(time.Second) 148 | assert.NoError(t, err) 149 | assert.Equal(t, resultString.Result, "test") 150 | resultMap, err := asyncMap.Get(time.Second) 151 | assert.NoError(t, err) 152 | assert.Equal(t, resultMap.Result, map[string]string{"result": "test"}) 153 | 154 | _, err = asyncBlank.Get(time.Millisecond) 155 | assert.NotNil(t, err) 156 | } 157 | -------------------------------------------------------------------------------- /task/worker.go: -------------------------------------------------------------------------------- 1 | package task 2 | 3 | import ( 4 | "context" 5 | "fmt" 6 | "reflect" 7 | "sync" 8 | "time" 9 | 10 | "github.com/baetyl/baetyl-go/v2/errors" 11 | "github.com/baetyl/baetyl-go/v2/log" 12 | ) 13 | 14 | const ( 15 | RatePeriod = 100 * time.Millisecond 16 | ) 17 | 18 | var ( 19 | ErrInvalidArgs = errors.New("failed to exec task, due to invalid args") 20 | ) 21 | 22 | type taskWorker struct { 23 | broker TaskBroker 24 | backend TaskBackend 25 | registeredTasks map[string]interface{} 26 | cancel context.CancelFunc 27 | rateLimitPeriod time.Duration 28 | lock sync.RWMutex 29 | log *log.Logger 30 | } 31 | 32 | func NewTaskWorker(broker TaskBroker, backend TaskBackend) TaskWorker { 33 | return &taskWorker{ 34 | broker: broker, 35 | backend: backend, 36 | registeredTasks: map[string]interface{}{}, 37 | rateLimitPeriod: RatePeriod, 38 | log: log.L().With(log.Any("task", "worker")), 39 | } 40 | } 41 | 42 | func (w *taskWorker) StartWorker(ctx context.Context) { 43 | var workerCtx context.Context 44 | workerCtx, w.cancel = context.WithCancel(ctx) 45 | go func() { 46 | ticker := time.NewTicker(w.rateLimitPeriod) 47 | defer ticker.Stop() 48 | for { 49 | select { 50 | case <-workerCtx.Done(): 51 | return 52 | case <-ticker.C: 53 | taskMsg, err := w.broker.GetMessage() 54 | if err != nil || taskMsg == nil { 55 | continue 56 | } 57 | decodedMsg, err := taskMsg.Decode() 58 | if err != nil { 59 | w.log.Error("failed to decode message ", log.Error(err)) 60 | continue 61 | } 62 | resultMsg, err := w.runTask(decodedMsg) 63 | if err != nil { 64 | w.log.Error("failed to run task ", log.Error(err)) 65 | continue 66 | } 67 | if resultMsg.Result != nil { 68 | err = w.backend.SetResult(taskMsg.ID, resultMsg) 69 | if err != nil { 70 | w.log.Error("failed to set result ", log.Error(err)) 71 | } 72 | } 73 | } 74 | } 75 | }() 76 | } 77 | 78 | func (w *taskWorker) StopWorker() { 79 | w.cancel() 80 | } 81 | 82 | func (w *taskWorker) Register(name string, task interface{}) { 83 | w.lock.Lock() 84 | defer w.lock.Unlock() 85 | w.registeredTasks[name] = task 86 | } 87 | 88 | func (w *taskWorker) getTask(name string) interface{} { 89 | w.lock.RLock() 90 | defer w.lock.RUnlock() 91 | task, ok := w.registeredTasks[name] 92 | if !ok { 93 | return nil 94 | } 95 | return task 96 | } 97 | 98 | func (w *taskWorker) runTask(msg *TaskMessage) (*ResultMessage, error) { 99 | if msg.Expires != nil && msg.Expires.UTC().Before(time.Now().UTC()) { 100 | return nil, fmt.Errorf("task %s is expired on %s", msg.ID, msg.Expires) 101 | } 102 | if msg.Args == nil { 103 | return nil, fmt.Errorf("task %s is malformed - args cannot be nil", msg.ID) 104 | } 105 | task := w.getTask(msg.Name) 106 | if task == nil { 107 | return nil, fmt.Errorf("task %s is not registered", msg.Name) 108 | } 109 | taskInterface, ok := task.(AsyncTask) 110 | // If realize paresKwargs or RunTask function 111 | if ok { 112 | if err := taskInterface.ParseKwargs(msg.Kwargs); err != nil { 113 | return nil, err 114 | } 115 | val, err := taskInterface.RunTask() 116 | result := &ResultMessage{ 117 | ID: msg.ID, 118 | Status: TaskSuccess, 119 | Traceback: "", 120 | Result: val, 121 | } 122 | if err != nil { 123 | result.Status = TaskFail 124 | result.Traceback = err.Error() 125 | } 126 | return result, nil 127 | } 128 | 129 | taskFunc := reflect.ValueOf(task) 130 | return runTaskFunc(&taskFunc, msg) 131 | } 132 | 133 | func runTaskFunc(taskFunc *reflect.Value, msg *TaskMessage) (*ResultMessage, error) { 134 | numArgs := taskFunc.Type().NumIn() 135 | msgNumArgs := len(msg.Args) 136 | if numArgs != msgNumArgs { 137 | return nil, ErrInvalidArgs 138 | } 139 | params := make([]reflect.Value, msgNumArgs) 140 | for i, arg := range msg.Args { 141 | origType := taskFunc.Type().In(i).Kind() 142 | msgType := reflect.TypeOf(arg).Kind() 143 | // special case - convert float64 to int if applicable 144 | // this is due to json limitation where all numbers are converted to float64 145 | if origType == reflect.Int && msgType == reflect.Float64 { 146 | arg = int(arg.(float64)) 147 | } 148 | if origType == reflect.Float32 && msgType == reflect.Float64 { 149 | arg = float32(arg.(float64)) 150 | } 151 | params[i] = reflect.ValueOf(arg) 152 | } 153 | 154 | res := taskFunc.Call(params) 155 | result := &ResultMessage{ 156 | ID: msg.ID, 157 | Status: TaskSuccess, 158 | Traceback: "", 159 | } 160 | if len(res) == 0 { 161 | return result, nil 162 | } 163 | 164 | result.Result = GetRealValue(&res[0]) 165 | errorResult := res[1] 166 | if !errorResult.IsNil() { 167 | result.Status = TaskFail 168 | result.Traceback = errorResult.Interface().(error).Error() 169 | } 170 | 171 | return result, nil 172 | } 173 | -------------------------------------------------------------------------------- /tools/issue_certificate.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "crypto/x509" 5 | "crypto/x509/pkix" 6 | "fmt" 7 | "io/ioutil" 8 | "net" 9 | "net/url" 10 | "os" 11 | 12 | "github.com/baetyl/baetyl-go/v2/errors" 13 | "github.com/baetyl/baetyl-go/v2/pki" 14 | ) 15 | 16 | type helper struct { 17 | cli pki.PKI 18 | } 19 | 20 | type AltNames struct { 21 | DNSNames []string `json:"dnsNames,omitempty"` 22 | IPs []net.IP `json:"ips,omitempty"` 23 | Emails []string `json:"emails,omitempty"` 24 | URIs []*url.URL `json:"uris,omitempty"` 25 | } 26 | 27 | func genCsr(cn string, alt AltNames) *x509.CertificateRequest { 28 | return &x509.CertificateRequest{ 29 | Subject: pkix.Name{ 30 | Country: []string{"CN"}, 31 | Organization: []string{"Linux Foundation Edge"}, 32 | OrganizationalUnit: []string{"BAETYL"}, 33 | Locality: []string{"Haidian District"}, 34 | Province: []string{"Beijing"}, 35 | StreetAddress: []string{"Baidu Campus"}, 36 | PostalCode: []string{"100093"}, 37 | CommonName: cn, 38 | }, 39 | DNSNames: alt.DNSNames, 40 | EmailAddresses: alt.Emails, 41 | IPAddresses: alt.IPs, 42 | URIs: alt.URIs, 43 | } 44 | } 45 | 46 | func (h *helper) createRoot() (*pki.CertPem, error) { 47 | cn := "root.ca" 48 | csrInfo := genCsr(cn, AltNames{ 49 | IPs: []net.IP{ 50 | net.IPv4(0, 0, 0, 0), 51 | net.IPv4(127, 0, 0, 1), 52 | }, 53 | }) 54 | cert, err := h.cli.CreateSelfSignedRootCert(csrInfo, 50*365) 55 | if err != nil { 56 | return nil, errors.Trace(err) 57 | } 58 | fmt.Println("ca.crt") 59 | fmt.Println(string(cert.Crt)) 60 | fmt.Println("ca.key") 61 | fmt.Println(string(cert.Key)) 62 | return cert, nil 63 | } 64 | 65 | func (h *helper) createSub(cn string, alt AltNames, parent *pki.CertPem) (*pki.CertPem, error) { 66 | cert, err := h.cli.CreateSubCertWithKey(genCsr(cn, alt), 20*365, parent) 67 | if err != nil { 68 | return nil, errors.Trace(err) 69 | } 70 | fmt.Println("sub.crt") 71 | fmt.Println(string(cert.Crt)) 72 | fmt.Println("sub.key") 73 | fmt.Println(string(cert.Key)) 74 | return cert, nil 75 | } 76 | 77 | func (h *helper) issueCert() error { 78 | ca, err := h.createRoot() 79 | if err != nil { 80 | return errors.Trace(err) 81 | } 82 | err = ioutil.WriteFile("output/ca.crt", ca.Crt, 0666) 83 | if err != nil { 84 | return errors.Trace(err) 85 | } 86 | err = ioutil.WriteFile("output/ca.key", ca.Key, 0666) 87 | if err != nil { 88 | return errors.Trace(err) 89 | } 90 | 91 | client, err := h.createSub("client", AltNames{ 92 | IPs: []net.IP{ 93 | net.IPv4(0, 0, 0, 0), 94 | net.IPv4(127, 0, 0, 1), 95 | }, 96 | }, ca) 97 | if err != nil { 98 | return errors.Trace(err) 99 | } 100 | err = ioutil.WriteFile("output/client.crt", client.Crt, 0666) 101 | if err != nil { 102 | return errors.Trace(err) 103 | } 104 | err = ioutil.WriteFile("output/client.key", client.Key, 0666) 105 | if err != nil { 106 | return errors.Trace(err) 107 | } 108 | 109 | server, err := h.createSub("server", AltNames{ 110 | IPs: []net.IP{ 111 | net.IPv4(0, 0, 0, 0), 112 | net.IPv4(127, 0, 0, 1), 113 | }, 114 | DNSNames: []string{ 115 | "localhost", 116 | }, 117 | }, ca) 118 | if err != nil { 119 | return errors.Trace(err) 120 | } 121 | err = ioutil.WriteFile("output/server.crt", server.Crt, 0666) 122 | if err != nil { 123 | return errors.Trace(err) 124 | } 125 | err = ioutil.WriteFile("output/server.key", server.Key, 0666) 126 | if err != nil { 127 | return errors.Trace(err) 128 | } 129 | return nil 130 | } 131 | 132 | func main() { 133 | cli, err := pki.NewPKIClient() 134 | if err != nil { 135 | fmt.Println(err) 136 | os.Exit(1) 137 | } 138 | h := helper{ 139 | cli: cli, 140 | } 141 | err = h.issueCert() 142 | if err != nil { 143 | fmt.Println(err) 144 | os.Exit(1) 145 | } 146 | } 147 | -------------------------------------------------------------------------------- /trigger/trigger.go: -------------------------------------------------------------------------------- 1 | package trigger 2 | 3 | import ( 4 | "reflect" 5 | "sync" 6 | 7 | "github.com/baetyl/baetyl-go/v2/errors" 8 | "github.com/baetyl/baetyl-go/v2/log" 9 | ) 10 | 11 | type eventTrigger struct { 12 | list sync.Map 13 | } 14 | 15 | type EventFunc struct { 16 | Args []interface{} //event func register with arg 17 | Event interface{} //exec func 18 | } 19 | 20 | var triggerCall eventTrigger 21 | 22 | func init() { 23 | triggerCall = eventTrigger{list: sync.Map{}} 24 | } 25 | 26 | func Register(triggerName string, eventFunc EventFunc) error { 27 | fc := reflect.ValueOf(eventFunc.Event) 28 | if fc.Kind() != reflect.Func { 29 | return errors.Errorf("eventFuc %s is not func", eventFunc.Event) 30 | } 31 | triggerCall.list.Store(triggerName, eventFunc) 32 | return nil 33 | } 34 | 35 | func Exec(triggerName string, params ...interface{}) ([]reflect.Value, error) { 36 | if execFunc, ok := triggerCall.list.Load(triggerName); ok { 37 | f, ok := execFunc.(EventFunc) 38 | if !ok { 39 | log.L().Error("data is not a eventfuc") 40 | return nil, errors.Trace(errors.New("data is not a eventfunc")) 41 | } 42 | fc := reflect.ValueOf(f.Event) 43 | 44 | paramsNum := fc.Type().NumIn() 45 | if len(params)+len(f.Args) != paramsNum { 46 | log.L().Error("event " + triggerName + " params not enough") 47 | return nil, errors.Trace(errors.Errorf("event %s params not enough", triggerName)) 48 | } 49 | in := make([]reflect.Value, paramsNum) 50 | k := 0 51 | for _, param := range f.Args { 52 | in[k] = reflect.ValueOf(param) 53 | k++ 54 | } 55 | for _, param := range params { 56 | in[k] = reflect.ValueOf(param) 57 | k++ 58 | } 59 | result := fc.Call(in) 60 | return result, nil 61 | } 62 | log.L().Error("event" + triggerName + " func not exist") 63 | return nil, errors.Trace(errors.Errorf("event %s func not exit", triggerName)) 64 | 65 | } 66 | 67 | func SyncExec(event string, params ...interface{}) { 68 | go func() ([]reflect.Value, error) { 69 | defer func() { 70 | if r := recover(); r != nil { 71 | log.L().Error("sync exec trigger error " + event) 72 | return 73 | } 74 | }() 75 | return Exec(event, params) 76 | }() 77 | 78 | } 79 | -------------------------------------------------------------------------------- /trigger/trigger_test.go: -------------------------------------------------------------------------------- 1 | package trigger 2 | 3 | import ( 4 | "fmt" 5 | "github.com/stretchr/testify/assert" 6 | "testing" 7 | ) 8 | 9 | func A(a, b int) struct{ A int } { 10 | return struct{ A int }{A: a + b} 11 | } 12 | 13 | func B(a, b int) { 14 | fmt.Println(a + b) 15 | } 16 | 17 | func Test_eventTrigger_Exec(t1 *testing.T) { 18 | err := Register("addFunc", EventFunc{ 19 | Args: []interface{}{1}, 20 | Event: A, 21 | }) 22 | assert.NoError(t1, err) 23 | 24 | c, err := Exec("addFunc", 2) 25 | assert.NoError(t1, err) 26 | assert.Equal(t1, struct { 27 | A int 28 | }{A: 3}, c[0].Interface().(struct{ A int })) 29 | 30 | _, err = Exec("addFunc") 31 | assert.NotNil(t1, err) 32 | 33 | _, err = Exec("deleteFuc", 1, 2) 34 | assert.NotNil(t1, err) 35 | 36 | err = Register("addFunc2", EventFunc{ 37 | Args: []interface{}{1}, 38 | Event: 1, 39 | }) 40 | assert.NotNil(t1, err) 41 | 42 | err = Register("addFuncB", EventFunc{ 43 | Args: []interface{}{}, 44 | Event: B, 45 | }) 46 | assert.NoError(t1, err) 47 | rs, err := Exec("addFuncB", 1, 2) 48 | assert.NoError(t1, err) 49 | assert.Nil(t1, rs) 50 | 51 | SyncExec("addFuncB", 2, 4) 52 | } 53 | -------------------------------------------------------------------------------- /utils/cert.go: -------------------------------------------------------------------------------- 1 | package utils 2 | 3 | import ( 4 | "crypto/tls" 5 | 6 | "github.com/docker/go-connections/tlsconfig" 7 | 8 | "github.com/baetyl/baetyl-go/v2/errors" 9 | ) 10 | 11 | // Certificate certificate config for server 12 | // Name : serverNameOverride, same to CommonName in server.pem 13 | // if Name == "" , link would not verifies the server's certificate chain and host name 14 | // AuthType : declares the policy the server will follow for TLS Client Authentication 15 | type Certificate struct { 16 | CA string `yaml:"ca" json:"ca"` 17 | Key string `yaml:"key" json:"key"` 18 | Cert string `yaml:"cert" json:"cert"` 19 | Name string `yaml:"name" json:"name"` 20 | Passphrase string `yaml:"passphrase" json:"passphrase"` 21 | InsecureSkipVerify bool `yaml:"insecureSkipVerify" json:"insecureSkipVerify"` // for client, for test purpose 22 | tls.ClientAuthType `yaml:"clientAuthType" json:"clientAuthType"` 23 | } 24 | 25 | // NewTLSConfigServer loads tls config for server 26 | func NewTLSConfigServer(c Certificate) (*tls.Config, error) { 27 | cfg, err := tlsconfig.Server(tlsconfig.Options{CAFile: c.CA, KeyFile: c.Key, CertFile: c.Cert, ClientAuth: c.ClientAuthType}) 28 | return cfg, errors.Trace(err) 29 | } 30 | 31 | // NewTLSConfigClient loads tls config for client 32 | func NewTLSConfigClient(c Certificate) (*tls.Config, error) { 33 | cfg, err := tlsconfig.Client(tlsconfig.Options{CAFile: c.CA, KeyFile: c.Key, CertFile: c.Cert, InsecureSkipVerify: c.InsecureSkipVerify}) 34 | return cfg, errors.Trace(err) 35 | } 36 | 37 | // NewTLSConfigClientWithPassphrase loads tls config for client with passphrase 38 | func NewTLSConfigClientWithPassphrase(c Certificate) (*tls.Config, error) { 39 | cfg, err := tlsconfig.Client(tlsconfig.Options{CAFile: c.CA, KeyFile: c.Key, CertFile: c.Cert, InsecureSkipVerify: c.InsecureSkipVerify, Passphrase: c.Passphrase}) 40 | return cfg, errors.Trace(err) 41 | } 42 | -------------------------------------------------------------------------------- /utils/cert_test.go: -------------------------------------------------------------------------------- 1 | package utils 2 | 3 | import ( 4 | "crypto/tls" 5 | "testing" 6 | 7 | "github.com/stretchr/testify/assert" 8 | ) 9 | 10 | func TestNewTLSConfigServer(t *testing.T) { 11 | tl, err := NewTLSConfigServer(Certificate{Key: "../example/var/lib/baetyl/testcert/server.key", ClientAuthType: tls.VerifyClientCertIfGiven}) 12 | assert.Error(t, err) 13 | 14 | tl, err = NewTLSConfigServer(Certificate{Cert: "../example/var/lib/baetyl/testcert/server.crt"}) 15 | assert.Error(t, err) 16 | 17 | c := Certificate{ 18 | Key: "../example/var/lib/baetyl/testcert/server.key", 19 | Cert: "../example/var/lib/baetyl/testcert/server.crt", 20 | } 21 | 22 | tl, err = NewTLSConfigServer(c) 23 | assert.NoError(t, err) 24 | assert.NotEmpty(t, tl) 25 | } 26 | 27 | func TestNewTLSConfigClient(t *testing.T) { 28 | tl, err := NewTLSConfigClient(Certificate{Key: "../example/var/lib/baetyl/testcert/client.key"}) 29 | assert.Error(t, err) 30 | 31 | tl, err = NewTLSConfigClient(Certificate{Cert: "../example/var/lib/baetyl/testcert/client.crt"}) 32 | assert.Error(t, err) 33 | assert.Empty(t, tl) 34 | 35 | c := Certificate{ 36 | Key: "../example/var/lib/baetyl/testcert/client.key", 37 | Cert: "../example/var/lib/baetyl/testcert/client.crt", 38 | } 39 | tl, err = NewTLSConfigClient(c) 40 | assert.NoError(t, err) 41 | assert.NotEmpty(t, tl) 42 | } 43 | 44 | func TestNewTLSConfigClientWithPassphrase(t *testing.T) { 45 | tl, err := NewTLSConfigClientWithPassphrase(Certificate{Key: "../example/var/lib/baetyl/testcert/client.key"}) 46 | assert.Error(t, err) 47 | 48 | tl, err = NewTLSConfigClientWithPassphrase(Certificate{Cert: "../example/var/lib/baetyl/testcert/client.crt"}) 49 | assert.Error(t, err) 50 | assert.Empty(t, tl) 51 | 52 | c := Certificate{ 53 | Key: "../example/var/lib/baetyl/testcert/client.key", 54 | Cert: "../example/var/lib/baetyl/testcert/client.crt", 55 | Passphrase: "1234", 56 | } 57 | tl, err = NewTLSConfigClientWithPassphrase(c) 58 | assert.NoError(t, err) 59 | assert.NotEmpty(t, tl) 60 | } 61 | -------------------------------------------------------------------------------- /utils/config.go: -------------------------------------------------------------------------------- 1 | package utils 2 | 3 | import ( 4 | "bytes" 5 | "encoding/json" 6 | "fmt" 7 | "io/ioutil" 8 | "os" 9 | "strconv" 10 | "strings" 11 | "text/template" 12 | 13 | "github.com/docker/go-units" 14 | "gopkg.in/yaml.v2" 15 | 16 | "github.com/baetyl/baetyl-go/v2/errors" 17 | ) 18 | 19 | // LoadYAML config into out interface, with defaults and validates 20 | func LoadYAML(path string, out interface{}) error { 21 | data, err := ioutil.ReadFile(path) 22 | if err != nil { 23 | return errors.Trace(err) 24 | } 25 | res, err := ParseEnv(data) 26 | if err != nil { 27 | fmt.Fprintf(os.Stderr, "config parse error: %s", err.Error()) 28 | res = data 29 | } 30 | return UnmarshalYAML(res, out) 31 | } 32 | 33 | // ParseEnv parses env 34 | func ParseEnv(data []byte) ([]byte, error) { 35 | text := string(data) 36 | envs := os.Environ() 37 | envMap := make(map[string]string) 38 | for _, s := range envs { 39 | t := strings.Split(s, "=") 40 | envMap[t[0]] = t[1] 41 | } 42 | tmpl, err := template.New("template").Option("missingkey=error").Parse(text) 43 | if err != nil { 44 | return nil, errors.Trace(err) 45 | } 46 | buffer := bytes.NewBuffer(nil) 47 | err = tmpl.Execute(buffer, envMap) 48 | if err != nil { 49 | return nil, errors.Trace(err) 50 | } 51 | return buffer.Bytes(), nil 52 | } 53 | 54 | // UnmarshalYAML unmarshals, defaults and validates 55 | func UnmarshalYAML(in []byte, out interface{}) error { 56 | err := yaml.Unmarshal(in, out) 57 | if err != nil { 58 | return errors.Trace(err) 59 | } 60 | err = SetDefaults(out) 61 | if err != nil { 62 | return errors.Trace(err) 63 | } 64 | err = GetValidator().Struct(out) 65 | if err != nil { 66 | return errors.Trace(err) 67 | } 68 | return nil 69 | } 70 | 71 | // UnmarshalJSON unmarshals, defaults and validates 72 | func UnmarshalJSON(in []byte, out interface{}) error { 73 | err := json.Unmarshal(in, out) 74 | if err != nil { 75 | return errors.Trace(err) 76 | } 77 | err = SetDefaults(out) 78 | if err != nil { 79 | return errors.Trace(err) 80 | } 81 | err = GetValidator().Struct(out) 82 | if err != nil { 83 | return errors.Trace(err) 84 | } 85 | return nil 86 | } 87 | 88 | // Size int64 89 | type Size int64 90 | 91 | // MarshalYAML customizes marshal 92 | func (s Size) MarshalYAML() (interface{}, error) { 93 | return int64(s), nil 94 | } 95 | 96 | // UnmarshalYAML customizes unmarshal 97 | func (s *Size) UnmarshalYAML(unmarshal func(interface{}) error) error { 98 | var str string 99 | err := unmarshal(&str) 100 | if err != nil { 101 | return errors.Trace(err) 102 | } 103 | v, err := units.RAMInBytes(str) 104 | if err != nil { 105 | return errors.Trace(err) 106 | } 107 | *s = Size(v) 108 | return nil 109 | } 110 | 111 | // MarshalJSON customizes marshal 112 | func (s Size) MarshalJSON() ([]byte, error) { 113 | return []byte(strconv.FormatInt(int64(s), 10)), nil 114 | } 115 | 116 | // UnmarshalJSON customizes unmarshal 117 | func (s *Size) UnmarshalJSON(data []byte) error { 118 | str := string(data) 119 | if str == "null" { 120 | return nil 121 | } 122 | str = strings.Trim(str, "\"") 123 | v, err := units.RAMInBytes(str) 124 | if err != nil { 125 | return errors.Trace(err) 126 | } 127 | *s = Size(v) 128 | return nil 129 | } 130 | 131 | /* 132 | "b" represents for "B" 133 | "k" represents for "KB" or "KiB" 134 | "m" represents for "MB" or "MiB" 135 | "g" represents for "GB" or "GiB" 136 | "t" represents for "TB" or "TiB" 137 | "p" represents for "PB" or "PiB" 138 | maxValue is (2 >> 63 -1). 139 | */ 140 | var decimapAbbrs = []string{"", "k", "m", "g", "t", "p"} 141 | 142 | // Length int64 143 | // ! Length is deprecated, please to use Size 144 | type Length struct { 145 | Max int64 `yaml:"max" json:"max"` 146 | } 147 | 148 | // UnmarshalYAML customizes unmarshal 149 | func (l *Length) UnmarshalYAML(unmarshal func(interface{}) error) error { 150 | var ls length 151 | err := unmarshal(&ls) 152 | if err != nil { 153 | return errors.Trace(err) 154 | } 155 | if ls.Max != "" { 156 | l.Max, err = units.RAMInBytes(ls.Max) 157 | if err != nil { 158 | return errors.Trace(err) 159 | } 160 | } 161 | return nil 162 | } 163 | 164 | // MarshalYAML implements the Marshaller interface 165 | func (l *Length) MarshalYAML() (interface{}, error) { 166 | var ls length 167 | ls.Max = units.CustomSize("%.4g%s", float64(l.Max), 1024.0, decimapAbbrs) 168 | return ls, nil 169 | } 170 | 171 | type length struct { 172 | Max string `yaml:"max" json:"max"` 173 | } 174 | -------------------------------------------------------------------------------- /utils/defaults.go: -------------------------------------------------------------------------------- 1 | package utils 2 | 3 | import ( 4 | "reflect" 5 | 6 | "github.com/creasty/defaults" 7 | 8 | "github.com/baetyl/baetyl-go/v2/errors" 9 | ) 10 | 11 | // SetDefaults set default values 12 | func SetDefaults(ptr interface{}) error { 13 | err := defaults.Set(ptr) 14 | if err != nil { 15 | return errors.Errorf("%v: %s", ptr, err.Error()) 16 | } 17 | 18 | v := reflect.ValueOf(ptr).Elem() 19 | t := v.Type() 20 | 21 | for i := 0; i < t.NumField(); i++ { 22 | tf := t.Field(i) 23 | vf := v.Field(i) 24 | if tf.Type.Kind() == reflect.Slice { 25 | for j := 0; j < vf.Len(); j++ { 26 | item := vf.Index(j) 27 | if item.Kind() != reflect.Struct { 28 | continue 29 | } 30 | err := setDefaults(item) 31 | if err != nil { 32 | return errors.Trace(err) 33 | } 34 | } 35 | } 36 | if tf.Type.Kind() == reflect.Map { 37 | for _, k := range vf.MapKeys() { 38 | item := vf.MapIndex(k) 39 | if item.Kind() != reflect.Struct { 40 | continue 41 | } 42 | tmp := reflect.New(item.Type()) 43 | tmp.Elem().Set(item) 44 | err := setDefaults(tmp.Elem()) 45 | vf.SetMapIndex(k, tmp.Elem()) 46 | if err != nil { 47 | return errors.Trace(err) 48 | } 49 | } 50 | } 51 | } 52 | return nil 53 | } 54 | 55 | func setDefaults(v reflect.Value) error { 56 | tmp := reflect.New(v.Type()) 57 | tmp.Elem().Set(v) 58 | err := SetDefaults(tmp.Interface()) 59 | if err != nil { 60 | return errors.Trace(err) 61 | } 62 | v.Set(tmp.Elem()) 63 | return nil 64 | } 65 | -------------------------------------------------------------------------------- /utils/defaults_test.go: -------------------------------------------------------------------------------- 1 | package utils 2 | 3 | import ( 4 | "testing" 5 | "time" 6 | 7 | "github.com/stretchr/testify/assert" 8 | ) 9 | 10 | type testDefaultsModule struct { 11 | Name string `yaml:"name"` 12 | Params []string `yaml:"params" default:"[\"-c\", \"conf.yml\"]"` 13 | } 14 | 15 | type testDefaultsStruct struct { 16 | Others string `yaml:"others"` 17 | Timeout time.Duration `yaml:"timeout" default:"1m"` 18 | Modules []testDefaultsModule `yaml:"modules" default:"[]"` 19 | Services map[string]testDefaultsModule `yaml:"modules" default:"{}"` 20 | } 21 | 22 | func TestSetDefaults(t *testing.T) { 23 | err := SetDefaults("") 24 | assert.NotNil(t, err) 25 | assert.Equal(t, ": not a struct pointer", err.Error()) 26 | 27 | tests := []struct { 28 | name string 29 | args *testDefaultsStruct 30 | want *testDefaultsStruct 31 | wantErr bool 32 | }{ 33 | { 34 | name: "defaults-struct-slice", 35 | args: &testDefaultsStruct{ 36 | Others: "others", 37 | Modules: []testDefaultsModule{ 38 | { 39 | Name: "m1", 40 | }, 41 | { 42 | Name: "m2", 43 | Params: []string{"arg1", "arg2"}, 44 | }, 45 | }, 46 | Services: map[string]testDefaultsModule{ 47 | "m1": {}, 48 | "m2": { 49 | Params: []string{"arg1", "arg2"}, 50 | }, 51 | }, 52 | }, 53 | want: &testDefaultsStruct{ 54 | Others: "others", 55 | Timeout: time.Minute, 56 | Modules: []testDefaultsModule{ 57 | { 58 | Name: "m1", 59 | Params: []string{"-c", "conf.yml"}, 60 | }, 61 | { 62 | Name: "m2", 63 | Params: []string{"arg1", "arg2"}, 64 | }, 65 | }, 66 | Services: map[string]testDefaultsModule{ 67 | "m1": { 68 | Params: []string{"-c", "conf.yml"}, 69 | }, 70 | "m2": { 71 | Params: []string{"arg1", "arg2"}, 72 | }, 73 | }, 74 | }, 75 | }, 76 | } 77 | for _, tt := range tests { 78 | t.Run(tt.name, func(t *testing.T) { 79 | if err := SetDefaults(tt.args); (err != nil) != tt.wantErr { 80 | t.Errorf("SetDefaults() error = %v, wantErr %v", err, tt.wantErr) 81 | } 82 | assert.Equal(t, tt.want, tt.args) 83 | }) 84 | } 85 | } 86 | -------------------------------------------------------------------------------- /utils/fingerprint.go: -------------------------------------------------------------------------------- 1 | package utils 2 | 3 | import ( 4 | "crypto/hmac" 5 | "crypto/sha256" 6 | "encoding/hex" 7 | 8 | "github.com/denisbrodbeck/machineid" 9 | "github.com/super-l/machine-code/machine" 10 | ) 11 | 12 | const DefaultAppID = "hBmNlyAWkmrKfqwCFWoSiJiTsZJWksvv" 13 | 14 | func GetFingerprint(appID string) (string, error) { 15 | if appID == "" { 16 | appID = DefaultAppID 17 | } 18 | machineID, err := machineid.ProtectedID(appID) 19 | if err != nil { 20 | return "", err 21 | } 22 | uuid, err := machine.GetPlatformUUID() 23 | if err != nil { 24 | return "", err 25 | } 26 | macInfo, err := machine.GetMACAddress() 27 | if err != nil { 28 | return "", err 29 | } 30 | mac := hmac.New(sha256.New, []byte(machineID+uuid+macInfo)) 31 | mac.Write([]byte(appID)) 32 | return hex.EncodeToString(mac.Sum(nil)), nil 33 | } 34 | -------------------------------------------------------------------------------- /utils/fingerprint_test.go: -------------------------------------------------------------------------------- 1 | package utils 2 | 3 | import ( 4 | "testing" 5 | 6 | "github.com/stretchr/testify/assert" 7 | ) 8 | 9 | func TestGetFingerprint(t *testing.T) { 10 | id1, _ := GetFingerprint("") 11 | id2, _ := GetFingerprint("") 12 | assert.Equal(t, id1, id2) 13 | } 14 | -------------------------------------------------------------------------------- /utils/flock.go: -------------------------------------------------------------------------------- 1 | //go:build darwin || linux 2 | // +build darwin linux 3 | 4 | package utils 5 | 6 | import ( 7 | "os" 8 | "syscall" 9 | "time" 10 | 11 | "github.com/baetyl/baetyl-go/v2/errors" 12 | ) 13 | 14 | const ( 15 | DefaultFlockRetry = time.Microsecond * 100 16 | ) 17 | 18 | // only works on unix 19 | func Flock(file *os.File, timeout time.Duration) error { 20 | var t time.Time 21 | if timeout != 0 { 22 | t = time.Now() 23 | } 24 | fd := file.Fd() 25 | flag := syscall.LOCK_NB | syscall.LOCK_EX 26 | for { 27 | err := syscall.Flock(int(fd), flag) 28 | if err == nil { 29 | return nil 30 | } else if err != syscall.EWOULDBLOCK { 31 | return err 32 | } 33 | if timeout != 0 && time.Since(t) > timeout-DefaultFlockRetry { 34 | return errors.New("timeout") 35 | } 36 | time.Sleep(DefaultFlockRetry) 37 | } 38 | } 39 | 40 | func Funlock(file *os.File) error { 41 | if file != nil { 42 | return syscall.Flock(int(file.Fd()), syscall.LOCK_UN) 43 | } 44 | return os.ErrNotExist 45 | } 46 | -------------------------------------------------------------------------------- /utils/flock_windows.go: -------------------------------------------------------------------------------- 1 | //go:build windows 2 | // +build windows 3 | 4 | package utils 5 | 6 | import ( 7 | "os" 8 | "syscall" 9 | "time" 10 | "unsafe" 11 | 12 | "github.com/baetyl/baetyl-go/v2/errors" 13 | ) 14 | 15 | var ( 16 | modkernel32 = syscall.NewLazyDLL("kernel32.dll") 17 | procLockFileEx = modkernel32.NewProc("LockFileEx") 18 | procUnlockFileEx = modkernel32.NewProc("UnlockFileEx") 19 | ) 20 | 21 | const ( 22 | flagLockExclusive = 2 23 | flagLockFailImmediately = 1 24 | errLockViolation syscall.Errno = 0x21 25 | DefaultFlockRetry = time.Microsecond * 100 26 | ) 27 | 28 | func Flock(file *os.File, timeout time.Duration) error { 29 | var t time.Time 30 | if timeout != 0 { 31 | t = time.Now() 32 | } 33 | fd := file.Fd() 34 | var flag uint32 = flagLockFailImmediately | flagLockExclusive 35 | var m1 uint32 = (1 << 32) - 1 36 | for { 37 | err := lockFileEx(syscall.Handle(fd), flag, 0, 1, 0, &syscall.Overlapped{ 38 | Offset: m1, 39 | OffsetHigh: m1, 40 | }) 41 | if err == nil { 42 | return nil 43 | } else if err != errLockViolation { 44 | return err 45 | } 46 | if timeout != 0 && time.Since(t) > timeout-DefaultFlockRetry { 47 | return errors.New("timeout") 48 | } 49 | time.Sleep(DefaultFlockRetry) 50 | } 51 | } 52 | 53 | func Funlock(file *os.File) error { 54 | var m1 uint32 = (1 << 32) - 1 55 | return unlockFileEx(syscall.Handle(file.Fd()), 0, 1, 0, &syscall.Overlapped{ 56 | Offset: m1, 57 | OffsetHigh: m1, 58 | }) 59 | } 60 | 61 | func lockFileEx(h syscall.Handle, flags, reserved, locklow, lockhigh uint32, ol *syscall.Overlapped) (err error) { 62 | r, _, err := procLockFileEx.Call(uintptr(h), uintptr(flags), uintptr(reserved), uintptr(locklow), uintptr(lockhigh), uintptr(unsafe.Pointer(ol))) 63 | if r == 0 { 64 | return err 65 | } 66 | return nil 67 | } 68 | 69 | func unlockFileEx(h syscall.Handle, reserved, locklow, lockhigh uint32, ol *syscall.Overlapped) (err error) { 70 | r, _, err := procUnlockFileEx.Call(uintptr(h), uintptr(reserved), uintptr(locklow), uintptr(lockhigh), uintptr(unsafe.Pointer(ol)), 0) 71 | if r == 0 { 72 | return err 73 | } 74 | return nil 75 | } 76 | -------------------------------------------------------------------------------- /utils/matcher.go: -------------------------------------------------------------------------------- 1 | package utils 2 | 3 | import ( 4 | "github.com/jinzhu/copier" 5 | kl "k8s.io/apimachinery/pkg/labels" 6 | ) 7 | 8 | // IsLabelMatch match resource according to labels 9 | func IsLabelMatch(labelSelector string, labels map[string]string) (bool, error) { 10 | selector, err := kl.Parse(labelSelector) 11 | 12 | if err != nil { 13 | return false, err 14 | } 15 | 16 | labelSet := kl.Set{} 17 | copier.Copy(&labelSet, &labels) 18 | 19 | return selector.Matches(labelSet), nil 20 | } 21 | -------------------------------------------------------------------------------- /utils/matcher_test.go: -------------------------------------------------------------------------------- 1 | package utils 2 | 3 | import ( 4 | "testing" 5 | 6 | "gotest.tools/assert" 7 | ) 8 | 9 | func TestLabelMatcher_Match(t *testing.T) { 10 | labels := make(map[string]string) 11 | labels["a"] = "b" 12 | labels["c"] = "d" 13 | 14 | sl := "a in (b),c=d" 15 | res, _ := IsLabelMatch(sl, labels) 16 | assert.Equal(t, true, res) 17 | 18 | sl = "a=bc=d" 19 | _, err := IsLabelMatch(sl, labels) 20 | assert.Equal(t, true, err != nil) 21 | 22 | sl = "" 23 | res, _ = IsLabelMatch(sl, labels) 24 | assert.Equal(t, true, res) 25 | 26 | var sl1 string 27 | res, _ = IsLabelMatch(sl1, labels) 28 | assert.Equal(t, true, res) 29 | } 30 | -------------------------------------------------------------------------------- /utils/path.go: -------------------------------------------------------------------------------- 1 | package utils 2 | 3 | import ( 4 | "crypto/md5" 5 | "encoding/base64" 6 | "encoding/hex" 7 | "io" 8 | "os" 9 | 10 | "github.com/baetyl/baetyl-go/v2/errors" 11 | ) 12 | 13 | // PathExists checks path exists 14 | func PathExists(path string) bool { 15 | _, err := os.Stat(path) 16 | if err != nil { 17 | if os.IsExist(err) { 18 | return true 19 | } 20 | return false 21 | } 22 | return true 23 | } 24 | 25 | // DirExists checks dir exists 26 | func DirExists(path string) bool { 27 | fi, err := os.Stat(path) 28 | if err != nil { 29 | return os.IsExist(err) 30 | } 31 | return fi.IsDir() 32 | } 33 | 34 | // FileExists checks file exists 35 | func FileExists(path string) bool { 36 | fi, err := os.Stat(path) 37 | if err != nil { 38 | return os.IsExist(err) 39 | } 40 | return !fi.IsDir() 41 | } 42 | 43 | // WriteFile writes data into file in chunk mode 44 | func WriteFile(fn string, r io.Reader) error { 45 | f, err := os.Create(fn) 46 | if err != nil { 47 | return errors.Trace(err) 48 | } 49 | defer f.Close() 50 | 51 | _, err = io.Copy(f, r) 52 | return errors.Trace(err) 53 | } 54 | 55 | // CopyFile copy data from one file to another 56 | func CopyFile(s, t string) error { 57 | sf, err := os.Open(s) 58 | if err != nil { 59 | return errors.Trace(err) 60 | } 61 | defer sf.Close() 62 | 63 | return errors.Trace(WriteFile(t, sf)) 64 | } 65 | 66 | // CalculateFileMD5 calculates file MD5 in hex format 67 | func CalculateFileMD5(fn string) (string, error) { 68 | f, err := os.Open(fn) 69 | if err != nil { 70 | return "", errors.Trace(err) 71 | } 72 | defer f.Close() 73 | 74 | hasher := md5.New() 75 | _, err = io.Copy(hasher, f) 76 | if err != nil { 77 | return "", errors.Trace(err) 78 | } 79 | return hex.EncodeToString(hasher.Sum(nil)), nil 80 | } 81 | 82 | // CalculateBase64 calculates base64 encoding value of target string 83 | func CalculateBase64(s string) string { 84 | return base64.StdEncoding.EncodeToString([]byte(s)) 85 | } 86 | 87 | // CreateSymlink create symlink of target 88 | func CreateSymlink(target, symlink string) error { 89 | if PathExists(symlink) { 90 | return nil 91 | } 92 | err := os.Symlink(target, symlink) 93 | if err != nil { 94 | return errors.Errorf("failed to make symlink %s of %s: %s", target, symlink, err.Error()) 95 | } 96 | return nil 97 | } 98 | -------------------------------------------------------------------------------- /utils/port.go: -------------------------------------------------------------------------------- 1 | package utils 2 | 3 | import ( 4 | "fmt" 5 | "net" 6 | ) 7 | 8 | // GetAvailablePort finds an available port 9 | func GetAvailablePort(host string) (int, error) { 10 | address, err := net.ResolveTCPAddr("tcp", host+":0") 11 | if err != nil { 12 | return 0, err 13 | } 14 | 15 | listener, err := net.ListenTCP("tcp", address) 16 | if err != nil { 17 | return 0, err 18 | } 19 | defer listener.Close() 20 | return listener.Addr().(*net.TCPAddr).Port, nil 21 | } 22 | 23 | func CheckPortAvailable(ip string, port int) bool { 24 | l, err := net.Listen("tcp", fmt.Sprintf("%s:%d", ip, port)) 25 | if err != nil { 26 | return false 27 | } 28 | l.Close() 29 | return true 30 | } 31 | -------------------------------------------------------------------------------- /utils/port_test.go: -------------------------------------------------------------------------------- 1 | package utils 2 | 3 | import ( 4 | "fmt" 5 | "net" 6 | "testing" 7 | 8 | "github.com/stretchr/testify/assert" 9 | ) 10 | 11 | func TestGetPortAvailable(t *testing.T) { 12 | got, err := GetAvailablePort("127.0.0.1") 13 | assert.NoError(t, err) 14 | listener, err := net.Listen("tcp", fmt.Sprintf(":%d", got)) 15 | assert.NoError(t, err) 16 | listener.Close() 17 | 18 | got, err = GetAvailablePort("0.0.0.0") 19 | assert.NoError(t, err) 20 | listener, err = net.Listen("tcp", fmt.Sprintf(":%d", got)) 21 | assert.NoError(t, err) 22 | listener.Close() 23 | } 24 | 25 | func TestCheckPortAvailable(t *testing.T) { 26 | got, err := GetAvailablePort("127.0.0.1") 27 | assert.NoError(t, err) 28 | 29 | listener1, err := net.Listen("tcp", fmt.Sprintf("127.0.0.1:%d", got)) 30 | assert.NoError(t, err) 31 | 32 | res := CheckPortAvailable("127.0.0.1", got) 33 | assert.False(t, res) 34 | 35 | listener1.Close() 36 | 37 | res = CheckPortAvailable("127.0.0.1", got) 38 | assert.True(t, res) 39 | } 40 | -------------------------------------------------------------------------------- /utils/string.go: -------------------------------------------------------------------------------- 1 | package utils 2 | 3 | import ( 4 | "math/rand" 5 | "strings" 6 | "time" 7 | "unsafe" 8 | ) 9 | 10 | var randSource rand.Source 11 | 12 | const ( 13 | bits = 6 14 | mask = 1<= 0; { 31 | if remain == 0 { 32 | cache, remain = randSource.Int63(), maxIndex 33 | } 34 | if idx := int(cache & mask); idx < len(characters) { 35 | b[i] = characters[idx] 36 | i-- 37 | } 38 | cache >>= bits 39 | remain-- 40 | } 41 | 42 | return strings.ToLower(*(*string)(unsafe.Pointer(&b))) 43 | } 44 | -------------------------------------------------------------------------------- /utils/string_test.go: -------------------------------------------------------------------------------- 1 | package utils 2 | 3 | import ( 4 | "testing" 5 | 6 | "github.com/stretchr/testify/assert" 7 | ) 8 | 9 | func TestRandString(t *testing.T) { 10 | res := RandString(6) 11 | assert.Equal(t, len(res), 6) 12 | } 13 | -------------------------------------------------------------------------------- /utils/tar.go: -------------------------------------------------------------------------------- 1 | package utils 2 | 3 | import ( 4 | "github.com/mholt/archiver" 5 | 6 | "github.com/baetyl/baetyl-go/v2/errors" 7 | ) 8 | 9 | var defaultTar = &archiver.Tar{ 10 | MkdirAll: true, 11 | OverwriteExisting: true, 12 | } 13 | 14 | // Tar tar source files to destination file 15 | func Tar(sources []string, destination string) error { 16 | return errors.Trace(defaultTar.Archive(sources, destination)) 17 | } 18 | 19 | // Untar untar source file to destination 20 | func Untar(source, destination string) error { 21 | return errors.Trace(defaultTar.Unarchive(source, destination)) 22 | } 23 | -------------------------------------------------------------------------------- /utils/tar_test.go: -------------------------------------------------------------------------------- 1 | package utils 2 | 3 | import ( 4 | "io/ioutil" 5 | "os" 6 | "testing" 7 | 8 | "github.com/stretchr/testify/assert" 9 | ) 10 | 11 | func TestTar(t *testing.T) { 12 | tmpdir, err := ioutil.TempDir("", "example") 13 | assert.NoError(t, err) 14 | defer os.RemoveAll(tmpdir) 15 | tmpfile, err := ioutil.TempFile(tmpdir, "test") 16 | assert.NoError(t, err) 17 | TarPath := tmpfile.Name() + ".tar" 18 | err = Tar([]string{tmpfile.Name()}, TarPath) 19 | assert.NoError(t, err) 20 | err = Untar(TarPath, tmpdir) 21 | assert.NoError(t, err) 22 | err = Untar(TarPath, tmpdir) 23 | assert.NoError(t, err) 24 | } 25 | -------------------------------------------------------------------------------- /utils/tgz.go: -------------------------------------------------------------------------------- 1 | package utils 2 | 3 | import ( 4 | "compress/flate" 5 | 6 | "github.com/mholt/archiver" 7 | 8 | "github.com/baetyl/baetyl-go/v2/errors" 9 | ) 10 | 11 | var defaultTgz = &archiver.TarGz{ 12 | Tar: &archiver.Tar{ 13 | MkdirAll: true, 14 | OverwriteExisting: true, 15 | }, 16 | CompressionLevel: flate.DefaultCompression, 17 | } 18 | 19 | // Tgz tar source files to destination file(.tgz/.tar.gz) 20 | func Tgz(sources []string, destination string) error { 21 | return errors.Trace(defaultTgz.Archive(sources, destination)) 22 | } 23 | 24 | // Untgz untar source file(.tgz/.tar.gz) to destination 25 | func Untgz(source, destination string) error { 26 | return errors.Trace(defaultTgz.Unarchive(source, destination)) 27 | } 28 | -------------------------------------------------------------------------------- /utils/tgz_test.go: -------------------------------------------------------------------------------- 1 | package utils 2 | 3 | import ( 4 | "io/ioutil" 5 | "os" 6 | "testing" 7 | 8 | "github.com/stretchr/testify/assert" 9 | ) 10 | 11 | func TestTgz(t *testing.T) { 12 | tmpdir, err := ioutil.TempDir("", "example") 13 | assert.NoError(t, err) 14 | defer os.RemoveAll(tmpdir) 15 | tmpfile, err := ioutil.TempFile(tmpdir, "test") 16 | assert.NoError(t, err) 17 | TarPath := tmpfile.Name() + ".tgz" 18 | err = Tgz([]string{tmpfile.Name()}, TarPath) 19 | assert.NoError(t, err) 20 | err = Untgz(TarPath, tmpdir) 21 | assert.NoError(t, err) 22 | err = Untgz(TarPath, tmpdir) 23 | assert.NoError(t, err) 24 | } 25 | 26 | func TestTargz(t *testing.T) { 27 | tmpdir, err := ioutil.TempDir("", "example") 28 | assert.NoError(t, err) 29 | defer os.RemoveAll(tmpdir) 30 | tmpfile, err := ioutil.TempFile(tmpdir, "test") 31 | assert.NoError(t, err) 32 | TarPath := tmpfile.Name() + ".tar.gz" 33 | err = Tgz([]string{tmpfile.Name()}, TarPath) 34 | assert.NoError(t, err) 35 | err = Untgz(TarPath, tmpdir) 36 | assert.NoError(t, err) 37 | err = Untgz(TarPath, tmpdir) 38 | assert.NoError(t, err) 39 | } 40 | -------------------------------------------------------------------------------- /utils/tomb.go: -------------------------------------------------------------------------------- 1 | package utils 2 | 3 | import ( 4 | "sync" 5 | 6 | "github.com/pkg/errors" 7 | tb "gopkg.in/tomb.v2" 8 | ) 9 | 10 | const ( 11 | ini = int32(0) 12 | gos = int32(1) 13 | ) 14 | 15 | // all errors 16 | var ( 17 | ErrStillAlive = tb.ErrStillAlive 18 | ErrDying = tb.ErrDying 19 | ) 20 | 21 | // Tomb wraps tomb.Tomb 22 | type Tomb struct { 23 | t tb.Tomb 24 | s int32 25 | m sync.Mutex 26 | } 27 | 28 | // Go runs functions in new goroutines. 29 | func (t *Tomb) Go(fs ...func() error) (err error) { 30 | defer func() { 31 | if p := recover(); p != nil { 32 | err = errors.Errorf("%v", p) 33 | } 34 | }() 35 | t.m.Lock() 36 | defer t.m.Unlock() 37 | t.s = gos 38 | for _, f := range fs { 39 | t.t.Go(f) 40 | } 41 | return 42 | } 43 | 44 | // Kill puts the tomb in a dying state for the given reason. 45 | func (t *Tomb) Kill(reason error) { 46 | t.t.Kill(reason) 47 | } 48 | 49 | // Dying returns the channel that can be used to wait until 50 | // t.Kill is called. 51 | func (t *Tomb) Dying() <-chan struct{} { 52 | return t.t.Dying() 53 | } 54 | 55 | // Dead returns the channel that can be used to wait until all goroutines have finished running. 56 | func (t *Tomb) Dead() <-chan struct{} { 57 | return t.t.Dead() 58 | } 59 | 60 | // Wait blocks until all goroutines have finished running, and 61 | // then returns the reason for their death. 62 | // 63 | // If tomb does not start any goroutine, return quickly 64 | func (t *Tomb) Wait() (err error) { 65 | t.m.Lock() 66 | if t.s == gos { 67 | err = t.t.Wait() 68 | } 69 | t.m.Unlock() 70 | return 71 | } 72 | 73 | // Alive returns true if the tomb is not in a dying or dead state. 74 | func (t *Tomb) Alive() bool { 75 | return t.t.Alive() 76 | } 77 | 78 | // Err returns the death reason, or ErrStillAlive if the tomb is not in a dying or dead state. 79 | func (t *Tomb) Err() (reason error) { 80 | return t.t.Err() 81 | } 82 | -------------------------------------------------------------------------------- /utils/tomb_test.go: -------------------------------------------------------------------------------- 1 | package utils 2 | 3 | import ( 4 | "fmt" 5 | "reflect" 6 | "testing" 7 | "time" 8 | 9 | "github.com/stretchr/testify/assert" 10 | ) 11 | 12 | func TestTomb(t *testing.T) { 13 | tb := new(Tomb) 14 | err := tb.Go(func() error { 15 | <-tb.Dying() 16 | return nil 17 | }) 18 | assert.NoError(t, err) 19 | assert.EqualError(t, tb.Err(), ErrStillAlive.Error()) 20 | tb.Kill(nil) 21 | err = tb.Wait() 22 | assert.NoError(t, err) 23 | 24 | tb = new(Tomb) 25 | err = tb.Go(func() error { 26 | <-tb.Dying() 27 | return fmt.Errorf("abc") 28 | }) 29 | assert.NoError(t, err) 30 | tb.Kill(nil) 31 | tb.Kill(nil) 32 | err = tb.Wait() 33 | assert.EqualError(t, err, "abc") 34 | err = tb.Wait() 35 | assert.EqualError(t, err, "abc") 36 | 37 | tb = new(Tomb) 38 | tb.Kill(fmt.Errorf("abc")) 39 | err = tb.Go(func() error { 40 | <-tb.Dying() 41 | return nil 42 | }) 43 | assert.NoError(t, err) 44 | err = tb.Wait() 45 | assert.EqualError(t, err, "abc") 46 | 47 | tb = new(Tomb) 48 | tb.Kill(fmt.Errorf("abc")) 49 | err = tb.Wait() 50 | assert.NoError(t, err) 51 | err = tb.Go(func() error { 52 | <-tb.Dying() 53 | return nil 54 | }) 55 | assert.NoError(t, err) 56 | tb.Kill(nil) 57 | err = tb.Wait() 58 | assert.EqualError(t, err, "abc") 59 | 60 | tb = new(Tomb) 61 | tb.Kill(nil) 62 | err = tb.Wait() 63 | assert.NoError(t, err) 64 | err = tb.Go(func() error { 65 | <-tb.Dying() 66 | return nil 67 | }) 68 | assert.NoError(t, err) 69 | tb.Kill(fmt.Errorf("abc")) 70 | tb.Kill(fmt.Errorf("efd")) 71 | err = tb.Wait() 72 | assert.EqualError(t, err, "abc") 73 | assert.EqualError(t, tb.Err(), "abc") 74 | 75 | tb = new(Tomb) 76 | err = tb.Go(func() error { 77 | <-tb.Dying() 78 | return nil 79 | }) 80 | assert.NoError(t, err) 81 | tb.Kill(nil) 82 | err = tb.Wait() 83 | assert.NoError(t, err) 84 | err = tb.Go(func() error { 85 | <-tb.Dying() 86 | return nil 87 | }) 88 | assert.EqualError(t, err, "tomb.Go called after all goroutines terminated") 89 | 90 | tb = new(Tomb) 91 | err = tb.Go(func() error { 92 | return nil 93 | }) 94 | assert.NoError(t, err) 95 | time.Sleep(time.Millisecond * 100) 96 | err = tb.Go(func() error { 97 | return nil 98 | }) 99 | assert.EqualError(t, err, "tomb.Go called after all goroutines terminated") 100 | tb.Kill(nil) 101 | err = tb.Wait() 102 | assert.NoError(t, err) 103 | } 104 | 105 | func TestKillErrStillAlivePanic(t *testing.T) { 106 | tb := new(Tomb) 107 | defer func() { 108 | err := recover() 109 | if err != "tomb: Kill with ErrStillAlive" { 110 | t.Fatalf("Wrong panic on Kill(ErrStillAlive): %v", err) 111 | } 112 | checkState(t, tb, false, false, ErrStillAlive) 113 | }() 114 | b := tb.Alive() 115 | assert.Equal(t, true, b) 116 | assert.EqualError(t, tb.Err(), ErrStillAlive.Error()) 117 | tb.Kill(ErrStillAlive) 118 | } 119 | 120 | func checkState(t *testing.T, tm *Tomb, wantDying, wantDead bool, wantErr error) { 121 | select { 122 | case <-tm.Dying(): 123 | if !wantDying { 124 | t.Error("<-Dying: should block") 125 | } 126 | default: 127 | if wantDying { 128 | t.Error("<-Dying: should not block") 129 | } 130 | } 131 | seemsDead := false 132 | select { 133 | case <-tm.Dead(): 134 | if !wantDead { 135 | t.Error("<-Dead: should block") 136 | } 137 | seemsDead = true 138 | default: 139 | if wantDead { 140 | t.Error("<-Dead: should not block") 141 | } 142 | } 143 | if err := tm.Err(); err != wantErr { 144 | t.Errorf("Err: want %#v, got %#v", wantErr, err) 145 | } 146 | if wantDead && seemsDead { 147 | waitErr := tm.Wait() 148 | switch { 149 | case waitErr == ErrStillAlive: 150 | t.Errorf("Wait should not return ErrStillAlive") 151 | case !reflect.DeepEqual(waitErr, wantErr): 152 | t.Errorf("Wait: want %#v, got %#v", wantErr, waitErr) 153 | } 154 | } 155 | } 156 | 157 | func BenchmarkA(b *testing.B) { 158 | msg := "aaa" 159 | msgchan := make(chan string, b.N) 160 | var tomb Tomb 161 | for i := 0; i < b.N; i++ { 162 | select { 163 | case <-tomb.Dying(): 164 | continue 165 | case msgchan <- msg: 166 | default: // discard if channel is full 167 | } 168 | } 169 | } 170 | 171 | func BenchmarkB(b *testing.B) { 172 | msg := "aaa" 173 | msgchan := make(chan string, b.N) 174 | var tomb Tomb 175 | for i := 0; i < b.N; i++ { 176 | if !tomb.Alive() { 177 | continue 178 | } 179 | select { 180 | case msgchan <- msg: 181 | default: // discard if channel is full 182 | } 183 | } 184 | } 185 | -------------------------------------------------------------------------------- /utils/trace.go: -------------------------------------------------------------------------------- 1 | package utils 2 | 3 | import ( 4 | "time" 5 | 6 | "github.com/baetyl/baetyl-go/v2/log" 7 | ) 8 | 9 | // Trace print elapsed time 10 | func Trace(f func(string, ...log.Field), msg string, fields ...log.Field) func() { 11 | start := time.Now() 12 | return func() { 13 | fields := append(fields, log.Any("cost", time.Since(start))) 14 | f(msg, fields...) 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /utils/url.go: -------------------------------------------------------------------------------- 1 | package utils 2 | 3 | import ( 4 | "net/url" 5 | "strings" 6 | 7 | "github.com/baetyl/baetyl-go/v2/errors" 8 | ) 9 | 10 | // ParseURL parses a url string 11 | func ParseURL(addr string) (*url.URL, error) { 12 | if strings.HasPrefix(addr, "unix://") { 13 | parts := strings.SplitN(addr, "://", 2) 14 | return &url.URL{ 15 | Scheme: parts[0], 16 | Host: parts[1], 17 | }, nil 18 | } 19 | res, err := url.Parse(addr) 20 | return res, errors.Trace(err) 21 | } 22 | -------------------------------------------------------------------------------- /utils/url_test.go: -------------------------------------------------------------------------------- 1 | package utils 2 | 3 | import ( 4 | "net/url" 5 | "reflect" 6 | "testing" 7 | ) 8 | 9 | func TestParseURL(t *testing.T) { 10 | tests := []struct { 11 | name string 12 | addr string 13 | want *url.URL 14 | wantErr bool 15 | }{ 16 | { 17 | name: "unix-1", 18 | addr: "unix:///var/run/baetyl.sock", 19 | want: &url.URL{ 20 | Scheme: "unix", 21 | Host: "/var/run/baetyl.sock", 22 | }, 23 | }, 24 | { 25 | name: "unix-2", 26 | addr: "unix://./var/run/baetyl.sock", 27 | want: &url.URL{ 28 | Scheme: "unix", 29 | Host: "./var/run/baetyl.sock", 30 | }, 31 | }, 32 | { 33 | name: "unix-3", 34 | addr: "unix://var/run/baetyl.sock", 35 | want: &url.URL{ 36 | Scheme: "unix", 37 | Host: "var/run/baetyl.sock", 38 | }, 39 | }, 40 | { 41 | name: "tcp-1", 42 | addr: "tcp://127.0.0.1:50050", 43 | want: &url.URL{ 44 | Scheme: "tcp", 45 | Host: "127.0.0.1:50050", 46 | }, 47 | }, 48 | { 49 | name: "tcp-2", 50 | addr: "tcp://127.0.0.1:50050/v1/api", 51 | want: &url.URL{ 52 | Scheme: "tcp", 53 | Host: "127.0.0.1:50050", 54 | Path: "/v1/api", 55 | }, 56 | }, 57 | { 58 | name: "http-1", 59 | addr: "http://127.0.0.1:50050/v1/api", 60 | want: &url.URL{ 61 | Scheme: "http", 62 | Host: "127.0.0.1:50050", 63 | Path: "/v1/api", 64 | }, 65 | }, 66 | { 67 | name: "https-1", 68 | addr: "https://127.0.0.1:50050/v1/api", 69 | want: &url.URL{ 70 | Scheme: "https", 71 | Host: "127.0.0.1:50050", 72 | Path: "/v1/api", 73 | }, 74 | }, 75 | { 76 | name: "error-1", 77 | want: &url.URL{}, 78 | }, 79 | { 80 | name: "error-2", 81 | addr: "unix://", 82 | want: &url.URL{ 83 | Scheme: "unix", 84 | }, 85 | }, 86 | { 87 | name: "error-3", 88 | addr: "dummy", 89 | want: &url.URL{ 90 | Path: "dummy", 91 | }, 92 | }, 93 | } 94 | for _, tt := range tests { 95 | t.Run(tt.name, func(t *testing.T) { 96 | got, err := ParseURL(tt.addr) 97 | if (err != nil) != tt.wantErr { 98 | t.Errorf("ParseURL() error = %v, wantErr %v", err, tt.wantErr) 99 | return 100 | } 101 | if !reflect.DeepEqual(got, tt.want) { 102 | t.Errorf("ParseURL() = %v, want %v", got, tt.want) 103 | } 104 | }) 105 | } 106 | } 107 | -------------------------------------------------------------------------------- /utils/validate.go: -------------------------------------------------------------------------------- 1 | package utils 2 | 3 | import ( 4 | "reflect" 5 | "regexp" 6 | "strings" 7 | "unicode/utf8" 8 | 9 | "github.com/go-playground/validator/v10" 10 | ) 11 | 12 | const ( 13 | resName = "res_name" 14 | svcName = "svc_name" 15 | fingerprint = "fingerprint" 16 | memory = "memory" 17 | duration = "duration" 18 | devModel = "dev_model" 19 | namespace = "namespace" 20 | validLabels = "label" 21 | validConfigKeys = "config_key" 22 | dateType = "data_type" 23 | dateEnumType = "enum_type" 24 | datePlusType = "data_plus_type" 25 | 26 | nonzero = "nonzero" 27 | nonnil = "nonnil" 28 | nonbaetyl = "nonbaetyl" 29 | ) 30 | 31 | var regexps = map[string]string{ 32 | resName: "^[a-z0-9][-a-z0-9.]{0,61}[a-z0-9]$", 33 | svcName: "^[a-z0-9][-a-z0-9]{0,61}[a-z0-9]$", 34 | fingerprint: "^[a-zA-Z0-9][-a-zA-Z0-9.]{0,61}[a-zA-Z0-9]$", 35 | memory: "^[1-9][0-9]*(k|m|g|t|p|)$", 36 | duration: "^[1-9][0-9]*(s|m|h)$", 37 | devModel: "^[a-zA-Z0-9\\-_]{1,32}$", 38 | namespace: "^[a-z0-9]([-a-z0-9]*[a-z0-9])?([a-z0-9]([-a-z0-9]*[a-z0-9])?)*$", 39 | validConfigKeys: "^[-._a-zA-Z0-9]+$", 40 | dateType: "^(int16|int32|int64|float32|float64|string|bool|time|date)?$", 41 | dateEnumType: "^(int16|int32|int64|string)?$", 42 | datePlusType: "^(int16|int32|int64|float32|float64|string|time|date|bool|array|enum|object)?$", 43 | } 44 | var validate *validator.Validate 45 | 46 | func init() { 47 | validate = validator.New() 48 | RegisterValidate(validate) 49 | } 50 | 51 | func GetValidator() *validator.Validate { 52 | return validate 53 | } 54 | 55 | func RegisterValidation(key string, fn validator.Func) { 56 | GetValidator().RegisterValidation(key, fn) 57 | } 58 | 59 | func RegisterValidate(v *validator.Validate) { 60 | if v != nil { 61 | for key, val := range regexps { 62 | key0, val0 := key, val 63 | v.RegisterValidation(key0, func(fl validator.FieldLevel) bool { 64 | match, _ := regexp.MatchString(val0, fl.Field().String()) 65 | return match 66 | }) 67 | } 68 | 69 | v.RegisterValidation(nonzero, func(fl validator.FieldLevel) bool { 70 | return nonzeroValid(fl.Field().Interface()) 71 | }) 72 | 73 | v.RegisterValidation(nonnil, func(fl validator.FieldLevel) bool { 74 | return nonnilValid(fl.Field().Interface()) 75 | }) 76 | 77 | v.RegisterValidation(nonbaetyl, func(fl validator.FieldLevel) bool { 78 | return !strings.Contains(strings.ToLower(fl.Field().String()), "baetyl") 79 | }) 80 | 81 | v.RegisterValidation(validLabels, validLabelsFunc()) 82 | } 83 | } 84 | 85 | func validLabelsFunc() validator.Func { 86 | return func(fl validator.FieldLevel) bool { 87 | labels, ok := fl.Field().Interface().(map[string]string) 88 | if !ok { 89 | return false 90 | } 91 | labelRegex, _ := regexp.Compile("^([A-Za-z0-9][-A-Za-z0-9_\\.]*)?[A-Za-z0-9]?$") 92 | for k, v := range labels { 93 | if strings.Contains(k, "/") { 94 | ss := strings.Split(k, "/") 95 | if len(ss) != 2 { 96 | return false 97 | } 98 | if len(ss[0]) > 253 || len(ss[0]) < 1 || !labelRegex.MatchString(ss[0]) || len(ss[1]) > 63 || !labelRegex.MatchString(ss[1]) { 99 | return false 100 | } 101 | } else { 102 | if len(k) > 63 || !labelRegex.MatchString(k) { 103 | return false 104 | } 105 | } 106 | if len(v) > 63 || !labelRegex.MatchString(v) { 107 | return false 108 | } 109 | } 110 | return true 111 | } 112 | } 113 | 114 | // nonzeroValid tests whether a variable value non-zero as defined by the golang spec. 115 | func nonzeroValid(v interface{}) bool { 116 | st := reflect.ValueOf(v) 117 | valid := true 118 | switch st.Kind() { 119 | case reflect.String: 120 | valid = utf8.RuneCountInString(st.String()) != 0 121 | case reflect.Ptr, reflect.Interface: 122 | valid = !st.IsNil() 123 | case reflect.Slice, reflect.Map, reflect.Array: 124 | valid = st.Len() != 0 125 | case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64: 126 | valid = st.Int() != 0 127 | case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64, reflect.Uintptr: 128 | valid = st.Uint() != 0 129 | case reflect.Float32, reflect.Float64: 130 | valid = st.Float() != 0 131 | case reflect.Bool: 132 | valid = st.Bool() 133 | case reflect.Invalid: 134 | valid = false // always invalid 135 | case reflect.Struct: 136 | valid = true // always valid since only nil pointers are empty 137 | default: 138 | valid = false 139 | } 140 | return valid 141 | } 142 | 143 | // nonnilValid validates that the given pointer is not nil 144 | func nonnilValid(v interface{}) bool { 145 | st := reflect.ValueOf(v) 146 | switch st.Kind() { 147 | case reflect.Ptr, reflect.Interface: 148 | if st.IsNil() { 149 | return false 150 | } 151 | case reflect.Invalid: 152 | return false 153 | } 154 | return true 155 | } 156 | -------------------------------------------------------------------------------- /utils/version.go: -------------------------------------------------------------------------------- 1 | package utils 2 | 3 | import ( 4 | "fmt" 5 | ) 6 | 7 | // Compile parameter 8 | var ( 9 | VERSION = "unknown" 10 | REVISION = "unknown" 11 | ) 12 | 13 | func Version() string { 14 | return fmt.Sprintf(" Version: %s\nRevision: %s", VERSION, REVISION) 15 | } 16 | 17 | func PrintVersion() { 18 | fmt.Println(Version()) 19 | } 20 | -------------------------------------------------------------------------------- /utils/version_test.go: -------------------------------------------------------------------------------- 1 | package utils 2 | 3 | import ( 4 | "testing" 5 | 6 | "github.com/stretchr/testify/assert" 7 | ) 8 | 9 | func TestPrintVersion(t *testing.T) { 10 | PrintVersion() 11 | assert.Equal(t, " Version: unknown\nRevision: unknown", Version()) 12 | 13 | VERSION = "1.0.0" 14 | REVISION = "git-xxx" 15 | PrintVersion() 16 | assert.Equal(t, " Version: 1.0.0\nRevision: git-xxx", Version()) 17 | } 18 | -------------------------------------------------------------------------------- /utils/zip.go: -------------------------------------------------------------------------------- 1 | package utils 2 | 3 | import ( 4 | "compress/flate" 5 | 6 | "github.com/mholt/archiver" 7 | 8 | "github.com/baetyl/baetyl-go/v2/errors" 9 | ) 10 | 11 | var defaultZip = &archiver.Zip{ 12 | CompressionLevel: flate.DefaultCompression, 13 | MkdirAll: true, 14 | SelectiveCompression: true, 15 | OverwriteExisting: true, 16 | } 17 | 18 | // Zip zip source files to destination file 19 | func Zip(sources []string, destination string) error { 20 | return errors.Trace(defaultZip.Archive(sources, destination)) 21 | } 22 | 23 | // Unzip unzip source file to destination 24 | func Unzip(source, destination string) error { 25 | return errors.Trace(defaultZip.Unarchive(source, destination)) 26 | } 27 | -------------------------------------------------------------------------------- /utils/zip_test.go: -------------------------------------------------------------------------------- 1 | package utils 2 | 3 | import ( 4 | "io/ioutil" 5 | "os" 6 | "testing" 7 | 8 | "github.com/stretchr/testify/assert" 9 | ) 10 | 11 | func TestZip(t *testing.T) { 12 | tmpdir, err := ioutil.TempDir("", "example") 13 | assert.NoError(t, err) 14 | defer os.RemoveAll(tmpdir) 15 | tmpfile, err := ioutil.TempFile(tmpdir, "test") 16 | assert.NoError(t, err) 17 | zipPath := tmpfile.Name() + ".zip" 18 | err = Zip([]string{tmpfile.Name()}, zipPath) 19 | assert.NoError(t, err) 20 | err = Unzip(zipPath, tmpdir) 21 | assert.NoError(t, err) 22 | err = Unzip(zipPath, tmpdir) 23 | assert.NoError(t, err) 24 | } 25 | -------------------------------------------------------------------------------- /websocket/client_test.go: -------------------------------------------------------------------------------- 1 | package websocket 2 | 3 | import ( 4 | "log" 5 | "net/http" 6 | "testing" 7 | "time" 8 | 9 | v1 "github.com/baetyl/baetyl-go/v2/spec/v1" 10 | "github.com/baetyl/baetyl-go/v2/utils" 11 | "github.com/gorilla/websocket" 12 | "github.com/stretchr/testify/assert" 13 | ) 14 | 15 | func echo(w http.ResponseWriter, r *http.Request) { 16 | var upgrader = websocket.Upgrader{} 17 | 18 | c, err := upgrader.Upgrade(w, r, nil) 19 | if err != nil { 20 | log.Print("upgrade:", err) 21 | return 22 | } 23 | defer c.Close() 24 | for { 25 | _, msg, err := c.ReadMessage() 26 | if err != nil { 27 | log.Println("read:", err) 28 | break 29 | } 30 | err = c.WriteMessage(websocket.TextMessage, msg) 31 | if err != nil { 32 | log.Println("read:", err) 33 | } 34 | 35 | } 36 | } 37 | func WsServer() { 38 | http.HandleFunc("/echo", echo) 39 | log.Fatal(http.ListenAndServe("127.0.0.1:9341", nil)) 40 | } 41 | func Test_client(t *testing.T) { 42 | go WsServer() 43 | 44 | cfg := ClientConfig{ 45 | Address: "127.0.0.1:9341", 46 | Path: "echo", 47 | Schema: "ws", 48 | IdleConnTimeout: 0, 49 | TLSHandshakeTimeout: 0, 50 | SyncMaxConcurrency: 10, 51 | Certificate: utils.Certificate{}, 52 | } 53 | options, err := cfg.ToClientOptions() 54 | assert.NoError(t, err) 55 | 56 | var msg []chan *v1.Message 57 | for i := 0; i < options.SyncMaxConcurrency; i++ { 58 | msg = append(msg, make(chan *v1.Message, 1)) 59 | } 60 | 61 | client, err := NewClient(options, msg) 62 | assert.NoError(t, err) 63 | result := make(chan *SyncResults, 1000) 64 | extra := map[string]interface{}{"a": 1} 65 | 66 | time.Sleep(time.Second * 2) 67 | for i := 0; i < 20; i++ { 68 | client.SyncSendMsg([]byte("hello"), result, extra) 69 | } 70 | time.Sleep(time.Second * 2) 71 | re := <-result 72 | assert.NoError(t, re.Err) 73 | assert.Equal(t, re.Extra["a"], 1) 74 | assert.Equal(t, 19, len(result)) 75 | 76 | for _, m := range msg { 77 | r := <-m 78 | assert.Equal(t, r.Content.Value.(WebsocketReadMsg).Data, []byte("hello")) 79 | assert.Equal(t, r.Kind, v1.MessageWebsocketRead) 80 | } 81 | 82 | } 83 | -------------------------------------------------------------------------------- /websocket/options.go: -------------------------------------------------------------------------------- 1 | package websocket 2 | 3 | import ( 4 | "crypto/tls" 5 | "time" 6 | 7 | "github.com/baetyl/baetyl-go/v2/errors" 8 | "github.com/baetyl/baetyl-go/v2/utils" 9 | ) 10 | 11 | const ( 12 | ByteUnitKB = "KB" 13 | ByteUnitMB = "MB" 14 | ) 15 | 16 | type SyncResults struct { 17 | Err error 18 | SendCost time.Duration 19 | SyncCost time.Duration 20 | Extra map[string]interface{} 21 | } 22 | 23 | type WebsocketReadMsg struct { 24 | MsgType int 25 | Data []byte 26 | } 27 | 28 | type ClientOptions struct { 29 | Address string 30 | Schema string 31 | Path string 32 | TLSConfig *tls.Config 33 | TLSHandshakeTimeout time.Duration 34 | SyncMaxConcurrency int 35 | } 36 | 37 | type ClientConfig struct { 38 | Address string `yaml:"address" json:"address"` 39 | Path string `yaml:"path" json:"path"` 40 | Schema string `yaml:"schema" json:"schema" default:"ws"` 41 | IdleConnTimeout time.Duration `yaml:"idleConnTimeout" json:"idleConnTimeout" default:"90s"` 42 | TLSHandshakeTimeout time.Duration `yaml:"tlsHandshakeTimeout" json:"tlsHandshakeTimeout" default:"10s"` 43 | SyncMaxConcurrency int `yaml:"syncMaxConcurrency" json:"syncMaxConcurrency" default:"0"` 44 | utils.Certificate `yaml:",inline" json:",inline"` 45 | } 46 | 47 | // ToClientOptions converts client config to client options 48 | func (cc ClientConfig) ToClientOptions() (*ClientOptions, error) { 49 | tlsConfig, err := utils.NewTLSConfigClient(cc.Certificate) 50 | if err != nil { 51 | return nil, errors.Trace(err) 52 | } 53 | return &ClientOptions{ 54 | Address: cc.Address, 55 | Path: cc.Path, 56 | Schema: cc.Schema, 57 | TLSConfig: tlsConfig, 58 | TLSHandshakeTimeout: cc.TLSHandshakeTimeout, 59 | SyncMaxConcurrency: cc.SyncMaxConcurrency, 60 | }, nil 61 | } 62 | 63 | // NewTLSConfigClientWithPassphrase converts client config to client options with passphrase 64 | func (cc ClientConfig) NewTLSConfigClientWithPassphrase() (*ClientOptions, error) { 65 | tlsConfig, err := utils.NewTLSConfigClientWithPassphrase(cc.Certificate) 66 | if err != nil { 67 | return nil, errors.Trace(err) 68 | } 69 | return &ClientOptions{ 70 | Address: cc.Address, 71 | Path: cc.Path, 72 | Schema: cc.Schema, 73 | TLSConfig: tlsConfig, 74 | TLSHandshakeTimeout: cc.TLSHandshakeTimeout, 75 | SyncMaxConcurrency: cc.SyncMaxConcurrency, 76 | }, nil 77 | } 78 | --------------------------------------------------------------------------------