Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add inventory support #1026

Open
wants to merge 1 commit into
base: main
Choose a base branch
from
Open

Add inventory support #1026

wants to merge 1 commit into from

Conversation

TaoZou1
Copy link
Contributor

@TaoZou1 TaoZou1 commented Feb 20, 2025

No description provided.

@TaoZou1 TaoZou1 requested a review from zhengxiexie February 20, 2025 06:47
@TaoZou1 TaoZou1 force-pushed the inventory branch 6 times, most recently from 6de446c to c2a85a1 Compare February 24, 2025 03:12
cmd/main.go Outdated
@@ -281,6 +288,8 @@ func startServiceController(mgr manager.Manager, nsxClient *nsx.Client) {
networkpolicycontroller.StartNetworkPolicyController(mgr, commonService, vpcService)
service.StartServiceLbController(mgr, commonService)
subnetbindingcontroller.StartSubnetBindingController(mgr, subnetService, subnetBindingService)
inventoryControll := inventory.NewInventoryController(mgr.GetClient(), inventoryService, cf)
inventoryControll.SetupWithManager(mgr)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Better comply with StartXXController?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done

log.Error(err, "Create pod Informer error")
}

podInformer.AddEventHandler(cache.ResourceEventHandlerFuncs{
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Unhandled error

package inventory

import (
"context"
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Imports are not sorted

return nil
}

func (c *InventoryController) handlePod(obj interface{}) {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Some receivers are c, and others are r, could you unify them?

case cache.DeletedFinalStateUnknown:
pod, ok = obj1.Obj.(*v1.Pod)
if !ok {
log.Error(errors.New("Unknown obj"), "DeletedFinalStateUnknown Obj is not *v1.Pod")
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Use fmt.Errorof as others modules.

}

const (
operation_create = "CREATE"
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Use camel case instead of snake case

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done

operation_delete = "DELETE"
operation_none = "NONE"

INVENTORY_STATUS_UP = "UP"
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

ditto

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done

@zhengxiexie
Copy link
Contributor

This implementation is quite similar to the existing controllers. We can continue using Kubebuilder's Reconcile pattern if we convert the Kubernetes objects into inventory request queue items, which should not lead to any significant issues.

Inventory controller can't use the Reconcile provider by the kube builder. If each inventory request is put in the inventory queue handled by kube builder, each inventory CR will be created. In a large scale system, there should be lots of inventory CR.

@zhengxiexie
Copy link
Contributor

Consider replacing the inventoryObjectQueue with a channel? I think this scenario is highly suited for the producer-consumer pattern.

log.Info("Send update to NSX clusterId ", "ContainerInventoryData", s.requestBuffer)
// TODO, check the context.TODO() be replaced by InventoryClient related todo
resp, err := s.NSXClient.InventoryClient.ContainerInventoryApi.AddContainerInventoryUpdateUpdates(context.TODO(), s.NSXConfig.Cluster, containerinventory.ContainerInventoryData{ContainerInventoryObjects: s.requestBuffer})

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I wonder AddContainerInventoryUpdateUpdates if the batch operation request is asynchronous,

  1. How do you know if the process is successful or not?
  2. If failed, after updateInventoryStore, how do you guarantee the consistency of the local store and NSX side?

return
}

func (s *InventoryService) compareAndMergeUpdate(pre interface{}, cur interface{}) (string, map[string]interface{}) {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This function is too large, I think we can also comply with builder and compare pattern.
Splitting it into small files is good for scaling.

@zhengxiexie
Copy link
Contributor

zhengxiexie commented Feb 24, 2025

image

BTW, I think these functions, especially the func compareAndMergeupdate are a bit too free and unrestrained, not very standardized or unified, and not very easy to expand. You can see that in other modules, we all follow the standards -- buildXX, wrapXX, GetXX, ListXX, CreateOrUpdateXX, Cleanup pattern

return nil
}

func (r *InventoryController) handlePodEvent(event watch.Event) {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Plan where to use it?

switch obj1 := obj.(type) {
case *v1.Pod:
pod = obj1
case cache.DeletedFinalStateUnknown:
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What does DeletedFinalStateUnknown mean, why other controllers don't have it?

}
}
log.V(1).Info("Inventory processing Pod", "namespace", pod.Namespace, "name", pod.Name)
key, _ := keyFunc(pod)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Here keyFunc is cache.DeletionHandlingMetaNamespaceKeyFunc, why?

}

func (c *InventoryController) processNextInventoryWorkItem() bool {
key, quit := c.inventoryObjectQueue.Get()
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If use chan here, we don't need lock any more.


case ContainerApplicationInstance:
pod := &corev1.Pod{}
err := s.Client.Get(context.TODO(), types.NamespacedName{Name: name, Namespace: namespace}, pod)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

So you keep a inventoryObjectQueue which contains only keys, any by client.get to check if we delete or add.
Why not construct a key and operation(delete/update/create) object into queue at handlePod, then we don't need to Get here again.

Client client.Client
service *inventory.InventoryService
inventoryObjectQueue workqueue.TypedRateLimitingInterface[any]
keyBuffer inventory.KeySet
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think keyBuffer is redundant, inventoryObjectQueue is enough, if you use channel, you don't need to get obj from key again at all, The pseudocode is as follows

func (b *BatchProcessor) Run(stopCh <-chan struct{}) {
batch := make([]NSXOperation, 0, batchSize)
ticker := time.NewTicker(flushInterval)

for {
    select {
    case op := <-b.operations:
        batch = append(batch, op)
        if len(batch) >= batchSize {
            b.processBatch(batch)
            batch = batch[:0]
        }
    case <-ticker.C:
        if len(batch) > 0 {
            b.processBatch(batch)
            batch = batch[:0]
        }
    case <-stopCh:
        return
    }
}

}

@zhengxiexie
Copy link
Contributor

To summarize, I think we can maintain two channels, one is of inventoryObjectQueue, another is of ContainerInventoryObject, We can try to postpone process batching as much as possible.

  1. A goroutine watches all k8s objects and puts all obj including adding or deleting events to inventoryObjectQueue, no need to consider batch or periodical at this stage.
  2. A goroutine gets this obj, like other controllers, builds this obj, compares it with local in-store, transforms it to ContainerInventoryObject, and then puts it to another channel.
  3. The last goroutine is responsible for aggregating the objects by resource type from the channel, then triggering every API client of InventoryClient to send batch requests or periodical requests.

This method has several benefits:

  1. It only uses two channels, following the principle of Occam's razor, reducing unnecessary queues or key indexes.
  2. Each goroutine is only responsible for handling its own tasks and decoupling them.
  3. It follows our existing controller, where each object has its own build, compare, and store methods, which can reuse existing functions and reduce maintenance costs.
  4. It considers batch processing or scheduled processing at the end, making the logic clearer.

@TaoZou1 TaoZou1 force-pushed the inventory branch 3 times, most recently from 1a8213c to 4b6586e Compare February 27, 2025 06:25
@zhengxiexie
Copy link
Contributor

/e2e

4 similar comments
@zhengxiexie
Copy link
Contributor

/e2e

@zhengxiexie
Copy link
Contributor

/e2e

@zhengxiexie
Copy link
Contributor

/e2e

@TaoZou1
Copy link
Contributor Author

TaoZou1 commented Feb 27, 2025

/e2e

@zhengxiexie
Copy link
Contributor

/e2e

1 similar comment
@TaoZou1
Copy link
Contributor Author

TaoZou1 commented Feb 28, 2025

/e2e

@TaoZou1
Copy link
Contributor Author

TaoZou1 commented Feb 28, 2025

/e2e

Add inventory controller, service.
Add ApplicationInstanceStore, ClusteStore

Test Done
1. inventory cluster could be created
2. pod inventory could be reported
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

2 participants