Skip to content

Commit 2e0be67

Browse files
committed
feat: Add InternalIPs to CreateMachineResponse
1 parent 78bb580 commit 2e0be67

6 files changed

Lines changed: 139 additions & 18 deletions

File tree

pkg/client/mock/client.go

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -59,7 +59,9 @@ func (m *StackitClient) GetNICsForServer(ctx context.Context, projectID, region,
5959
if m.GetNICsFunc != nil {
6060
return m.GetNICsFunc(ctx, projectID, region, serverID)
6161
}
62-
return []*client.NIC{}, nil
62+
return []*client.NIC{
63+
{ID: "default-nic-id", NetworkID: "default-network-id"},
64+
}, nil
6365
}
6466

6567
func (m *StackitClient) UpdateNIC(ctx context.Context, projectID, region, networkID, nicID string, allowedAddresses []string) (*client.NIC, error) {

pkg/client/sdk.go

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -339,6 +339,8 @@ func convertSDKNICtoNIC(nic *iaas.NIC) *NIC {
339339
ID: nic.GetId(),
340340
NetworkID: nic.GetNetworkId(),
341341
AllowedAddresses: addresses,
342+
IPv4: nic.GetIpv4(),
343+
IPv6: nic.GetIpv6(),
342344
}
343345
}
344346

pkg/client/sdk_test.go

Lines changed: 60 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,8 @@ import (
88
. "github.com/onsi/ginkgo/v2"
99
. "github.com/onsi/gomega"
1010
"github.com/stackitcloud/stackit-sdk-go/core/oapierror"
11+
"github.com/stackitcloud/stackit-sdk-go/services/iaas"
12+
"k8s.io/utils/ptr"
1113
)
1214

1315
var _ = Describe("SDK Client Helpers", func() {
@@ -203,6 +205,64 @@ var _ = Describe("SDK Type Conversion Helpers", func() {
203205
})
204206
})
205207

208+
Describe("convertSDKNICtoNIC", func() {
209+
It("should populate IPv4 and IPv6 from SDK NIC", func() {
210+
sdkNIC := &iaas.NIC{
211+
Id: ptr.To("nic-1"),
212+
NetworkId: ptr.To("net-1"),
213+
Ipv4: ptr.To("10.0.0.5"),
214+
Ipv6: ptr.To("fd00::1"),
215+
}
216+
217+
result := convertSDKNICtoNIC(sdkNIC)
218+
219+
Expect(result.ID).To(Equal("nic-1"))
220+
Expect(result.NetworkID).To(Equal("net-1"))
221+
Expect(result.IPv4).To(Equal("10.0.0.5"))
222+
Expect(result.IPv6).To(Equal("fd00::1"))
223+
})
224+
225+
It("should handle a NIC with only IPv4", func() {
226+
sdkNIC := &iaas.NIC{
227+
Id: ptr.To("nic-1"),
228+
NetworkId: ptr.To("net-1"),
229+
Ipv4: ptr.To("10.0.0.5"),
230+
}
231+
232+
result := convertSDKNICtoNIC(sdkNIC)
233+
234+
Expect(result.IPv4).To(Equal("10.0.0.5"))
235+
Expect(result.IPv6).To(BeEmpty())
236+
})
237+
238+
It("should handle a NIC with neither IPv4 nor IPv6", func() {
239+
sdkNIC := &iaas.NIC{
240+
Id: ptr.To("nic-1"),
241+
NetworkId: ptr.To("net-1"),
242+
}
243+
244+
result := convertSDKNICtoNIC(sdkNIC)
245+
246+
Expect(result.IPv4).To(BeEmpty())
247+
Expect(result.IPv6).To(BeEmpty())
248+
})
249+
250+
It("should populate AllowedAddresses", func() {
251+
addr := "10.0.0.0/8"
252+
sdkNIC := &iaas.NIC{
253+
Id: ptr.To("nic-1"),
254+
NetworkId: ptr.To("net-1"),
255+
AllowedAddresses: &[]iaas.AllowedAddressesInner{
256+
{String: &addr},
257+
},
258+
}
259+
260+
result := convertSDKNICtoNIC(sdkNIC)
261+
262+
Expect(result.AllowedAddresses).To(ConsistOf("10.0.0.0/8"))
263+
})
264+
})
265+
206266
Describe("NewStackitClient", func() {
207267
Context("with STACKIT_NO_AUTH enabled", func() {
208268
It("should create client successfully without authentication", func() {

pkg/client/stackit.go

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -90,7 +90,9 @@ type Server struct {
9090

9191
// NIC represents a STACKIT network interface
9292
type NIC struct {
93-
ID string
94-
NetworkID string
95-
AllowedAddresses []string
93+
ID string `json:"id"`
94+
NetworkID string `json:"networkId"`
95+
AllowedAddresses []string `json:"allowedAddresses,omitempty"`
96+
IPv4 string `json:"ipv4,omitempty"`
97+
IPv6 string `json:"ipv6,omitempty"`
9698
}

pkg/provider/create.go

Lines changed: 35 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@ import (
1313
"github.com/stackitcloud/machine-controller-manager-provider-stackit/pkg/client"
1414
api "github.com/stackitcloud/machine-controller-manager-provider-stackit/pkg/provider/apis"
1515
"github.com/stackitcloud/machine-controller-manager-provider-stackit/pkg/provider/apis/validation"
16+
corev1 "k8s.io/api/core/v1"
1617
"k8s.io/apimachinery/pkg/util/wait"
1718
"k8s.io/klog/v2"
1819
"k8s.io/utils/ptr"
@@ -27,10 +28,14 @@ import (
2728
// Returns:
2829
// - ProviderID: Unique identifier in format "stackit://<projectId>/<serverId>"
2930
// - NodeName: Name that the VM will register with in Kubernetes (matches Machine name)
31+
// - Addresses: Internal IP addresses of the server's NICs (NodeInternalIP)
3032
//
31-
// Error codes:
32-
// - InvalidArgument: Invalid ProviderSpec or missing required fields
33-
// - Internal: Failed to create server or communicate with STACKIT API
33+
// Error codes (see machine_error_codes.md for retry semantics):
34+
// - InvalidArgument (no retry): Invalid ProviderSpec fields or missing required values
35+
// - Internal (no retry): Malformed ProviderSpec JSON or failed to initialize STACKIT client
36+
// - Unavailable (retry): Transient API failure (create/get server, get NICs, patch NIC)
37+
// - ResourceExhausted (no retry): No capacity available (e.g. "no valid host was found")
38+
// - DeadlineExceeded (retry): Server did not reach ACTIVE state within the polling timeout
3439
func (p *Provider) CreateMachine(ctx context.Context, req *driver.CreateMachineRequest) (*driver.CreateMachineResponse, error) {
3540
// Log messages to track request
3641
klog.V(2).Infof("Machine creation request has been received for %q", req.Machine.Name)
@@ -89,7 +94,18 @@ func (p *Provider) CreateMachine(ctx context.Context, req *driver.CreateMachineR
8994
return nil, status.Error(codes.DeadlineExceeded, fmt.Sprintf("failed waiting for server to be ACTIVE: %v", err))
9095
}
9196

92-
if err := p.patchNetworkInterface(ctx, projectID, server.ID, providerSpec); err != nil {
97+
nics, err := p.client.GetNICsForServer(ctx, projectID, providerSpec.Region, server.ID)
98+
if err != nil {
99+
klog.Errorf("Failed to get NICs for server %q: %v", req.Machine.Name, err)
100+
return nil, status.Error(codes.Unavailable, fmt.Sprintf("failed to get NICs for server: %v", err))
101+
}
102+
103+
if len(nics) == 0 {
104+
klog.Errorf("No NICs found for server %q (ID: %s)", req.Machine.Name, server.ID)
105+
return nil, status.Error(codes.Unavailable, fmt.Sprintf("no NICs found for server %q", server.ID))
106+
}
107+
108+
if err := p.patchNetworkInterface(ctx, projectID, nics, providerSpec); err != nil {
93109
klog.Errorf("Failed to patch network interface for server %q: %v", req.Machine.Name, err)
94110
return nil, status.Error(codes.Unavailable, fmt.Sprintf("failed to patch network interface for server: %v", err))
95111
}
@@ -101,6 +117,7 @@ func (p *Provider) CreateMachine(ctx context.Context, req *driver.CreateMachineR
101117
return &driver.CreateMachineResponse{
102118
ProviderID: providerID,
103119
NodeName: req.Machine.Name,
120+
Addresses: nicAddresses(nics),
104121
}, nil
105122
}
106123

@@ -213,6 +230,19 @@ func (p *Provider) createServerRequest(req *driver.CreateMachineRequest, provide
213230
return createReq
214231
}
215232

233+
func nicAddresses(nics []*client.NIC) []corev1.NodeAddress {
234+
var addresses []corev1.NodeAddress
235+
for _, nic := range nics {
236+
if nic.IPv4 != "" {
237+
addresses = append(addresses, corev1.NodeAddress{Type: corev1.NodeInternalIP, Address: nic.IPv4})
238+
}
239+
if nic.IPv6 != "" {
240+
addresses = append(addresses, corev1.NodeAddress{Type: corev1.NodeInternalIP, Address: nic.IPv6})
241+
}
242+
}
243+
return addresses
244+
}
245+
216246
func (p *Provider) getServerByName(ctx context.Context, projectID, region, serverName string) (*client.Server, error) {
217247
// Check if the server got already created
218248
labelSelector := map[string]string{
@@ -235,20 +265,11 @@ func (p *Provider) getServerByName(ctx context.Context, projectID, region, serve
235265
return nil, nil
236266
}
237267

238-
func (p *Provider) patchNetworkInterface(ctx context.Context, projectID, serverID string, providerSpec *api.ProviderSpec) error {
268+
func (p *Provider) patchNetworkInterface(ctx context.Context, projectID string, nics []*client.NIC, providerSpec *api.ProviderSpec) error {
239269
if len(providerSpec.AllowedAddresses) == 0 {
240270
return nil
241271
}
242272

243-
nics, err := p.client.GetNICsForServer(ctx, projectID, providerSpec.Region, serverID)
244-
if err != nil {
245-
return fmt.Errorf("failed to get NICs for server %q: %w", serverID, err)
246-
}
247-
248-
if len(nics) == 0 {
249-
return fmt.Errorf("failed to find NIC for server %q", serverID)
250-
}
251-
252273
for _, nic := range nics {
253274
// if networking is not set, server is inside the default network
254275
// just patch the interface since the server should only have one

pkg/provider/create_basic_test.go

Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -118,6 +118,24 @@ var _ = Describe("CreateMachine", func() {
118118
Expect(capturedReq.ImageID).To(Equal("12345678-1234-1234-1234-123456789abc"))
119119
})
120120

121+
It("should return internal IPs from NICs in Addresses", func() {
122+
mockClient.GetNICsFunc = func(_ context.Context, _, _, _ string) ([]*client.NIC, error) {
123+
return []*client.NIC{
124+
{ID: "nic-1", NetworkID: "net-1", IPv4: "10.0.0.5", IPv6: "fd00::1"},
125+
{ID: "nic-2", NetworkID: "net-2", IPv4: "10.0.1.5"},
126+
}, nil
127+
}
128+
129+
resp, err := provider.CreateMachine(ctx, req)
130+
131+
Expect(err).NotTo(HaveOccurred())
132+
Expect(resp.Addresses).To(ConsistOf(
133+
corev1.NodeAddress{Type: corev1.NodeInternalIP, Address: "10.0.0.5"},
134+
corev1.NodeAddress{Type: corev1.NodeInternalIP, Address: "fd00::1"},
135+
corev1.NodeAddress{Type: corev1.NodeInternalIP, Address: "10.0.1.5"},
136+
))
137+
})
138+
121139
It("should poll GetServer until server is ACTIVE", func() {
122140
getServerCallCount := 0
123141

@@ -210,6 +228,22 @@ var _ = Describe("CreateMachine", func() {
210228
})
211229
})
212230

231+
Context("when server has no NICs", func() {
232+
It("should return Unavailable error", func() {
233+
mockClient.GetNICsFunc = func(_ context.Context, _, _, _ string) ([]*client.NIC, error) {
234+
return []*client.NIC{}, nil
235+
}
236+
237+
_, err := provider.CreateMachine(ctx, req)
238+
239+
Expect(err).To(HaveOccurred())
240+
statusErr, ok := status.FromError(err)
241+
Expect(ok).To(BeTrue())
242+
Expect(statusErr.Code()).To(Equal(codes.Unavailable))
243+
Expect(err.Error()).To(ContainSubstring("no NICs found"))
244+
})
245+
})
246+
213247
Context("when STACKIT API fails", func() {
214248
It("should return Internal error on API failure", func() {
215249
mockClient.CreateServerFunc = func(_ context.Context, _, _ string, _ *client.CreateServerRequest) (*client.Server, error) {

0 commit comments

Comments
 (0)