diff --git a/datasource/common.go b/datasource/common.go index 1e8047fd7..49ceb853d 100644 --- a/datasource/common.go +++ b/datasource/common.go @@ -27,10 +27,11 @@ const ( RegistryAppID = "default" Provider = "p" - ResourceAccount = "account" - ResourceRole = "role" - ResourceService = "service" - ResourceKV = "kv" - ResourceInstance = "instance" - ResourceHeartbeat = "heartbeat" + ResourceAccount = "account" + ResourceRole = "role" + ResourceService = "service" + ResourceEnvironment = "env" + ResourceKV = "kv" + ResourceInstance = "instance" + ResourceHeartbeat = "heartbeat" ) diff --git a/datasource/etcd/ms.go b/datasource/etcd/ms.go index d98889c57..853a49e63 100644 --- a/datasource/etcd/ms.go +++ b/datasource/etcd/ms.go @@ -28,6 +28,7 @@ import ( "time" pb "github.com/go-chassis/cari/discovery" + ev "github.com/go-chassis/cari/env" "github.com/go-chassis/cari/pkg/errsvc" "github.com/go-chassis/cari/sync" "github.com/go-chassis/etcdadpt" @@ -47,6 +48,7 @@ import ( "github.com/apache/servicecomb-service-center/pkg/util" "github.com/apache/servicecomb-service-center/server/core" "github.com/apache/servicecomb-service-center/server/plugin/uuid" + "github.com/apache/servicecomb-service-center/server/service/disco" quotasvc "github.com/apache/servicecomb-service-center/server/service/quota" "github.com/apache/servicecomb-service-center/syncer/service/event" ) @@ -1693,3 +1695,227 @@ func (ds *MetadataManager) UpdateManyInstanceStatus(ctx context.Context, match * } return nil } + +func (ds *MetadataManager) ListEnvironments(ctx context.Context) ( + *ev.GetEnvironmentsResponse, error) { + envs, err := eutil.GetAllEnvironmentUtil(ctx) + if err != nil { + log.Error("get all services by domain failed", err) + return nil, pb.NewError(pb.ErrInternal, err.Error()) + } + + return &ev.GetEnvironmentsResponse{ + Environments: envs, + }, nil +} + +func (ds *MetadataManager) RegisterEnvironment(ctx context.Context, request *ev.CreateEnvironmentRequest) ( + response *ev.CreateEnvironmentResponse, err error) { + remoteIP := util.GetIPFromContext(ctx) + env := request.Environment + envFlag := util.StringJoin([]string{ + env.Name, env.Description}, "/") + log.Info("will create environment:" + envFlag) + domainProject := util.ParseDomainProject(ctx) + envKey := &ev.EnvironmentKey{ + Tenant: domainProject, + Name: env.Name, + } + envIndex := path.GenerateEnvironmentIndexKey(envKey) + // 产生全局environment id + requestEnvID := env.ID + if len(requestEnvID) == 0 { + ctx = util.SetContext(ctx, uuid.ContextKey, envIndex) + env.ID = uuid.Generator().GetEnvID(ctx) + } + data, err := json.Marshal(env) + if err != nil { + log.Error(fmt.Sprintf("create Environment[%s] failed, json marshal environment failed, operator: %s", + envFlag, remoteIP), err) + return nil, pb.NewError(pb.ErrInternal, err.Error()) + } + key := path.GenerateEnvironmentKey(domainProject, env.ID) + + opts := []etcdadpt.OpOptions{ + etcdadpt.OpPut(etcdadpt.WithStrKey(key), etcdadpt.WithValue(data)), + etcdadpt.OpPut(etcdadpt.WithStrKey(envIndex), etcdadpt.WithStrValue(env.ID)), + } + uniqueCmpOpts := []etcdadpt.CmpOptions{ + etcdadpt.NotExistKey(key), + etcdadpt.NotExistKey(envIndex), + } + failOpts := []etcdadpt.OpOptions{ + etcdadpt.OpGet(etcdadpt.WithStrKey(envIndex)), + } + + syncOpts, err := esync.GenCreateOpts(ctx, datasource.ResourceEnvironment, request) + if err != nil { + log.Error("fail to create sync opts", err) + return nil, pb.NewError(pb.ErrInternal, err.Error()) + } + opts = append(opts, syncOpts...) + + resp, err := etcdadpt.TxnWithCmp(ctx, opts, uniqueCmpOpts, failOpts) + if err != nil { + log.Error(fmt.Sprintf("create environment[%s] failed, operator: %s", + envFlag, remoteIP), err) + return nil, pb.NewError(pb.ErrUnavailableBackend, err.Error()) + } + + if resp.Succeeded { + log.Info(fmt.Sprintf("create environment[%s][%s] successfully, operator: %s", + env.ID, envFlag, remoteIP)) + disco.EnvMap.Store(request.Environment.Name, struct{}{}) + return &ev.CreateEnvironmentResponse{ + EnvId: env.ID, + }, nil + } + + if len(requestEnvID) != 0 { + if len(resp.Kvs) == 0 || requestEnvID != util.BytesToStringWithNoCopy(resp.Kvs[0].Value) { + log.Warn(fmt.Sprintf("create environment[%s] failed, environment already exists, operator: %s", + envFlag, remoteIP)) + return nil, pb.NewError(pb.ErrEnvironmentAlreadyExists, + "ServiceID conflict or found the same service with different id.") + } + } + + if len(resp.Kvs) == 0 { + // internal error? + log.Error(fmt.Sprintf("create environment[%s] failed, unexpected txn response, operator: %s", + envFlag, remoteIP), nil) + return nil, pb.NewError(pb.ErrInternal, "Unexpected txn response.") + } + + existEnvironmentID := util.BytesToStringWithNoCopy(resp.Kvs[0].Value) + log.Warn(fmt.Sprintf("create environment[%s][%s] failed, environment already exists, operator: %s", + existEnvironmentID, envFlag, remoteIP)) + disco.EnvMap.Store(request.Environment.Name, struct{}{}) + return &ev.CreateEnvironmentResponse{ + EnvId: existEnvironmentID, + }, nil +} + +func (ds *MetadataManager) GetEnvironment(ctx context.Context, request *ev.GetEnvironmentRequest) ( + *ev.Environment, error) { + domainProject := util.ParseDomainProject(ctx) + singleEnvironment, err := eutil.GetEnvironment(ctx, domainProject, request.EnvironmentId) + + if err != nil { + if errors.Is(err, datasource.ErrNoData) { + log.Debug(fmt.Sprintf("get environment[%s] failed, environment does not exist in db", request.EnvironmentId)) + return nil, pb.NewError(pb.ErrEnvironmentNotExists, "environment does not exist.") + } + log.Error(fmt.Sprintf("get environment[%s] failed, get environment file failed", request.EnvironmentId), err) + return nil, pb.NewError(pb.ErrInternal, err.Error()) + } + return singleEnvironment, nil +} + +func (ds *MetadataManager) UpdateEnvironment(ctx context.Context, request *ev.UpdateEnvironmentRequest) (err error) { + remoteIP := util.GetIPFromContext(ctx) + domainProject := util.ParseDomainProject(ctx) + envkey := path.GenerateEnvironmentKey(domainProject, request.Environment.ID) + oldEnvironment, err := eutil.GetEnvironment(ctx, domainProject, request.Environment.ID) + if err != nil { + if errors.Is(err, datasource.ErrNoData) { + log.Debug(fmt.Sprintf("environment does not exist, update environment[%s] failed, operator: %s", + request.Environment.ID, remoteIP)) + return pb.NewError(pb.ErrEnvironmentNotExists, "Environment does not exist.") + } + log.Error(fmt.Sprintf("update environment[%s] failed, get environment failed, operator: %s", + request.Environment.ID, remoteIP), err) + return pb.NewError(pb.ErrInternal, err.Error()) + } + request.Environment.ModTimestamp = strconv.FormatInt(time.Now().Unix(), 10) + request.Environment.Timestamp = oldEnvironment.Timestamp + data, err := json.Marshal(request.Environment) + if err != nil { + log.Error(fmt.Sprintf("update environment[%s] failed, json marshal environment failed, operator: %s", + request.Environment.ID, remoteIP), err) + return pb.NewError(pb.ErrInternal, err.Error()) + } + + opts := []etcdadpt.OpOptions{ + etcdadpt.OpPut(etcdadpt.WithStrKey(envkey), etcdadpt.WithValue(data)), + } + syncOpts, err := esync.GenUpdateOpts(ctx, datasource.ResourceEnvironment, request) + if err != nil { + log.Error("fail to create task", err) + return pb.NewError(pb.ErrInternal, err.Error()) + } + opts = append(opts, syncOpts...) + + // Set key file + resp, err := etcdadpt.TxnWithCmp(ctx, opts, etcdadpt.If(etcdadpt.NotEqualVer(envkey, 0)), nil) + if err != nil { + log.Error(fmt.Sprintf("update environment[%s] properties failed, operator: %s", request.Environment.ID, remoteIP), err) + return pb.NewError(pb.ErrUnavailableBackend, err.Error()) + } + if !resp.Succeeded { + log.Error(fmt.Sprintf("update environment[%s] failed, environment does not exist, operator: %s", + request.Environment.ID, remoteIP), err) + return pb.NewError(pb.ErrEnvironmentNotExists, "environment does not exist.") + } + + log.Info(fmt.Sprintf("update environment[%s] successfully, operator: %s", request.Environment.ID, remoteIP)) + return nil +} + +func (ds *MetadataManager) UnregisterEnvironment(ctx context.Context, request *ev.DeleteEnvironmentRequest) (err error) { + environmentId := request.EnvironmentId + remoteIP := util.GetIPFromContext(ctx) + domainProject := util.ParseDomainProject(ctx) + + environment, err := eutil.GetEnvironment(ctx, domainProject, environmentId) + if err != nil { + if errors.Is(err, datasource.ErrNoData) { + log.Debug(fmt.Sprintf("environment does not exist, del environmentId[%s] failed, operator: %s", + environmentId, remoteIP)) + return pb.NewError(pb.ErrEnvironmentNotExists, "environment does not exist.") + } + log.Error(fmt.Sprintf("del environment[%s] failed, get environment file failed, operator: %s", + environmentId, remoteIP), err) + return pb.NewError(pb.ErrInternal, err.Error()) + } + + serviceEnvKey := path.GenerateServiceEnvIndexKey(domainProject, environment.Name) + if serviceUtil.ServiceEnvExist(ctx, serviceEnvKey) { + log.Error(fmt.Sprintf("del environment[%s] failed, get environment file failed, operator: %s", + environmentId, remoteIP), errors.New("this env has services")) + return pb.NewError(pb.ErrInternal, "this env has services") + } + environmentIdKey := path.GenerateEnvironmentKey(domainProject, environmentId) + envKey := &ev.EnvironmentKey{ + Tenant: domainProject, + Name: environment.Name, + } + opts := []etcdadpt.OpOptions{ + etcdadpt.OpDel(etcdadpt.WithStrKey(path.GenerateEnvironmentIndexKey(envKey))), + etcdadpt.OpDel(etcdadpt.WithStrKey(environmentIdKey)), + } + syncOpts, err := esync.GenDeleteOpts(ctx, datasource.ResourceEnvironment, environmentId, + &ev.DeleteEnvironmentRequest{EnvironmentId: environmentId}) + if err != nil { + log.Error("fail to sync opt", err) + return pb.NewError(pb.ErrInternal, err.Error()) + } + opts = append(opts, syncOpts...) + + resp, err := etcdadpt.TxnWithCmp(ctx, opts, etcdadpt.If(etcdadpt.NotEqualVer(environmentIdKey, 0)), nil) + if err != nil { + log.Error(fmt.Sprintf("del environment[%s] failed, operator: %s", environmentId, remoteIP), err) + return pb.NewError(pb.ErrUnavailableBackend, err.Error()) + } + if !resp.Succeeded { + log.Error(fmt.Sprintf("del environment[%s] failed, environment does not exist, operator: %s", + environmentId, remoteIP), err) + return pb.NewError(pb.ErrEnvironmentNotExists, "environmentId does not exist.") + } + + quotasvc.RemandEnvironment(ctx) + + log.Info(fmt.Sprintf("del environment[%s] successfully, operator: %s", environmentId, remoteIP)) + disco.EnvMap.Delete(environment.Name) + return nil +} diff --git a/datasource/etcd/ops.go b/datasource/etcd/ops.go index 224dece60..2bbdd5f84 100644 --- a/datasource/etcd/ops.go +++ b/datasource/etcd/ops.go @@ -21,12 +21,16 @@ import ( "context" "strings" + pb "github.com/go-chassis/cari/discovery" + ev "github.com/go-chassis/cari/env" + "github.com/apache/servicecomb-service-center/datasource" "github.com/apache/servicecomb-service-center/datasource/etcd/path" serviceUtil "github.com/apache/servicecomb-service-center/datasource/etcd/util" - pb "github.com/go-chassis/cari/discovery" ) +const preEnvNum int64 = 4 + func (ds *MetadataManager) CountService(ctx context.Context, request *pb.GetServiceCountRequest) (*pb.GetServiceCountResponse, error) { domainProject := request.Domain if request.Project != "" { @@ -84,3 +88,20 @@ func (ds *MetadataManager) getGlobalInstanceCount(ctx context.Context, domainPro } return global, nil } + +func (ds *MetadataManager) CountEnvironment(ctx context.Context, request *ev.GetEnvironmentCountRequest) (*ev.GetEnvironmentCountResponse, error) { + domainProject := request.Domain + if request.Project != "" { + domainProject += path.SPLIT + request.Project + } + all, err := serviceUtil.GetOneDomainProjectEnvironmentCount(ctx, domainProject) + if err != nil { + return nil, err + } + if err != nil { + return nil, err + } + return &ev.GetEnvironmentCountResponse{ + Count: all - preEnvNum, + }, nil +} diff --git a/datasource/etcd/path/key_generator.go b/datasource/etcd/path/key_generator.go index 59137d621..0b996f91e 100644 --- a/datasource/etcd/path/key_generator.go +++ b/datasource/etcd/path/key_generator.go @@ -21,6 +21,8 @@ import ( "github.com/go-chassis/cari/discovery" "github.com/apache/servicecomb-service-center/pkg/util" + + ev "github.com/go-chassis/cari/env" ) const ( @@ -28,6 +30,7 @@ const ( RegistryRootKey = "cse-sr" RegistrySysKey = "sys" RegistryServiceKey = "ms" + RegistryEnvironmentKey = "envs" RegistryInstanceKey = "inst" RegistryFile = "files" RegistryIndex = "indexes" @@ -383,3 +386,41 @@ func GenerateMetricsKey(name, utc, domain string) string { domain, }, SPLIT) } + +func GenerateEnvironmentKey(domainProject string, envId string) string { + return util.StringJoin([]string{ + GetEnvironmentRootKey(domainProject), + envId, + }, SPLIT) +} + +func GetEnvironmentRootKey(domainProject string) string { + return util.StringJoin([]string{ + GetRootKey(), + RegistryEnvironmentKey, + domainProject, + }, SPLIT) +} + +func GenerateEnvironmentIndexKey(key *ev.EnvironmentKey) string { + return util.StringJoin([]string{ + GetEnvironmentIndexRootKey(key.Tenant), + key.Name, + }, SPLIT) +} + +func GetEnvironmentIndexRootKey(domainProject string) string { + return util.StringJoin([]string{ + GetRootKey(), + RegistryEnvironmentKey, + RegistryIndex, + domainProject, + }, SPLIT) +} + +func GenerateServiceEnvIndexKey(domainProject string, name string) string { + return util.StringJoin([]string{ + GetServiceIndexRootKey(domainProject), + name, + }, SPLIT) +} diff --git a/datasource/etcd/sd/api.go b/datasource/etcd/sd/api.go index 5aebdf052..05f60968f 100644 --- a/datasource/etcd/sd/api.go +++ b/datasource/etcd/sd/api.go @@ -31,3 +31,4 @@ func DependencyRule() state.State { return state.Get(TypeDependencyRule) } func DependencyQueue() state.State { return state.Get(TypeDependencyQueue) } func Domain() state.State { return state.Get(TypeDomain) } func Project() state.State { return state.Get(TypeProject) } +func Environment() state.State { return state.Get(TypeEnvironment) } diff --git a/datasource/etcd/sd/types.go b/datasource/etcd/sd/types.go index b4ce50fa4..d31bfea62 100644 --- a/datasource/etcd/sd/types.go +++ b/datasource/etcd/sd/types.go @@ -46,6 +46,7 @@ var ( TypeDependencyQueue kvstore.Type TypeInstance kvstore.Type TypeLease kvstore.Type + TypeEnvironment kvstore.Type ) func RegisterInnerTypes() { @@ -80,4 +81,7 @@ func RegisterInnerTypes() { TypeProject = state.MustRegister("PROJECT", path.GetProjectRootKey(""), state.WithInitSize(100), state.WithParser(parser.StringParser)) + TypeEnvironment = state.MustRegister("ENVIRONMENT", path.GetEnvironmentRootKey(""), + state.WithInitSize(100), + state.WithParser(value.EnvironmentParser)) } diff --git a/datasource/etcd/util/environment_util.go b/datasource/etcd/util/environment_util.go new file mode 100644 index 000000000..be7309670 --- /dev/null +++ b/datasource/etcd/util/environment_util.go @@ -0,0 +1,59 @@ +package util + +import ( + "context" + + ev "github.com/go-chassis/cari/env" + "github.com/go-chassis/etcdadpt" + + "github.com/apache/servicecomb-service-center/datasource/etcd/path" + "github.com/apache/servicecomb-service-center/datasource/etcd/sd" + "github.com/apache/servicecomb-service-center/datasource/etcd/state/kvstore" + "github.com/apache/servicecomb-service-center/pkg/util" +) + +func GetAllEnvironmentUtil(ctx context.Context) ([]*ev.Environment, error) { + domainProject := util.ParseDomainProject(ctx) + envs, err := GetEnvironmentsByDomainProject(ctx, domainProject) + if err != nil { + return nil, err + } + return envs, nil +} + +func GetEnvironmentsByDomainProject(ctx context.Context, domainProject string) ([]*ev.Environment, error) { + kvs, err := getEnvironmentsRawData(ctx, domainProject) + if err != nil { + return nil, err + } + envs := []*ev.Environment{} + for _, kv := range kvs { + envs = append(envs, kv.Value.(*ev.Environment)) + } + return envs, nil +} + +func getEnvironmentsRawData(ctx context.Context, domainProject string) ([]*kvstore.KeyValue, error) { + key := path.GenerateEnvironmentKey(domainProject, "") + opts := append(FromContext(ctx), + etcdadpt.WithStrKey(key), + etcdadpt.WithPrefix()) + resp, err := sd.Environment().Search(ctx, opts...) + if err != nil { + return nil, err + } + return resp.Kvs, err +} + +func GetOneDomainProjectEnvironmentCount(ctx context.Context, domainProject string) (int64, error) { + key := path.GetEnvironmentRootKey(domainProject) + path.SPLIT + opts := append(FromContext(ctx), + etcdadpt.WithStrKey(key), + etcdadpt.WithCountOnly(), + etcdadpt.WithPrefix()) + resp, err := sd.Environment().Search(ctx, opts...) + if err != nil { + return 0, err + } + return resp.Count, nil +} diff --git a/datasource/etcd/util/microservice_util.go b/datasource/etcd/util/microservice_util.go index 582736573..f218fb415 100644 --- a/datasource/etcd/util/microservice_util.go +++ b/datasource/etcd/util/microservice_util.go @@ -25,6 +25,7 @@ import ( "strings" pb "github.com/go-chassis/cari/discovery" + ev "github.com/go-chassis/cari/env" "github.com/go-chassis/etcdadpt" "github.com/apache/servicecomb-service-center/datasource" @@ -381,3 +382,28 @@ func getGlobalEnvironment() string { } return env } + +func GetEnvironment(ctx context.Context, domainProject string, EnvironmentID string) (*ev.Environment, error) { + key := path.GenerateEnvironmentKey(domainProject, EnvironmentID) + opts := append(FromContext(ctx), etcdadpt.WithStrKey(key)) + environmentResp, err := sd.Environment().Search(ctx, opts...) + if err != nil { + return nil, err + } + if len(environmentResp.Kvs) == 0 { + return nil, datasource.ErrNoData + } + return environmentResp.Kvs[0].Value.(*ev.Environment), nil +} + +func ServiceEnvExist(ctx context.Context, serviceEnvKey string) bool { + opts := append(FromContext(ctx), + etcdadpt.WithStrKey(serviceEnvKey), + etcdadpt.WithCountOnly(), + etcdadpt.WithPrefix()) + resp, err := sd.ServiceIndex().Search(ctx, opts...) + if err != nil || resp.Count == 0 { + return false + } + return true +} diff --git a/datasource/etcd/value/parser.go b/datasource/etcd/value/parser.go index dc0ca7209..adf37df5a 100644 --- a/datasource/etcd/value/parser.go +++ b/datasource/etcd/value/parser.go @@ -18,8 +18,11 @@ package value import ( - "github.com/apache/servicecomb-service-center/datasource/etcd/state/parser" "github.com/go-chassis/cari/discovery" + + ev "github.com/go-chassis/cari/env" + + "github.com/apache/servicecomb-service-center/datasource/etcd/state/parser" ) var ( @@ -28,10 +31,12 @@ var ( newRule parser.CreateValueFunc = func() interface{} { return new(discovery.ServiceRule) } newDependencyRule parser.CreateValueFunc = func() interface{} { return new(discovery.MicroServiceDependency) } newDependencyQueue parser.CreateValueFunc = func() interface{} { return new(discovery.ConsumerDependency) } + newEnvironment parser.CreateValueFunc = func() interface{} { return new(ev.Environment) } ServiceParser = parser.New(newService, parser.JSONUnmarshal) InstanceParser = parser.New(newInstance, parser.JSONUnmarshal) RuleParser = parser.New(newRule, parser.JSONUnmarshal) DependencyRuleParser = parser.New(newDependencyRule, parser.JSONUnmarshal) DependencyQueueParser = parser.New(newDependencyQueue, parser.JSONUnmarshal) + EnvironmentParser = parser.New(newEnvironment, parser.JSONUnmarshal) ) diff --git a/datasource/mongo/ms.go b/datasource/mongo/ms.go index 23448454c..e8c774f3b 100644 --- a/datasource/mongo/ms.go +++ b/datasource/mongo/ms.go @@ -37,6 +37,8 @@ import ( "go.mongodb.org/mongo-driver/mongo" "go.mongodb.org/mongo-driver/mongo/options" + ev "github.com/go-chassis/cari/env" + "github.com/apache/servicecomb-service-center/datasource" "github.com/apache/servicecomb-service-center/datasource/cache" "github.com/apache/servicecomb-service-center/datasource/mongo/dao" @@ -298,7 +300,7 @@ func (ds *MetadataManager) UnregisterService(ctx context.Context, request *disco serviceID, l, remoteIP), err) return discovery.NewError(discovery.ErrDependedOnConsumer, "Can not delete this service, other service rely it.") } - //todo wait for dep interface + // todo wait for dep interface num, err := dmongo.GetClient().GetDB().Collection(model.CollectionInstance).CountDocuments(ctx, bson.M{mutil.ConnectWithDot([]string{model.ColumnInstance, model.ColumnServiceID}): serviceID}) if err != nil { log.Error(fmt.Sprintf("delete micro-service[%s] failed, get instances number failed, operator: %s", @@ -1437,7 +1439,7 @@ func (ds *MetadataManager) findInstance(ctx context.Context, request *discovery. } func (ds *MetadataManager) reshapeProviderKey(ctx context.Context, provider *discovery.MicroServiceKey, providerID string) (*discovery.MicroServiceKey, error) { - //维护version的规则,service name 可能是别名,所以重新获取 + // 维护version的规则,service name 可能是别名,所以重新获取 providerService, err := GetServiceByID(ctx, providerID) if err != nil { return nil, err @@ -1680,3 +1682,24 @@ func (ds *MetadataManager) Statistics(ctx context.Context, withShared bool) (*di func (ds *MetadataManager) UpdateManyInstanceStatus(_ context.Context, _ *datasource.MatchPolicy, _ string) error { return nil } + +func (ds *MetadataManager) ListEnvironments(ctx context.Context) (*ev.GetEnvironmentsResponse, error) { + return nil, nil +} + +func (ds *MetadataManager) RegisterEnvironment(ctx context.Context, request *ev.CreateEnvironmentRequest) (*ev.CreateEnvironmentResponse, error) { + return nil, nil +} + +func (ds *MetadataManager) GetEnvironment(ctx context.Context, request *ev.GetEnvironmentRequest) ( + *ev.Environment, error) { + return nil, nil +} + +func (ds *MetadataManager) UpdateEnvironment(ctx context.Context, request *ev.UpdateEnvironmentRequest) (err error) { + return nil +} + +func (ds *MetadataManager) UnregisterEnvironment(ctx context.Context, request *ev.DeleteEnvironmentRequest) (err error) { + return nil +} diff --git a/datasource/mongo/ops.go b/datasource/mongo/ops.go index b166fa40d..d940900f9 100644 --- a/datasource/mongo/ops.go +++ b/datasource/mongo/ops.go @@ -21,6 +21,7 @@ import ( "context" "github.com/go-chassis/cari/discovery" + ev "github.com/go-chassis/cari/env" "go.mongodb.org/mongo-driver/bson" "github.com/apache/servicecomb-service-center/datasource/mongo/dao" @@ -73,3 +74,7 @@ func (ds *MetadataManager) getNotGlobalServiceFilter(ctx context.Context) (bson. } return util.NewFilter(util.NotIn(serviceIDs)), nil } + +func (ds *MetadataManager) CountEnvironment(ctx context.Context, request *ev.GetEnvironmentCountRequest) (*ev.GetEnvironmentCountResponse, error) { + return nil, nil +} diff --git a/datasource/ms.go b/datasource/ms.go index 371759c0e..dff1f06cd 100644 --- a/datasource/ms.go +++ b/datasource/ms.go @@ -22,6 +22,7 @@ import ( "errors" pb "github.com/go-chassis/cari/discovery" + ev "github.com/go-chassis/cari/env" ) const ( @@ -60,6 +61,14 @@ type MetadataManager interface { CountService(ctx context.Context, request *pb.GetServiceCountRequest) (*pb.GetServiceCountResponse, error) FindService(ctx context.Context, request *pb.MicroServiceKey) (*pb.GetServicesResponse, error) + // Environment management + ListEnvironments(ctx context.Context) (*ev.GetEnvironmentsResponse, error) + RegisterEnvironment(ctx context.Context, request *ev.CreateEnvironmentRequest) (*ev.CreateEnvironmentResponse, error) + GetEnvironment(ctx context.Context, request *ev.GetEnvironmentRequest) (*ev.Environment, error) + UpdateEnvironment(ctx context.Context, request *ev.UpdateEnvironmentRequest) error + UnregisterEnvironment(ctx context.Context, request *ev.DeleteEnvironmentRequest) error + CountEnvironment(ctx context.Context, request *ev.GetEnvironmentCountRequest) (*ev.GetEnvironmentCountResponse, error) + // Instance management RegisterInstance(ctx context.Context, request *pb.RegisterInstanceRequest) (*pb.RegisterInstanceResponse, error) ExistInstance(ctx context.Context, request *pb.MicroServiceInstanceKey) (*pb.GetExistenceByIDResponse, error) diff --git a/go.mod b/go.mod index 70c135f68..779088946 100644 --- a/go.mod +++ b/go.mod @@ -21,7 +21,7 @@ require ( github.com/cloudflare/gokey v0.1.2 github.com/deckarep/golang-set v1.8.0 github.com/elithrar/simple-scrypt v1.3.0 - github.com/go-chassis/cari v0.9.1-0.20240328115504-88da93faaca7 + github.com/go-chassis/cari v0.9.1-0.20240531100749-8955107d022d github.com/go-chassis/etcdadpt v0.5.3-0.20240328092602-984e34b756fe github.com/go-chassis/foundation v0.4.0 github.com/go-chassis/go-archaius v1.5.6 @@ -181,7 +181,7 @@ require ( go.uber.org/multierr v1.6.0 // indirect golang.org/x/net v0.7.0 // indirect golang.org/x/oauth2 v0.0.0-20220223155221-ee480838109b // indirect - golang.org/x/sync v0.0.0-20220601150217-0de741cfad7f // indirect + golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4 // indirect golang.org/x/sys v0.5.0 // indirect golang.org/x/term v0.5.0 // indirect golang.org/x/text v0.7.0 // indirect diff --git a/go.sum b/go.sum index 5322f66ff..73d3a1a11 100644 --- a/go.sum +++ b/go.sum @@ -235,8 +235,8 @@ github.com/go-chassis/cari v0.4.0/go.mod h1:av/19fqwEP4eOC8unL/z67AAbFDwXUCko6SK github.com/go-chassis/cari v0.5.0/go.mod h1:av/19fqwEP4eOC8unL/z67AAbFDwXUCko6SKa4Avrd8= github.com/go-chassis/cari v0.5.1-0.20210823023004-74041d1363c4/go.mod h1:av/19fqwEP4eOC8unL/z67AAbFDwXUCko6SKa4Avrd8= github.com/go-chassis/cari v0.6.0/go.mod h1:mSDRCOQXGmlD69A6NG0hsv0UP1xbVPtL6HCGI6X1tqs= -github.com/go-chassis/cari v0.9.1-0.20240328115504-88da93faaca7 h1:XlCtMt+l1hcpfbiFRoSYWYr0q6Ak9g/UGFXSKoqmbT4= -github.com/go-chassis/cari v0.9.1-0.20240328115504-88da93faaca7/go.mod h1:ibqLyh+Q+1n9PlldW3glD9G+2s/yeSyVMCCkQWKRwuE= +github.com/go-chassis/cari v0.9.1-0.20240531100749-8955107d022d h1:nQnEl2jV0MEzN+/+019tiwKKliRGiXLGvau4zmTIyIc= +github.com/go-chassis/cari v0.9.1-0.20240531100749-8955107d022d/go.mod h1:ibqLyh+Q+1n9PlldW3glD9G+2s/yeSyVMCCkQWKRwuE= github.com/go-chassis/etcdadpt v0.5.3-0.20240328092602-984e34b756fe h1:peLHEt3wzab6nKVcmcu0qkj1+ZXK6D1ymtiyyMBv/XA= github.com/go-chassis/etcdadpt v0.5.3-0.20240328092602-984e34b756fe/go.mod h1:HV8OZ1Npu+lttD+pJA5nUxWZR3/SBFetTh7w8nYFkUA= github.com/go-chassis/foundation v0.2.2-0.20201210043510-9f6d3de40234/go.mod h1:2PjwqpVwYEVaAldl5A58a08viH8p27pNeYaiE3ZxOBA= @@ -1038,8 +1038,8 @@ golang.org/x/sync v0.0.0-20200625203802-6e8e738ad208/go.mod h1:RxMgew5VJxzue5/jJ golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20201207232520-09787c993a3a/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20210220032951-036812b2e83c/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= -golang.org/x/sync v0.0.0-20220601150217-0de741cfad7f h1:Ax0t5p6N38Ga0dThY21weqDEyz2oklo4IvDkpigvkD8= -golang.org/x/sync v0.0.0-20220601150217-0de741cfad7f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4 h1:uVc8UZUe6tr40fFVnUP5Oj+veunVezqYl9z7DYw9xzw= +golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sys v0.0.0-20170830134202-bb24a47a89ea/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20180823144017-11551d06cbcc/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= diff --git a/server/plugin/quota/buildin/buildin.go b/server/plugin/quota/buildin/buildin.go index de20ce26b..0536bb8b7 100644 --- a/server/plugin/quota/buildin/buildin.go +++ b/server/plugin/quota/buildin/buildin.go @@ -21,6 +21,9 @@ import ( "context" "fmt" + pb "github.com/go-chassis/cari/discovery" + ev "github.com/go-chassis/cari/env" + "github.com/apache/servicecomb-service-center/pkg/log" "github.com/apache/servicecomb-service-center/pkg/plugin" "github.com/apache/servicecomb-service-center/pkg/util" @@ -29,16 +32,16 @@ import ( "github.com/apache/servicecomb-service-center/server/service/disco" quotasvc "github.com/apache/servicecomb-service-center/server/service/quota" "github.com/apache/servicecomb-service-center/server/service/rbac" - pb "github.com/go-chassis/cari/discovery" ) const ( - defaultServiceLimit int64 = 50000 - defaultInstanceLimit int64 = 150000 - defaultSchemaLimit int64 = 100 - defaultTagLimit int64 = 100 - defaultAccountLimit int64 = 1000 - defaultRoleLimit int64 = 100 + defaultServiceLimit int64 = 50000 + defaultInstanceLimit int64 = 150000 + defaultSchemaLimit int64 = 100 + defaultTagLimit int64 = 100 + defaultAccountLimit int64 = 1000 + defaultRoleLimit int64 = 100 + defaultEnvironmentLimit int64 = 50 ) func init() { @@ -47,12 +50,13 @@ func init() { func New() plugin.Instance { q := &Quota{ - ServiceQuota: config.GetInt64("quota.cap.service.limit", defaultServiceLimit, config.WithENV("QUOTA_SERVICE")), - InstanceQuota: config.GetInt64("quota.cap.instance.limit", defaultInstanceLimit, config.WithENV("QUOTA_INSTANCE")), - SchemaQuota: config.GetInt64("quota.cap.schema.limit", defaultSchemaLimit, config.WithENV("QUOTA_SCHEMA")), - TagQuota: config.GetInt64("quota.cap.tag.limit", defaultTagLimit, config.WithENV("QUOTA_TAG")), - AccountQuota: config.GetInt64("quota.cap.account.limit", defaultAccountLimit, config.WithENV("QUOTA_ACCOUNT")), - RoleQuota: config.GetInt64("quota.cap.role.limit", defaultRoleLimit, config.WithENV("QUOTA_ROLE")), + ServiceQuota: config.GetInt64("quota.cap.service.limit", defaultServiceLimit, config.WithENV("QUOTA_SERVICE")), + InstanceQuota: config.GetInt64("quota.cap.instance.limit", defaultInstanceLimit, config.WithENV("QUOTA_INSTANCE")), + SchemaQuota: config.GetInt64("quota.cap.schema.limit", defaultSchemaLimit, config.WithENV("QUOTA_SCHEMA")), + TagQuota: config.GetInt64("quota.cap.tag.limit", defaultTagLimit, config.WithENV("QUOTA_TAG")), + AccountQuota: config.GetInt64("quota.cap.account.limit", defaultAccountLimit, config.WithENV("QUOTA_ACCOUNT")), + RoleQuota: config.GetInt64("quota.cap.role.limit", defaultRoleLimit, config.WithENV("QUOTA_ROLE")), + EnvironmentQuota: config.GetInt64("quota.cap.environment.limit", defaultEnvironmentLimit, config.WithENV("QUOTA_ENVIRONMENT")), } log.Info(fmt.Sprintf("quota init, service: %d, instance: %d, schema: %d/service, tag: %d/service"+ ", account: %d, role: %d", @@ -62,12 +66,13 @@ func New() plugin.Instance { } type Quota struct { - ServiceQuota int64 - InstanceQuota int64 - SchemaQuota int64 - TagQuota int64 - AccountQuota int64 - RoleQuota int64 + ServiceQuota int64 + InstanceQuota int64 + SchemaQuota int64 + TagQuota int64 + AccountQuota int64 + RoleQuota int64 + EnvironmentQuota int64 } func (q *Quota) GetQuota(_ context.Context, t quota.ResourceType) int64 { @@ -84,6 +89,8 @@ func (q *Quota) GetQuota(_ context.Context, t quota.ResourceType) int64 { return q.AccountQuota case quotasvc.TypeRole: return q.RoleQuota + case quotasvc.TypeEnvironment: + return q.EnvironmentQuota default: return 0 } @@ -119,6 +126,11 @@ func (q *Quota) Usage(ctx context.Context, req *quota.Request) (int64, error) { return rbac.AccountUsage(ctx) case quotasvc.TypeRole: return rbac.RoleUsage(ctx) + case quotasvc.TypeEnvironment: + return disco.EnvironmentUsage(ctx, &ev.GetEnvironmentCountRequest{ + Domain: util.ParseDomain(ctx), + Project: util.ParseProject(ctx), + }) default: return 0, nil } diff --git a/server/plugin/uuid/buildin/buildin.go b/server/plugin/uuid/buildin/buildin.go index c467874c9..6c07fcd94 100644 --- a/server/plugin/uuid/buildin/buildin.go +++ b/server/plugin/uuid/buildin/buildin.go @@ -51,3 +51,11 @@ func (du *UUID) GetInstanceID(_ context.Context) string { } return util.GenerateUUID() } + +func (du *UUID) GetEnvID(_ context.Context) string { + df, ok := plugin.DynamicPluginFunc(uuid.UUID, "GetEnvID").(func() string) + if ok { + return df() + } + return util.GenerateUUID() +} diff --git a/server/plugin/uuid/uuid.go b/server/plugin/uuid/uuid.go index 63d0836e2..a82dd7340 100644 --- a/server/plugin/uuid/uuid.go +++ b/server/plugin/uuid/uuid.go @@ -32,6 +32,7 @@ const ( type IDGenerator interface { GetServiceID(ctx context.Context) string GetInstanceID(ctx context.Context) string + GetEnvID(ctx context.Context) string } func Generator() IDGenerator { diff --git a/server/resource/disco/environment_resource.go b/server/resource/disco/environment_resource.go new file mode 100644 index 000000000..ca015673d --- /dev/null +++ b/server/resource/disco/environment_resource.go @@ -0,0 +1,118 @@ +package disco + +import ( + "encoding/json" + "fmt" + "io" + "net/http" + + pb "github.com/go-chassis/cari/discovery" + ev "github.com/go-chassis/cari/env" + + "github.com/apache/servicecomb-service-center/pkg/log" + "github.com/apache/servicecomb-service-center/pkg/rest" + "github.com/apache/servicecomb-service-center/pkg/util" + discosvc "github.com/apache/servicecomb-service-center/server/service/disco" +) + +type EnvironmentResource struct { + // +} + +func (s *EnvironmentResource) URLPatterns() []rest.Route { + return []rest.Route{ + {Method: http.MethodGet, Path: "/v4/:project/registry/environments", Func: s.ListEnvironments}, + {Method: http.MethodPost, Path: "/v4/:project/registry/environments", Func: s.RegistryEnvironment}, + {Method: http.MethodGet, Path: "/v4/:project/registry/environments/:environmentId", Func: s.GetEnvironment}, + {Method: http.MethodPut, Path: "/v4/:project/registry/environments/:environmentId", Func: s.UpdateEnvironment}, + {Method: http.MethodDelete, Path: "/v4/:project/registry/environments/:environmentId", Func: s.UnRegistryEnvironment}, + } +} + +func (s *EnvironmentResource) ListEnvironments(w http.ResponseWriter, r *http.Request) { + resp, err := discosvc.ListEnvironments(r.Context()) + if err != nil { + log.Error("list envs failed", err) + rest.WriteServiceError(w, err) + return + } + rest.WriteResponse(w, r, nil, resp) +} + +func (s *EnvironmentResource) RegistryEnvironment(w http.ResponseWriter, r *http.Request) { + message, err := io.ReadAll(r.Body) + if err != nil { + log.Error("read body failed", err) + rest.WriteError(w, pb.ErrInvalidParams, err.Error()) + return + } + var request ev.CreateEnvironmentRequest + err = json.Unmarshal(message, &request.Environment) + if err != nil { + log.Error(fmt.Sprintf("invalid json: %s", util.BytesToStringWithNoCopy(message)), err) + rest.WriteError(w, pb.ErrInvalidParams, err.Error()) + return + } + resp, err := discosvc.RegistryEnvironment(r.Context(), &request) + if err != nil { + log.Error("create service failed", err) + rest.WriteServiceError(w, err) + return + } + rest.WriteResponse(w, r, nil, resp) +} + +func (s *EnvironmentResource) GetEnvironment(w http.ResponseWriter, r *http.Request) { + request := &ev.GetEnvironmentRequest{ + EnvironmentId: r.URL.Query().Get(":environmentId"), + } + environment, err := discosvc.GetEnvironment(r.Context(), request) + if err != nil { + log.Error(fmt.Sprintf("get environment[%s] failed", request.EnvironmentId), err) + rest.WriteServiceError(w, err) + return + } + rest.WriteResponse(w, r, nil, &ev.GetEnvironmentResponse{Environment: environment}) +} + +func (s *EnvironmentResource) UpdateEnvironment(w http.ResponseWriter, r *http.Request) { + query := r.URL.Query() + environmentId := query.Get(":environmentId") + message, err := io.ReadAll(r.Body) + if err != nil { + log.Error("read body failed", err) + rest.WriteError(w, pb.ErrInvalidParams, err.Error()) + return + } + var request ev.UpdateEnvironmentRequest + err = json.Unmarshal(message, &request.Environment) + if err != nil { + log.Error(fmt.Sprintf("invalid json: %s", util.BytesToStringWithNoCopy(message)), err) + rest.WriteError(w, pb.ErrInvalidParams, err.Error()) + return + } + request.Environment.ID = environmentId + err = discosvc.UpdateEnvironment(r.Context(), &request) + if err != nil { + log.Error("update environment failed", err) + rest.WriteServiceError(w, err) + return + } + rest.WriteResponse(w, r, nil, nil) +} + +func (s *EnvironmentResource) UnRegistryEnvironment(w http.ResponseWriter, r *http.Request) { + query := r.URL.Query() + environmentId := query.Get(":environmentId") + + request := &ev.DeleteEnvironmentRequest{ + EnvironmentId: environmentId, + } + err := discosvc.UnRegistryEnvironment(r.Context(), request) + if err != nil { + log.Error(fmt.Sprintf("delete environment[%s] failed", environmentId), err) + rest.WriteServiceError(w, err) + return + } + rest.WriteResponse(w, r, nil, nil) +} diff --git a/server/resource/register.go b/server/resource/register.go index 0dec9b61d..2b1f9a506 100644 --- a/server/resource/register.go +++ b/server/resource/register.go @@ -40,4 +40,5 @@ func initRouter() { roa.RegisterServant(&disco.InstanceResource{}) roa.RegisterServant(&gov.Governance{}) roa.RegisterServant(&govern.Resource{}) + roa.RegisterServant(&disco.EnvironmentResource{}) } diff --git a/server/service/disco/environment.go b/server/service/disco/environment.go new file mode 100644 index 000000000..e74b3a917 --- /dev/null +++ b/server/service/disco/environment.go @@ -0,0 +1,96 @@ +package disco + +import ( + "context" + "fmt" + "strconv" + sync1 "sync" + "time" + + pb "github.com/go-chassis/cari/discovery" + ev "github.com/go-chassis/cari/env" + + "github.com/apache/servicecomb-service-center/datasource" + "github.com/apache/servicecomb-service-center/pkg/log" + "github.com/apache/servicecomb-service-center/pkg/util" + quotasvc "github.com/apache/servicecomb-service-center/server/service/quota" + "github.com/apache/servicecomb-service-center/server/service/validator" +) + +var PreEnv util.CtxKey = "_pre_env" +var EnvMap sync1.Map + +func ListEnvironments(ctx context.Context) (*ev.GetEnvironmentsResponse, error) { + return datasource.GetMetadataManager().ListEnvironments(ctx) +} + +func RegistryEnvironment(ctx context.Context, request *ev.CreateEnvironmentRequest) (*ev.CreateEnvironmentResponse, error) { + remoteIP := util.GetIPFromContext(ctx) + + if request == nil || request.Environment == nil { + log.Error(fmt.Sprintf("create micro-service failed: request body is empty, operator: %s", remoteIP), nil) + return nil, pb.NewError(pb.ErrInvalidParams, "Request body is empty") + } + + env := request.Environment + envFlag := util.StringJoin([]string{ + env.Name, env.Description}, "/") + log.Info("will create environment:" + envFlag) + + if err := validator.ValidateCreateEnvironmentRequest(request); err != nil { + log.Error(fmt.Sprintf("create environment[%s] failed, operator: %s", envFlag, remoteIP), err) + return nil, pb.NewError(pb.ErrInvalidParams, err.Error()) + } + + if quotaErr := checkEnvironmentQuota(ctx); quotaErr != nil { + log.Error(fmt.Sprintf("create environment[%s] failed, operator: %s", envFlag, remoteIP), quotaErr) + return nil, quotaErr + } + + assignDefaultVal(env) + + return datasource.GetMetadataManager().RegisterEnvironment(ctx, request) +} + +func assignDefaultVal(service *ev.Environment) { + formatTenBase := 10 + service.Timestamp = strconv.FormatInt(time.Now().Unix(), formatTenBase) + service.ModTimestamp = service.Timestamp +} + +func GetEnvironment(ctx context.Context, in *ev.GetEnvironmentRequest) (*ev.Environment, error) { + return datasource.GetMetadataManager().GetEnvironment(ctx, in) +} + +func UpdateEnvironment(ctx context.Context, request *ev.UpdateEnvironmentRequest) error { + remoteIP := util.GetIPFromContext(ctx) + + if err := validator.ValidateUpdateEnvironmentRequest(request); err != nil { + log.Error(fmt.Sprintf("update environment[%s] failed, operator: %s", + request.Environment.ID, remoteIP), err) + return pb.NewError(pb.ErrInvalidParams, err.Error()) + } + + return datasource.GetMetadataManager().UpdateEnvironment(ctx, request) +} + +func UnRegistryEnvironment(ctx context.Context, request *ev.DeleteEnvironmentRequest) error { + return datasource.GetMetadataManager().UnregisterEnvironment(ctx, request) +} + +func checkEnvironmentQuota(ctx context.Context) error { + b, _ := ctx.Value(PreEnv).(bool) + if b { + log.Debug("skip env quota check") + return nil + } + return quotasvc.ApplyEnvironment(ctx, 1) +} + +func EnvironmentUsage(ctx context.Context, request *ev.GetEnvironmentCountRequest) (int64, error) { + resp, err := datasource.GetMetadataManager().CountEnvironment(ctx, request) + if err != nil { + return 0, err + } + return resp.Count, nil +} diff --git a/server/service/disco/metadata.go b/server/service/disco/metadata.go index 8ba736f8f..d61093b29 100644 --- a/server/service/disco/metadata.go +++ b/server/service/disco/metadata.go @@ -19,22 +19,24 @@ package disco import ( "context" + "errors" "fmt" "strconv" "time" + pb "github.com/go-chassis/cari/discovery" + "github.com/go-chassis/foundation/gopool" + "github.com/apache/servicecomb-service-center/datasource" "github.com/apache/servicecomb-service-center/pkg/log" "github.com/apache/servicecomb-service-center/pkg/util" "github.com/apache/servicecomb-service-center/server/core" quotasvc "github.com/apache/servicecomb-service-center/server/service/quota" "github.com/apache/servicecomb-service-center/server/service/validator" - pb "github.com/go-chassis/cari/discovery" - "github.com/go-chassis/foundation/gopool" ) func RegisterService(ctx context.Context, request *pb.CreateServiceRequest) (*pb.CreateServiceResponse, error) { - //create service + // create service resp, err := registerService(ctx, request) if err != nil { return nil, err @@ -44,7 +46,7 @@ func RegisterService(ctx context.Context, request *pb.CreateServiceRequest) (*pb return resp, nil } - //create tag,rule,instances + // create tag,rule,instances return registerServiceDetails(ctx, request, resp.ServiceId) } @@ -59,6 +61,11 @@ func registerService(ctx context.Context, request *pb.CreateServiceRequest) (*pb service := request.Service serviceFlag := util.StringJoin([]string{ service.Environment, service.AppId, service.ServiceName, service.Version}, "/") + _, ok := EnvMap.Load(service.Environment) + if !ok { + log.Error(fmt.Sprintf("create micro-service[%s] failed, operator: %s", serviceFlag, remoteIP), errors.New("env not exist")) + return nil, pb.NewError(pb.ErrInvalidParams, "env not exist") + } datasource.SetServiceDefaultValue(service) if err := validator.ValidateCreateServiceRequest(request); err != nil { @@ -85,7 +92,7 @@ func assignDefaultValue(service *pb.MicroService) { func registerServiceDetails(ctx context.Context, in *pb.CreateServiceRequest, serviceID string) (*pb.CreateServiceResponse, error) { var chanLen = 0 errorsCh := make(chan error, 10) - //create tags + // create tags if in.Tags != nil && len(in.Tags) != 0 { chanLen++ gopool.Go(func(_ context.Context) { @@ -198,7 +205,7 @@ func UnregisterManyService(ctx context.Context, request *pb.DelServicesRequest) // 批量删除服务 serviceRespChan := make(chan *pb.DelServicesRspInfo, len(request.ServiceIds)) for _, serviceID := range request.ServiceIds { - //ServiceId重复性检查 + // ServiceId重复性检查 if _, ok := existFlag[serviceID]; ok { log.Warn(fmt.Sprintf("duplicate micro-service[%s] serviceID", serviceID)) continue @@ -206,11 +213,11 @@ func UnregisterManyService(ctx context.Context, request *pb.DelServicesRequest) existFlag[serviceID] = true nuoMultiCount++ - //执行删除服务操作 + // 执行删除服务操作 gopool.Go(getDeleteServiceFunc(ctx, serviceID, request.Force, serviceRespChan)) } - //获取批量删除服务的结果 + // 获取批量删除服务的结果 count := 0 responseCode := pb.ResponseSuccess delServiceRspInfo := make([]*pb.DelServicesRspInfo, 0, len(serviceRespChan)) @@ -220,7 +227,7 @@ func UnregisterManyService(ctx context.Context, request *pb.DelServicesRequest) responseCode = pb.ErrInvalidParams } delServiceRspInfo = append(delServiceRspInfo, serviceRespItem) - //结果收集over,关闭通道 + // 结果收集over,关闭通道 if count == nuoMultiCount { close(serviceRespChan) } diff --git a/server/service/quota/environment.go b/server/service/quota/environment.go new file mode 100644 index 000000000..18f2bea64 --- /dev/null +++ b/server/service/quota/environment.go @@ -0,0 +1,23 @@ +package quota + +import ( + "context" + + "github.com/apache/servicecomb-service-center/pkg/util" + "github.com/apache/servicecomb-service-center/server/plugin/quota" +) + +const TypeEnvironment quota.ResourceType = "ENVIRONMENT" + +func ApplyEnvironment(ctx context.Context, size int64) error { + return quota.Apply(ctx, "a.Request{ + QuotaType: TypeEnvironment, + Domain: util.ParseDomain(ctx), + Project: util.ParseProject(ctx), + QuotaSize: size, + }) +} + +func RemandEnvironment(ctx context.Context) { + quota.Remand(ctx, TypeEnvironment) +} diff --git a/server/service/validator/environment_validator.go b/server/service/validator/environment_validator.go new file mode 100644 index 000000000..00d056fcd --- /dev/null +++ b/server/service/validator/environment_validator.go @@ -0,0 +1,36 @@ +package validator + +import ( + "regexp" + + ev "github.com/go-chassis/cari/env" + + "github.com/apache/servicecomb-service-center/pkg/validate" +) + +var createEnvironmentReqValidator validate.Validator +var updateEnvironmentReqValidator validate.Validator +var envRegx, _ = regexp.Compile(`^\S*$`) +var desRegx, _ = regexp.Compile(`^\S*$`) + +func ValidateCreateEnvironmentRequest(v *ev.CreateEnvironmentRequest) error { + return CreateEnvironmentReqValidator().Validate(v) +} + +func ValidateUpdateEnvironmentRequest(v *ev.UpdateEnvironmentRequest) error { + return UpdateEnvironmentReqValidator().Validate(v) +} + +func CreateEnvironmentReqValidator() *validate.Validator { + return createEnvironmentReqValidator.Init(func(v *validate.Validator) { + v.AddRule("Environment", &validate.Rule{Min: 0, Max: 128, Regexp: envRegx}) + v.AddRule("Description", &validate.Rule{Min: 0, Max: 128, Regexp: desRegx}) + }) +} + +func UpdateEnvironmentReqValidator() *validate.Validator { + return updateEnvironmentReqValidator.Init(func(v *validate.Validator) { + v.AddRule("Environment", &validate.Rule{Min: 0, Max: 128, Regexp: envRegx}) + v.AddRule("Description", &validate.Rule{Min: 0, Max: 128, Regexp: desRegx}) + }) +} diff --git a/server/service/validator/microservice_validator.go b/server/service/validator/microservice_validator.go index b68e9ab37..d3d9a94e2 100644 --- a/server/service/validator/microservice_validator.go +++ b/server/service/validator/microservice_validator.go @@ -20,10 +20,11 @@ package validator import ( "regexp" + "github.com/go-chassis/cari/discovery" + "github.com/apache/servicecomb-service-center/pkg/util" "github.com/apache/servicecomb-service-center/pkg/validate" quotasvc "github.com/apache/servicecomb-service-center/server/service/quota" - "github.com/go-chassis/cari/discovery" ) var ( @@ -50,24 +51,23 @@ var ( serviceIDRangeRegex, _ = regexp.Compile(`^\S{1,64}$`) aliasRegex, _ = regexp.Compile(`^[a-zA-Z0-9_\-.:]*$`) registerByRegex, _ = regexp.Compile("^(" + util.StringJoin([]string{discovery.REGISTERBY_SDK, discovery.REGISTERBY_SIDECAR, discovery.REGISTERBY_PLATFORM}, "|") + ")*$") - envRegex, _ = regexp.Compile("^(" + util.StringJoin([]string{ - discovery.ENV_DEV, discovery.ENV_TEST, discovery.ENV_ACCEPT, discovery.ENV_PROD}, "|") + ")*$") - schemaIDRegex, _ = regexp.Compile(`^[a-zA-Z0-9]{1,160}$|^[a-zA-Z0-9][a-zA-Z0-9_\-.]{0,158}[a-zA-Z0-9]$`) - accountStatusRegex, _ = regexp.Compile(`^(active|inactive)$|^$`) + envRegex, _ = regexp.Compile(`^\S*$`) + schemaIDRegex, _ = regexp.Compile(`^[a-zA-Z0-9]{1,160}$|^[a-zA-Z0-9][a-zA-Z0-9_\-.]{0,158}[a-zA-Z0-9]$`) + accountStatusRegex, _ = regexp.Compile(`^(active|inactive)$|^$`) ) func MicroServiceKeyValidator() *validate.Validator { return microServiceKeyValidator.Init(func(v *validate.Validator) { - v.AddRule("Environment", &validate.Rule{Regexp: envRegex}) + v.AddRule("Environment", &validate.Rule{Min: 1, Max: 128, Regexp: envRegex}) v.AddRule("AppId", &validate.Rule{Min: 1, Max: 160, Regexp: nameRegex}) - v.AddRule("ServiceName", &validate.Rule{Min: 1, Max: 128, Regexp: nameRegex}) + v.AddRule("ServiceName", &validate.Rule{Max: 128, Regexp: nameRegex}) v.AddRule("Version", &validate.Rule{Min: 1, Max: 64, Regexp: versionRegex}) }) } func MicroServiceSearchKeyValidator() *validate.Validator { return microServiceKeySearchValidator.Init(func(v *validate.Validator) { - v.AddRule("Environment", &validate.Rule{Regexp: envRegex}) + v.AddRule("Environment", &validate.Rule{Min: 0, Max: 128, Regexp: envRegex}) v.AddRule("AppId", &validate.Rule{Min: 1, Max: 160, Regexp: nameRegex}) // support name or alias v.AddRule("ServiceName", &validate.Rule{Min: 1, Max: 160 + 1 + 128, Regexp: serviceNameForFindRegex}) diff --git a/syncer/service/replicator/resource/environment.go b/syncer/service/replicator/resource/environment.go new file mode 100644 index 000000000..d572fdced --- /dev/null +++ b/syncer/service/replicator/resource/environment.go @@ -0,0 +1,134 @@ +package resource + +import ( + "context" + "sync" + + pb "github.com/go-chassis/cari/discovery" + ev "github.com/go-chassis/cari/env" + "github.com/go-chassis/cari/pkg/errsvc" + + v1sync "github.com/apache/servicecomb-service-center/syncer/api/v1" +) + +var envManager environmentManager + +const ( + Environment = "environment" +) + +func NewEnvironment(e *v1sync.Event) Resource { + m := &environment{ + event: e, + } + m.manager = environmentManage() + return m +} + +var envOnce sync.Once + +func environmentManage() environmentManager { + envOnce.Do(InitEnvironmentManager) + return envManager +} + +func InitEnvironmentManager() { + envManager = new(metadataManage) +} + +type environment struct { + event *v1sync.Event + + createInput *ev.CreateEnvironmentRequest + updateInput *ev.UpdateEnvironmentRequest + deleteInput *ev.DeleteEnvironmentRequest + + envId string + + cur *ev.Environment + + manager environmentManager + + defaultFailHandler +} + +type environmentManager interface { + RegisterEnvironment(ctx context.Context, request *ev.CreateEnvironmentRequest) (*ev.CreateEnvironmentResponse, error) + GetEnvironment(ctx context.Context, in *ev.GetEnvironmentRequest) (*ev.Environment, error) + UpdateEnvironment(ctx context.Context, request *ev.UpdateEnvironmentRequest) error + UnregisterEnvironment(ctx context.Context, request *ev.DeleteEnvironmentRequest) error +} + +func (e *environment) loadInput() error { + e.createInput = new(ev.CreateEnvironmentRequest) + cre := newInputParam(e.createInput, func() { + e.envId = e.createInput.Environment.ID + }) + + e.updateInput = new(ev.UpdateEnvironmentRequest) + upd := newInputParam(e.updateInput, func() { + e.envId = e.updateInput.Environment.ID + }) + + e.deleteInput = new(ev.DeleteEnvironmentRequest) + del := newInputParam(e.deleteInput, func() { + e.envId = e.deleteInput.EnvironmentId + }) + + return newInputLoader( + e.event, + cre, + upd, + del, + ).loadInput() +} + +func (e *environment) LoadCurrentResource(ctx context.Context) *Result { + err := e.loadInput() + if err != nil { + return FailResult(err) + } + + cur, err := e.manager.GetEnvironment(ctx, &ev.GetEnvironmentRequest{ + EnvironmentId: e.envId, + }) + if err != nil { + if errsvc.IsErrEqualCode(err, pb.ErrServiceNotExists) { + return nil + } + + return FailResult(err) + } + e.cur = cur + return nil +} + +func (e *environment) NeedOperate(ctx context.Context) *Result { + c := &checker{ + curNotNil: e.cur != nil, + event: e.event, + updateTime: func() (int64, error) { + return formatUpdateTimeSecond(e.cur.ModTimestamp) + }, + resourceID: e.envId, + } + c.tombstoneLoader = c + return c.needOperate(ctx) +} + +func (e *environment) CreateHandle(ctx context.Context) error { + _, err := e.manager.RegisterEnvironment(ctx, e.createInput) + return err +} + +func (e *environment) UpdateHandle(ctx context.Context) error { + return e.manager.UpdateEnvironment(ctx, e.updateInput) +} + +func (e *environment) DeleteHandle(ctx context.Context) error { + return e.manager.UnregisterEnvironment(ctx, e.deleteInput) +} + +func (e *environment) Operate(ctx context.Context) *Result { + return newOperator(e).operate(ctx, e.event.Action) +} diff --git a/syncer/service/replicator/resource/instance.go b/syncer/service/replicator/resource/instance.go index e0371a6b9..caa9e0f1e 100644 --- a/syncer/service/replicator/resource/instance.go +++ b/syncer/service/replicator/resource/instance.go @@ -27,6 +27,7 @@ import ( v1sync "github.com/apache/servicecomb-service-center/syncer/api/v1" pb "github.com/go-chassis/cari/discovery" + ev "github.com/go-chassis/cari/env" "github.com/go-chassis/cari/pkg/errsvc" ) @@ -124,6 +125,22 @@ func (m *metadataManage) UnregisterInstance(ctx context.Context, in *pb.Unregist return datasource.GetMetadataManager().UnregisterInstance(ctx, in) } +func (m *metadataManage) RegisterEnvironment(ctx context.Context, in *ev.CreateEnvironmentRequest) (*ev.CreateEnvironmentResponse, error) { + return datasource.GetMetadataManager().RegisterEnvironment(ctx, in) +} + +func (m *metadataManage) GetEnvironment(ctx context.Context, in *ev.GetEnvironmentRequest) (*ev.Environment, error) { + return datasource.GetMetadataManager().GetEnvironment(ctx, in) +} + +func (m *metadataManage) UpdateEnvironment(ctx context.Context, in *ev.UpdateEnvironmentRequest) error { + return datasource.GetMetadataManager().UpdateEnvironment(ctx, in) +} + +func (m *metadataManage) UnregisterEnvironment(ctx context.Context, in *ev.DeleteEnvironmentRequest) error { + return datasource.GetMetadataManager().UnregisterEnvironment(ctx, in) +} + type metadataManager interface { serviceManager instanceManager diff --git a/syncer/service/replicator/resource/resource.go b/syncer/service/replicator/resource/resource.go index fe1a128b0..0ee278e75 100644 --- a/syncer/service/replicator/resource/resource.go +++ b/syncer/service/replicator/resource/resource.go @@ -27,6 +27,7 @@ import ( "github.com/apache/servicecomb-service-center/eventbase/datasource" "github.com/apache/servicecomb-service-center/eventbase/model" "github.com/apache/servicecomb-service-center/eventbase/service/tombstone" + "github.com/apache/servicecomb-service-center/pkg/log" v1sync "github.com/apache/servicecomb-service-center/syncer/api/v1" @@ -71,6 +72,7 @@ var ( Heartbeat: NewHeartbeat, Config: NewConfig, KV: NewKV, + Environment: NewEnvironment, } )