Skip to content

Commit

Permalink
Merge pull request #47 from GDATASoftwareAG/fix-failover-behavior
Browse files Browse the repository at this point in the history
fix failover behavior
  • Loading branch information
farodin91 authored Nov 29, 2023
2 parents 1208e39 + 6524f3a commit 0589b57
Show file tree
Hide file tree
Showing 3 changed files with 162 additions and 90 deletions.
130 changes: 117 additions & 13 deletions internal/controller/ionoscloudmachine_controller.go
Original file line number Diff line number Diff line change
Expand Up @@ -20,11 +20,14 @@ import (
goctx "context"
b64 "encoding/base64"
"fmt"
"go.uber.org/multierr"
"net/http"
"strings"
"time"

"github.com/GDATASoftwareAG/cluster-api-provider-ionoscloud/internal/ionos"

"go.uber.org/multierr"

v1alpha1 "github.com/GDATASoftwareAG/cluster-api-provider-ionoscloud/api/v1alpha1"
"github.com/GDATASoftwareAG/cluster-api-provider-ionoscloud/internal/context"
"github.com/GDATASoftwareAG/cluster-api-provider-ionoscloud/internal/utils"
Expand All @@ -47,6 +50,8 @@ import (
"sigs.k8s.io/controller-runtime/pkg/reconcile"
)

const csiVolumePrefix = "csi-pv."

var defaultMachineRetryIntervalOnBusy = time.Second * 30

// IONOSCloudMachineReconciler reconciles a IONOSCloudMachine object
Expand All @@ -67,7 +72,7 @@ type IONOSCloudMachineReconciler struct {
// - https://pkg.go.dev/sigs.k8s.io/[email protected]/pkg/reconcile
func (r *IONOSCloudMachineReconciler) Reconcile(ctx goctx.Context, req ctrl.Request) (_ ctrl.Result, reterr error) {
logger := r.Logger.WithName(req.Namespace).WithName(req.Name)
logger.Info("Starting Reconcile ionoscloudMachine")
logger.Info("Starting Reconcile IONOSCloudMachine")

// Fetch the ionosCloudMachine instance
ionoscloudMachine := &v1alpha1.IONOSCloudMachine{}
Expand Down Expand Up @@ -180,13 +185,24 @@ func (r *IONOSCloudMachineReconciler) reconcileDelete(ctx *context.MachineContex
}

for _, volume := range *server.Entities.Volumes.Items {
if strings.HasPrefix(*volume.Properties.Name, csiVolumePrefix) { //ignore csi volumes
continue
}
_, err = ctx.IONOSClient.DeleteVolume(ctx, ctx.IONOSCloudCluster.Spec.DataCenterID, *volume.Id)
if err != nil {
return reconcile.Result{}, err
}
}
}

// ensure server is deleted
_, resp, err = ctx.IONOSClient.GetServer(ctx, ctx.IONOSCloudCluster.Spec.DataCenterID, ctx.IONOSCloudMachine.Spec.ProviderID)
if resp.StatusCode == http.StatusOK {
return reconcile.Result{RequeueAfter: defaultMachineRetryIntervalOnBusy}, nil
} else if resp.StatusCode != http.StatusNotFound {
return reconcile.Result{}, err
}

if err = r.reconcileDeleteLoadBalancerForwardingRule(ctx); err != nil {
return reconcile.Result{}, err
}
Expand Down Expand Up @@ -255,19 +271,23 @@ func (r *IONOSCloudMachineReconciler) reconcileDeleteLoadBalancerForwardingRule(
}

func (r *IONOSCloudMachineReconciler) reconcileNormal(ctx *context.MachineContext) (reconcile.Result, error) {
ctx.Logger.Info("Reconciling IONOSCloudCluster")
ctx.Logger.Info("Reconciling IONOSCloudMachine")

// If the IONOSCloudMachine doesn't have our finalizer, add it.
ctrlutil.AddFinalizer(ctx.IONOSCloudMachine, v1alpha1.MachineFinalizer)

if result, err := r.reconcileServer(ctx); err != nil {
conditions.MarkFalse(ctx.IONOSCloudMachine, v1alpha1.ServerCreatedCondition, v1alpha1.ServerCreationFailedReason, clusterv1.ConditionSeverityError, err.Error())
return *result, err
return *result, errors.Wrap(err, "failed reconcileServer")
} else if result != nil {
return *result, nil
}

if result, err := r.reconcileLoadBalancerForwardingRule(ctx); err != nil {
conditions.MarkFalse(ctx.IONOSCloudMachine, v1alpha1.LoadBalancerForwardingRuleCreatedCondition, v1alpha1.LoadBalancerForwardingRuleCreationFailedReason, clusterv1.ConditionSeverityError, err.Error())
return *result, err
return *result, errors.Wrap(err, "failed reconcileLoadBalancerForwardingRule")
} else if result != nil {
return *result, nil
}

server, _, err := ctx.IONOSClient.GetServer(ctx, ctx.IONOSCloudCluster.Spec.DataCenterID, ctx.IONOSCloudMachine.Spec.ProviderID)
Expand Down Expand Up @@ -431,7 +451,7 @@ func (r *IONOSCloudMachineReconciler) reconcileServer(ctx *context.MachineContex
}

func (r *IONOSCloudMachineReconciler) reconcileFailoverGroups(ctx *context.MachineContext, server ionoscloud.Server) error {
var errors error
var multiErr error
for i := range ctx.IONOSCloudCluster.Spec.Lans {
lanSpec := &ctx.IONOSCloudCluster.Spec.Lans[i]
serverNic := serverNicByLan(server, lanSpec)
Expand All @@ -443,23 +463,22 @@ func (r *IONOSCloudMachineReconciler) reconcileFailoverGroups(ctx *context.Machi
ctx.Logger.Info("Reconciling failover group " + group.ID)
block, _, err := ctx.IONOSClient.GetIPBlock(ctx, group.ID)
if err != nil {
errors = multierr.Append(errors, err)
multiErr = multierr.Append(multiErr, err)
continue
}
ips := *block.Properties.Ips
err = ctx.IONOSClient.EnsureAdditionalIPsOnNic(ctx, ctx.IONOSCloudCluster.Spec.DataCenterID, ctx.IONOSCloudMachine.Spec.ProviderID, *serverNic.Id, ips)
err = r.reconcileNicsWithAdditionalIPs(ctx, *serverNic, ctx.IONOSCloudCluster.Spec.DataCenterID, ctx.IONOSCloudMachine.Spec.ProviderID, ips)
if err != nil {
errors = multierr.Append(errors, err)
multiErr = multierr.Append(multiErr, err)
}
//TODO: only once per cluster and change if machine gets delete
lanId := fmt.Sprint(*lanSpec.LanID)
err = ctx.IONOSClient.EnsureFailoverIPsOnLan(ctx, ctx.IONOSCloudCluster.Spec.DataCenterID, lanId, *serverNic.Id, ips)
err = r.reconcileLanWithAdditionalIPFailover(ctx, ctx.IONOSCloudCluster.Spec.DataCenterID, lanId, ips)
if err != nil {
errors = multierr.Append(errors, err)
multiErr = multierr.Append(multiErr, err)
}
}
}
return errors
return multiErr
}

func (r *IONOSCloudMachineReconciler) reconcileLoadBalancerForwardingRule(ctx *context.MachineContext) (*reconcile.Result, error) {
Expand Down Expand Up @@ -537,6 +556,91 @@ func (r *IONOSCloudMachineReconciler) SetupWithManager(mgr ctrl.Manager) error {
Complete(r)
}

func (r *IONOSCloudMachineReconciler) reconcileNicsWithAdditionalIPs(ctx *context.MachineContext, nic ionoscloud.Nic, datacenterId, serverId string, toEnsureIPs []string) error {
requirePatch := false
ips := make([]string, 0)
if nic.Properties.Ips != nil {
ips = *nic.Properties.Ips
}
for _, ip := range toEnsureIPs {
toAdd := true
for i := range ips {
if ips[i] == ip {
toAdd = false
break
}
}
if toAdd {
requirePatch = true
ips = append(ips, ip)
}
}
if requirePatch {
return ctx.IONOSClient.PatchServerNicsWithIPs(ctx, datacenterId, serverId, *nic.Id, ips)
}
return nil
}

func (r *IONOSCloudMachineReconciler) reconcileLanWithAdditionalIPFailover(ctx *context.MachineContext, datacenterId, lanId string, toEnsureIPs []string) error {
reqCtx := goctx.WithValue(ctx.Context, ionos.DepthKey, int32(2))
lan, _, err := ctx.IONOSClient.GetLan(reqCtx, datacenterId, lanId)
if err != nil {
return err
}
nicUuid := ""
nics := *lan.Entities.Nics.Items
for i := range nics {
nic := &nics[i]
if nic.Properties.Ips == nil {
continue
}
hasIps := 0
for k := range toEnsureIPs {
for _, s := range *nic.Properties.Ips {
if s == toEnsureIPs[k] {
hasIps += 1
break
}
}
}
if len(toEnsureIPs) == hasIps {
nicUuid = *nic.Id
break
}
}
if nicUuid == "" {
return errors.New("no nic found with all ips")
}
ctx.Logger.Info("nic with all ips", "nicUuid", nicUuid, "toEnsureIPs", toEnsureIPs)
// always override
requirePatch := false
failovers := make([]ionoscloud.IPFailover, 0)
if lan.Properties.IpFailover != nil {
failovers = append(failovers, *lan.Properties.IpFailover...)
}
for k := range toEnsureIPs {
toAdd := true
for i := range failovers {
if *failovers[i].Ip == toEnsureIPs[k] {
toAdd = false
break
}
}
if toAdd {
requirePatch = true
failovers = append(failovers, ionoscloud.IPFailover{
Ip: &toEnsureIPs[k],
NicUuid: &nicUuid,
})
}
}
ctx.Logger.Info("patch", "requirePatch", requirePatch, "lanId", lanId, "failovers", failovers)
if requirePatch {
return ctx.IONOSClient.PatchLanWithIPFailover(ctx, datacenterId, lanId, failovers)
}
return nil
}

func serverNicByLan(server ionoscloud.Server, lan *v1alpha1.IONOSLanSpec) *ionoscloud.Nic {
var serverNic *ionoscloud.Nic
for _, nic := range *server.Entities.Nics.Items {
Expand Down
118 changes: 43 additions & 75 deletions internal/ionos/apiclient.go
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,12 @@ import (
ionoscloud "github.com/ionos-cloud/sdk-go/v6"
)

type key int

const (
DepthKey key = iota
)

var _ Client = (*APIClient)(nil)

var (
Expand All @@ -30,7 +36,7 @@ type IPBlockAPI interface {
type LanAPI interface {
CreateLan(ctx context.Context, datacenterId string, public bool) (ionoscloud.LanPost, *ionoscloud.APIResponse, error)
GetLan(ctx context.Context, datacenterId, lanId string) (ionoscloud.Lan, *ionoscloud.APIResponse, error)
EnsureFailoverIPsOnLan(ctx context.Context, datacenterId, lanId, nicUuid string, ips []string) error
PatchLanWithIPFailover(ctx context.Context, datacenterId, lanId string, ipFailover []ionoscloud.IPFailover) error
}

type DefaultAPI interface {
Expand All @@ -52,7 +58,7 @@ type ServerAPI interface {
CreateServer(ctx context.Context, datacenterId string, server ionoscloud.Server) (ionoscloud.Server, *ionoscloud.APIResponse, error)
GetServer(ctx context.Context, datacenterId, serverId string) (ionoscloud.Server, *ionoscloud.APIResponse, error)
DeleteServer(ctx context.Context, datacenterId, serverId string) (*ionoscloud.APIResponse, error)
EnsureAdditionalIPsOnNic(ctx context.Context, datacenterId, serverId, nicUuid string, ips []string) error
PatchServerNicsWithIPs(ctx context.Context, datacenterId, serverId, nicUuid string, ips []string) error
}

type Client interface {
Expand Down Expand Up @@ -86,69 +92,17 @@ type APIClient struct {
client *ionoscloud.APIClient
}

func (c *APIClient) EnsureAdditionalIPsOnNic(ctx context.Context, datacenterId, serverId, nicUuid string, toEnsureIPs []string) error {
serverId = strings.TrimPrefix(serverId, "ionos://")
nic, _, err := c.client.NetworkInterfacesApi.DatacentersServersNicsFindById(ctx, datacenterId, serverId, nicUuid).Execute()
if err != nil {
return err
}
ips := make([]string, 0)
if nic.Properties.Ips != nil {
ips = *nic.Properties.Ips
}
for _, ip := range toEnsureIPs {
toAdd := true
for _, current := range ips {
if current == ip {
toAdd = false
break
}
}
if toAdd {
ips = append(ips, ip)
}
}

_, _, err = c.client.NetworkInterfacesApi.DatacentersServersNicsPatch(ctx, datacenterId, serverId, nicUuid).Nic(ionoscloud.NicProperties{
Ips: &ips,
func (c *APIClient) PatchLanWithIPFailover(ctx context.Context, datacenterId, lanId string, ipFailover []ionoscloud.IPFailover) error {
_, _, err := c.client.LANsApi.DatacentersLansPatch(ctx, datacenterId, lanId).Lan(ionoscloud.LanProperties{
IpFailover: &ipFailover,
}).Execute()
return err
}

func (c *APIClient) EnsureFailoverIPsOnLan(ctx context.Context, datacenterId, lanId, nicUuid string, toEnsureIPs []string) error {
lan, _, err := c.client.LANsApi.DatacentersLansFindById(ctx, datacenterId, lanId).Execute()
if err != nil {
return err
}

requireRegister := false
failovers := make([]ionoscloud.IPFailover, 0)
if lan.Properties.IpFailover != nil {
failovers = *lan.Properties.IpFailover
}
for _, ip := range toEnsureIPs {
toAdd := true
for _, current := range failovers {
if *current.Ip == ip {
toAdd = false
break
}
}
if toAdd {
requireRegister = true
failovers = append(failovers, ionoscloud.IPFailover{
Ip: &ip,
NicUuid: &nicUuid,
})
}
}
fmt.Printf("requireRegister %t, failovers %d", requireRegister, len(failovers))
if !requireRegister {
return nil
}

_, _, err = c.client.LANsApi.DatacentersLansPatch(ctx, datacenterId, lanId).Lan(ionoscloud.LanProperties{
IpFailover: &failovers,
func (c *APIClient) PatchServerNicsWithIPs(ctx context.Context, datacenterId, serverId, nicUuid string, ips []string) error {
serverId = strings.TrimPrefix(serverId, "ionos://")
_, _, err := c.client.NetworkInterfacesApi.DatacentersServersNicsPatch(ctx, datacenterId, serverId, nicUuid).Nic(ionoscloud.NicProperties{
Ips: &ips,
}).Execute()
return err
}
Expand Down Expand Up @@ -181,8 +135,10 @@ func (c *APIClient) CreateDatacenter(ctx context.Context, name string, location
Name: &name,
},
}
datacenterReq := c.client.DataCentersApi.DatacentersPost(ctx)
return datacenterReq.Datacenter(datacenter).Execute()
return c.client.DataCentersApi.
DatacentersPost(ctx).
Datacenter(datacenter).
Execute()
}

func (c *APIClient) CreateLan(ctx context.Context, datacenterId string, public bool) (ionoscloud.LanPost, *ionoscloud.APIResponse, error) {
Expand All @@ -191,37 +147,49 @@ func (c *APIClient) CreateLan(ctx context.Context, datacenterId string, public b
Public: ionoscloud.ToPtr(public),
},
}
lanReq := c.client.LANsApi.DatacentersLansPost(ctx, datacenterId)
return lanReq.Lan(lan).Execute()
return c.client.LANsApi.
DatacentersLansPost(ctx, datacenterId).
Lan(lan).
Execute()
}

func (c *APIClient) GetLan(ctx context.Context, datacenterId, lanId string) (ionoscloud.Lan, *ionoscloud.APIResponse, error) {
lanReq := c.client.LANsApi.DatacentersLansFindById(ctx, datacenterId, lanId)
return lanReq.Execute()
u, ok := ctx.Value(DepthKey).(int32)
if !ok {
u = 1
}
return c.client.LANsApi.DatacentersLansFindById(ctx, datacenterId, lanId).Depth(u).Execute()
}

func (c *APIClient) GetDatacenter(ctx context.Context, datacenterId string) (ionoscloud.Datacenter, *ionoscloud.APIResponse, error) {
return c.client.DataCentersApi.DatacentersFindById(ctx, datacenterId).Execute()
}

func (c *APIClient) CreateLoadBalancer(ctx context.Context, datacenterId string, loadBalancer ionoscloud.NetworkLoadBalancer) (ionoscloud.NetworkLoadBalancer, *ionoscloud.APIResponse, error) {
loadBalancerReq := c.client.NetworkLoadBalancersApi.DatacentersNetworkloadbalancersPost(ctx, datacenterId)
return loadBalancerReq.NetworkLoadBalancer(loadBalancer).Execute()
return c.client.NetworkLoadBalancersApi.
DatacentersNetworkloadbalancersPost(ctx, datacenterId).
NetworkLoadBalancer(loadBalancer).
Execute()
}

func (c *APIClient) GetLoadBalancer(ctx context.Context, datacenterId, loadBalancerId string) (ionoscloud.NetworkLoadBalancer, *ionoscloud.APIResponse, error) {
loadBalancerReq := c.client.NetworkLoadBalancersApi.DatacentersNetworkloadbalancersFindByNetworkLoadBalancerId(ctx, datacenterId, loadBalancerId)
return loadBalancerReq.Execute()
return c.client.NetworkLoadBalancersApi.
DatacentersNetworkloadbalancersFindByNetworkLoadBalancerId(ctx, datacenterId, loadBalancerId).
Execute()
}

func (c *APIClient) GetLoadBalancerForwardingRules(ctx context.Context, datacenterId, loadBalancerId string) (ionoscloud.NetworkLoadBalancerForwardingRules, *ionoscloud.APIResponse, error) {
loadBalancerReq := c.client.NetworkLoadBalancersApi.DatacentersNetworkloadbalancersForwardingrulesGet(ctx, datacenterId, loadBalancerId)
return loadBalancerReq.Depth(2).Execute()
return c.client.NetworkLoadBalancersApi.
DatacentersNetworkloadbalancersForwardingrulesGet(ctx, datacenterId, loadBalancerId).
Depth(2).
Execute()
}

func (c *APIClient) PatchLoadBalancerForwardingRule(ctx context.Context, datacenterId, loadBalancerId, ruleId string, properties ionoscloud.NetworkLoadBalancerForwardingRuleProperties) (ionoscloud.NetworkLoadBalancerForwardingRule, *ionoscloud.APIResponse, error) {
updateReq := c.client.NetworkLoadBalancersApi.DatacentersNetworkloadbalancersForwardingrulesPatch(ctx, datacenterId, loadBalancerId, ruleId)
return updateReq.NetworkLoadBalancerForwardingRuleProperties(properties).Execute()
return c.client.NetworkLoadBalancersApi.
DatacentersNetworkloadbalancersForwardingrulesPatch(ctx, datacenterId, loadBalancerId, ruleId).
NetworkLoadBalancerForwardingRuleProperties(properties).
Execute()
}

func (c *APIClient) CreateServer(ctx context.Context, datacenterId string, server ionoscloud.Server) (ionoscloud.Server, *ionoscloud.APIResponse, error) {
Expand Down
Loading

0 comments on commit 0589b57

Please sign in to comment.