Skip to content
Open
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
16 changes: 16 additions & 0 deletions pkg/client/mock/client.go
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,8 @@ type StackitClient struct {
DeleteServerFunc func(ctx context.Context, projectID, region, serverID string) error
ListServersFunc func(ctx context.Context, projectID, region string, labelSelector map[string]string) ([]*client.Server, error)
GetNICsFunc func(ctx context.Context, projectID, region, serverID string) ([]*client.NIC, error)
ListNICsFunc func(ctx context.Context, projectID, region, networkID string) ([]*client.NIC, error)
DeleteNICFunc func(ctx context.Context, projectID, region, networkID, nicID string) error
UpdateNICFunc func(ctx context.Context, projectID, region, networkID, nicID string, allowedAddresses []string) (*client.NIC, error)
}

Expand Down Expand Up @@ -64,6 +66,20 @@ func (m *StackitClient) GetNICsForServer(ctx context.Context, projectID, region,
}, nil
}

func (m *StackitClient) ListNICs(ctx context.Context, projectID, region, networkID string) ([]*client.NIC, error) {
if m.ListNICsFunc != nil {
return m.ListNICsFunc(ctx, projectID, region, networkID)
}
return []*client.NIC{}, nil
}

func (m *StackitClient) DeleteNIC(ctx context.Context, projectID, region, networkID, nicID string) error {
if m.DeleteNICFunc != nil {
return m.DeleteNICFunc(ctx, projectID, region, networkID, nicID)
}
return nil
}

func (m *StackitClient) UpdateNIC(ctx context.Context, projectID, region, networkID, nicID string, allowedAddresses []string) (*client.NIC, error) {
if m.UpdateNICFunc != nil {
return m.UpdateNICFunc(ctx, projectID, region, networkID, nicID, allowedAddresses)
Expand Down
39 changes: 39 additions & 0 deletions pkg/client/sdk.go
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,7 @@ func NewStackitClient(serviceAccountKey string) (*SdkStackitClient, error) {
var (
// ErrServerNotFound indicates the server was not found (404)
ErrServerNotFound = errors.New("server not found")
ErrNicNotFound = errors.New("nic not found")
)

// createIAASClient creates a new STACKIT SDK IAAS API client
Expand Down Expand Up @@ -296,6 +297,36 @@ func (c *SdkStackitClient) GetNICsForServer(ctx context.Context, projectID, regi
return nics, nil
}

func (c *SdkStackitClient) ListNICs(ctx context.Context, projectID, region, networkID string) ([]*NIC, error) {
res, err := c.iaasClient.DefaultAPI.ListNics(ctx, projectID, region, networkID).Execute()
if err != nil {
return nil, fmt.Errorf("SDK ListServerNICs failed: %w", err)
}

if res.Items == nil {
return []*NIC{}, nil
}

nics := make([]*NIC, 0, len(res.Items))
for i := range res.Items {
nics = append(nics, convertSDKNICtoNIC(&res.Items[i]))
}

return nics, nil
}

func (c *SdkStackitClient) DeleteNIC(ctx context.Context, projectID, region, networkID, nicID string) error {
err := c.iaasClient.DefaultAPI.DeleteNic(ctx, projectID, region, networkID, nicID).Execute()
if err != nil {
// Check if error is 404 Not Found - this is OK (idempotent)
if isNotFoundError(err) {
return fmt.Errorf("%w: %v", ErrNicNotFound, err)
}
return fmt.Errorf("SDK DeleteNic failed: %w", err)
}
return nil
}

func (c *SdkStackitClient) UpdateNIC(ctx context.Context, projectID, region, networkID, nicID string, allowedAddresses []string) (*NIC, error) {
addresses := make([]iaas.AllowedAddressesInner, len(allowedAddresses))

Expand Down Expand Up @@ -337,6 +368,7 @@ func convertSDKNICtoNIC(nic *iaas.NIC) *NIC {
AllowedAddresses: addresses,
IPv4: nic.GetIpv4(),
IPv6: nic.GetIpv6(),
Name: getStringValue(nic.Name),
}
}

Expand All @@ -361,3 +393,10 @@ func isNotFoundError(err error) bool {
}
return false
}

func getStringValue(s *string) string {
if s == nil {
return ""
}
return *s
}
5 changes: 5 additions & 0 deletions pkg/client/stackit.go
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,10 @@ type StackitClient interface {
ListServers(ctx context.Context, projectID, region string, labelSelector map[string]string) ([]*Server, error)
// GetNICsForServer retrieves a network interfaces for a given server
GetNICsForServer(ctx context.Context, projectID, region, serverID string) ([]*NIC, error)
// ListNICs list all nics for a network
ListNICs(ctx context.Context, projectID, region, networkID string) ([]*NIC, error)
// DeleteNIC delete a given nic by ID
DeleteNIC(ctx context.Context, projectID, region, networkID, nicID string) error
// UpdateNIC updates a network interface
UpdateNIC(ctx context.Context, projectID, region, networkID, nicID string, allowedAddresses []string) (*NIC, error)
}
Expand Down Expand Up @@ -95,4 +99,5 @@ type NIC struct {
AllowedAddresses []string `json:"allowedAddresses,omitempty"`
IPv4 string `json:"ipv4,omitempty"`
IPv6 string `json:"ipv6,omitempty"`
Name string `json:"name"`
}
2 changes: 2 additions & 0 deletions pkg/provider/core.go
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,8 @@ const (
StackitProviderName = "stackit"
StackitMachineLabel = "kubernetes.io/machine"
StackitMachineClassLabel = "kubernetes.io/machineclass"

migratedMachineAnnotation = "stackit.cloud/migrated-machine"
)

// GetVolumeIDs extracts volume IDs from PersistentVolume specs
Expand Down
40 changes: 25 additions & 15 deletions pkg/provider/create.go
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import (
"fmt"
"maps"
"slices"
"strconv"

"github.com/gardener/machine-controller-manager/pkg/util/provider/driver"
"github.com/gardener/machine-controller-manager/pkg/util/provider/machinecodes/codes"
Expand Down Expand Up @@ -47,6 +48,10 @@ func (p *Provider) CreateMachine(ctx context.Context, req *driver.CreateMachineR
return nil, status.Error(codes.InvalidArgument, err.Error())
}

if m, _ := strconv.ParseBool(req.Machine.Annotations[migratedMachineAnnotation]); m {
return nil, status.Error(codes.AlreadyExists, fmt.Errorf("create for migrated machine %s will not work", req.Machine.Name).Error())
}

// Decode ProviderSpec from MachineClass
providerSpec, err := decodeProviderSpec(req.MachineClass)
if err != nil {
Expand All @@ -68,12 +73,25 @@ func (p *Provider) CreateMachine(ctx context.Context, req *driver.CreateMachineR
}

// check if server already exists
server, err := p.getServerByName(ctx, projectID, providerSpec.Region, req.Machine.Name)
servers, err := p.getServersByName(ctx, projectID, providerSpec.Region, map[string]string{
StackitMachineLabel: req.Machine.Name,
})
if err != nil {
klog.Errorf("Failed to fetch server for machine %q: %v", req.Machine.Name, err)
return nil, status.Error(codes.Unavailable, fmt.Sprintf("failed to fetch server: %v", err))
}

if len(servers) > 1 {
klog.Errorf("Multiple servers already exists for this machine %q: %v", req.Machine.Name, err)
return nil, status.Error(codes.AlreadyExists, fmt.Sprintf("failed to fetch server: %v", err))
}

var server *client.Server

if len(servers) == 1 {
server = servers[0]
}

if server == nil {
// Call STACKIT API to create server
server, err = p.client.CreateServer(ctx, projectID, providerSpec.Region, p.createServerRequest(req, providerSpec))
Expand Down Expand Up @@ -233,26 +251,18 @@ func nicAddresses(nics []*client.NIC) []corev1.NodeAddress {
return addresses
}

func (p *Provider) getServerByName(ctx context.Context, projectID, region, serverName string) (*client.Server, error) {
func (p *Provider) getServersByName(ctx context.Context, projectID, region string, selector map[string]string) ([]*client.Server, error) {
// Check if the server got already created
labelSelector := map[string]string{
StackitMachineLabel: serverName,
}
servers, err := p.client.ListServers(ctx, projectID, region, labelSelector)
servers, err := p.client.ListServers(ctx, projectID, region, selector)
if err != nil {
return nil, fmt.Errorf("SDK ListServers with labelSelector: %v failed: %w", labelSelector, err)
return nil, fmt.Errorf("SDK ListServers with labelSelector: %v failed: %w", selector, err)
}

if len(servers) > 1 {
return nil, fmt.Errorf("%v servers found for server name %v", len(servers), serverName)
}

if len(servers) == 1 {
return servers[0], nil
if len(servers) == 0 {
return nil, nil
}

// no servers found len == 0
return nil, nil
return servers, nil
}

func (p *Provider) patchNetworkInterfaces(ctx context.Context, projectID, serverID string, providerSpec *api.ProviderSpec) ([]*client.NIC, error) {
Expand Down
57 changes: 57 additions & 0 deletions pkg/provider/create_basic_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -85,6 +85,63 @@ var _ = Describe("CreateMachine", func() {
})

Context("with valid inputs", func() {
It("rejects creation of a migrated machine", func() {
machine.Annotations = map[string]string{migratedMachineAnnotation: "true"}
listServersCalled := false
mockClient.ListServersFunc = func(_ context.Context, _, _ string, _ map[string]string) ([]*client.Server, error) {
listServersCalled = true
return nil, nil
}

_, err := provider.CreateMachine(ctx, req)

Expect(err).To(HaveOccurred())
statusErr, ok := status.FromError(err)
Expect(ok).To(BeTrue())
Expect(statusErr.Code()).To(Equal(codes.AlreadyExists))
Expect(listServersCalled).To(BeFalse())
})

It("returns AlreadyExists when more than one server has the machine label", func() {
createServerCalled := false
mockClient.ListServersFunc = func(_ context.Context, projectID, region string, selector map[string]string) ([]*client.Server, error) {
Expect(projectID).To(Equal("11111111-2222-3333-4444-555555555555"))
Expect(region).To(Equal("eu01"))
Expect(selector).To(Equal(map[string]string{StackitMachineLabel: "test-machine"}))
return []*client.Server{{ID: "server-1"}, {ID: "server-2"}}, nil
}
mockClient.CreateServerFunc = func(_ context.Context, _, _ string, _ *client.CreateServerRequest) (*client.Server, error) {
createServerCalled = true
return nil, nil
}

_, err := provider.CreateMachine(ctx, req)

Expect(err).To(HaveOccurred())
statusErr, ok := status.FromError(err)
Expect(ok).To(BeTrue())
Expect(statusErr.Code()).To(Equal(codes.AlreadyExists))
Expect(createServerCalled).To(BeFalse())
})

It("reuses the sole server returned for the machine label", func() {
createServerCalled := false
mockClient.ListServersFunc = func(_ context.Context, _, _ string, selector map[string]string) ([]*client.Server, error) {
Expect(selector).To(Equal(map[string]string{StackitMachineLabel: "test-machine"}))
return []*client.Server{{ID: "existing-server", Name: "test-machine", Status: "ACTIVE"}}, nil
}
mockClient.CreateServerFunc = func(_ context.Context, _, _ string, _ *client.CreateServerRequest) (*client.Server, error) {
createServerCalled = true
return nil, nil
}

resp, err := provider.CreateMachine(ctx, req)

Expect(err).NotTo(HaveOccurred())
Expect(resp.ProviderID).To(Equal("stackit://11111111-2222-3333-4444-555555555555/existing-server"))
Expect(createServerCalled).To(BeFalse())
})

It("should successfully create a machine", func() {
resp, err := provider.CreateMachine(ctx, req)

Expand Down
Loading