| /* |
| * Licensed to the Apache Software Foundation (ASF) under one or more |
| * contributor license agreements. See the NOTICE file distributed with |
| * this work for additional information regarding copyright ownership. |
| * The ASF licenses this file to You 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 mongo |
| |
| import ( |
| "context" |
| "errors" |
| "fmt" |
| "strings" |
| |
| "github.com/go-chassis/cari/db/mongo" |
| "github.com/go-chassis/cari/discovery" |
| "go.mongodb.org/mongo-driver/bson" |
| "go.mongodb.org/mongo-driver/mongo/options" |
| |
| "github.com/apache/servicecomb-service-center/datasource" |
| "github.com/apache/servicecomb-service-center/datasource/etcd/path" |
| "github.com/apache/servicecomb-service-center/datasource/mongo/model" |
| "github.com/apache/servicecomb-service-center/datasource/mongo/util" |
| "github.com/apache/servicecomb-service-center/pkg/log" |
| ) |
| |
| type DependencyRelation struct { |
| ctx context.Context |
| domainProject string |
| consumer *discovery.MicroService |
| provider *discovery.MicroService |
| } |
| |
| type DependencyRelationFilterOpt struct { |
| SameDomainProject bool |
| NonSelf bool |
| } |
| |
| type DependencyRelationFilterOption func(opt DependencyRelationFilterOpt) DependencyRelationFilterOpt |
| |
| func NewConsumerDependencyRelation(ctx context.Context, domainProject string, consumer *discovery.MicroService) *DependencyRelation { |
| return NewDependencyRelation(ctx, domainProject, consumer, nil) |
| } |
| |
| func NewProviderDependencyRelation(ctx context.Context, domainProject string, provider *discovery.MicroService) *DependencyRelation { |
| return NewDependencyRelation(ctx, domainProject, nil, provider) |
| } |
| |
| func NewDependencyRelation(ctx context.Context, domainProject string, consumer *discovery.MicroService, provider *discovery.MicroService) *DependencyRelation { |
| return &DependencyRelation{ |
| ctx: ctx, |
| domainProject: domainProject, |
| consumer: consumer, |
| provider: provider, |
| } |
| } |
| |
| func (dr *DependencyRelation) GetDependencyProviders(opts ...DependencyRelationFilterOption) ([]*discovery.MicroService, error) { |
| keys, err := dr.getProviderKeys() |
| if err != nil { |
| return nil, err |
| } |
| services := make([]*discovery.MicroService, 0, len(keys)) |
| op := ToDependencyRelationFilterOpt(opts...) |
| |
| for _, key := range keys { |
| if op.SameDomainProject && key.Tenant != dr.domainProject { |
| continue |
| } |
| providerIDs, err := dr.parseDependencyRule(key) |
| |
| if err != nil { |
| return nil, err |
| } |
| |
| for _, providerID := range providerIDs { |
| provider, err := GetServiceByID(dr.ctx, providerID) |
| if err != nil { |
| if errors.Is(err, datasource.ErrNoData) { |
| log.Warn(fmt.Sprintf("provider[%s/%s/%s/%s] does not exist", |
| key.Environment, key.AppId, key.ServiceName, key.Version)) |
| } else { |
| log.Warn(fmt.Sprintf("get provider[%s/%s/%s/%s] failed", |
| key.Environment, key.AppId, key.ServiceName, key.Version)) |
| } |
| continue |
| } |
| if op.NonSelf && providerID == dr.consumer.ServiceId { |
| continue |
| } |
| services = append(services, provider.Service) |
| } |
| } |
| return services, nil |
| } |
| |
| func (dr *DependencyRelation) GetDependencyConsumers(opts ...DependencyRelationFilterOption) ([]*discovery.MicroService, error) { |
| consumerDependAllList, err := dr.GetDependencyConsumersOfProvider() |
| if err != nil { |
| log.Error(fmt.Sprintf("get service[%s]'s consumers failed", dr.provider.ServiceId), err) |
| return nil, err |
| } |
| consumers := make([]*discovery.MicroService, 0) |
| op := ToDependencyRelationFilterOpt(opts...) |
| for _, consumer := range consumerDependAllList { |
| if op.SameDomainProject && consumer.Tenant != dr.domainProject { |
| continue |
| } |
| service, err := dr.GetServiceByMicroServiceKey(consumer) |
| if err != nil { |
| return nil, err |
| } |
| if service == nil { |
| log.Warn(fmt.Sprintf("consumer[%s/%s/%s/%s] does not exist", |
| consumer.Environment, consumer.AppId, consumer.ServiceName, consumer.Version)) |
| continue |
| } |
| if op.NonSelf && service.ServiceId == dr.provider.ServiceId { |
| continue |
| } |
| consumers = append(consumers, service) |
| } |
| return consumers, nil |
| } |
| |
| func (dr *DependencyRelation) GetDependencyConsumersOfProvider() ([]*discovery.MicroServiceKey, error) { |
| if dr.provider == nil { |
| return nil, util.ErrInvalidConsumer |
| } |
| providerService := discovery.MicroServiceToKey(dr.domainProject, dr.provider) |
| consumerDependList, err := dr.GetConsumerOfSameServiceNameAndAppID(providerService) |
| if err != nil { |
| log.Error(fmt.Sprintf("get consumers that depend on rule[%s/%s/%s/%s] failed", |
| dr.provider.Environment, dr.provider.AppId, dr.provider.ServiceName, dr.provider.Version), err) |
| return nil, err |
| } |
| return consumerDependList, nil |
| } |
| |
| func (dr *DependencyRelation) GetConsumerOfSameServiceNameAndAppID(provider *discovery.MicroServiceKey) ([]*discovery.MicroServiceKey, error) { |
| filter := GenerateRuleKeyWithSameServiceNameAndAppID(path.DepsProvider, dr.domainProject, provider) |
| depRules, err := getServiceKeysInDep(dr.ctx, filter) |
| if err != nil { |
| return nil, err |
| } |
| var allConsumers []*discovery.MicroServiceKey |
| for _, depRule := range depRules { |
| allConsumers = append(allConsumers, depRule.Dep.Dependency...) |
| } |
| return allConsumers, nil |
| } |
| |
| func (dr *DependencyRelation) GetServiceByMicroServiceKey(service *discovery.MicroServiceKey) (*discovery.MicroService, error) { |
| filter, err := MicroServiceKeyFilter(service) |
| if err != nil { |
| log.Error("get serivce failed", err) |
| return nil, err |
| } |
| findRes, err := mongo.GetClient().GetDB().Collection(model.CollectionService).Find(dr.ctx, filter) |
| if err != nil { |
| return nil, err |
| } |
| if findRes.Err() != nil { |
| return nil, findRes.Err() |
| } |
| |
| for findRes.Next(dr.ctx) { |
| var service model.Service |
| err = findRes.Decode(&service) |
| if err != nil { |
| return nil, err |
| } |
| if service.Service != nil { |
| return service.Service, nil |
| } |
| } |
| return nil, nil |
| } |
| |
| func getServiceKeysInDep(ctx context.Context, filter interface{}) ([]*model.DependencyRule, error) { |
| findRes, err := mongo.GetClient().GetDB().Collection(model.CollectionDep).Find(ctx, filter) |
| if err != nil { |
| return nil, err |
| } |
| defer findRes.Close(ctx) |
| var depRules []*model.DependencyRule |
| for findRes.Next(ctx) { |
| var tmp *model.DependencyRule |
| err := findRes.Decode(&tmp) |
| if err != nil { |
| return nil, err |
| } |
| depRules = append(depRules, tmp) |
| } |
| return depRules, nil |
| } |
| |
| func (dr *DependencyRelation) getProviderKeys() ([]*discovery.MicroServiceKey, error) { |
| if dr.consumer == nil { |
| return nil, util.ErrInvalidConsumer |
| } |
| consumerMicroServiceKey := discovery.MicroServiceToKey(dr.domainProject, dr.consumer) |
| filter := GenerateConsumerDependencyRuleKey(dr.domainProject, consumerMicroServiceKey) |
| |
| consumerDependency, err := TransferToMicroServiceDependency(dr.ctx, filter) |
| if err != nil { |
| return nil, err |
| } |
| return consumerDependency.Dependency, nil |
| } |
| |
| func (dr *DependencyRelation) parseDependencyRule(dependencyRule *discovery.MicroServiceKey) (serviceIDs []string, err error) { |
| serviceIDs, _, err = FindServiceIds(dr.ctx, dependencyRule, false) |
| return |
| } |
| |
| func (dr *DependencyRelation) GetDependencyConsumerIds() ([]string, error) { |
| consumerDependAllList, err := dr.GetDependencyConsumersOfProvider() |
| if err != nil { |
| return nil, err |
| } |
| consumerIDs := make([]string, 0, len(consumerDependAllList)) |
| for _, consumer := range consumerDependAllList { |
| consumerID, err := GetServiceID(dr.ctx, consumer) |
| if err != nil && !errors.Is(err, datasource.ErrNoData) { |
| log.Error(fmt.Sprintf("get consumer[%s/%s/%s/%s] failed", |
| consumer.Environment, consumer.AppId, consumer.ServiceName, consumer.Version), err) |
| return nil, err |
| } |
| if len(consumerID) == 0 { |
| log.Warn(fmt.Sprintf("get consumer[%s/%s/%s/%s] not exist", |
| consumer.Environment, consumer.AppId, consumer.ServiceName, consumer.Version)) |
| continue |
| } |
| consumerIDs = append(consumerIDs, consumerID) |
| } |
| return consumerIDs, nil |
| } |
| |
| func MicroServiceKeyFilter(key *discovery.MicroServiceKey) (bson.M, error) { |
| tenant := strings.Split(key.Tenant, "/") |
| if len(tenant) != 2 { |
| return nil, util.ErrInvalidDomainProject |
| } |
| filter := util.NewDomainProjectFilter(tenant[0], tenant[1], |
| util.ServiceEnv(key.Environment), |
| util.ServiceAppID(key.AppId), |
| util.ServiceServiceName(key.ServiceName), |
| util.ServiceVersion(key.Version), |
| ) |
| return filter, nil |
| } |
| |
| func FindServiceIds(ctx context.Context, key *discovery.MicroServiceKey, matchVersion bool) ([]string, bool, error) { |
| tenant := strings.Split(key.Tenant, "/") |
| if len(tenant) != 2 { |
| return nil, false, util.ErrInvalidDomainProject |
| } |
| |
| baseFilter := bson.D{ |
| {Key: model.ColumnDomain, Value: tenant[0]}, |
| {Key: model.ColumnProject, Value: tenant[1]}, |
| {Key: util.ConnectWithDot([]string{model.ColumnService, model.ColumnEnv}), Value: key.Environment}, |
| {Key: util.ConnectWithDot([]string{model.ColumnService, model.ColumnAppID}), Value: key.AppId}} |
| |
| serviceIds, exist, err := findServiceKeysByServiceName(ctx, key, baseFilter, matchVersion) |
| if err != nil { |
| return nil, false, err |
| } |
| if len(serviceIds) == 0 { |
| if exist { |
| // service exist but version not matched |
| return nil, true, nil |
| } |
| if len(key.Alias) == 0 { |
| return nil, false, nil |
| } |
| serviceIds, exist, err = findServiceKeysByAlias(ctx, key, baseFilter, matchVersion) |
| if err != nil { |
| return nil, false, err |
| } |
| return serviceIds, exist, nil |
| } |
| return serviceIds, exist, nil |
| } |
| |
| func serviceVersionFilter(ctx context.Context, version string, filter bson.D, matchVersion bool) ([]string, bool, error) { |
| num, err := mongo.GetClient().GetDB().Collection(model.CollectionService).CountDocuments(ctx, filter) |
| if err != nil || num == 0 { |
| return nil, false, err |
| } |
| newFilter := filter |
| if matchVersion { |
| newFilter = findServiceKeys(ctx, version, filter) |
| } |
| ids, err := GetVersionService(ctx, newFilter) |
| if err != nil { |
| return nil, false, err |
| } |
| return ids, true, nil |
| } |
| |
| func findServiceKeysByServiceName(ctx context.Context, key *discovery.MicroServiceKey, baseFilter bson.D, matchVersion bool) ([]string, bool, error) { |
| filter := append(baseFilter, |
| bson.E{Key: util.ConnectWithDot([]string{model.ColumnService, model.ColumnServiceName}), Value: key.ServiceName}) |
| return serviceVersionFilter(ctx, key.Version, filter, matchVersion) |
| } |
| |
| func findServiceKeysByAlias(ctx context.Context, key *discovery.MicroServiceKey, baseFilter bson.D, matchVersion bool) ([]string, bool, error) { |
| filter := append(baseFilter, |
| bson.E{Key: util.ConnectWithDot([]string{model.ColumnService, model.ColumnAlias}), Value: key.Alias}) |
| return serviceVersionFilter(ctx, key.Version, filter, matchVersion) |
| } |
| |
| type ServiceVersionFilter func(ctx context.Context, filter bson.D) ([]string, error) |
| |
| func findServiceKeys(_ context.Context, version string, filter bson.D) (newFilter bson.D) { |
| filter = append(filter, bson.E{Key: util.ConnectWithDot([]string{model.ColumnService, model.ColumnVersion}), Value: version}) |
| return filter |
| } |
| |
| func GetVersionService(ctx context.Context, m bson.D) (serviceIds []string, err error) { |
| findRes, err := mongo.GetClient().GetDB().Collection(model.CollectionService).Find(ctx, m, &options.FindOptions{ |
| Sort: bson.M{util.ConnectWithDot([]string{model.ColumnService, model.ColumnVersion}): -1}}) |
| if err != nil { |
| return |
| } |
| if findRes.Err() != nil { |
| return nil, findRes.Err() |
| } |
| for findRes.Next(ctx) { |
| var service *model.Service |
| err = findRes.Decode(&service) |
| if err != nil { |
| return |
| } |
| serviceIds = append(serviceIds, service.Service.ServiceId) |
| } |
| return |
| } |
| |
| func WithSameDomainProject() DependencyRelationFilterOption { |
| return func(opt DependencyRelationFilterOpt) DependencyRelationFilterOpt { |
| opt.SameDomainProject = true |
| return opt |
| } |
| } |
| |
| func WithoutSelfDependency() DependencyRelationFilterOption { |
| return func(opt DependencyRelationFilterOpt) DependencyRelationFilterOpt { |
| opt.NonSelf = true |
| return opt |
| } |
| } |
| |
| func ToDependencyFilterOptions(in *discovery.GetDependenciesRequest) (opts []DependencyRelationFilterOption) { |
| if in.SameDomain { |
| opts = append(opts, WithSameDomainProject()) |
| } |
| if in.NoSelf { |
| opts = append(opts, WithoutSelfDependency()) |
| } |
| return opts |
| } |
| |
| func ToDependencyRelationFilterOpt(opts ...DependencyRelationFilterOption) (op DependencyRelationFilterOpt) { |
| for _, opt := range opts { |
| op = opt(op) |
| } |
| return |
| } |
| |
| func GenerateConsumerDependencyRuleKey(domainProject string, in *discovery.MicroServiceKey) bson.M { |
| return GenerateServiceDependencyRuleKey(path.DepsConsumer, domainProject, in) |
| } |
| |
| func GenerateProviderDependencyRuleKey(domainProject string, in *discovery.MicroServiceKey) bson.M { |
| return GenerateServiceDependencyRuleKey(path.DepsProvider, domainProject, in) |
| } |
| |
| func GenerateRuleKeyWithSameServiceNameAndAppID(serviceType string, domainProject string, in *discovery.MicroServiceKey) bson.M { |
| return util.NewFilter( |
| util.ServiceType(serviceType), |
| util.ServiceKeyTenant(domainProject), |
| util.ServiceKeyAppID(in.AppId), |
| util.ServiceKeyServiceName(in.ServiceName), |
| ) |
| } |
| |
| func GenerateServiceDependencyRuleKey(serviceType string, domainProject string, in *discovery.MicroServiceKey) bson.M { |
| if in == nil { |
| return util.NewFilter( |
| util.ServiceType(serviceType), |
| util.ServiceKeyTenant(domainProject), |
| ) |
| } |
| return util.NewFilter( |
| util.ServiceType(serviceType), |
| util.ServiceKeyTenant(domainProject), |
| util.ServiceKeyServiceEnv(in.Environment), |
| util.ServiceKeyAppID(in.AppId), |
| util.ServiceKeyServiceVersion(in.Version), |
| util.ServiceKeyServiceName(in.ServiceName), |
| ) |
| } |