Files
s3-csi/pkg/k8s/client_test.go
zeekayandHanzo Dev 69ddc9947a s3-csi: ZAP-native CSI driver for the Hanzo filer
Fork of seaweedfs/seaweedfs-csi-driver, renamed seaweedfs -> s3 throughout and
ported off protobuf entirely.

WHY a fork was unavoidable: hanzoai/s3 serves HanzoFiler over the native ZAP
transport on the port still named "grpc" (18888) — s3/command/filer.go binds it
with transport.ListenStream and says it "replaces the legacy gRPC HanzoFiler
server: the whole filer (28 unary + 5 streaming RPCs) now answers over ZAP, no
gRPC". RegisterHanzoFilerServer exists nowhere in that tree. So the upstream
driver's gRPC client cannot connect at all; it fails as
`Unimplemented: unknown service filer_pb.SeaweedFiler`, which LOOKS like a
service-name mismatch but is a gRPC client hitting a non-gRPC port. Renaming the
service in the client would not have helped.

pkg/filer is the only place this driver touches storage, and it speaks ZAP:
  Mkdir  -> CreateEntry           (CreateVolume)
  Remove -> DeleteEntry           (DeleteVolume)
  Exists -> LookupDirectoryEntry  (ValidateVolumeCapabilities)
One shared, concurrency-safe client multiplexes over a single ZAP connection,
dials lazily so controller start does not depend on filer readiness, and redials
exactly once on a transport fault (covers a filer restart closing an idle conn
without becoming a retry storm against a struggling backend).

pkg/driver/volume.go Quota() likewise moves to s3/wire/mount — the fork generates
no mount_pb at all. Dead gRPC plumbing (WithFilerClient, AdjustedUrl,
grpcDialOption) is deleted rather than left inert.

The kubelet-facing side stays CSI gRPC: that is the Kubernetes contract between
kubelet and any CSI driver, not a Hanzo choice.

Zero protobuf call sites remain. Whole module compiles clean.

Co-authored-by: Hanzo Dev <[email protected]>
2026-07-27 16:42:29 -07:00

104 lines
3.1 KiB
Go

package k8s
import (
"context"
"testing"
corev1 "k8s.io/api/core/v1"
"k8s.io/apimachinery/pkg/api/resource"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/client-go/kubernetes/fake"
)
func TestGetVolumeCapacityByPersistentVolumeName(t *testing.T) {
client := fake.NewSimpleClientset(newPersistentVolume("pvc-legacy", "pvc-legacy", "5Gi"))
got, err := getVolumeCapacity(context.Background(), client, "s3-csi", "pvc-legacy")
if err != nil {
t.Fatalf("getVolumeCapacity: %v", err)
}
if want := int64(5 * 1024 * 1024 * 1024); got != want {
t.Fatalf("capacity = %d, want %d", got, want)
}
}
func TestGetVolumeCapacityByCSIVolumeHandle(t *testing.T) {
client := fake.NewSimpleClientset(newPersistentVolume(
"pvc-1234",
"/buckets/pvc-1234",
"10Gi",
))
got, err := getVolumeCapacity(context.Background(), client, "s3-csi", "/buckets/pvc-1234")
if err != nil {
t.Fatalf("getVolumeCapacity: %v", err)
}
if want := int64(10 * 1024 * 1024 * 1024); got != want {
t.Fatalf("capacity = %d, want %d", got, want)
}
}
func TestGetVolumeCapacityRejectsDuplicateHandles(t *testing.T) {
client := fake.NewSimpleClientset(
newPersistentVolume("pv-a", "/shared/path", "1Gi"),
newPersistentVolume("pv-b", "/shared/path", "2Gi"),
)
if _, err := getVolumeCapacity(context.Background(), client, "s3-csi", "/shared/path"); err == nil {
t.Fatal("expected duplicate volume handles to return an error")
}
}
func TestGetVolumeCapacityScopesHandleToDriver(t *testing.T) {
otherDriverVolume := newPersistentVolume("other-pv", "/shared/path", "1Gi")
otherDriverVolume.Spec.CSI.Driver = "other.csi.example"
client := fake.NewSimpleClientset(
otherDriverVolume,
newPersistentVolume("s3-pv", "/shared/path", "2Gi"),
)
got, err := getVolumeCapacity(context.Background(), client, "s3-csi", "/shared/path")
if err != nil {
t.Fatalf("getVolumeCapacity: %v", err)
}
if want := int64(2 * 1024 * 1024 * 1024); got != want {
t.Fatalf("capacity = %d, want %d", got, want)
}
}
func TestGetVolumeCapacityIgnoresNameCollisionOnFastPath(t *testing.T) {
// A PV named "data" exists but belongs to a different volume handle. The
// fast-path Get by basename ("data") must not match it; the handle lookup
// has to fall back to the List scan and find the correct volume.
decoy := newPersistentVolume("data", "/some/other/path", "1Gi")
client := fake.NewSimpleClientset(
decoy,
newPersistentVolume("real-pv", "/mnt/data", "7Gi"),
)
got, err := getVolumeCapacity(context.Background(), client, "s3-csi", "/mnt/data")
if err != nil {
t.Fatalf("getVolumeCapacity: %v", err)
}
if want := int64(7 * 1024 * 1024 * 1024); got != want {
t.Fatalf("capacity = %d, want %d", got, want)
}
}
func newPersistentVolume(name, handle, capacity string) *corev1.PersistentVolume {
return &corev1.PersistentVolume{
ObjectMeta: metav1.ObjectMeta{Name: name},
Spec: corev1.PersistentVolumeSpec{
Capacity: corev1.ResourceList{
corev1.ResourceStorage: resource.MustParse(capacity),
},
PersistentVolumeSource: corev1.PersistentVolumeSource{
CSI: &corev1.CSIPersistentVolumeSource{
Driver: "s3-csi",
VolumeHandle: handle,
},
},
},
}
}