Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 3 additions & 2 deletions go.work.sum
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
cloud.google.com/go v0.115.1 h1:Jo0SM9cQnSkYfp44+v+NQXHpcHqlnRJk2qxh6yvxxxQ=
cloud.google.com/go/compute v1.23.1 h1:V97tBoDaZHb6leicZ1G6DLK2BAaZLJ/7+9BB/En3hR0=
github.com/google/pprof v0.0.0-20250403155104-27863c87afa6/go.mod h1:boTsfXsheKC2y+lKOCMpSfarhxDeIzfZG1jqGcPl3cA=
github.com/google/pprof v0.0.0-20250820193118-f64d9cf942d6/go.mod h1:I6V7YzU0XDpsHqbsyrghnFZLO1gwK6NPTNvmetQIk9U=
github.com/onsi/gomega v1.38.2/go.mod h1:W2MJcYxRGV63b418Ai34Ud0hEdTVXq9NW9+Sx6uXf3k=
github.com/spf13/pflag v1.0.9/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg=
github.com/vishvananda/netns v0.0.5 h1:DfiHV+j8bA32MFM7bfEunvT8IAqQ/NzSJHtcmW5zdEY=
github.com/vishvananda/netns v0.0.5/go.mod h1:SpkAiCQRtJ6TvvxPnOSyH3BMl6unz3xZlaprSwhNNJM=
golang.org/x/sys v0.2.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
215 changes: 215 additions & 0 deletions metis/cmd/cni.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,215 @@
/*
Copyright 2026 The Kubernetes Authors.

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"
"encoding/json"
"fmt"
"net"
"path/filepath"
"time"

"github.com/containernetworking/cni/pkg/skel"
"github.com/containernetworking/cni/pkg/types"
current "github.com/containernetworking/cni/pkg/types/100"
"github.com/containernetworking/cni/pkg/version"
"google.golang.org/grpc"
"google.golang.org/grpc/credentials/insecure"

pb "k8s.io/metis/api/adaptiveipam/v1"
)

// NetConf extends standard CNI network configuration.
type NetConf struct {
types.NetConf
DaemonSocket string `json:"daemon_socket"`
InitialPodCIDR string `json:"initial_pod_cidr"`
}

// K8sArgs contains the standard Kubernetes CNI arguments.
type K8sArgs struct {
types.CommonArgs
K8S_POD_NAME types.UnmarshallableString `json:"K8S_POD_NAME"`
K8S_POD_NAMESPACE types.UnmarshallableString `json:"K8S_POD_NAMESPACE"`
}

const defaultDaemonSocket = "/var/run/metis.sock"

var clientFactory = getGrpcClient

func loadNetConf(bytes []byte) (*NetConf, error) {
conf := &NetConf{
DaemonSocket: defaultDaemonSocket,
}
if err := json.Unmarshal(bytes, conf); err != nil {
return nil, fmt.Errorf("failed to parse network configuration: %v", err)
}
return conf, nil
}

func loadK8sArgs(args string) (*K8sArgs, error) {
k8sArgs := &K8sArgs{}
if err := types.LoadArgs(args, k8sArgs); err != nil {
return nil, fmt.Errorf("failed to parse CNI args: %v", err)
}
return k8sArgs, nil
}

func getGrpcClient(socketPath string) (pb.AdaptiveIpamClient, *grpc.ClientConn, error) {
dialOption := grpc.WithTransportCredentials(insecure.NewCredentials())

absPath, err := filepath.Abs(socketPath)
if err != nil {
return nil, nil, fmt.Errorf("failed to get absolute path for socket %s: %v", socketPath, err)
}
dialTarget := fmt.Sprintf("unix://%s", absPath)

conn, err := grpc.NewClient(dialTarget, dialOption)
if err != nil {
return nil, nil, fmt.Errorf("failed to connect to daemon: %v", err)
}

return pb.NewAdaptiveIpamClient(conn), conn, nil
}

func cmdAdd(args *skel.CmdArgs) error {
conf, err := loadNetConf(args.StdinData)
if err != nil {
return err
}

k8sArgs, err := loadK8sArgs(args.Args)
if err != nil {
return err
}

client, conn, err := clientFactory(conf.DaemonSocket)
if err != nil {
return err
}
if conn != nil {
defer conn.Close()
}

ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second)
defer cancel()

req := &pb.AllocatePodIPRequest{
Network: conf.Name,
Ipv4Config: &pb.IPConfig{
InterfaceName: args.IfName,
ContainerId: args.ContainerID,
InitialPodCidr: conf.InitialPodCIDR,
},
PodName: string(k8sArgs.K8S_POD_NAME),
PodNamespace: string(k8sArgs.K8S_POD_NAMESPACE),
}

resp, err := client.AllocatePodIP(ctx, req)
if err != nil {
return fmt.Errorf("failed to allocate IP via daemon: %v", err)
}

result := &current.Result{
CNIVersion: conf.CNIVersion,
}

if resp.Ipv4 != nil {
_, ipNet, err := net.ParseCIDR(resp.Ipv4.Cidr)
if err != nil {
return fmt.Errorf("failed to parse allocated CIDR: %v", err)
}
ip := net.ParseIP(resp.Ipv4.IpAddress)

result.IPs = append(result.IPs, &current.IPConfig{
Address: net.IPNet{IP: ip, Mask: ipNet.Mask},
})
}

// TODO: Handle IPv6 if needed

return types.PrintResult(result, conf.CNIVersion)
}

func cmdDel(args *skel.CmdArgs) error {
conf, err := loadNetConf(args.StdinData)
if err != nil {
return err
}

k8sArgs, err := loadK8sArgs(args.Args)
if err != nil {
return err
}

client, conn, err := clientFactory(conf.DaemonSocket)
if err != nil {
return err
}
if conn != nil {
defer conn.Close()
}

ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second)
defer cancel()

req := &pb.DeallocatePodIPRequest{
Network: conf.Name,
InterfaceName: args.IfName,
ContainerId: args.ContainerID,
PodName: string(k8sArgs.K8S_POD_NAME),
PodNamespace: string(k8sArgs.K8S_POD_NAMESPACE),
}

_, err = client.DeallocatePodIP(ctx, req)
if err != nil {
return fmt.Errorf("failed to deallocate IP via daemon: %v", err)
}

return nil
}

func cmdCheck(args *skel.CmdArgs) error {
conf, err := loadNetConf(args.StdinData)
if err != nil {
return err
}

// For check, we just verify we can connect to the daemon
_, conn, err := clientFactory(conf.DaemonSocket)
if err != nil {
return err
}
if conn != nil {
defer conn.Close()
}

return nil
}

func RunCni() {
skel.PluginMainFuncs(
skel.CNIFuncs{
Add: cmdAdd,
Check: cmdCheck,
Del: cmdDel,
},
version.All,
"metis CNI plugin",
)
}
Loading