| /* |
| * 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 util |
| |
| import ( |
| "context" |
| "errors" |
| "fmt" |
| |
| "github.com/apache/servicecomb-service-center/datasource" |
| "github.com/apache/servicecomb-service-center/datasource/etcd/path" |
| "github.com/apache/servicecomb-service-center/datasource/etcd/sd" |
| "github.com/apache/servicecomb-service-center/pkg/log" |
| pb "github.com/go-chassis/cari/discovery" |
| "github.com/little-cui/etcdadpt" |
| ) |
| |
| // DependencyRelationFilterOpt contains SameDomainProject and NonSelf flag |
| type DependencyRelationFilterOpt struct { |
| SameDomainProject bool |
| NonSelf bool |
| } |
| |
| type DependencyRelationFilterOption func(opt DependencyRelationFilterOpt) DependencyRelationFilterOpt |
| |
| 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 ToDependencyRelationFilterOpt(opts ...DependencyRelationFilterOption) (op DependencyRelationFilterOpt) { |
| for _, opt := range opts { |
| op = opt(op) |
| } |
| return |
| } |
| |
| type DependencyRelation struct { |
| ctx context.Context |
| domainProject string |
| consumer *pb.MicroService |
| provider *pb.MicroService |
| } |
| |
| func (dr *DependencyRelation) GetDependencyProviders(opts ...DependencyRelationFilterOption) ([]*pb.MicroService, error) { |
| keys, err := dr.getProviderKeys() |
| if err != nil { |
| return nil, err |
| } |
| services := make([]*pb.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 := GetService(dr.ctx, key.Tenant, 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) |
| } |
| } |
| return services, nil |
| } |
| |
| func (dr *DependencyRelation) getDependencyProviderIds() ([]string, error) { |
| keys, err := dr.getProviderKeys() |
| if err != nil { |
| return nil, err |
| } |
| return dr.GetProviderIdsByRules(keys) |
| } |
| |
| func (dr *DependencyRelation) getProviderKeys() ([]*pb.MicroServiceKey, error) { |
| if dr.consumer == nil { |
| return nil, errors.New("invalid consumer") |
| } |
| consumerMicroServiceKey := pb.MicroServiceToKey(dr.domainProject, dr.consumer) |
| |
| conKey := path.GenerateConsumerDependencyRuleKey(dr.domainProject, consumerMicroServiceKey) |
| consumerDependency, err := TransferToMicroServiceDependency(dr.ctx, conKey) |
| if err != nil { |
| return nil, err |
| } |
| return consumerDependency.Dependency, nil |
| } |
| |
| func (dr *DependencyRelation) GetProviderIdsByRules(providerRules []*pb.MicroServiceKey) ([]string, error) { |
| provideServiceIds := make([]string, 0, len(providerRules)) |
| for _, provider := range providerRules { |
| serviceIDs, err := dr.parseDependencyRule(provider) |
| if err != nil { |
| log.Error(fmt.Sprintf("get service[%s/%s/%s/%s]'s providerIDs failed", |
| provider.Environment, provider.AppId, provider.ServiceName, provider.Version), err) |
| return provideServiceIds, err |
| } |
| if len(serviceIDs) == 0 { |
| log.Warn(fmt.Sprintf("get service[%s/%s/%s/%s]'s providerIDs is empty", |
| provider.Environment, provider.AppId, provider.ServiceName, provider.Version)) |
| continue |
| } |
| provideServiceIds = append(provideServiceIds, serviceIDs...) |
| } |
| return provideServiceIds, nil |
| } |
| |
| func (dr *DependencyRelation) parseDependencyRule(dependencyRule *pb.MicroServiceKey) (serviceIDs []string, err error) { |
| serviceIDs, _, err = FindServiceIds(dr.ctx, dependencyRule, false) |
| return |
| } |
| |
| func (dr *DependencyRelation) GetDependencyConsumers(opts ...DependencyRelationFilterOption) ([]*pb.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([]*pb.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 { |
| if errors.Is(err, datasource.ErrNoData) { |
| log.Warn(fmt.Sprintf("consumer[%s/%s/%s/%s] does not exist", |
| consumer.Environment, consumer.AppId, consumer.ServiceName, consumer.Version)) |
| continue |
| } |
| return nil, err |
| } |
| |
| if op.NonSelf && service.ServiceId == dr.provider.ServiceId { |
| continue |
| } |
| |
| consumers = append(consumers, service) |
| } |
| return consumers, nil |
| } |
| |
| func (dr *DependencyRelation) GetServiceByMicroServiceKey(service *pb.MicroServiceKey) (*pb.MicroService, error) { |
| serviceID, err := GetServiceID(dr.ctx, service) |
| if err != nil { |
| return nil, err |
| } |
| if len(serviceID) == 0 { |
| log.Warn(fmt.Sprintf("service[%s/%s/%s/%s] not exist", |
| service.Environment, service.AppId, service.ServiceName, service.Version)) |
| return nil, datasource.ErrNoData |
| } |
| return GetService(dr.ctx, service.Tenant, serviceID) |
| } |
| |
| 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 { |
| 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 (dr *DependencyRelation) GetDependencyConsumersOfProvider() ([]*pb.MicroServiceKey, error) { |
| if dr.provider == nil { |
| return nil, errors.New("invalid provider") |
| } |
| providerService := pb.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 *pb.MicroServiceKey) ([]*pb.MicroServiceKey, error) { |
| copyProvider := *provider |
| copyProvider.Version = "" |
| prefix := path.GenerateProviderDependencyRuleKey(dr.domainProject, ©Provider) |
| |
| opts := append(FromContext(dr.ctx), |
| etcdadpt.WithStrKey(prefix), |
| etcdadpt.WithPrefix()) |
| rsp, err := sd.DependencyRule().Search(dr.ctx, opts...) |
| if err != nil { |
| log.Error(fmt.Sprintf("get service[%s/%s/%s]'s dependency rules failed", |
| provider.Environment, provider.AppId, provider.ServiceName), err) |
| return nil, err |
| } |
| |
| allConsumers := make([]*pb.MicroServiceKey, 0, len(rsp.Kvs)) |
| for _, kv := range rsp.Kvs { |
| allConsumers = append(allConsumers, kv.Value.(*pb.MicroServiceDependency).Dependency...) |
| } |
| return allConsumers, nil |
| } |
| |
| func NewProviderDependencyRelation(ctx context.Context, domainProject string, provider *pb.MicroService) *DependencyRelation { |
| return NewDependencyRelation(ctx, domainProject, nil, provider) |
| } |
| |
| func NewConsumerDependencyRelation(ctx context.Context, domainProject string, consumer *pb.MicroService) *DependencyRelation { |
| return NewDependencyRelation(ctx, domainProject, consumer, nil) |
| } |
| |
| func NewDependencyRelation(ctx context.Context, domainProject string, consumer *pb.MicroService, provider *pb.MicroService) *DependencyRelation { |
| return &DependencyRelation{ |
| ctx: ctx, |
| domainProject: domainProject, |
| consumer: consumer, |
| provider: provider, |
| } |
| } |