Skip to content

Commit 9a6b3bc

Browse files
authored
Merge pull request #431 from linode/add-ipv6-slaac-addrs-to-status
[improvement] : Add ipv6 slaac addrs to node's status
2 parents 1387ae6 + 8f042b7 commit 9a6b3bc

File tree

9 files changed

+144
-38
lines changed

9 files changed

+144
-38
lines changed

cloud/linode/client/client.go

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -39,6 +39,7 @@ type Client interface {
3939
GetVPCSubnet(context.Context, int, int) (*linodego.VPCSubnet, error)
4040
ListVPCs(context.Context, *linodego.ListOptions) ([]linodego.VPC, error)
4141
ListVPCIPAddresses(context.Context, int, *linodego.ListOptions) ([]linodego.VPCIP, error)
42+
ListVPCIPv6Addresses(context.Context, int, *linodego.ListOptions) ([]linodego.VPCIP, error)
4243
ListVPCSubnets(context.Context, int, *linodego.ListOptions) ([]linodego.VPCSubnet, error)
4344

4445
CreateNodeBalancer(context.Context, linodego.NodeBalancerCreateOptions) (*linodego.NodeBalancer, error)

cloud/linode/client/client_with_metrics.go

Lines changed: 13 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

cloud/linode/client/mocks/mock_client.go

Lines changed: 15 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

cloud/linode/instances.go

Lines changed: 38 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -39,12 +39,22 @@ type nodeCache struct {
3939
}
4040

4141
// getInstanceAddresses returns all addresses configured on a linode.
42-
func (nc *nodeCache) getInstanceAddresses(instance linodego.Instance, vpcips []string) []nodeIP {
42+
func (nc *nodeCache) getInstanceAddresses(instance linodego.Instance, vpcips []string, vpcIPv6AddrTypes map[string]v1.NodeAddressType) []nodeIP {
4343
ips := []nodeIP{}
4444

45+
// We store vpc IPv6 addrs separately so that we can list them after IPv4 addresses.
46+
// Ordering matters in k8s, first address marked as externalIP will be used as external IP for node.
47+
// We prefer to use IPv4 address as external IP if possible, so we list them first.
48+
vpcIPv6Addrs := []nodeIP{}
49+
4550
// If vpc ips are present, list them first
4651
for _, ip := range vpcips {
4752
ipType := v1.NodeInternalIP
53+
if _, ok := vpcIPv6AddrTypes[ip]; ok {
54+
ipType = vpcIPv6AddrTypes[ip]
55+
vpcIPv6Addrs = append(vpcIPv6Addrs, nodeIP{ip: ip, ipType: ipType})
56+
continue
57+
}
4858
ips = append(ips, nodeIP{ip: ip, ipType: ipType})
4959
}
5060

@@ -56,6 +66,9 @@ func (nc *nodeCache) getInstanceAddresses(instance linodego.Instance, vpcips []s
5666
ips = append(ips, nodeIP{ip: ip.String(), ipType: ipType})
5767
}
5868

69+
// Add vpc IPv6 addresses after IPv4 addresses
70+
ips = append(ips, vpcIPv6Addrs...)
71+
5972
if instance.IPv6 != "" {
6073
ips = append(ips, nodeIP{ip: strings.TrimSuffix(instance.IPv6, "/128"), ipType: v1.NodeExternalIP})
6174
}
@@ -80,6 +93,7 @@ func (nc *nodeCache) refreshInstances(ctx context.Context, client client.Client)
8093

8194
// If running within VPC, find instances and store their ips
8295
vpcNodes := map[int][]string{}
96+
vpcIPv6AddrTypes := map[string]v1.NodeAddressType{}
8397
for _, name := range Options.VPCNames {
8498
vpcName := strings.TrimSpace(name)
8599
if vpcName == "" {
@@ -90,11 +104,30 @@ func (nc *nodeCache) refreshInstances(ctx context.Context, client client.Client)
90104
klog.Errorf("failed updating instances cache for VPC %s. Error: %s", vpcName, err.Error())
91105
continue
92106
}
93-
for _, r := range resp {
94-
if r.Address == nil {
107+
for _, vpcip := range resp {
108+
if vpcip.Address == nil {
109+
continue
110+
}
111+
vpcNodes[vpcip.LinodeID] = append(vpcNodes[vpcip.LinodeID], *vpcip.Address)
112+
}
113+
114+
resp, err = GetVPCIPv6Addresses(ctx, client, vpcName)
115+
if err != nil {
116+
klog.Errorf("failed updating instances cache for VPC %s. Error: %s", vpcName, err.Error())
117+
continue
118+
}
119+
for _, vpcip := range resp {
120+
if len(vpcip.IPv6Addresses) == 0 {
95121
continue
96122
}
97-
vpcNodes[r.LinodeID] = append(vpcNodes[r.LinodeID], *r.Address)
123+
vpcIPv6AddrType := v1.NodeInternalIP
124+
if vpcip.IPv6IsPublic != nil && *vpcip.IPv6IsPublic {
125+
vpcIPv6AddrType = v1.NodeExternalIP
126+
}
127+
for _, ipv6 := range vpcip.IPv6Addresses {
128+
vpcNodes[vpcip.LinodeID] = append(vpcNodes[vpcip.LinodeID], ipv6.SLAACAddress)
129+
vpcIPv6AddrTypes[ipv6.SLAACAddress] = vpcIPv6AddrType
130+
}
98131
}
99132
}
100133

@@ -106,7 +139,7 @@ func (nc *nodeCache) refreshInstances(ctx context.Context, client client.Client)
106139
}
107140
node := linodeInstance{
108141
instance: &instances[index],
109-
ips: nc.getInstanceAddresses(instance, vpcNodes[instance.ID]),
142+
ips: nc.getInstanceAddresses(instance, vpcNodes[instance.ID], vpcIPv6AddrTypes),
110143
}
111144
newNodes[instance.ID] = node
112145
}

cloud/linode/instances_test.go

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -200,6 +200,7 @@ func TestMetadataRetrieval(t *testing.T) {
200200

201201
client.EXPECT().ListInstances(gomock.Any(), nil).Times(1).Return([]linodego.Instance{instance}, nil)
202202
client.EXPECT().ListVPCIPAddresses(gomock.Any(), vpcIDs["test"], gomock.Any()).Return(routesInVPC, nil)
203+
client.EXPECT().ListVPCIPv6Addresses(gomock.Any(), vpcIDs["test"], gomock.Any()).Return([]linodego.VPCIP{}, nil)
203204

204205
meta, err := instances.InstanceMetadata(ctx, node)
205206
require.NoError(t, err)

cloud/linode/route_controller_test.go

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -52,6 +52,7 @@ func TestListRoutes(t *testing.T) {
5252

5353
client.EXPECT().ListInstances(gomock.Any(), gomock.Any()).Times(1).Return([]linodego.Instance{}, nil)
5454
client.EXPECT().ListVPCIPAddresses(gomock.Any(), gomock.Any(), gomock.Any()).Times(2).Return([]linodego.VPCIP{}, nil)
55+
client.EXPECT().ListVPCIPv6Addresses(gomock.Any(), gomock.Any(), gomock.Any()).AnyTimes().Return([]linodego.VPCIP{}, nil)
5556
routes, err := routeController.ListRoutes(ctx, "test")
5657
require.NoError(t, err)
5758
assert.Empty(t, routes)
@@ -76,6 +77,7 @@ func TestListRoutes(t *testing.T) {
7677

7778
client.EXPECT().ListInstances(gomock.Any(), nil).Times(1).Return([]linodego.Instance{validInstance}, nil)
7879
client.EXPECT().ListVPCIPAddresses(gomock.Any(), gomock.Any(), gomock.Any()).Times(2).Return([]linodego.VPCIP{}, nil)
80+
client.EXPECT().ListVPCIPv6Addresses(gomock.Any(), gomock.Any(), gomock.Any()).AnyTimes().Return([]linodego.VPCIP{}, nil)
7981
routes, err := routeController.ListRoutes(ctx, "test")
8082
require.NoError(t, err)
8183
assert.Empty(t, routes)
@@ -103,6 +105,7 @@ func TestListRoutes(t *testing.T) {
103105

104106
client.EXPECT().ListInstances(gomock.Any(), nil).Times(1).Return([]linodego.Instance{validInstance}, nil)
105107
client.EXPECT().ListVPCIPAddresses(gomock.Any(), gomock.Any(), gomock.Any()).Times(4).Return(noRoutesInVPC, nil)
108+
client.EXPECT().ListVPCIPv6Addresses(gomock.Any(), gomock.Any(), gomock.Any()).AnyTimes().Return([]linodego.VPCIP{}, nil)
106109
routes, err := routeController.ListRoutes(ctx, "test")
107110
require.NoError(t, err)
108111
assert.Empty(t, routes)
@@ -151,6 +154,7 @@ func TestListRoutes(t *testing.T) {
151154

152155
client.EXPECT().ListInstances(gomock.Any(), nil).Times(1).Return([]linodego.Instance{validInstance}, nil)
153156
client.EXPECT().ListVPCIPAddresses(gomock.Any(), gomock.Any(), gomock.Any()).Times(4).Return(routesInVPC, nil)
157+
client.EXPECT().ListVPCIPv6Addresses(gomock.Any(), gomock.Any(), gomock.Any()).AnyTimes().Return([]linodego.VPCIP{}, nil)
154158
routes, err := routeController.ListRoutes(ctx, "test")
155159
require.NoError(t, err)
156160
assert.NotEmpty(t, routes)
@@ -199,6 +203,7 @@ func TestListRoutes(t *testing.T) {
199203

200204
client.EXPECT().ListInstances(gomock.Any(), nil).Times(1).Return([]linodego.Instance{validInstance}, nil)
201205
client.EXPECT().ListVPCIPAddresses(gomock.Any(), gomock.Any(), gomock.Any()).Times(4).Return(routesInDifferentVPC, nil)
206+
client.EXPECT().ListVPCIPv6Addresses(gomock.Any(), gomock.Any(), gomock.Any()).AnyTimes().Return([]linodego.VPCIP{}, nil)
202207
routes, err := routeController.ListRoutes(ctx, "test")
203208
require.NoError(t, err)
204209
assert.Empty(t, routes)
@@ -297,6 +302,7 @@ func TestListRoutes(t *testing.T) {
297302
c2 := client.EXPECT().ListVPCIPAddresses(gomock.Any(), gomock.Any(), gomock.Any()).After(c1).Times(1).Return(routesInVPC2, nil)
298303
c3 := client.EXPECT().ListVPCIPAddresses(gomock.Any(), gomock.Any(), gomock.Any()).After(c2).Times(1).Return(routesInVPC, nil)
299304
client.EXPECT().ListVPCIPAddresses(gomock.Any(), gomock.Any(), gomock.Any()).After(c3).Times(1).Return(routesInVPC2, nil)
305+
client.EXPECT().ListVPCIPv6Addresses(gomock.Any(), gomock.Any(), gomock.Any()).AnyTimes().Return([]linodego.VPCIP{}, nil)
300306
routes, err := routeController.ListRoutes(ctx, "test")
301307
require.NoError(t, err)
302308
assert.NotEmpty(t, routes)
@@ -378,6 +384,7 @@ func TestCreateRoute(t *testing.T) {
378384

379385
client.EXPECT().ListInstances(gomock.Any(), nil).Times(1).Return([]linodego.Instance{validInstance}, nil)
380386
client.EXPECT().ListVPCIPAddresses(gomock.Any(), gomock.Any(), gomock.Any()).Times(2).Return(noRoutesInVPC, nil)
387+
client.EXPECT().ListVPCIPv6Addresses(gomock.Any(), gomock.Any(), gomock.Any()).AnyTimes().Return([]linodego.VPCIP{}, nil)
381388
client.EXPECT().UpdateInstanceConfigInterface(gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any()).Times(1).Return(&instanceConfigIntfWithVPCAndRoute, nil)
382389
err = routeController.CreateRoute(ctx, "dummy", "dummy", route)
383390
assert.NoError(t, err)
@@ -445,6 +452,7 @@ func TestCreateRoute(t *testing.T) {
445452

446453
client.EXPECT().ListInstances(gomock.Any(), nil).Times(1).Return([]linodego.Instance{validInstance}, nil)
447454
client.EXPECT().ListVPCIPAddresses(gomock.Any(), gomock.Any(), gomock.Any()).Times(2).Return(routesInVPC, nil)
455+
client.EXPECT().ListVPCIPv6Addresses(gomock.Any(), gomock.Any(), gomock.Any()).AnyTimes().Return([]linodego.VPCIP{}, nil)
448456
err = routeController.CreateRoute(ctx, "dummy", "dummy", route)
449457
assert.NoError(t, err)
450458
})
@@ -459,6 +467,7 @@ func TestCreateRoute(t *testing.T) {
459467

460468
client.EXPECT().ListInstances(gomock.Any(), nil).Times(1).Return([]linodego.Instance{}, nil)
461469
client.EXPECT().ListVPCIPAddresses(gomock.Any(), gomock.Any(), gomock.Any()).Times(1).Return([]linodego.VPCIP{}, nil)
470+
client.EXPECT().ListVPCIPv6Addresses(gomock.Any(), gomock.Any(), gomock.Any()).AnyTimes().Return([]linodego.VPCIP{}, nil)
462471
err = routeController.CreateRoute(ctx, "dummy", "dummy", route)
463472
assert.Error(t, err)
464473
})
@@ -502,6 +511,7 @@ func TestDeleteRoute(t *testing.T) {
502511

503512
client.EXPECT().ListInstances(gomock.Any(), nil).Times(1).Return([]linodego.Instance{}, nil)
504513
client.EXPECT().ListVPCIPAddresses(gomock.Any(), gomock.Any(), gomock.Any()).Times(1).Return([]linodego.VPCIP{}, nil)
514+
client.EXPECT().ListVPCIPv6Addresses(gomock.Any(), gomock.Any(), gomock.Any()).AnyTimes().Return([]linodego.VPCIP{}, nil)
505515
err = routeController.DeleteRoute(ctx, "dummy", route)
506516
assert.Error(t, err)
507517
})
@@ -533,6 +543,7 @@ func TestDeleteRoute(t *testing.T) {
533543

534544
client.EXPECT().ListInstances(gomock.Any(), nil).Times(1).Return([]linodego.Instance{validInstance}, nil)
535545
client.EXPECT().ListVPCIPAddresses(gomock.Any(), gomock.Any(), gomock.Any()).Times(2).Return(noRoutesInVPC, nil)
546+
client.EXPECT().ListVPCIPv6Addresses(gomock.Any(), gomock.Any(), gomock.Any()).AnyTimes().Return([]linodego.VPCIP{}, nil)
536547
client.EXPECT().UpdateInstanceConfigInterface(gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any()).Times(1).Return(&instanceConfigIntfWithVPCAndNoRoute, nil)
537548
err = routeController.DeleteRoute(ctx, "dummy", route)
538549
assert.NoError(t, err)
@@ -565,6 +576,7 @@ func TestDeleteRoute(t *testing.T) {
565576

566577
client.EXPECT().ListInstances(gomock.Any(), nil).Times(1).Return([]linodego.Instance{validInstance}, nil)
567578
client.EXPECT().ListVPCIPAddresses(gomock.Any(), gomock.Any(), gomock.Any()).Times(2).Return(routesInVPC, nil)
579+
client.EXPECT().ListVPCIPv6Addresses(gomock.Any(), gomock.Any(), gomock.Any()).AnyTimes().Return([]linodego.VPCIP{}, nil)
568580
client.EXPECT().UpdateInstanceConfigInterface(gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any()).Times(1).Return(&instanceConfigIntfWithVPCAndNoRoute, nil)
569581
err = routeController.DeleteRoute(ctx, "dummy", route)
570582
assert.NoError(t, err)

cloud/linode/vpc.go

Lines changed: 39 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -100,11 +100,11 @@ func GetSubnetID(ctx context.Context, client client.Client, vpcID int, subnetNam
100100
return 0, subnetLookupError{subnetName}
101101
}
102102

103-
// GetVPCIPAddresses returns vpc ip's for given VPC label
104-
func GetVPCIPAddresses(ctx context.Context, client client.Client, vpcName string) ([]linodego.VPCIP, error) {
103+
// getVPCIDAndFilter returns the VPC ID and a resultFilter for subnet names (if any)
104+
func getVPCIDAndFilter(ctx context.Context, client client.Client, vpcName string) (int, string, error) {
105105
vpcID, err := GetVPCID(ctx, client, strings.TrimSpace(vpcName))
106106
if err != nil {
107-
return nil, err
107+
return 0, "", err
108108
}
109109

110110
resultFilter := ""
@@ -133,20 +133,49 @@ func GetVPCIPAddresses(ctx context.Context, client client.Client, vpcName string
133133
filter, err = json.Marshal(subnetFilter{SubnetID: strings.Join(subnetIDList, ",")})
134134
if err != nil {
135135
klog.Error("could not create JSON filter for subnet_id")
136+
} else {
137+
resultFilter = string(filter)
136138
}
137-
resultFilter = string(filter)
139+
}
140+
141+
return vpcID, resultFilter, nil
142+
}
143+
144+
// handleNotFoundError checks if the error is a '404 Not Found error' and deletes the entry from the cache.
145+
func handleNotFoundError(err error, vpcName string) error {
146+
if linodego.ErrHasStatus(err, http.StatusNotFound) {
147+
Mu.Lock()
148+
defer Mu.Unlock()
149+
klog.Errorf("vpc %s not found. Deleting entry from cache", vpcName)
150+
delete(vpcIDs, vpcName)
151+
}
152+
return err
153+
}
154+
155+
// GetVPCIPAddresses returns vpc ip's for given VPC label
156+
func GetVPCIPAddresses(ctx context.Context, client client.Client, vpcName string) ([]linodego.VPCIP, error) {
157+
vpcID, resultFilter, err := getVPCIDAndFilter(ctx, client, vpcName)
158+
if err != nil {
159+
return nil, err
138160
}
139161

140162
resp, err := client.ListVPCIPAddresses(ctx, vpcID, linodego.NewListOptions(0, resultFilter))
141163
if err != nil {
142-
if linodego.ErrHasStatus(err, http.StatusNotFound) {
143-
Mu.Lock()
144-
defer Mu.Unlock()
145-
klog.Errorf("vpc %s not found. Deleting entry from cache", vpcName)
146-
delete(vpcIDs, vpcName)
147-
}
164+
return nil, handleNotFoundError(err, vpcName)
165+
}
166+
return resp, nil
167+
}
168+
169+
func GetVPCIPv6Addresses(ctx context.Context, client client.Client, vpcName string) ([]linodego.VPCIP, error) {
170+
vpcID, resultFilter, err := getVPCIDAndFilter(ctx, client, vpcName)
171+
if err != nil {
148172
return nil, err
149173
}
174+
175+
resp, err := client.ListVPCIPv6Addresses(ctx, vpcID, linodego.NewListOptions(0, resultFilter))
176+
if err != nil {
177+
return nil, handleNotFoundError(err, vpcName)
178+
}
150179
return resp, nil
151180
}
152181

go.mod

Lines changed: 9 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -141,16 +141,16 @@ require (
141141
go.uber.org/multierr v1.11.0 // indirect
142142
go.uber.org/zap v1.27.0 // indirect
143143
go4.org/netipx v0.0.0-20231129151722-fdeea329fbba // indirect
144-
golang.org/x/crypto v0.39.0 // indirect
144+
golang.org/x/crypto v0.40.0 // indirect
145145
golang.org/x/mod v0.25.0 // indirect
146-
golang.org/x/net v0.41.0 // indirect
146+
golang.org/x/net v0.42.0 // indirect
147147
golang.org/x/oauth2 v0.30.0 // indirect
148-
golang.org/x/sync v0.15.0 // indirect
149-
golang.org/x/sys v0.33.0 // indirect
150-
golang.org/x/term v0.32.0 // indirect
151-
golang.org/x/text v0.26.0 // indirect
148+
golang.org/x/sync v0.16.0 // indirect
149+
golang.org/x/sys v0.34.0 // indirect
150+
golang.org/x/term v0.33.0 // indirect
151+
golang.org/x/text v0.27.0 // indirect
152152
golang.org/x/time v0.9.0 // indirect
153-
golang.org/x/tools v0.33.0 // indirect
153+
golang.org/x/tools v0.34.0 // indirect
154154
google.golang.org/genproto/googleapis/api v0.0.0-20250519155744-55703ea1f237 // indirect
155155
google.golang.org/genproto/googleapis/rpc v0.0.0-20250519155744-55703ea1f237 // indirect
156156
google.golang.org/grpc v1.72.1 // indirect
@@ -193,3 +193,5 @@ replace (
193193
k8s.io/pod-security-admission => k8s.io/pod-security-admission v0.33.0
194194
k8s.io/sample-apiserver => k8s.io/sample-apiserver v0.33.0
195195
)
196+
197+
replace github.com/linode/linodego => github.com/rahulait/linodego v1.50.1-0.20250805021045-93a6855d693c

0 commit comments

Comments
 (0)