Skip to content

Commit 4903a1f

Browse files
committed
fix: move link setup from CmdAdd to DetectIPConflictAndGatewayReachable
* move netlink link setup logic from command_add.go to ipam_detection.go * set link up inside DetectIPConflictAndGatewayReachable before detection * change early return to continue when IP version is nil during detection * remove netlink import from command_add.go Signed-off-by: Cyclinder Kuo <qifeng.guo@daocloud.io>
1 parent e3e26bd commit 4903a1f

15 files changed

Lines changed: 212 additions & 229 deletions

File tree

AGENTS.md

Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
1+
# Repository Guidelines
2+
3+
## Project Structure & Module Organization
4+
5+
Spiderpool is a Go Kubernetes networking project. Main binaries live in `cmd/`, reusable packages in `pkg/`, and Kubernetes APIs, generated clients, and OpenAPI specs in `api/`. Helm packaging is under `charts/spiderpool/`; container build assets are in `images/`. End-to-end assets and cluster scripts live in `test/`, documentation in `docs/`, design/spec work in `specs/`, and shared automation in `tools/` and `contrib/`. Avoid editing `vendor/` directly unless dependency vendoring is the explicit task.
6+
7+
## Build, Test, and Development Commands
8+
9+
- `make build-bin`: build Spiderpool binaries into the local output path.
10+
- `make install-bin`: install built binaries.
11+
- `make build_image`: build Docker images with buildx using the current commit version.
12+
- `make build_docker_image`: local Docker fallback when buildx has pull issues.
13+
- `make dev-doctor`: verify Go and required e2e tools such as Docker, kubectl, kind, and p2ctl.
14+
- `make gofmt`: run `go fmt` on Go packages.
15+
- `make lint-golang`: run format checks, lock checks, `go vet`, and `golangci-lint`.
16+
- `make manifests generate-k8s-api`: regenerate CRDs/RBAC/webhooks and deepcopy code.
17+
- `make openapi-code-gen`: regenerate OpenAPI clients from `api/v1/*/openapi.yaml`.
18+
19+
## Coding Style & Naming Conventions
20+
21+
Use Go 1.25 as declared in `go.mod`. Keep Go code `gofmt`/`gofumpt` clean and satisfy `.golangci.yaml` linters: `govet`, `errcheck`, `staticcheck`, `ineffassign`, and `errorlint`. Package names are lowercase and directory-oriented, for example `pkg/ippoolmanager` and `pkg/workloadendpointmanager`. Tests use `_test.go`; suite files follow `*_suite_test.go`.
22+
23+
## Testing Guidelines
24+
25+
Unit tests use Ginkgo v2 and Gomega. Run `make unittest-tests` for package and command tests; it also checks that non-suite test files include a Ginkgo `Label(...)`. For e2e work, build or pull images first, then use targets such as `make e2e_init_spiderpool` and `make e2e_test_spiderpool`. Narrow e2e runs with `E2E_GINKGO_LABELS=smoke` or `GINKGO_OPTION="--label-filter=CaseLabel"`.
26+
27+
## Commit & Pull Request Guidelines
28+
29+
History uses short imperative subjects with optional scopes, such as `fix: ...`, `test: ...`, `CI: ...`, `charts: ...`, and release bumps. Keep commits focused and sign them when following the contribution docs (`git commit -s`). PRs should link issues with `Fixes #...`, state unit or e2e coverage, mention docs impact, include reviewer notes when needed, and fill the release-note block with either content or `NONE`. Apply one release label: `release/none`, `release/bug`, or `release/feature`.
30+
31+
## Agent-Specific Instructions
32+
33+
Before changing generated Kubernetes or OpenAPI files, update the source definitions and run the matching generation or verify target. Do not revert unrelated local changes; this repository may contain concurrent contributor work.

charts/spiderpool/templates/configmap.yaml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -37,7 +37,7 @@ data:
3737
namespacesExclude: {{ toJson .Values.spiderpoolController.podResourceInject.namespacesExclude }}
3838
namespacesInclude: {{ toJson .Values.spiderpoolController.podResourceInject.namespacesInclude }}
3939
iaasNetworkProvider:
40-
serverUrl: {{ .Values.iaasNetworkProvider.serverUrl | quote }}
40+
serverUrl: {{ (.Values.iaasNetworkProvider).serverUrl | default "" | quote }}
4141
{{- if .Values.multus.multusCNI.install }}
4242
---
4343
kind: ConfigMap

cmd/spiderpool/cmd/command_add.go

Lines changed: 0 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,6 @@ import (
1616
current "github.com/containernetworking/cni/pkg/types/100"
1717
"github.com/containernetworking/plugins/pkg/ns"
1818
"github.com/go-openapi/strfmt"
19-
"github.com/vishvananda/netlink"
2019
"go.uber.org/multierr"
2120
"go.uber.org/zap"
2221

@@ -66,25 +65,6 @@ func CmdAdd(args *skel.CmdArgs) (err error) {
6665
return fmt.Errorf("failed to setup file logging: %w", err)
6766
}
6867

69-
// When IPAM is invoked, the NIC is down and must be set it up in order to detect IP conflicts and
70-
// gateway reachability.
71-
err = netns.Do(func(netNS ns.NetNS) error {
72-
l, err := netlink.LinkByName(args.IfName)
73-
if err != nil {
74-
return fmt.Errorf("failed to get link: %w", err)
75-
}
76-
77-
if err = netlink.LinkSetUp(l); err != nil {
78-
return fmt.Errorf("failed to set link up: %w", err)
79-
}
80-
81-
logger.Sugar().Debugf("Set link %s to up for IP conflict and gateway detection", args.IfName)
82-
return nil
83-
})
84-
if err != nil {
85-
return fmt.Errorf("failed to set link up: %w", err)
86-
}
87-
8868
hostNs, err := ns.GetCurrentNS()
8969
if err != nil {
9070
return fmt.Errorf("failed to get current netns: %w", err)

cmd/spiderpool/cmd/command_delete.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -89,7 +89,7 @@ func CmdDel(args *skel.CmdArgs) (err error) {
8989
return err
9090
}
9191

92-
ctx, cancel := context.WithTimeout(context.Background(), 30*time.Second)
92+
ctx, cancel := context.WithTimeout(context.Background(), 100*time.Second)
9393
defer cancel()
9494

9595
params := daemonset.NewDeleteIpamIPParams().

images/spiderpool-plugins/version.sh

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -18,4 +18,4 @@ export IB_SRIOV_VERSION=${IB_SRIOV_VERSION:-"v1.3.0"}
1818
# https://github.com/Mellanox/ipoib-cni
1919
export IPOIB_VERSION=${IPOIB_VERSION:-"v1.2.2"}
2020
# https://github.com/spidernet-io/vlan-cni
21-
export VLAN_VERSION=${VLAN_VERSION:-"0.0.1"}
21+
export VLAN_VERSION=${VLAN_VERSION:-"v0.0.1"}

pkg/gcmanager/scanAll_IPPool.go

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -402,12 +402,13 @@ func (s *SpiderGC) executeScanAll(ctx context.Context) {
402402
if endpoint != nil {
403403
nodeName = endpoint.Status.Current.Node
404404
}
405-
if releaseErr := s.iaasClient.ReleaseIPs(ctx, &iaasclient.ReleaseIPsRequest{
405+
if releaseErr := s.iaasClient.ReleaseIP(ctx, &iaasclient.ReleaseIPRequest{
406406
PodName: podName,
407407
PodNamespace: podNS,
408408
PodUID: poolIPAllocation.PodUID,
409409
NodeName: nodeName,
410-
IPAddresses: []string{poolIP},
410+
Subnet: pool.Spec.Subnet,
411+
IPAddress: poolIP,
411412
}); releaseErr != nil {
412413
scanAllLogger.Sugar().Errorf("failed to release IaaS IP '%s', error: '%v'", poolIP, releaseErr)
413414
} else {

pkg/gcmanager/tracePod_worker.go

Lines changed: 21 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ package gcmanager
66
import (
77
"context"
88
"fmt"
9+
"net"
910
"sync"
1011
"sync/atomic"
1112
"time"
@@ -163,28 +164,30 @@ func (s *SpiderGC) releaseIPPoolIPExecutor(ctx context.Context, workerIndex int)
163164

164165
// Release IPs from IaaS provider after releasing from internal IPPools
165166
if s.iaasClient != nil {
166-
var ipAddresses []string
167167
for _, detail := range endpoint.Status.Current.IPs {
168168
if detail.IPv4 != nil {
169-
ipAddresses = append(ipAddresses, *detail.IPv4)
169+
ip, subnet, err := net.ParseCIDR(*detail.IPv4)
170+
if err != nil {
171+
log.Sugar().Errorf("failed to parse CIDR '%s', error: %v, skip releasing IaaS IP '%s'", *detail.IPv4, err, *detail.IPv4)
172+
continue
173+
}
174+
req := &iaasclient.ReleaseIPRequest{
175+
PodName: podCache.PodName,
176+
PodNamespace: podCache.Namespace,
177+
PodUID: podCache.UID,
178+
NodeName: endpoint.Status.Current.Node,
179+
Subnet: subnet.String(),
180+
IPAddress: ip.String(),
181+
}
182+
if err := s.iaasClient.ReleaseIP(ctx, req); err != nil {
183+
log.Sugar().Errorf("failed to release IaaS IP '%s' for '%s/%s', error: %v",
184+
ip.String(), podCache.Namespace, podCache.PodName, err)
185+
return err
186+
}
187+
log.Sugar().Infof("successfully released IaaS IP '%s' for '%s/%s'",
188+
ip.String(), podCache.Namespace, podCache.PodName)
170189
}
171190
}
172-
if len(ipAddresses) > 0 {
173-
req := &iaasclient.ReleaseIPsRequest{
174-
PodName: podCache.PodName,
175-
PodNamespace: podCache.Namespace,
176-
PodUID: podCache.UID,
177-
NodeName: endpoint.Status.Current.Node,
178-
IPAddresses: ipAddresses,
179-
}
180-
if err := s.iaasClient.ReleaseIPs(ctx, req); err != nil {
181-
log.Sugar().Errorf("failed to release IaaS IPs for '%s/%s', error: %v",
182-
podCache.Namespace, podCache.PodName, err)
183-
return err
184-
}
185-
log.Sugar().Infof("successfully released IaaS IPs %v for '%s/%s'",
186-
ipAddresses, podCache.Namespace, podCache.PodName)
187-
}
188191
}
189192

190193
// delete StatefulSet/kubevirtVMI wep (other controller wep has OwnerReference, its lifecycle is same with pod)

pkg/iaas/client/client.go

Lines changed: 23 additions & 65 deletions
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,6 @@ import (
1010
"encoding/json"
1111
"fmt"
1212
"io"
13-
"net"
1413
"net/http"
1514
"net/url"
1615
"sync"
@@ -26,25 +25,17 @@ const (
2625
releaseAPIPath = "/v1/apis/network.iaas.io/ipam/release-ip"
2726
)
2827

29-
// ParentNicMacLookupFunc is a fallback function to look up parentNicMac
30-
// when the cache does not have the value. It receives the context and the IP CIDR string.
31-
type ParentNicMacLookupFunc func(ctx context.Context, ipCIDR string) (string, error)
32-
3328
// Client is the interface for IaaS provider API client
3429
type Client interface {
3530
// AllocateIPs calls the IaaS provider to allocate IPs
3631
AllocateIPs(ctx context.Context, req *AllocateIPRequest) (*AllocateIPResponse, error)
3732
// ReleaseIPs calls the IaaS provider to release IPs
38-
ReleaseIPs(ctx context.Context, req *ReleaseIPsRequest) error
33+
ReleaseIP(ctx context.Context, req *ReleaseIPRequest) error
3934
// GetCachedParentNicMac returns the cached parent NIC MAC for the given key,
40-
// or empty string if not cached. Key can be SpiderMultusConfig namespace/name
41-
// or IP CIDR string.
35+
// or empty string if not cached. Key is SpiderMultusConfig namespace/name.
4236
GetCachedParentNicMac(key string) (string, bool)
4337
// CacheParentNicMac stores a parent NIC MAC for the given key.
4438
CacheParentNicMac(key string, mac string)
45-
// SetParentNicMacLookupFunc sets a fallback lookup function for parentNicMac
46-
// when cache misses (e.g., after agent restart).
47-
SetParentNicMacLookupFunc(fn ParentNicMacLookupFunc)
4839
}
4940

5041
// IaaSClient implements the Client interface
@@ -54,13 +45,8 @@ type IaaSClient struct {
5445
logger *zap.Logger
5546

5647
// parentNicMacCache caches key -> parent NIC MAC address.
57-
// Keys include both SpiderMultusConfig namespace/name and IP CIDR strings,
58-
// so that release path can look up parentNicMac by IP.
48+
// Keys use SpiderMultusConfig namespace/name.
5949
parentNicMacCache sync.Map
60-
61-
// parentNicMacLookupFunc is a fallback function to look up parentNicMac
62-
// when the cache does not have the value (e.g., after agent restart).
63-
parentNicMacLookupFunc ParentNicMacLookupFunc
6450
}
6551

6652
// ValidateConfig validates the IaaS provider configuration.
@@ -177,62 +163,40 @@ func (c *IaaSClient) AllocateIPs(ctx context.Context, req *AllocateIPRequest) (*
177163
return &allocateResp, nil
178164
}
179165

180-
// ReleaseIPs calls the IaaS provider to release IPs.
181-
// The provider only supports releasing one IP per request, so this method
182-
// loops over each IP and calls the API individually.
183-
func (c *IaaSClient) ReleaseIPs(ctx context.Context, req *ReleaseIPsRequest) error {
166+
// ReleaseIP calls the IaaS provider to release an IP.
167+
func (c *IaaSClient) ReleaseIP(ctx context.Context, req *ReleaseIPRequest) error {
184168
c.logger.Debug("Calling IaaS release API",
185169
zap.String("url", c.baseURL),
186170
zap.String("nodeName", req.NodeName),
187-
zap.String("podName", req.PodName),
188-
zap.String("podNamespace", req.PodNamespace),
189-
zap.Strings("ipAddresses", req.IPAddresses),
171+
zap.String("ipAddress", req.IPAddress),
172+
zap.String("subnet", req.Subnet),
173+
zap.String("parentNicMac", req.ParentNicMac),
190174
)
191175

192176
reqURL, err := url.JoinPath(c.baseURL, releaseAPIPath)
193177
if err != nil {
194178
return fmt.Errorf("failed to construct release URL: %w", err)
195179
}
196180

197-
for _, ip := range req.IPAddresses {
198-
c.logger.Debug("Releasing single IP via IaaS", zap.String("ip", ip))
199-
200-
ipstr, ipnet, err := net.ParseCIDR(ip)
201-
if err != nil {
202-
c.logger.Error("Failed to parse IP for release", zap.String("ip", ip), zap.Error(err))
203-
return fmt.Errorf("failed to parse IP %s: %w", ip, err)
204-
}
205-
206-
// Look up parentNicMac via lookup function (queries SMC-keyed cache or resolves from SpiderMultusConfig)
207-
var parentNicMac string
208-
if c.parentNicMacLookupFunc != nil {
209-
mac, lookupErr := c.parentNicMacLookupFunc(ctx, ip)
210-
if lookupErr != nil {
211-
c.logger.Warn("Failed to lookup parentNicMac, proceeding with empty value",
212-
zap.String("ip", ip), zap.Error(lookupErr))
213-
} else {
214-
parentNicMac = mac
215-
}
216-
} else {
217-
c.logger.Warn("No parentNicMac lookup function configured, proceeding with empty value",
218-
zap.String("ip", ip))
219-
}
220-
221-
singleReq := &ReleaseIPRequest{
222-
NodeName: req.NodeName,
223-
IPAddress: ipstr.String(),
224-
Subnet: ipnet.String(),
225-
ParentNicMac: parentNicMac,
226-
}
227-
228-
if err := c.releaseSingleIP(ctx, reqURL, singleReq); err != nil {
229-
return fmt.Errorf("failed to release IP %s: %w", ip, err)
230-
}
181+
singleReq := &ReleaseIPRequest{
182+
PodName: req.PodName,
183+
PodNamespace: req.PodNamespace,
184+
PodUID: req.PodUID,
185+
NodeName: req.NodeName,
186+
IPAddress: req.IPAddress,
187+
Subnet: req.Subnet,
188+
ParentNicMac: req.ParentNicMac,
189+
}
190+
191+
if err := c.releaseSingleIP(ctx, reqURL, singleReq); err != nil {
192+
return fmt.Errorf("failed to release IP %s: %w", req.IPAddress, err)
231193
}
232194

233195
c.logger.Info("IaaS release API succeeded",
234196
zap.String("nodeName", req.NodeName),
235-
zap.Strings("ipAddresses", req.IPAddresses),
197+
zap.String("ipAddress", req.IPAddress),
198+
zap.String("subnet", req.Subnet),
199+
zap.String("parentNicMac", req.ParentNicMac),
236200
)
237201

238202
return nil
@@ -293,12 +257,6 @@ func (c *IaaSClient) CacheParentNicMac(key string, mac string) {
293257
c.parentNicMacCache.Store(key, mac)
294258
}
295259

296-
// SetParentNicMacLookupFunc sets a fallback lookup function for parentNicMac
297-
// when cache misses (e.g., after agent restart).
298-
func (c *IaaSClient) SetParentNicMacLookupFunc(fn ParentNicMacLookupFunc) {
299-
c.parentNicMacLookupFunc = fn
300-
}
301-
302260
// Close closes the IaaS client
303261
func (c *IaaSClient) Close() error {
304262
return nil

pkg/iaas/client/types.go

Lines changed: 6 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -54,7 +54,7 @@ type IaaSIPAllocationResult struct {
5454
}
5555

5656
// ReleaseIPRequest represents the request body for IaaS IP release API
57-
type ReleaseIPsRequest struct {
57+
type ReleaseIPRequest struct {
5858
// PodName is optional
5959
PodName string `json:"podName,omitempty"`
6060
// PodNamespace is optional
@@ -63,15 +63,10 @@ type ReleaseIPsRequest struct {
6363
PodUID string `json:"podUID,omitempty"`
6464
// NodeName is required
6565
NodeName string `json:"nodeName"`
66-
// IPAddresses are the IPs being released
67-
IPAddresses []string `json:"ipAddresses"`
68-
}
69-
70-
type ReleaseIPRequest struct {
71-
// NodeName is required
72-
NodeName string `json:"nodeName"`
66+
// ParentNicMac is optional
67+
ParentNicMac string `json:"parentNicMac,omitempty"`
68+
// Subnet is required
69+
Subnet string `json:"subnet"`
7370
// IPAddress is the IP being released
74-
IPAddress string `json:"ipAddress"`
75-
Subnet string `json:"subnet"`
76-
ParentNicMac string `json:"parentNicMac"`
71+
IPAddress string `json:"ipAddress"`
7772
}

pkg/ipam/allocate.go

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -439,9 +439,10 @@ func (i *ipam) allocateInStandardMode(ctx context.Context, addArgs *models.IpamA
439439
if i.config.IaaSClient != nil {
440440
logger.Debug("Calling IaaS provider to allocate IPs", zap.String("nic", *addArgs.IfName))
441441
if _, iaasErr := i.callIaaSAllocate(ctx, pod, results); iaasErr != nil {
442-
logger.Error("IaaS allocate failed, continuing without IaaS allocation", zap.Error(iaasErr))
442+
logger.Error("IaaS allocate failed, aborting IPAM allocation", zap.Error(iaasErr))
443443
return nil, fmt.Errorf("IaaS IP allocate failed: %w", iaasErr)
444444
}
445+
logger.Debug("IaaS allocate succeeded")
445446
}
446447

447448
logger.Debug("Group custom routes by IP allocation results")

0 commit comments

Comments
 (0)