Skip to content

Commit cb318e0

Browse files
committed
Implement multi-NIC-same-VPC validation logic to validate that secondary NICs are in the same VPC as the primary NIC.
1 parent eb1181d commit cb318e0

File tree

6 files changed

+187
-34
lines changed

6 files changed

+187
-34
lines changed

cmd/csi_driver/main.go

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -77,9 +77,6 @@ func main() {
7777

7878
ctx, cancel := context.WithCancel(context.Background())
7979
defer cancel()
80-
netlinker := network.NewNetlink()
81-
nodeClient := network.NewK8sClient()
82-
networkIntf := network.Manager(netlinker, nodeClient)
8380

8481
config := &driver.LustreDriverConfig{
8582
Name: driver.DefaultName,
@@ -102,8 +99,12 @@ func main() {
10299
klog.Infof("Metadata service setup: %+v", meta)
103100
config.MetadataService = meta
104101

102+
netlinker := network.NewNetlink()
103+
nodeClient := network.NewK8sClient()
104+
networkIntf := network.Manager(netlinker, nodeClient, meta)
105+
105106
config.Mounter = mount.New("")
106-
nics, err := networkIntf.GetGvnicNames()
107+
nics, err := networkIntf.GetStandardNICs()
107108
if err != nil {
108109
klog.Fatalf("Error getting nic names: %v", err)
109110
}

pkg/cloud_provider/metadata/fake.go

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,9 @@ limitations under the License.
1616

1717
package metadata
1818

19-
type fakeServiceManager struct{}
19+
type fakeServiceManager struct {
20+
GetNetworkInterfacesHook func() ([]NetworkInterface, error)
21+
}
2022

2123
var _ Service = &fakeServiceManager{}
2224

@@ -31,3 +33,10 @@ func (manager *fakeServiceManager) GetZone() string {
3133
func (manager *fakeServiceManager) GetProject() string {
3234
return "test-project"
3335
}
36+
37+
func (manager *fakeServiceManager) GetNetworkInterfaces() ([]NetworkInterface, error) {
38+
if manager.GetNetworkInterfacesHook != nil {
39+
return manager.GetNetworkInterfacesHook()
40+
}
41+
return nil, nil
42+
}

pkg/cloud_provider/metadata/metadata.go

Lines changed: 25 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -18,20 +18,28 @@ package metadata
1818

1919
import (
2020
"context"
21+
"encoding/json"
2122
"fmt"
2223

2324
"cloud.google.com/go/compute/metadata"
2425
)
2526

27+
type NetworkInterface struct {
28+
Network string `json:"network"`
29+
Mac string `json:"mac"`
30+
}
31+
2632
type Service interface {
2733
GetZone() string
2834
GetProject() string
35+
GetNetworkInterfaces() ([]NetworkInterface, error)
2936
}
3037

3138
type metadataServiceManager struct {
3239
// Current zone the driver is running in.
3340
zone string
3441
project string
42+
nics []NetworkInterface
3543
}
3644

3745
var _ Service = &metadataServiceManager{}
@@ -46,9 +54,21 @@ func NewMetadataService(ctx context.Context) (Service, error) {
4654
return nil, fmt.Errorf("failed to get project: %w", err)
4755
}
4856

57+
// Fetch network interfaces recursively
58+
nicsJSON, err := metadata.GetWithContext(ctx, "instance/network-interfaces/?recursive=true")
59+
if err != nil {
60+
return nil, fmt.Errorf("failed to get network interfaces: %w", err)
61+
}
62+
63+
var nics []NetworkInterface
64+
if err := json.Unmarshal([]byte(nicsJSON), &nics); err != nil {
65+
return nil, fmt.Errorf("failed to unmarshal network interfaces: %w", err)
66+
}
67+
4968
return &metadataServiceManager{
50-
project: projectID,
5169
zone: zone,
70+
project: projectID,
71+
nics: nics,
5272
}, nil
5373
}
5474

@@ -59,3 +79,7 @@ func (manager *metadataServiceManager) GetZone() string {
5979
func (manager *metadataServiceManager) GetProject() string {
6080
return manager.project
6181
}
82+
83+
func (manager *metadataServiceManager) GetNetworkInterfaces() ([]NetworkInterface, error) {
84+
return manager.nics, nil
85+
}

pkg/csi_driver/node.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -159,7 +159,7 @@ func (s *nodeServer) NodeStageVolume(_ context.Context, req *csi.NodeStageVolume
159159
if !s.driver.config.DisableMultiNIC {
160160
netlinker := network.NewNetlink()
161161
nodeClient := network.NewK8sClient()
162-
networkIntf := network.Manager(netlinker, nodeClient)
162+
networkIntf := network.Manager(netlinker, nodeClient, s.driver.config.MetadataService)
163163
klog.V(4).Infof("Multi Nic feature is enabled and will configure route for Lustre instance: %v, IP: %v", volumeID, source)
164164
nics := s.driver.config.AdditionalNics
165165
for _, nicName := range nics {

pkg/network/network.go

Lines changed: 71 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,9 @@ import (
2222
"net"
2323
"os"
2424
"strconv"
25+
"strings"
2526

27+
"github.com/GoogleCloudPlatform/lustre-csi-driver/pkg/cloud_provider/metadata"
2628
"github.com/GoogleCloudPlatform/lustre-csi-driver/pkg/k8sclient"
2729
"github.com/safchain/ethtool"
2830
"github.com/vishvananda/netlink"
@@ -47,17 +49,23 @@ type netlinker interface {
4749
RuleAdd(rule *netlink.Rule) error
4850
RuleList(family int) ([]netlink.Rule, error)
4951
RouteListFiltered(family int, filter *netlink.Route, mask uint64) ([]netlink.Route, error)
50-
GetGvnicNames() ([]string, error)
52+
GetStandardNICs() ([]string, error)
5153
}
5254

5355
type nodeClient interface {
5456
GetNodeWithRetry(ctx context.Context, nodeName string) (*v1.Node, error)
5557
}
5658

59+
// MetadataClient abstracts the metadata service.
60+
type MetadataClient interface {
61+
GetNetworkInterfaces() ([]metadata.NetworkInterface, error)
62+
}
63+
5764
// RouteManager provides methods for network configuration using the netlinker interface.
5865
type RouteManager struct {
5966
nl netlinker
6067
nc nodeClient
68+
mc MetadataClient
6169
}
6270

6371
// realNetlink is the real implementation of netlinker,
@@ -89,7 +97,7 @@ func (r *realNetlink) RouteListFiltered(family int, filter *netlink.Route, mask
8997
return netlink.RouteListFiltered(family, filter, mask)
9098
}
9199

92-
func (r *realNetlink) GetGvnicNames() ([]string, error) {
100+
func (r *realNetlink) GetStandardNICs() ([]string, error) {
93101
links, err := netlink.LinkList()
94102
if err != nil {
95103
return nil, fmt.Errorf("failed to list network interfaces: %w", err)
@@ -136,13 +144,69 @@ func NewK8sClient() nodeClient {
136144
return &k8sNodeClient{}
137145
}
138146

139-
func Manager(nl netlinker, nc nodeClient) *RouteManager {
140-
return &RouteManager{nl: nl, nc: nc}
147+
func Manager(nl netlinker, nc nodeClient, mc MetadataClient) *RouteManager {
148+
return &RouteManager{nl: nl, nc: nc, mc: mc}
141149
}
142150

143-
// GetGvnicNames gets all available nics on the node.
144-
func (rm *RouteManager) GetGvnicNames() ([]string, error) {
145-
return rm.nl.GetGvnicNames()
151+
// GetStandardNICs gets all standard NICs on the node which are within the same VPC network
152+
// as the GCE instance primary network.
153+
func (rm *RouteManager) GetStandardNICs() ([]string, error) {
154+
nics, err := rm.nl.GetStandardNICs()
155+
if err != nil {
156+
return nil, err
157+
}
158+
159+
// If we have 0 or 1 NIC, no need to validate VPC as single NIC is always valid (primary).
160+
// Actually, if 1 NIC, it must be primary.
161+
if len(nics) <= 1 {
162+
return nics, nil
163+
}
164+
165+
if rm.mc == nil {
166+
klog.Warning("Metadata client is nil, skipping VPC validation")
167+
return nics, nil
168+
}
169+
170+
// Get network interfaces from metadata (cached).
171+
metaNics, err := rm.mc.GetNetworkInterfaces()
172+
if err != nil {
173+
return nil, fmt.Errorf("failed to get network interfaces from metadata: %w", err)
174+
}
175+
176+
if len(metaNics) == 0 {
177+
klog.Warning("No network interfaces found in metadata, skipping VPC validation")
178+
return nics, nil
179+
}
180+
181+
// The first network interface in the metadata list corresponds to the primary NIC (index 0).
182+
primaryNetwork := metaNics[0].Network
183+
184+
// Iterate over all discovered NICs and validate them against the metadata.
185+
// We match NICs by MAC address and ensure they belong to the same VPC network
186+
// as the primary interface.
187+
validNics := []string{}
188+
for _, nic := range nics {
189+
link, err := rm.nl.LinkByName(nic)
190+
if err != nil {
191+
klog.Warningf("Failed to get link for %s: %v", nic, err)
192+
continue
193+
}
194+
mac := link.Attrs().HardwareAddr.String()
195+
196+
for _, metaNic := range metaNics {
197+
if strings.EqualFold(metaNic.Mac, mac) {
198+
if metaNic.Network == primaryNetwork {
199+
validNics = append(validNics, nic)
200+
} else {
201+
klog.Infof("Skipping nic %s because it is in a different VPC network: %s (primary: %s)", nic, metaNic.Network, primaryNetwork)
202+
}
203+
break
204+
}
205+
}
206+
}
207+
208+
klog.Infof("Filtered NICs: %v (original: %v)", validNics, nics)
209+
return validNics, nil
146210
}
147211

148212
func (rm *RouteManager) configureRoutesForNic(nicName string, nicIPAddr net.IP, instanceIPAddr string, tableID int) error {

0 commit comments

Comments
 (0)