Files
cloudpods/pkg/cloudcommon/etcd/models/base/basemanager.go
2020-08-19 12:52:26 +08:00

242 lines
6.5 KiB
Go

// Copyright 2019 Yunion
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package base
import (
"context"
"errors"
"fmt"
"net/http"
"reflect"
"strings"
"yunion.io/x/jsonutils"
"yunion.io/x/log"
"yunion.io/x/pkg/util/stringutils"
"yunion.io/x/onecloud/pkg/appsrv"
"yunion.io/x/onecloud/pkg/cloudcommon/etcd"
"yunion.io/x/onecloud/pkg/cloudcommon/object"
)
var (
ErrNotJson = errors.New("Not a JSON")
)
const (
MODEL_KEY_SEPARATOR = "/"
GLOBAL_MODEL_NAMESPACE = "models"
)
type SEtcdBaseModelManager struct {
object.SObject
keyword string
keywordPlural string
dataType reflect.Type
}
func NewEtcdBaseModelManager(model IEtcdModel, keyword string, keywordPlural string) SEtcdBaseModelManager {
return SEtcdBaseModelManager{
keyword: keyword,
keywordPlural: keywordPlural,
dataType: reflect.Indirect(reflect.ValueOf(model)).Type(),
}
}
func (manager *SEtcdBaseModelManager) Keyword() string {
return manager.keyword
}
func (manager *SEtcdBaseModelManager) KeywordPlural() string {
return manager.keywordPlural
}
func (manager *SEtcdBaseModelManager) CustomizeHandlerInfo(handler *appsrv.SHandlerInfo) {
// do nothing
}
func (manager *SEtcdBaseModelManager) FetchCreateHeaderData(ctx context.Context, header http.Header) (jsonutils.JSONObject, error) {
return nil, nil
}
func (manager *SEtcdBaseModelManager) FetchUpdateHeaderData(ctx context.Context, header http.Header) (jsonutils.JSONObject, error) {
return nil, nil
}
func path2key(segs []string) string {
return strings.Join(segs, MODEL_KEY_SEPARATOR)
}
func key2Path(key string) []string {
return strings.Split(key, MODEL_KEY_SEPARATOR)
}
func (manager *SEtcdBaseModelManager) managerKey() string {
return path2key([]string{GLOBAL_MODEL_NAMESPACE, manager.keywordPlural})
}
func (manager *SEtcdBaseModelManager) modelKey(idstr string) string {
return path2key([]string{GLOBAL_MODEL_NAMESPACE, manager.keywordPlural, idstr})
}
func (manager *SEtcdBaseModelManager) key2Id(key string) string {
segs := key2Path(key)
if len(segs) >= 3 && segs[0] == GLOBAL_MODEL_NAMESPACE && segs[1] == manager.keywordPlural {
return segs[2]
} else {
return ""
}
}
func (manager *SEtcdBaseModelManager) Allocate() IEtcdModel {
return reflect.New(manager.dataType).Interface().(IEtcdModel)
}
func (manager *SEtcdBaseModelManager) AllJson(ctx context.Context) ([]jsonutils.JSONObject, error) {
prefix := manager.managerKey()
kvs, err := etcd.Default().List(ctx, prefix)
if err != nil {
return nil, err
}
dest := make([]jsonutils.JSONObject, 0)
for i := range kvs {
key := string(kvs[i].Key)
jsonVal, err := jsonutils.Parse(kvs[i].Value)
if err == nil {
jsonDict, ok := jsonVal.(*jsonutils.JSONDict)
if ok {
idStr := manager.key2Id(key)
if len(idStr) > 0 {
jsonDict.Set("id", jsonutils.NewString(idStr))
dest = append(dest, jsonDict)
} else {
log.Warningf("invalid key %s", key)
}
} else {
log.Warningf("value %s is not a json dict??", jsonVal)
}
} else {
log.Warningf("fail to json decode %s: %s", string(kvs[i].Value), err)
}
}
return dest, nil
}
func (manager *SEtcdBaseModelManager) GetJson(ctx context.Context, idstr string) (jsonutils.JSONObject, error) {
prefix := manager.modelKey(idstr)
val, err := etcd.Default().Get(ctx, prefix)
if err != nil {
return nil, err
}
jsonVal, err := jsonutils.Parse(val)
if err != nil {
return nil, err
}
jsonDict, ok := jsonVal.(*jsonutils.JSONDict)
if !ok {
return nil, ErrNotJson
}
jsonDict.Set("id", jsonutils.NewString(idstr))
return jsonDict, nil
}
func (manager *SEtcdBaseModelManager) json2Model(jsonObj jsonutils.JSONObject, model IEtcdModel) error {
err := jsonObj.Unmarshal(model)
if err != nil {
return err
}
model.SetModelManager(manager, model)
return nil
}
func (manager *SEtcdBaseModelManager) Get(ctx context.Context, idstr string, model IEtcdModel) error {
jsonVal, err := manager.GetJson(ctx, idstr)
if err != nil {
return err
}
return manager.json2Model(jsonVal, model)
}
func (manager *SEtcdBaseModelManager) jsonArray2Models(jsonArray []jsonutils.JSONObject, dest interface{}) error {
arrayType := reflect.TypeOf(dest).Elem()
if arrayType.Kind() != reflect.Array && arrayType.Kind() != reflect.Slice {
return fmt.Errorf("dest is not an array or slice")
}
elemType := arrayType.Elem()
arrayValue := reflect.ValueOf(dest).Elem()
for i := 0; i < len(jsonArray); i += 1 {
elemPtrValue := reflect.New(elemType)
model := elemPtrValue.Interface().(IEtcdModel)
err := manager.json2Model(jsonArray[i], model)
if err != nil {
log.Errorf("fail to convert 2 model %s", err)
return err
}
elemValue := reflect.Indirect(elemPtrValue)
newArray := reflect.Append(arrayValue, elemValue)
arrayValue.Set(newArray)
}
return nil
}
func (manager *SEtcdBaseModelManager) All(ctx context.Context, dest interface{}) error {
jsonArray, err := manager.AllJson(ctx)
if err != nil {
return err
}
return manager.jsonArray2Models(jsonArray, dest)
}
func (manager *SEtcdBaseModelManager) Save(ctx context.Context, model IEtcdModel) error {
if len(model.GetId()) == 0 {
model.SetId(stringutils.UUID4())
}
prefix := manager.modelKey(model.GetId())
return etcd.Default().Put(ctx, prefix, jsonutils.Marshal(model).String())
}
func (manager *SEtcdBaseModelManager) Session(ctx context.Context, model IEtcdModel) error {
if len(model.GetId()) == 0 {
model.SetId(stringutils.UUID4())
}
prefix := manager.modelKey(model.GetId())
return etcd.Default().PutSession(ctx, prefix, jsonutils.Marshal(model).String())
}
func (manager *SEtcdBaseModelManager) Delete(ctx context.Context, model IEtcdModel) error {
prefix := manager.modelKey(model.GetId())
_, err := etcd.Default().Delete(ctx, prefix)
if err != nil && err != etcd.ErrNoSuchKey {
return err
}
return nil
}
func (manager *SEtcdBaseModelManager) Watch(ctx context.Context,
onCreate etcd.TEtcdCreateEventFunc,
onModify etcd.TEtcdModifyEventFunc,
onDelete etcd.TEtcdDeleteEventFunc,
) {
prefix := manager.managerKey()
etcd.Default().Watch(ctx, prefix, onCreate, onModify, onDelete)
}