diff --git a/src/mapper/cmd/main.go b/src/mapper/cmd/main.go index 6ea6475b..11bb8273 100644 --- a/src/mapper/cmd/main.go +++ b/src/mapper/cmd/main.go @@ -78,13 +78,7 @@ func main() { defer cancelFn() mgr.GetCache().WaitForCacheSync(initCtx) // needed to let the manager initialize before used in intentsHolder - intentHolderCfg, err := resolvers.IntentsHolderConfigFromViper() - if err != nil { - logrus.Error(intentHolderCfg) - os.Exit(1) - } - - intentsHolder := resolvers.NewIntentsHolder(mgr.GetClient(), intentHolderCfg) + intentsHolder := resolvers.NewIntentsHolder(mgr.GetClient()) resolver := resolvers.NewResolver(kubeFinder, serviceidresolver.NewResolver(mgr.GetClient()), intentsHolder) resolver.Register(e) diff --git a/src/mapper/pkg/clouduploader/cloud_uploader_test.go b/src/mapper/pkg/clouduploader/cloud_uploader_test.go index d280a98f..5abc7220 100644 --- a/src/mapper/pkg/clouduploader/cloud_uploader_test.go +++ b/src/mapper/pkg/clouduploader/cloud_uploader_test.go @@ -6,7 +6,6 @@ import ( "github.com/golang/mock/gomock" "github.com/otterize/network-mapper/src/mapper/pkg/cloudclient" cloudclientmocks "github.com/otterize/network-mapper/src/mapper/pkg/cloudclient/mocks" - "github.com/otterize/network-mapper/src/mapper/pkg/config" "github.com/otterize/network-mapper/src/mapper/pkg/graph/model" "github.com/otterize/network-mapper/src/mapper/pkg/resolvers" "github.com/samber/lo" @@ -29,7 +28,7 @@ type CloudUploaderTestSuite struct { func (s *CloudUploaderTestSuite) SetupTest() { s.testNamespace = "test-namespace" - s.intentsHolder = resolvers.NewIntentsHolder(nil, resolvers.IntentsHolderConfig{StoreConfigMap: config.StoreConfigMapDefault, Namespace: s.testNamespace}) + s.intentsHolder = resolvers.NewIntentsHolder(nil) } func (s *CloudUploaderTestSuite) BeforeTest(_, testName string) { diff --git a/src/mapper/pkg/config/config.go b/src/mapper/pkg/config/config.go index 87294e91..417eb45b 100644 --- a/src/mapper/pkg/config/config.go +++ b/src/mapper/pkg/config/config.go @@ -12,8 +12,6 @@ const ( ClusterDomainDefault = kubeutils.DefaultClusterDomain DebugKey = "debug" DebugDefault = false - StoreConfigMapKey = "store-config-map" - StoreConfigMapDefault = "otterize-network-mapper-store" NamespaceKey = "namespace" CloudApiAddrKey = "api-address" CloudApiAddrDefault = "https://app.otterize.com/api" @@ -26,7 +24,6 @@ const ( func init() { viper.SetDefault(DebugKey, DebugDefault) viper.SetDefault(ClusterDomainKey, ClusterDomainDefault) // If not set by the user, the main.go of mapper will try to find the cluster domain and set it itself. - viper.SetDefault(StoreConfigMapKey, StoreConfigMapDefault) viper.SetDefault(CloudApiAddrKey, CloudApiAddrDefault) viper.SetDefault(UploadIntervalSecondsKey, UploadIntervalSecondsDefault) viper.SetEnvPrefix(EnvPrefix) diff --git a/src/mapper/pkg/graph/generated/generated.go b/src/mapper/pkg/graph/generated/generated.go index 4d5707eb..974731d8 100644 --- a/src/mapper/pkg/graph/generated/generated.go +++ b/src/mapper/pkg/graph/generated/generated.go @@ -51,12 +51,18 @@ type ComplexityRoot struct { } OtterizeServiceIdentity struct { + Labels func(childComplexity int) int Name func(childComplexity int) int Namespace func(childComplexity int) int } + PodLabel struct { + Key func(childComplexity int) int + Value func(childComplexity int) int + } + Query struct { - ServiceIntents func(childComplexity int, namespaces []string) int + ServiceIntents func(childComplexity int, namespaces []string, includeLabels []string) int } ServiceIntents struct { @@ -71,7 +77,7 @@ type MutationResolver interface { ReportSocketScanResults(ctx context.Context, results model.SocketScanResults) (bool, error) } type QueryResolver interface { - ServiceIntents(ctx context.Context, namespaces []string) ([]model.ServiceIntents, error) + ServiceIntents(ctx context.Context, namespaces []string, includeLabels []string) ([]model.ServiceIntents, error) } type executableSchema struct { @@ -120,6 +126,13 @@ func (e *executableSchema) Complexity(typeName, field string, childComplexity in return e.complexity.Mutation.ResetCapture(childComplexity), true + case "OtterizeServiceIdentity.labels": + if e.complexity.OtterizeServiceIdentity.Labels == nil { + break + } + + return e.complexity.OtterizeServiceIdentity.Labels(childComplexity), true + case "OtterizeServiceIdentity.name": if e.complexity.OtterizeServiceIdentity.Name == nil { break @@ -134,6 +147,20 @@ func (e *executableSchema) Complexity(typeName, field string, childComplexity in return e.complexity.OtterizeServiceIdentity.Namespace(childComplexity), true + case "PodLabel.key": + if e.complexity.PodLabel.Key == nil { + break + } + + return e.complexity.PodLabel.Key(childComplexity), true + + case "PodLabel.value": + if e.complexity.PodLabel.Value == nil { + break + } + + return e.complexity.PodLabel.Value(childComplexity), true + case "Query.serviceIntents": if e.complexity.Query.ServiceIntents == nil { break @@ -144,7 +171,7 @@ func (e *executableSchema) Complexity(typeName, field string, childComplexity in return 0, false } - return e.complexity.Query.ServiceIntents(childComplexity, args["namespaces"].([]string)), true + return e.complexity.Query.ServiceIntents(childComplexity, args["namespaces"].([]string), args["includeLabels"].([]string)), true case "ServiceIntents.client": if e.complexity.ServiceIntents.Client == nil { @@ -249,9 +276,15 @@ input SocketScanResults { results: [SocketScanResultForSrcIp!]! } +type PodLabel { + key: String! + value: String! +} + type OtterizeServiceIdentity { name: String! namespace: String! + labels: [PodLabel!] } type ServiceIntents { @@ -259,8 +292,9 @@ type ServiceIntents { intents: [OtterizeServiceIdentity!]! } + type Query { - serviceIntents(namespaces: [String!]): [ServiceIntents!]! + serviceIntents(namespaces: [String!], includeLabels: [String!]): [ServiceIntents!]! } type Mutation { @@ -332,6 +366,15 @@ func (ec *executionContext) field_Query_serviceIntents_args(ctx context.Context, } } args["namespaces"] = arg0 + var arg1 []string + if tmp, ok := rawArgs["includeLabels"]; ok { + ctx := graphql.WithPathContext(ctx, graphql.NewPathWithField("includeLabels")) + arg1, err = ec.unmarshalOString2ᚕstringᚄ(ctx, tmp) + if err != nil { + return nil, err + } + } + args["includeLabels"] = arg1 return args, nil } @@ -562,6 +605,108 @@ func (ec *executionContext) _OtterizeServiceIdentity_namespace(ctx context.Conte return ec.marshalNString2string(ctx, field.Selections, res) } +func (ec *executionContext) _OtterizeServiceIdentity_labels(ctx context.Context, field graphql.CollectedField, obj *model.OtterizeServiceIdentity) (ret graphql.Marshaler) { + defer func() { + if r := recover(); r != nil { + ec.Error(ctx, ec.Recover(ctx, r)) + ret = graphql.Null + } + }() + fc := &graphql.FieldContext{ + Object: "OtterizeServiceIdentity", + Field: field, + Args: nil, + IsMethod: false, + IsResolver: false, + } + + ctx = graphql.WithFieldContext(ctx, fc) + resTmp, err := ec.ResolverMiddleware(ctx, func(rctx context.Context) (interface{}, error) { + ctx = rctx // use context from middleware stack in children + return obj.Labels, nil + }) + if err != nil { + ec.Error(ctx, err) + return graphql.Null + } + if resTmp == nil { + return graphql.Null + } + res := resTmp.([]model.PodLabel) + fc.Result = res + return ec.marshalOPodLabel2ᚕgithubᚗcomᚋotterizeᚋnetworkᚑmapperᚋsrcᚋmapperᚋpkgᚋgraphᚋmodelᚐPodLabelᚄ(ctx, field.Selections, res) +} + +func (ec *executionContext) _PodLabel_key(ctx context.Context, field graphql.CollectedField, obj *model.PodLabel) (ret graphql.Marshaler) { + defer func() { + if r := recover(); r != nil { + ec.Error(ctx, ec.Recover(ctx, r)) + ret = graphql.Null + } + }() + fc := &graphql.FieldContext{ + Object: "PodLabel", + Field: field, + Args: nil, + IsMethod: false, + IsResolver: false, + } + + ctx = graphql.WithFieldContext(ctx, fc) + resTmp, err := ec.ResolverMiddleware(ctx, func(rctx context.Context) (interface{}, error) { + ctx = rctx // use context from middleware stack in children + return obj.Key, nil + }) + if err != nil { + ec.Error(ctx, err) + return graphql.Null + } + if resTmp == nil { + if !graphql.HasFieldError(ctx, fc) { + ec.Errorf(ctx, "must not be null") + } + return graphql.Null + } + res := resTmp.(string) + fc.Result = res + return ec.marshalNString2string(ctx, field.Selections, res) +} + +func (ec *executionContext) _PodLabel_value(ctx context.Context, field graphql.CollectedField, obj *model.PodLabel) (ret graphql.Marshaler) { + defer func() { + if r := recover(); r != nil { + ec.Error(ctx, ec.Recover(ctx, r)) + ret = graphql.Null + } + }() + fc := &graphql.FieldContext{ + Object: "PodLabel", + Field: field, + Args: nil, + IsMethod: false, + IsResolver: false, + } + + ctx = graphql.WithFieldContext(ctx, fc) + resTmp, err := ec.ResolverMiddleware(ctx, func(rctx context.Context) (interface{}, error) { + ctx = rctx // use context from middleware stack in children + return obj.Value, nil + }) + if err != nil { + ec.Error(ctx, err) + return graphql.Null + } + if resTmp == nil { + if !graphql.HasFieldError(ctx, fc) { + ec.Errorf(ctx, "must not be null") + } + return graphql.Null + } + res := resTmp.(string) + fc.Result = res + return ec.marshalNString2string(ctx, field.Selections, res) +} + func (ec *executionContext) _Query_serviceIntents(ctx context.Context, field graphql.CollectedField) (ret graphql.Marshaler) { defer func() { if r := recover(); r != nil { @@ -587,7 +732,7 @@ func (ec *executionContext) _Query_serviceIntents(ctx context.Context, field gra fc.Args = args resTmp, err := ec.ResolverMiddleware(ctx, func(rctx context.Context) (interface{}, error) { ctx = rctx // use context from middleware stack in children - return ec.resolvers.Query().ServiceIntents(rctx, args["namespaces"].([]string)) + return ec.resolvers.Query().ServiceIntents(rctx, args["namespaces"].([]string), args["includeLabels"].([]string)) }) if err != nil { ec.Error(ctx, err) @@ -2165,6 +2310,54 @@ func (ec *executionContext) _OtterizeServiceIdentity(ctx context.Context, sel as out.Values[i] = innerFunc(ctx) + if out.Values[i] == graphql.Null { + invalids++ + } + case "labels": + innerFunc := func(ctx context.Context) (res graphql.Marshaler) { + return ec._OtterizeServiceIdentity_labels(ctx, field, obj) + } + + out.Values[i] = innerFunc(ctx) + + default: + panic("unknown field " + strconv.Quote(field.Name)) + } + } + out.Dispatch() + if invalids > 0 { + return graphql.Null + } + return out +} + +var podLabelImplementors = []string{"PodLabel"} + +func (ec *executionContext) _PodLabel(ctx context.Context, sel ast.SelectionSet, obj *model.PodLabel) graphql.Marshaler { + fields := graphql.CollectFields(ec.OperationContext, sel, podLabelImplementors) + out := graphql.NewFieldSet(fields) + var invalids uint32 + for i, field := range fields { + switch field.Name { + case "__typename": + out.Values[i] = graphql.MarshalString("PodLabel") + case "key": + innerFunc := func(ctx context.Context) (res graphql.Marshaler) { + return ec._PodLabel_key(ctx, field, obj) + } + + out.Values[i] = innerFunc(ctx) + + if out.Values[i] == graphql.Null { + invalids++ + } + case "value": + innerFunc := func(ctx context.Context) (res graphql.Marshaler) { + return ec._PodLabel_value(ctx, field, obj) + } + + out.Values[i] = innerFunc(ctx) + if out.Values[i] == graphql.Null { invalids++ } @@ -2832,6 +3025,10 @@ func (ec *executionContext) marshalNOtterizeServiceIdentity2ᚖgithubᚗcomᚋot return ec._OtterizeServiceIdentity(ctx, sel, v) } +func (ec *executionContext) marshalNPodLabel2githubᚗcomᚋotterizeᚋnetworkᚑmapperᚋsrcᚋmapperᚋpkgᚋgraphᚋmodelᚐPodLabel(ctx context.Context, sel ast.SelectionSet, v model.PodLabel) graphql.Marshaler { + return ec._PodLabel(ctx, sel, &v) +} + func (ec *executionContext) marshalNServiceIntents2githubᚗcomᚋotterizeᚋnetworkᚑmapperᚋsrcᚋmapperᚋpkgᚋgraphᚋmodelᚐServiceIntents(ctx context.Context, sel ast.SelectionSet, v model.ServiceIntents) graphql.Marshaler { return ec._ServiceIntents(ctx, sel, &v) } @@ -3216,6 +3413,53 @@ func (ec *executionContext) marshalOBoolean2ᚖbool(ctx context.Context, sel ast return res } +func (ec *executionContext) marshalOPodLabel2ᚕgithubᚗcomᚋotterizeᚋnetworkᚑmapperᚋsrcᚋmapperᚋpkgᚋgraphᚋmodelᚐPodLabelᚄ(ctx context.Context, sel ast.SelectionSet, v []model.PodLabel) graphql.Marshaler { + if v == nil { + return graphql.Null + } + ret := make(graphql.Array, len(v)) + var wg sync.WaitGroup + isLen1 := len(v) == 1 + if !isLen1 { + wg.Add(len(v)) + } + for i := range v { + i := i + fc := &graphql.FieldContext{ + Index: &i, + Result: &v[i], + } + ctx := graphql.WithFieldContext(ctx, fc) + f := func(i int) { + defer func() { + if r := recover(); r != nil { + ec.Error(ctx, ec.Recover(ctx, r)) + ret = nil + } + }() + if !isLen1 { + defer wg.Done() + } + ret[i] = ec.marshalNPodLabel2githubᚗcomᚋotterizeᚋnetworkᚑmapperᚋsrcᚋmapperᚋpkgᚋgraphᚋmodelᚐPodLabel(ctx, sel, v[i]) + } + if isLen1 { + f(i) + } else { + go f(i) + } + + } + wg.Wait() + + for _, e := range ret { + if e == graphql.Null { + return graphql.Null + } + } + + return ret +} + func (ec *executionContext) unmarshalOString2ᚕstringᚄ(ctx context.Context, v interface{}) ([]string, error) { if v == nil { return nil, nil diff --git a/src/mapper/pkg/graph/model/models_gen.go b/src/mapper/pkg/graph/model/models_gen.go index 1d58cbf4..f8edb0f0 100644 --- a/src/mapper/pkg/graph/model/models_gen.go +++ b/src/mapper/pkg/graph/model/models_gen.go @@ -21,8 +21,14 @@ type Destination struct { } type OtterizeServiceIdentity struct { - Name string `json:"name"` - Namespace string `json:"namespace"` + Name string `json:"name"` + Namespace string `json:"namespace"` + Labels []PodLabel `json:"labels"` +} + +type PodLabel struct { + Key string `json:"key"` + Value string `json:"value"` } type ServiceIntents struct { diff --git a/src/mapper/pkg/resolvers/helpers.go b/src/mapper/pkg/resolvers/helpers.go index 46fa76e0..f133fcb7 100644 --- a/src/mapper/pkg/resolvers/helpers.go +++ b/src/mapper/pkg/resolvers/helpers.go @@ -1,70 +1,63 @@ package resolvers import ( - "fmt" "github.com/amit7itz/goset" - "github.com/otterize/network-mapper/src/mapper/pkg/config" "github.com/otterize/network-mapper/src/mapper/pkg/graph/model" - "github.com/otterize/network-mapper/src/shared/kubeutils" - "github.com/spf13/viper" + "github.com/samber/lo" + corev1 "k8s.io/api/core/v1" "sigs.k8s.io/controller-runtime/pkg/client" "sync" "time" ) -type SourceDestPair struct { - Source model.OtterizeServiceIdentity - Destination model.OtterizeServiceIdentity +type NamespacedName struct { + Name string `json:"name"` + Namespace string `json:"namespace"` } -type DiscoveredIntent struct { - Source model.OtterizeServiceIdentity `json:"source"` - Destination model.OtterizeServiceIdentity `json:"destination"` - Timestamp time.Time `json:"timestamp"` +type clientWithDestinations struct { + Client model.OtterizeServiceIdentity + Destinations []model.OtterizeServiceIdentity } -type IntentsHolderConfig struct { - StoreConfigMap string - Namespace string +type FullInfoIntentWithTime struct { + SourceFullInfo model.OtterizeServiceIdentity + DestinationFullInfo model.OtterizeServiceIdentity + Timestamp time.Time } -func namespaceFromConfig() (string, error) { - if viper.IsSet(config.NamespaceKey) { - return viper.GetString(config.NamespaceKey), nil - } - namespace, err := kubeutils.GetCurrentNamespace() - if err != nil { - return "", fmt.Errorf("could not deduce the store's configmap namespace: %w", err) - } - return namespace, nil +type SourceDestPair struct { + Source NamespacedName + Destination NamespacedName } -func IntentsHolderConfigFromViper() (IntentsHolderConfig, error) { - namespace, err := namespaceFromConfig() - if err != nil { - return IntentsHolderConfig{}, err +func serviceIdentityToNameNamespacePair(identity model.OtterizeServiceIdentity) NamespacedName { + return NamespacedName{ + Name: identity.Name, + Namespace: identity.Namespace, } - return IntentsHolderConfig{ - StoreConfigMap: viper.GetString(config.StoreConfigMapKey), - Namespace: namespace, - }, nil +} + +type DiscoveredIntent struct { + Source model.OtterizeServiceIdentity `json:"source"` + Destination model.OtterizeServiceIdentity `json:"destination"` + Timestamp time.Time `json:"timestamp"` } type IntentsHolder struct { - accumulatingStore map[SourceDestPair]time.Time - sinceLastGetStore map[SourceDestPair]time.Time + accumulatingStore map[SourceDestPair]FullInfoIntentWithTime + sinceLastGetStore map[SourceDestPair]FullInfoIntentWithTime lock sync.Mutex client client.Client - config IntentsHolderConfig + lastIntentsUpdate time.Time } -func NewIntentsHolder(client client.Client, config IntentsHolderConfig) *IntentsHolder { +func NewIntentsHolder(client client.Client) *IntentsHolder { return &IntentsHolder{ - accumulatingStore: make(map[SourceDestPair]time.Time), - sinceLastGetStore: make(map[SourceDestPair]time.Time), + accumulatingStore: make(map[SourceDestPair]FullInfoIntentWithTime), + sinceLastGetStore: make(map[SourceDestPair]FullInfoIntentWithTime), lock: sync.Mutex{}, client: client, - config: config, } } @@ -72,63 +65,92 @@ func (i *IntentsHolder) Reset() { i.lock.Lock() defer i.lock.Unlock() - i.accumulatingStore = make(map[SourceDestPair]time.Time) + i.accumulatingStore = make(map[SourceDestPair]FullInfoIntentWithTime) } func (i *IntentsHolder) AddIntent(srcService model.OtterizeServiceIdentity, dstService model.OtterizeServiceIdentity, newTimestamp time.Time) { i.lock.Lock() defer i.lock.Unlock() - pair := SourceDestPair{Source: srcService, Destination: dstService} - currentTimestamp, alreadyExists := i.accumulatingStore[pair] - if !alreadyExists || newTimestamp.After(currentTimestamp) { - i.accumulatingStore[pair] = newTimestamp - i.sinceLastGetStore[pair] = newTimestamp + pair := SourceDestPair{Source: serviceIdentityToNameNamespacePair(srcService), Destination: serviceIdentityToNameNamespacePair(dstService)} + timestampedPair, alreadyExists := i.accumulatingStore[pair] + if !alreadyExists || newTimestamp.After(timestampedPair.Timestamp) { + i.accumulatingStore[pair] = FullInfoIntentWithTime{SourceFullInfo: srcService, DestinationFullInfo: dstService, Timestamp: newTimestamp} + i.sinceLastGetStore[pair] = FullInfoIntentWithTime{SourceFullInfo: srcService, DestinationFullInfo: dstService, Timestamp: newTimestamp} + i.lastIntentsUpdate = time.Now() } } -func (i *IntentsHolder) GetIntents(namespaces []string) []DiscoveredIntent { +func (i *IntentsHolder) GetIntents(namespaces []string, includeLabels []string) []DiscoveredIntent { i.lock.Lock() defer i.lock.Unlock() - return i.getIntentsFromStore(i.accumulatingStore, namespaces...) + return i.getIntentsFromStore(i.accumulatingStore, namespaces, includeLabels) } func (i *IntentsHolder) GetNewIntentsSinceLastGet() []DiscoveredIntent { i.lock.Lock() defer i.lock.Unlock() - intents := i.getIntentsFromStore(i.sinceLastGetStore) - i.sinceLastGetStore = make(map[SourceDestPair]time.Time) + intents := i.getIntentsFromStore(i.sinceLastGetStore, nil, nil) + i.sinceLastGetStore = make(map[SourceDestPair]FullInfoIntentWithTime) return intents } -func (i *IntentsHolder) getIntentsFromStore(store map[SourceDestPair]time.Time, namespaces ...string) []DiscoveredIntent { +func (i *IntentsHolder) getIntentsFromStore(store map[SourceDestPair]FullInfoIntentWithTime, namespaces []string, includeLabels []string) []DiscoveredIntent { namespacesSet := goset.FromSlice(namespaces) + includeLabelsSet := goset.FromSlice(includeLabels) result := make([]DiscoveredIntent, 0) - for pair, timestamp := range store { + for pair, timestampedInfo := range store { if !namespacesSet.IsEmpty() && !namespacesSet.Contains(pair.Source.Namespace) { continue } + timestampedInfoCopy := timestampedInfo + + timestampedInfoCopy.SourceFullInfo.Labels = lo.Filter(timestampedInfoCopy.SourceFullInfo.Labels, func(label model.PodLabel, _ int) bool { + return includeLabelsSet.Contains(label.Key) + }) + timestampedInfoCopy.DestinationFullInfo.Labels = lo.Filter(timestampedInfoCopy.DestinationFullInfo.Labels, func(label model.PodLabel, _ int) bool { + return includeLabelsSet.Contains(label.Key) + }) result = append(result, DiscoveredIntent{ - Source: pair.Source, - Destination: pair.Destination, - Timestamp: timestamp, + Source: timestampedInfoCopy.SourceFullInfo, + Destination: timestampedInfoCopy.DestinationFullInfo, + Timestamp: timestampedInfoCopy.Timestamp, }) } return result } -func groupDestinationsBySource(discoveredIntents []DiscoveredIntent) map[model.OtterizeServiceIdentity][]model.OtterizeServiceIdentity { - serviceMap := make(map[model.OtterizeServiceIdentity][]model.OtterizeServiceIdentity, 0) +func groupDestinationsBySource(discoveredIntents []DiscoveredIntent) []clientWithDestinations { + serviceMap := make(map[NamespacedName]*clientWithDestinations, 0) for _, intents := range discoveredIntents { - if _, ok := serviceMap[intents.Source]; !ok { - serviceMap[intents.Source] = make([]model.OtterizeServiceIdentity, 0) + srcIdentity := serviceIdentityToNameNamespacePair(intents.Source) + if _, ok := serviceMap[srcIdentity]; !ok { + serviceMap[srcIdentity] = &clientWithDestinations{ + Client: intents.Source, + Destinations: make([]model.OtterizeServiceIdentity, 0), + } } - serviceMap[intents.Source] = append(serviceMap[intents.Source], intents.Destination) + destinations := append(serviceMap[srcIdentity].Destinations, intents.Destination) + serviceMap[srcIdentity].Destinations = destinations } - return serviceMap + return lo.MapToSlice(serviceMap, func(_ NamespacedName, client *clientWithDestinations) clientWithDestinations { + return *client + }) +} + +func podLabelsToOtterizeLabels(pod *corev1.Pod) []model.PodLabel { + labels := make([]model.PodLabel, 0, len(pod.Labels)) + for key, value := range pod.Labels { + labels = append(labels, model.PodLabel{ + Key: key, + Value: value, + }) + } + + return labels } diff --git a/src/mapper/pkg/resolvers/resolver_test.go b/src/mapper/pkg/resolvers/resolver_test.go index e3356655..053d2679 100644 --- a/src/mapper/pkg/resolvers/resolver_test.go +++ b/src/mapper/pkg/resolvers/resolver_test.go @@ -6,7 +6,6 @@ import ( "github.com/Khan/genqlient/graphql" "github.com/labstack/echo/v4" "github.com/otterize/intents-operator/src/shared/serviceidresolver" - "github.com/otterize/network-mapper/src/mapper/pkg/config" "github.com/otterize/network-mapper/src/mapper/pkg/kubefinder" "github.com/otterize/network-mapper/src/mapper/pkg/resolvers/test_gql_client" "github.com/otterize/network-mapper/src/shared/testbase" @@ -29,7 +28,7 @@ func (s *ResolverTestSuite) SetupTest() { var err error s.kubeFinder, err = kubefinder.NewKubeFinder(s.Mgr) s.Require().NoError(err) - s.intentsHolder = NewIntentsHolder(s.Mgr.GetClient(), IntentsHolderConfig{StoreConfigMap: config.StoreConfigMapDefault, Namespace: s.TestNamespace}) + s.intentsHolder = NewIntentsHolder(s.Mgr.GetClient()) resolver := NewResolver(s.kubeFinder, serviceidresolver.NewResolver(s.Mgr.GetClient()), s.intentsHolder) resolver.Register(e) s.server = httptest.NewServer(e) diff --git a/src/mapper/pkg/resolvers/schema.resolvers.go b/src/mapper/pkg/resolvers/schema.resolvers.go index 7fef0f7f..1c91a3aa 100644 --- a/src/mapper/pkg/resolvers/schema.resolvers.go +++ b/src/mapper/pkg/resolvers/schema.resolvers.go @@ -69,9 +69,10 @@ func (r *mutationResolver) ReportCaptureResults(ctx context.Context, results mod logrus.WithError(err).Debugf("Could not resolve pod %s to identity", destPod.Name) continue } + r.intentsHolder.AddIntent( - model.OtterizeServiceIdentity{Name: srcService, Namespace: srcPod.Namespace}, - model.OtterizeServiceIdentity{Name: dstService, Namespace: destPod.Namespace}, + model.OtterizeServiceIdentity{Name: srcService, Namespace: srcPod.Namespace, Labels: podLabelsToOtterizeLabels(srcPod)}, + model.OtterizeServiceIdentity{Name: dstService, Namespace: destPod.Namespace, Labels: podLabelsToOtterizeLabels(destPod)}, dest.LastSeen, ) } @@ -111,8 +112,8 @@ func (r *mutationResolver) ReportSocketScanResults(ctx context.Context, results continue } r.intentsHolder.AddIntent( - model.OtterizeServiceIdentity{Name: srcService, Namespace: srcPod.Namespace}, - model.OtterizeServiceIdentity{Name: dstService, Namespace: destPod.Namespace}, + model.OtterizeServiceIdentity{Name: srcService, Namespace: srcPod.Namespace, Labels: podLabelsToOtterizeLabels(srcPod)}, + model.OtterizeServiceIdentity{Name: dstService, Namespace: destPod.Namespace, Labels: podLabelsToOtterizeLabels(destPod)}, destIp.LastSeen, ) } @@ -120,22 +121,24 @@ func (r *mutationResolver) ReportSocketScanResults(ctx context.Context, results return true, nil } -func (r *queryResolver) ServiceIntents(ctx context.Context, namespaces []string) ([]model.ServiceIntents, error) { - discoveredIntents := r.intentsHolder.GetIntents(namespaces) +func (r *queryResolver) ServiceIntents(ctx context.Context, namespaces []string, includeLabels []string) ([]model.ServiceIntents, error) { + discoveredIntents := r.intentsHolder.GetIntents(namespaces, includeLabels) serviceToDestinations := groupDestinationsBySource(discoveredIntents) result := make([]model.ServiceIntents, 0) - for service, destinations := range serviceToDestinations { + for _, clientAndDestinations := range serviceToDestinations { + destinations := clientAndDestinations.Destinations sort.Slice(destinations, func(i, j int) bool { if destinations[i].Name != destinations[j].Name { return destinations[i].Name < destinations[j].Name } return destinations[i].Namespace < destinations[j].Namespace }) + clientAndDestinations.Destinations = destinations result = append(result, model.ServiceIntents{ - Client: lo.ToPtr(service), - Intents: destinations, + Client: lo.ToPtr(clientAndDestinations.Client), + Intents: clientAndDestinations.Destinations, }) } diff --git a/src/mappergraphql/schema.graphql b/src/mappergraphql/schema.graphql index a4f1b8df..59df5942 100644 --- a/src/mappergraphql/schema.graphql +++ b/src/mappergraphql/schema.graphql @@ -23,9 +23,15 @@ input SocketScanResults { results: [SocketScanResultForSrcIp!]! } +type PodLabel { + key: String! + value: String! +} + type OtterizeServiceIdentity { name: String! namespace: String! + labels: [PodLabel!] } type ServiceIntents { @@ -33,8 +39,9 @@ type ServiceIntents { intents: [OtterizeServiceIdentity!]! } + type Query { - serviceIntents(namespaces: [String!]): [ServiceIntents!]! + serviceIntents(namespaces: [String!], includeLabels: [String!]): [ServiceIntents!]! } type Mutation {