diff --git a/pkg/client/client.go b/pkg/client/client.go index 85ecad4..1e60466 100644 --- a/pkg/client/client.go +++ b/pkg/client/client.go @@ -28,12 +28,6 @@ type userpassword struct { Tokens []string `json:"tokens,omitempty"` } -type Server struct { - Name string - ProviderID string - DatacenterID string -} - func New(datacenterId string, secret []byte) (IONOSClient, error) { var cfg *ionoscloud.Configuration if secret[0] == '{' { @@ -71,40 +65,6 @@ func (a *IONOSClient) GetServer(ctx context.Context, providerID string) (*cloudp return a.convertServerToInstanceMetadata(ctx, &server) } -func (a *IONOSClient) RemoveIPFromNode(ctx context.Context, loadBalancerIP, providerID string) error { - if a.client == nil { - return errors.New("client isn't initialized") - } - - serverReq := a.client.NetworkInterfacesApi.DatacentersServersNicsGet(ctx, a.DatacenterId, providerID) - nics, req, err := serverReq.Depth(3).Execute() - if err != nil { - if req != nil && req.StatusCode == 404 { - return nil - } - return err - } - - if !nics.HasItems() { - return errors.New("node has no nics") - } - - primaryNic := (*nics.Items)[0] - ips := *primaryNic.Properties.Ips - - for idx, v := range ips { - if v == loadBalancerIP { - ips = append(ips[:idx], ips[idx+1:]...) - } - } - - _, _, err = a.client.NetworkInterfacesApi.DatacentersServersNicsPatch(ctx, a.DatacenterId, providerID, *primaryNic.Id).Nic(ionoscloud.NicProperties{ - Ips: &ips, - }).Execute() - - return err -} - func (a *IONOSClient) AttachIPToNode(ctx context.Context, loadBalancerIP, providerID string) (bool, error) { if a.client == nil { return false, errors.New("client isn't initialized") @@ -112,18 +72,15 @@ func (a *IONOSClient) AttachIPToNode(ctx context.Context, loadBalancerIP, provid serverReq := a.client.NetworkInterfacesApi.DatacentersServersNicsGet(ctx, a.DatacenterId, providerID) nics, req, err := serverReq.Depth(3).Execute() - if err != nil { - if req != nil && req.StatusCode == 404 { - return false, nil - } + if req != nil && req.StatusCode == 404 { return false, err } if !nics.HasItems() { - return false, errors.New("node has no nics") + return false, nil } - primaryNic := (*nics.Items)[1] + primaryNic := (*nics.Items)[0] ips := *primaryNic.Properties.Ips ips = append(ips, loadBalancerIP) @@ -131,17 +88,20 @@ func (a *IONOSClient) AttachIPToNode(ctx context.Context, loadBalancerIP, provid Ips: &ips, }).Execute() - return true, err + return err != nil, err } -func (a *IONOSClient) GetServerByIP(ctx context.Context, loadBalancerIP string) (*Server, error) { +func (a *IONOSClient) GetServerByIP(ctx context.Context, loadBalancerIP string) (*string, error) { if a.client == nil { return nil, errors.New("client isn't initialized") } serverReq := a.client.ServersApi.DatacentersServersGet(ctx, a.DatacenterId) - servers, _, err := serverReq.Depth(3).Execute() - if err != nil { + servers, req, err := serverReq.Depth(3).Execute() + if err != nil || req != nil && req.StatusCode == 404 { + if err != nil { + return nil, nil + } return nil, err } @@ -150,27 +110,19 @@ func (a *IONOSClient) GetServerByIP(ctx context.Context, loadBalancerIP string) } for _, server := range *servers.Items { - klog.Infof("Checking server %s", server.Properties.Name) if !server.Entities.HasNics() { continue } for _, nic := range *server.Entities.Nics.Items { if nic.Properties.HasIps() { for _, ip := range *nic.Properties.Ips { - klog.Infof("Found ip %s", ip) if loadBalancerIP == ip { - klog.Info("Its a match!") - return &Server{ - Name: *server.Properties.Name, - ProviderID: *server.Id, - DatacenterID: a.DatacenterId, - }, nil + return server.Properties.Name, nil } } } } } - klog.Infof("IP %s not found on any node in datacenter %s", loadBalancerIP, a.DatacenterId) return nil, nil } diff --git a/pkg/ionos/cloud.go b/pkg/ionos/cloud.go index 8e513d7..66c53e1 100644 --- a/pkg/ionos/cloud.go +++ b/pkg/ionos/cloud.go @@ -2,6 +2,7 @@ package ionos import ( "context" + "encoding/json" client2 "github.com/GDATASoftwareAG/cloud-provider-ionoscloud/pkg/client" "github.com/GDATASoftwareAG/cloud-provider-ionoscloud/pkg/config" "io" @@ -12,21 +13,16 @@ import ( func init() { cloudprovider.RegisterCloudProvider(config.RegisteredProviderName, func(cfg io.Reader) (cloudprovider.Interface, error) { - // - //byConfig, err := io.ReadAll(cfg) - //if err != nil { - // klog.Errorf("ReadAll failed: %s", err) - // return nil, err - //} - //var conf config.Config - //err = json.Unmarshal(byConfig, &conf) - //if err != nil { - // return nil, err - //} - + byConfig, err := io.ReadAll(cfg) + if err != nil { + klog.Errorf("ReadAll failed: %s", err) + return nil, err + } var conf config.Config - conf.TokenSecretNamespace = "kube-system" - conf.TokenSecretName = "ionos-cpi-secret" + err = json.Unmarshal(byConfig, &conf) + if err != nil { + return nil, err + } return newProvider(conf), nil }) diff --git a/pkg/ionos/loadbalancer.go b/pkg/ionos/loadbalancer.go index 2911bb1..d52f121 100644 --- a/pkg/ionos/loadbalancer.go +++ b/pkg/ionos/loadbalancer.go @@ -6,9 +6,7 @@ import ( client2 "github.com/GDATASoftwareAG/cloud-provider-ionoscloud/pkg/client" v1 "k8s.io/api/core/v1" cloudprovider "k8s.io/cloud-provider" - "k8s.io/klog/v2" "math/rand" - "strings" "time" ) @@ -35,24 +33,25 @@ func (l loadbalancer) AddClient(datacenterId string, token []byte) error { // there exists a LoadBalancer instance created by ServiceController. // In all other cases, GetLoadBalancer must return a NotFound error. func (l loadbalancer) GetLoadBalancer(ctx context.Context, clusterName string, service *v1.Service) (status *v1.LoadBalancerStatus, exists bool, err error) { - klog.Infof("getLoadBalancer (service %s/%s)", service.Namespace, service.Name) - - server, err := l.ServerWithLoadBalancer(ctx, service.Spec.LoadBalancerIP) - if err != nil { - return nil, false, err + //TODO check if any node has service.spec.loadbalancerip + // TODO check spec.loadbalancerclass + use .spec.loadbalancerip + if service.Spec.Type != v1.ServiceTypeLoadBalancer { + return nil, false, errors.New("NotFound") } - if server != nil { - return &v1.LoadBalancerStatus{Ingress: []v1.LoadBalancerIngress{{IP: service.Spec.LoadBalancerIP}}}, true, nil + if service.Spec.LoadBalancerIP == "" { + return nil, false, errors.New("NotFound") } - return nil, false, nil + panic("implement me") } // GetLoadBalancerName returns the name of the load balancer. Implementations must treat the // *v1.Service parameter as read-only and not modify it. func (l loadbalancer) GetLoadBalancerName(ctx context.Context, clusterName string, service *v1.Service) string { - return cloudprovider.DefaultLoadBalancerName(service) + //TODO return service.metadata.uid + + panic("implement me") } // EnsureLoadBalancer creates a new load balancer 'name', or updates the existing one. Returns the status of the balancer @@ -66,89 +65,16 @@ func (l loadbalancer) GetLoadBalancerName(ctx context.Context, clusterName strin // polling at a fixed rate is preferred over backing off exponentially in // order to minimize latency. func (l loadbalancer) EnsureLoadBalancer(ctx context.Context, clusterName string, service *v1.Service, nodes []*v1.Node) (*v1.LoadBalancerStatus, error) { - return l.syncLoadBalancer(ctx, clusterName, service, nodes) -} - -// UpdateLoadBalancer updates hosts under the specified load balancer. -// Implementations must treat the *v1.Service and *v1.Node -// parameters as read-only and not modify them. -// Parameter 'clusterName' is the name of the cluster as presented to kube-controller-manager -func (l loadbalancer) UpdateLoadBalancer(ctx context.Context, clusterName string, service *v1.Service, nodes []*v1.Node) error { - _, err := l.syncLoadBalancer(ctx, clusterName, service, nodes) - return err -} - -// EnsureLoadBalancerDeleted deletes the specified load balancer if it -// exists, returning nil if the load balancer specified either didn't exist or -// was successfully deleted. -// This construction is useful because many cloud providers' load balancers -// have multiple underlying components, meaning a Get could say that the LB -// doesn't exist even if some part of it is still laying around. -// Implementations must treat the *v1.Service parameter as read-only and not modify it. -// Parameter 'clusterName' is the name of the cluster as presented to kube-controller-manager -// EnsureLoadBalancerDeleted must not return ImplementedElsewhere to ensure -// proper teardown of resources that were allocated by the ServiceController. -func (l loadbalancer) EnsureLoadBalancerDeleted(ctx context.Context, clusterName string, service *v1.Service) error { - klog.Infof("ensureLoadBalancerDeleted (service %s/%s)", service.Namespace, service.Name) - - if service.Spec.LoadBalancerIP == "" { - return errors.New("we are only handling LoadBalancers with spec.loadBalancerID != ''") - } - - server, err := l.ServerWithLoadBalancer(ctx, service.Spec.LoadBalancerIP) - if err != nil { - return err - } - - if server != nil { - return l.deleteLoadBalancerFromNode(ctx, service.Spec.LoadBalancerIP, server) - } - - return nil -} - -func (l loadbalancer) deleteLoadBalancerFromNode(ctx context.Context, loadBalancerIP string, server *client2.Server) error { - for _, client := range l.ionosClients { - if client.DatacenterId != server.DatacenterID { - continue - } - - server, err := client.GetServerByIP(ctx, loadBalancerIP) - if err != nil { - return err - } - - if server != nil { - return client.RemoveIPFromNode(ctx, loadBalancerIP, server.ProviderID) - } - } - - klog.Infof("IP %s not found in any datacenter", loadBalancerIP) - return nil -} - -func (l loadbalancer) syncLoadBalancer(ctx context.Context, clusterName string, service *v1.Service, nodes []*v1.Node) (*v1.LoadBalancerStatus, error) { - klog.Infof("syncLoadBalancer (service %s/%s, nodes %s)", service.Namespace, service.Name, nodes) - - if service.Spec.LoadBalancerIP == "" { - return nil, errors.New("we are only handling LoadBalancers with spec.loadBalancerID != ''") - } - - server, err := l.ServerWithLoadBalancer(ctx, service.Spec.LoadBalancerIP) + //TODO check if ip already attached to some node, attach loadbalancerIP to some node (not on controlplanes) + server, err := l.GetServerWithLoadBalancer(ctx, service.Spec.LoadBalancerIP) if err != nil { return nil, err } if server != nil { - klog.Infof("found server %s has IP %s ", server, service.Spec.LoadBalancerIP) node := getNode(*server, nodes) - if node == nil { - return nil, errors.New("no node found for server which has loadbalancerIP attached") - } - if IsLoadBalancerCandidate(node) { - klog.Infof("server %s is valid loadbalancer node", server) - return &v1.LoadBalancerStatus{Ingress: []v1.LoadBalancerIngress{{ + return &v1.LoadBalancerStatus{Ingress: []v1.LoadBalancerIngress{v1.LoadBalancerIngress{ IP: service.Spec.LoadBalancerIP, }}}, nil } @@ -157,31 +83,28 @@ func (l loadbalancer) syncLoadBalancer(ctx context.Context, clusterName string, loadBalancerNode := l.GetLoadBalancerNode(nodes) if loadBalancerNode == nil { - return nil, errors.New("no valid nodes found") + return nil, errors.New("No valid Nodes found") } - klog.Infof("server %s is elected as new loadbalancer node", server) for _, client := range l.ionosClients { - ok, err := client.AttachIPToNode(ctx, service.Spec.LoadBalancerIP, stripProviderFromID(loadBalancerNode.Spec.ProviderID)) + ok, err := client.AttachIPToNode(ctx, service.Spec.LoadBalancerIP, loadBalancerNode.Spec.ProviderID) if err != nil { return nil, err } if ok { - klog.Infof("successfully attached ip %s to server %s", service.Spec.LoadBalancerIP, server) - return &v1.LoadBalancerStatus{Ingress: []v1.LoadBalancerIngress{{ + return &v1.LoadBalancerStatus{Ingress: []v1.LoadBalancerIngress{v1.LoadBalancerIngress{ IP: service.Spec.LoadBalancerIP, }}}, nil } } - klog.Infof("Could not attach ip %s to any node", service.Spec.LoadBalancerIP) return nil, nil } -func getNode(server client2.Server, nodes []*v1.Node) *v1.Node { +func getNode(server string, nodes []*v1.Node) *v1.Node { for _, node := range nodes { - if stripProviderFromID(node.Spec.ProviderID) == server.ProviderID { + if node.Name == server { return node } } @@ -195,15 +118,13 @@ func (l loadbalancer) GetLoadBalancerNode(nodes []*v1.Node) *v1.Node { candidates = append(candidates, node) } } - if candidates == nil && len(candidates) == 0 { - return nil - } + rand.Seed(time.Now().UnixNano()) randomIndex := rand.Intn(len(candidates)) return candidates[randomIndex] } -func (l loadbalancer) ServerWithLoadBalancer(ctx context.Context, loadBalancerIP string) (*client2.Server, error) { +func (l loadbalancer) GetServerWithLoadBalancer(ctx context.Context, loadBalancerIP string) (*string, error) { for _, client := range l.ionosClients { server, err := client.GetServerByIP(ctx, loadBalancerIP) if err != nil { @@ -215,20 +136,52 @@ func (l loadbalancer) ServerWithLoadBalancer(ctx context.Context, loadBalancerIP } } - klog.Infof("IP %s not found in any datacenter", loadBalancerIP) return nil, nil } func IsLoadBalancerCandidate(node *v1.Node) bool { + if IsControlPlane(node) { + return false + } + for _, condition := range node.Status.Conditions { if condition.Type == v1.NodeReady && condition.Status == v1.ConditionTrue { return true } + return false } return false } -func stripProviderFromID(providerID string) string { - s, _ := strings.CutPrefix(providerID, "ionos://") - return s +func IsControlPlane(node *v1.Node) bool { + for _, taint := range node.Spec.Taints { + if taint.Key == "node-role.kubernetes.io/control-plane" { + return true + } + } + return false +} + +// UpdateLoadBalancer updates hosts under the specified load balancer. +// Implementations must treat the *v1.Service and *v1.Node +// parameters as read-only and not modify them. +// Parameter 'clusterName' is the name of the cluster as presented to kube-controller-manager +func (l loadbalancer) UpdateLoadBalancer(ctx context.Context, clusterName string, service *v1.Service, nodes []*v1.Node) error { + //TODO same as EnsureLoadBalancer + panic("implement me") +} + +// EnsureLoadBalancerDeleted deletes the specified load balancer if it +// exists, returning nil if the load balancer specified either didn't exist or +// was successfully deleted. +// This construction is useful because many cloud providers' load balancers +// have multiple underlying components, meaning a Get could say that the LB +// doesn't exist even if some part of it is still laying around. +// Implementations must treat the *v1.Service parameter as read-only and not modify it. +// Parameter 'clusterName' is the name of the cluster as presented to kube-controller-manager +// EnsureLoadBalancerDeleted must not return ImplementedElsewhere to ensure +// proper teardown of resources that were allocated by the ServiceController. +func (l loadbalancer) EnsureLoadBalancerDeleted(ctx context.Context, clusterName string, service *v1.Service) error { + //TODO remove ip from node + panic("implement me") }