e0e362d700
SDK Tests / changes (push) Successful in 2m29s
Real E2E Tests / changes (push) Successful in 2m29s
Deploy Docs Pages / build (push) Has been cancelled
Deploy Docs Pages / deploy (push) Has been cancelled
Real E2E Tests / JavaScript E2E (docker bridge) (push) Has been cancelled
Real E2E Tests / Python E2E (docker bridge) (push) Has been cancelled
Real E2E Tests / Java E2E (docker bridge) (push) Has been cancelled
Real E2E Tests / C# E2E (docker bridge) (push) Has been cancelled
Real E2E Tests / Go E2E (docker bridge) (push) Has been cancelled
Real E2E Tests / Real E2E CI (push) Has been cancelled
SDK Tests / SDK CI (push) Has been cancelled
SDK Tests / CLI Tests (push) Has been cancelled
SDK Tests / Python SDK Quality (code-interpreter) (push) Has been cancelled
SDK Tests / Python SDK Quality (sandbox) (push) Has been cancelled
SDK Tests / Python SDK Tests (code-interpreter) (push) Has been cancelled
SDK Tests / JavaScript SDK Quality And Tests (code-interpreter) (push) Has been cancelled
SDK Tests / JavaScript SDK Quality And Tests (sandbox) (push) Has been cancelled
SDK Tests / Python SDK Tests (sandbox) (push) Has been cancelled
SDK Tests / CLI Quality (push) Has been cancelled
SDK Tests / Kotlin SDK Quality And Tests (sandbox) (push) Has been cancelled
SDK Tests / Kotlin SDK Quality And Tests (code-interpreter) (push) Has been cancelled
SDK Tests / C# SDK Quality And Tests (code-interpreter) (push) Has been cancelled
SDK Tests / C# SDK Quality And Tests (sandbox) (push) Has been cancelled
SDK Tests / Go SDK Quality And Tests (push) Has been cancelled
414 lines
13 KiB
Go
414 lines
13 KiB
Go
// Copyright 2025 Alibaba Group Holding Ltd.
|
|
//
|
|
// Licensed 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 main
|
|
|
|
import (
|
|
"context"
|
|
"flag"
|
|
"fmt"
|
|
"time"
|
|
|
|
corev1 "k8s.io/api/core/v1"
|
|
"k8s.io/apimachinery/pkg/api/errors"
|
|
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
|
"k8s.io/apimachinery/pkg/labels"
|
|
"k8s.io/apimachinery/pkg/util/wait"
|
|
"k8s.io/client-go/tools/cache"
|
|
"k8s.io/client-go/tools/clientcmd"
|
|
"k8s.io/client-go/util/workqueue"
|
|
"k8s.io/klog/v2"
|
|
|
|
sandboxv1alpha1 "github.com/alibaba/OpenSandbox/sandbox-k8s/apis/sandbox/v1alpha1"
|
|
clientset "github.com/alibaba/OpenSandbox/sandbox-k8s/pkg/client/clientset/versioned"
|
|
informers "github.com/alibaba/OpenSandbox/sandbox-k8s/pkg/client/informers/externalversions"
|
|
listers "github.com/alibaba/OpenSandbox/sandbox-k8s/pkg/client/listers/sandbox/v1alpha1"
|
|
)
|
|
|
|
// Controller demonstrates how to use the generated clientset, informer, and lister
|
|
type Controller struct {
|
|
// clientset is used to directly manipulate API objects
|
|
clientset clientset.Interface
|
|
|
|
// listers are used to read objects from local cache, avoiding frequent API Server access
|
|
batchSandboxLister listers.BatchSandboxLister
|
|
poolLister listers.PoolLister
|
|
|
|
// informer cache is used to check if objects are synced
|
|
batchSandboxSynced cache.InformerSynced
|
|
poolSynced cache.InformerSynced
|
|
|
|
// workqueue is used to process events
|
|
workqueue workqueue.RateLimitingInterface
|
|
}
|
|
|
|
func NewController(
|
|
clientset clientset.Interface,
|
|
informerFactory informers.SharedInformerFactory,
|
|
) *Controller {
|
|
// Get BatchSandbox and Pool informers
|
|
batchSandboxInformer := informerFactory.Sandbox().V1alpha1().BatchSandboxes()
|
|
poolInformer := informerFactory.Sandbox().V1alpha1().Pools()
|
|
|
|
controller := &Controller{
|
|
clientset: clientset,
|
|
batchSandboxLister: batchSandboxInformer.Lister(),
|
|
poolLister: poolInformer.Lister(),
|
|
batchSandboxSynced: batchSandboxInformer.Informer().HasSynced,
|
|
poolSynced: poolInformer.Informer().HasSynced,
|
|
workqueue: workqueue.NewNamedRateLimitingQueue(workqueue.DefaultControllerRateLimiter(), "Example"),
|
|
}
|
|
|
|
// Register event handlers
|
|
batchSandboxInformer.Informer().AddEventHandler(cache.ResourceEventHandlerFuncs{
|
|
AddFunc: func(obj interface{}) {
|
|
bs := obj.(*sandboxv1alpha1.BatchSandbox)
|
|
klog.Infof("BatchSandbox added: %s/%s", bs.Namespace, bs.Name)
|
|
},
|
|
UpdateFunc: func(old, new interface{}) {
|
|
bs := new.(*sandboxv1alpha1.BatchSandbox)
|
|
klog.Infof("BatchSandbox updated: %s/%s", bs.Namespace, bs.Name)
|
|
},
|
|
DeleteFunc: func(obj interface{}) {
|
|
bs := obj.(*sandboxv1alpha1.BatchSandbox)
|
|
klog.Infof("BatchSandbox deleted: %s/%s", bs.Namespace, bs.Name)
|
|
},
|
|
})
|
|
|
|
poolInformer.Informer().AddEventHandler(cache.ResourceEventHandlerFuncs{
|
|
AddFunc: func(obj interface{}) {
|
|
pool := obj.(*sandboxv1alpha1.Pool)
|
|
klog.Infof("Pool added: %s/%s", pool.Namespace, pool.Name)
|
|
},
|
|
UpdateFunc: func(old, new interface{}) {
|
|
pool := new.(*sandboxv1alpha1.Pool)
|
|
klog.Infof("Pool updated: %s/%s", pool.Namespace, pool.Name)
|
|
},
|
|
DeleteFunc: func(obj interface{}) {
|
|
pool := obj.(*sandboxv1alpha1.Pool)
|
|
klog.Infof("Pool deleted: %s/%s", pool.Namespace, pool.Name)
|
|
},
|
|
})
|
|
|
|
return controller
|
|
}
|
|
|
|
func (c *Controller) Run(ctx context.Context, workers int) error {
|
|
defer c.workqueue.ShutDown()
|
|
|
|
klog.Info("Waiting for cache sync...")
|
|
if ok := cache.WaitForCacheSync(ctx.Done(), c.batchSandboxSynced, c.poolSynced); !ok {
|
|
return fmt.Errorf("failed to sync cache")
|
|
}
|
|
|
|
klog.Info("Cache synced, starting controller")
|
|
|
|
// Start worker goroutines
|
|
for i := 0; i < workers; i++ {
|
|
go wait.UntilWithContext(ctx, c.runWorker, time.Second)
|
|
}
|
|
|
|
<-ctx.Done()
|
|
klog.Info("Stopping controller")
|
|
return nil
|
|
}
|
|
|
|
func (c *Controller) runWorker(ctx context.Context) {
|
|
for c.processNextWorkItem(ctx) {
|
|
}
|
|
}
|
|
|
|
func (c *Controller) processNextWorkItem(ctx context.Context) bool {
|
|
obj, shutdown := c.workqueue.Get()
|
|
if shutdown {
|
|
return false
|
|
}
|
|
|
|
defer c.workqueue.Done(obj)
|
|
// Process actual business logic here
|
|
return true
|
|
}
|
|
|
|
// DemonstrateClientsetUsage demonstrates how to use clientset for CRUD operations
|
|
func DemonstrateClientsetUsage(ctx context.Context, client clientset.Interface) {
|
|
namespace := "default"
|
|
|
|
klog.Info("========================================")
|
|
klog.Info("Demonstrating Clientset Usage")
|
|
klog.Info("========================================")
|
|
|
|
// 1. Create Pool
|
|
klog.Info("\n1. Creating Pool resource")
|
|
pool := &sandboxv1alpha1.Pool{
|
|
ObjectMeta: metav1.ObjectMeta{
|
|
Name: "example-pool",
|
|
Namespace: namespace,
|
|
},
|
|
Spec: sandboxv1alpha1.PoolSpec{
|
|
Template: &corev1.PodTemplateSpec{
|
|
Spec: corev1.PodSpec{
|
|
Containers: []corev1.Container{
|
|
{
|
|
Name: "nginx",
|
|
Image: "nginx:latest",
|
|
},
|
|
},
|
|
},
|
|
},
|
|
CapacitySpec: sandboxv1alpha1.CapacitySpec{
|
|
PoolMin: 2,
|
|
PoolMax: 10,
|
|
BufferMin: 1,
|
|
BufferMax: 5,
|
|
},
|
|
},
|
|
}
|
|
|
|
createdPool, err := client.SandboxV1alpha1().Pools(namespace).Create(ctx, pool, metav1.CreateOptions{})
|
|
if err != nil {
|
|
if errors.IsAlreadyExists(err) {
|
|
klog.Infof("Pool already exists: %s", pool.Name)
|
|
} else {
|
|
klog.Errorf("Failed to create Pool: %v", err)
|
|
}
|
|
} else {
|
|
klog.Infof("Successfully created Pool: %s", createdPool.Name)
|
|
}
|
|
|
|
// 2. Get Pool
|
|
klog.Info("\n2. Getting Pool resource")
|
|
getPool, err := client.SandboxV1alpha1().Pools(namespace).Get(ctx, "example-pool", metav1.GetOptions{})
|
|
if err != nil {
|
|
klog.Errorf("Failed to get Pool: %v", err)
|
|
} else {
|
|
klog.Infof("Successfully retrieved Pool: %s, PoolMin: %d, PoolMax: %d",
|
|
getPool.Name, getPool.Spec.CapacitySpec.PoolMin, getPool.Spec.CapacitySpec.PoolMax)
|
|
}
|
|
|
|
// 3. List all Pools
|
|
klog.Info("\n3. Listing all Pool resources")
|
|
poolList, err := client.SandboxV1alpha1().Pools(namespace).List(ctx, metav1.ListOptions{})
|
|
if err != nil {
|
|
klog.Errorf("Failed to list Pools: %v", err)
|
|
} else {
|
|
klog.Infof("Found %d Pool(s):", len(poolList.Items))
|
|
for _, p := range poolList.Items {
|
|
klog.Infof(" - %s (PoolMin: %d, PoolMax: %d)",
|
|
p.Name, p.Spec.CapacitySpec.PoolMin, p.Spec.CapacitySpec.PoolMax)
|
|
}
|
|
}
|
|
|
|
// 4. Update Pool
|
|
klog.Info("\n4. Updating Pool resource")
|
|
if getPool != nil {
|
|
getPool.Spec.CapacitySpec.PoolMax = 20
|
|
updatedPool, err := client.SandboxV1alpha1().Pools(namespace).Update(ctx, getPool, metav1.UpdateOptions{})
|
|
if err != nil {
|
|
klog.Errorf("Failed to update Pool: %v", err)
|
|
} else {
|
|
klog.Infof("Successfully updated Pool: %s, new PoolMax: %d", updatedPool.Name, updatedPool.Spec.CapacitySpec.PoolMax)
|
|
}
|
|
}
|
|
|
|
// 5. Create BatchSandbox
|
|
klog.Info("\n5. Creating BatchSandbox resource")
|
|
replicas := int32(3)
|
|
batchSandbox := &sandboxv1alpha1.BatchSandbox{
|
|
ObjectMeta: metav1.ObjectMeta{
|
|
Name: "example-batchsandbox",
|
|
Namespace: namespace,
|
|
},
|
|
Spec: sandboxv1alpha1.BatchSandboxSpec{
|
|
Replicas: &replicas,
|
|
PoolRef: "example-pool",
|
|
},
|
|
}
|
|
|
|
createdBS, err := client.SandboxV1alpha1().BatchSandboxes(namespace).Create(ctx, batchSandbox, metav1.CreateOptions{})
|
|
if err != nil {
|
|
if errors.IsAlreadyExists(err) {
|
|
klog.Infof("BatchSandbox already exists: %s", batchSandbox.Name)
|
|
} else {
|
|
klog.Errorf("Failed to create BatchSandbox: %v", err)
|
|
}
|
|
} else {
|
|
klog.Infof("Successfully created BatchSandbox: %s, Replicas: %d", createdBS.Name, *createdBS.Spec.Replicas)
|
|
}
|
|
|
|
// 6. Get BatchSandbox
|
|
klog.Info("\n6. Getting BatchSandbox resource")
|
|
getBS, err := client.SandboxV1alpha1().BatchSandboxes(namespace).Get(ctx, "example-batchsandbox", metav1.GetOptions{})
|
|
if err != nil {
|
|
klog.Errorf("Failed to get BatchSandbox: %v", err)
|
|
} else {
|
|
klog.Infof("Successfully retrieved BatchSandbox: %s, Replicas: %d, PoolRef: %s",
|
|
getBS.Name, *getBS.Spec.Replicas, getBS.Spec.PoolRef)
|
|
}
|
|
|
|
// 7. Update BatchSandbox
|
|
klog.Info("\n7. Updating BatchSandbox resource")
|
|
if getBS != nil {
|
|
newReplicas := int32(5)
|
|
getBS.Spec.Replicas = &newReplicas
|
|
updatedBS, err := client.SandboxV1alpha1().BatchSandboxes(namespace).Update(ctx, getBS, metav1.UpdateOptions{})
|
|
if err != nil {
|
|
klog.Errorf("Failed to update BatchSandbox: %v", err)
|
|
} else {
|
|
klog.Infof("Successfully updated BatchSandbox: %s, new Replicas: %d", updatedBS.Name, *updatedBS.Spec.Replicas)
|
|
}
|
|
}
|
|
|
|
// 8. List all BatchSandboxes
|
|
klog.Info("\n8. Listing all BatchSandbox resources")
|
|
bsList, err := client.SandboxV1alpha1().BatchSandboxes(namespace).List(ctx, metav1.ListOptions{})
|
|
if err != nil {
|
|
klog.Errorf("Failed to list BatchSandboxes: %v", err)
|
|
} else {
|
|
klog.Infof("Found %d BatchSandbox(es):", len(bsList.Items))
|
|
for _, bs := range bsList.Items {
|
|
klog.Infof(" - %s (Replicas: %d, PoolRef: %s)",
|
|
bs.Name, *bs.Spec.Replicas, bs.Spec.PoolRef)
|
|
}
|
|
}
|
|
|
|
// Wait for informer to process events
|
|
klog.Info("\nWaiting 3 seconds for informer to process events...")
|
|
time.Sleep(3 * time.Second)
|
|
|
|
// 9. Delete BatchSandbox
|
|
klog.Info("\n9. Deleting BatchSandbox resource")
|
|
err = client.SandboxV1alpha1().BatchSandboxes(namespace).Delete(ctx, "example-batchsandbox", metav1.DeleteOptions{})
|
|
if err != nil {
|
|
klog.Errorf("Failed to delete BatchSandbox: %v", err)
|
|
} else {
|
|
klog.Infof("Successfully deleted BatchSandbox: example-batchsandbox")
|
|
}
|
|
|
|
// 10. Delete Pool
|
|
klog.Info("\n10. Deleting Pool resource")
|
|
err = client.SandboxV1alpha1().Pools(namespace).Delete(ctx, "example-pool", metav1.DeleteOptions{})
|
|
if err != nil {
|
|
klog.Errorf("Failed to delete Pool: %v", err)
|
|
} else {
|
|
klog.Infof("Successfully deleted Pool: example-pool")
|
|
}
|
|
}
|
|
|
|
// DemonstrateListerUsage demonstrates how to use lister to read objects from cache
|
|
func DemonstrateListerUsage(
|
|
batchSandboxLister listers.BatchSandboxLister,
|
|
poolLister listers.PoolLister,
|
|
) {
|
|
klog.Info("\n========================================")
|
|
klog.Info("Demonstrating Lister Usage (reading from local cache)")
|
|
klog.Info("========================================")
|
|
|
|
namespace := "default"
|
|
|
|
// 1. Use lister to get a specific Pool
|
|
klog.Info("\n1. Using Lister to get Pool")
|
|
pool, err := poolLister.Pools(namespace).Get("example-pool")
|
|
if err != nil {
|
|
if errors.IsNotFound(err) {
|
|
klog.Info("Pool not found (may have been deleted)")
|
|
} else {
|
|
klog.Errorf("Lister failed to get Pool: %v", err)
|
|
}
|
|
} else {
|
|
klog.Infof("Retrieved Pool from cache: %s, PoolMax: %d", pool.Name, pool.Spec.CapacitySpec.PoolMax)
|
|
}
|
|
|
|
// 2. Use lister to list all Pools
|
|
klog.Info("\n2. Using Lister to list all Pools")
|
|
pools, err := poolLister.Pools(namespace).List(labels.Everything())
|
|
if err != nil {
|
|
klog.Errorf("Lister failed to list Pools: %v", err)
|
|
} else {
|
|
klog.Infof("Found %d Pool(s) from cache:", len(pools))
|
|
for _, p := range pools {
|
|
klog.Infof(" - %s", p.Name)
|
|
}
|
|
}
|
|
|
|
// 3. Use lister to get a specific BatchSandbox
|
|
klog.Info("\n3. Using Lister to get BatchSandbox")
|
|
bs, err := batchSandboxLister.BatchSandboxes(namespace).Get("example-batchsandbox")
|
|
if err != nil {
|
|
if errors.IsNotFound(err) {
|
|
klog.Info("BatchSandbox not found (may have been deleted)")
|
|
} else {
|
|
klog.Errorf("Lister failed to get BatchSandbox: %v", err)
|
|
}
|
|
} else {
|
|
klog.Infof("Retrieved BatchSandbox from cache: %s, Replicas: %d", bs.Name, *bs.Spec.Replicas)
|
|
}
|
|
|
|
// 4. Use lister to list all BatchSandboxes
|
|
klog.Info("\n4. Using Lister to list all BatchSandboxes")
|
|
batchSandboxes, err := batchSandboxLister.BatchSandboxes(namespace).List(labels.Everything())
|
|
if err != nil {
|
|
klog.Errorf("Lister failed to list BatchSandboxes: %v", err)
|
|
} else {
|
|
klog.Infof("Found %d BatchSandbox(es) from cache:", len(batchSandboxes))
|
|
for _, bs := range batchSandboxes {
|
|
klog.Infof(" - %s (Replicas: %d)", bs.Name, *bs.Spec.Replicas)
|
|
}
|
|
}
|
|
}
|
|
|
|
func main() {
|
|
var kubeconfig string
|
|
flag.StringVar(&kubeconfig, "kubeconfig", "", "Path to a kubeconfig file")
|
|
flag.Parse()
|
|
|
|
// Build configuration
|
|
config, err := clientcmd.BuildConfigFromFlags("", kubeconfig)
|
|
if err != nil {
|
|
klog.Fatalf("Failed to build config: %v", err)
|
|
}
|
|
|
|
// Create clientset
|
|
client, err := clientset.NewForConfig(config)
|
|
if err != nil {
|
|
klog.Fatalf("Failed to create clientset: %v", err)
|
|
}
|
|
|
|
// Create informer factory
|
|
informerFactory := informers.NewSharedInformerFactory(client, time.Second*30)
|
|
|
|
// Create controller
|
|
controller := NewController(client, informerFactory)
|
|
|
|
// Start informers
|
|
ctx := context.Background()
|
|
informerFactory.Start(ctx.Done())
|
|
|
|
// Wait for cache sync
|
|
klog.Info("Waiting for informer cache sync...")
|
|
if ok := cache.WaitForCacheSync(ctx.Done(), controller.batchSandboxSynced, controller.poolSynced); !ok {
|
|
klog.Fatal("Failed to sync cache")
|
|
}
|
|
klog.Info("Informer cache synced successfully")
|
|
|
|
// Demonstrate clientset usage
|
|
DemonstrateClientsetUsage(ctx, client)
|
|
|
|
// Demonstrate lister usage
|
|
DemonstrateListerUsage(controller.batchSandboxLister, controller.poolLister)
|
|
|
|
klog.Info("\n========================================")
|
|
klog.Info("Demonstration completed!")
|
|
klog.Info("========================================")
|
|
}
|