Skip to content

Commit

Permalink
Merge pull request #6 from civo/reserved-ip
Browse files Browse the repository at this point in the history
Reserved ip
  • Loading branch information
RealHarshThakur authored Jul 13, 2022
2 parents 709ee1b + ac7ec22 commit 92d9fcb
Show file tree
Hide file tree
Showing 58 changed files with 2,685 additions and 1,150 deletions.
1 change: 1 addition & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ Read More: https://www.civo.com/learn/managing-external-load-balancers-on-civo
| kubernetes.civo.com/firewall-id | If provided, an existing Firewall will be used. | 03093EF6-31E6-48B1-AB1D-152AC3A8C90A |
| kubernetes.civo.com/loadbalancer-enable-proxy-protocol | If set, a proxy-protocol header will be sent via the load balancer. <br /><br />NB: This requires support from the Service End Points within the cluster. | send-proxy<br />send-proxy-v2 |
| kubernetes.civo.com/loadbalancer-algorithm | Custom the algorithm the external load balancer uses | round_robin<br />least_connections |
|kubernetes.civo.com/ipv4-address| If set, LoadBalancer will have the mentioned IP as the public IP. Please note: the reserved IP should be present in the account before claiming it. | 10.0.0.20<br/> my-reserved-ip |

### Load Balancer Status Annotations

Expand Down
14 changes: 10 additions & 4 deletions cloud-controller-manager/civo/cloud.go
Original file line number Diff line number Diff line change
Expand Up @@ -9,14 +9,20 @@ import (
)

const (
// ProviderName is the name of the provider.
ProviderName string = "civo"
)

var (
ApiURL string
ApiKey string
Region string
// APIURL is the URL of the Civo API.
APIURL string
// APIKey is the API key for the Civo API.
APIKey string
// Region is the region of the Civo API.
Region string
// Namespace of the cluster
Namespace string
// ClusterID is the ID of the Civo cluster.
ClusterID string
)

Expand All @@ -33,7 +39,7 @@ func init() {
}

func newCloud() (cloudprovider.Interface, error) {
client, err := civogo.NewClientWithURL(ApiKey, ApiURL, Region)
client, err := civogo.NewClientWithURL(APIKey, APIURL, Region)
if err != nil {
return nil, err
}
Expand Down
168 changes: 107 additions & 61 deletions cloud-controller-manager/civo/loadbalancer.go
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,9 @@ const (

// annotationCivoLoadBalancerAlgorithm is the annotation specifying the CivoLoadbalancer algorith.
annotationCivoLoadBalancerAlgorithm = "kubernetes.civo.com/loadbalancer-algorithm"

// annotationCivoIPv4 is the annotation specifying the reserved IP.
annotationCivoIPv4 = "kubernetes.civo.com/ipv4-address"
)

type loadbalancer struct {
Expand Down Expand Up @@ -88,30 +91,15 @@ func (l *loadbalancer) EnsureLoadBalancer(ctx context.Context, clusterName strin
return nil, err
}

// CivLB has been found
// CivoLB has been found
if err == nil {
lbuc := civogo.LoadBalancerUpdateConfig{
ExternalTrafficPolicy: string(service.Spec.ExternalTrafficPolicy),
Region: Region,
}

if enableProxyProtocol := getEnableProxyProtocol(service); enableProxyProtocol != "" {
lbuc.EnableProxyProtocol = enableProxyProtocol
}
if algorithm := getAlgorithm(service); algorithm != "" {
lbuc.Algorithm = algorithm
}
if firewallID := getFirewallID(service); firewallID != "" {
lbuc.FirewallID = firewallID
}

updatedlb, err := l.client.civoClient.UpdateLoadBalancer(civolb.ID, &lbuc)
ul, err := l.updateLBConfig(civolb, service, nodes)
if err != nil {
klog.Errorf("Unable to update loadbalancer, error: %v", err)
return nil, err
}

return lbStatusFor(updatedlb), nil
return lbStatusFor(ul)
}

err = createLoadBalancer(ctx, clusterName, service, nodes, l.client.civoClient, l.client.kclient)
Expand All @@ -125,41 +113,10 @@ func (l *loadbalancer) EnsureLoadBalancer(ctx context.Context, clusterName strin
return nil, err
}

if civolb.State != statusAvailable {
klog.Errorf("Loadbalancer is not available, state: %s", civolb.State)
return nil, fmt.Errorf("loadbalancer is not yet available, current state: %s", civolb.State)
}

return lbStatusFor(civolb), nil
return lbStatusFor(civolb)
}

func lbStatusFor(civolb *civogo.LoadBalancer) *v1.LoadBalancerStatus {
status := &v1.LoadBalancerStatus{
Ingress: make([]v1.LoadBalancerIngress, 1),
}

if civolb.EnableProxyProtocol == "" {
status.Ingress[0].IP = civolb.PublicIP
}
status.Ingress[0].Hostname = fmt.Sprintf("%s.lb.civo.com", civolb.ID)

return status
}

// 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 {
civolb, err := getLoadBalancer(ctx, l.client.civoClient, l.client.kclient, clusterName, service)
if err != nil {
if strings.Contains(err.Error(), string(civogo.ZeroMatchesError)) || strings.Contains(err.Error(), string(civogo.DatabaseLoadBalancerNotFoundError)) {
return nil
}
klog.Errorf("Unable to get loadbalancer, error: %v", err)
return err
}

func (l *loadbalancer) updateLBConfig(civolb *civogo.LoadBalancer, service *v1.Service, nodes []*v1.Node) (*civogo.LoadBalancer, error) {
lbuc := civogo.LoadBalancerUpdateConfig{
ExternalTrafficPolicy: string(service.Spec.ExternalTrafficPolicy),
Region: Region,
Expand Down Expand Up @@ -189,7 +146,97 @@ func (l *loadbalancer) UpdateLoadBalancer(ctx context.Context, clusterName strin
}
lbuc.Backends = backends

ulb, err := l.client.civoClient.UpdateLoadBalancer(civolb.ID, &lbuc)
if ip := getReservedIPFromAnnotation(service); ip != "" {
rip, err := l.client.civoClient.FindIP(ip)
if err != nil {
klog.Errorf("Unable to find reserved IP, error: %v", err)
return nil, err
}

// this is so that we don't try to reassign the reserved IP to the loadbalancer
if rip.AssignedTo.ID != civolb.ID {
_, err = l.client.civoClient.AssignIP(rip.ID, civolb.ID, "loadbalancer")
if err != nil {
klog.Errorf("Unable to assign reserved IP, error: %v", err)
return nil, err
}
}
} else {
ip, err := findIPWithLBID(l.client.civoClient, civolb.ID)
if err != nil {
klog.Errorf("Unable to find IP with loadbalancer ID, error: %v", err)
return nil, err
}

if ip != nil {
_, err = l.client.civoClient.UnassignIP(ip.ID)
if err != nil {
klog.Errorf("Unable to unassign IP, error: %v", err)
return nil, err
}
}
}

updatedlb, err := l.client.civoClient.UpdateLoadBalancer(civolb.ID, &lbuc)
if err != nil {
klog.Errorf("Unable to update loadbalancer, error: %v", err)
return nil, err
}

return updatedlb, nil

}

// there's no direct way to find if the LB is using a reserved IP. This method lists all the reserved IPs in the account
// and checks if the loadbalancer is using one of them.
func findIPWithLBID(civo civogo.Clienter, lbID string) (*civogo.IP, error) {
ips, err := civo.ListIPs()
if err != nil {
klog.Errorf("Unable to list IPs, error: %v", err)
return nil, err
}

for _, ip := range ips.Items {
if ip.AssignedTo.ID == lbID {
return &ip, nil
}
}
return nil, nil
}

func lbStatusFor(civolb *civogo.LoadBalancer) (*v1.LoadBalancerStatus, error) {
status := &v1.LoadBalancerStatus{
Ingress: make([]v1.LoadBalancerIngress, 1),
}

if civolb.State != statusAvailable {
klog.Errorf("Loadbalancer is not available, state: %s", civolb.State)
return nil, fmt.Errorf("loadbalancer is not yet available, current state: %s", civolb.State)
}

if civolb.EnableProxyProtocol == "" {
status.Ingress[0].IP = civolb.PublicIP
}
status.Ingress[0].Hostname = fmt.Sprintf("%s.lb.civo.com", civolb.ID)

return status, nil
}

// 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 {
civolb, err := getLoadBalancer(ctx, l.client.civoClient, l.client.kclient, clusterName, service)
if err != nil {
if strings.Contains(err.Error(), string(civogo.ZeroMatchesError)) || strings.Contains(err.Error(), string(civogo.DatabaseLoadBalancerNotFoundError)) {
return nil
}
klog.Errorf("Unable to get loadbalancer, error: %v", err)
return err
}

ulb, err := l.updateLBConfig(civolb, service, nodes)
if err != nil {
klog.Errorf("Unable to update loadbalancer, error: %v", err)
return err
Expand Down Expand Up @@ -350,20 +397,19 @@ func getEnableProxyProtocol(service *v1.Service) string {

// getAlgorithm returns the algorithm value from the service annotation.
func getAlgorithm(service *v1.Service) string {
algorithm, ok := service.Annotations[annotationCivoLoadBalancerAlgorithm]
if !ok {
return ""
}
algorithm, _ := service.Annotations[annotationCivoLoadBalancerAlgorithm]

return algorithm
}

// getReservedIPFromAnnotation returns the reservedIP value from the service annotation.
func getReservedIPFromAnnotation(service *v1.Service) string {
ip, _ := service.Annotations[annotationCivoIPv4]
return ip
}

// getFirewallID returns the firewallID value from the service annotation.
func getFirewallID(service *v1.Service) string {
firewallID, ok := service.Annotations[annotationCivoFirewallID]
if !ok {
return ""
}

firewallID, _ := service.Annotations[annotationCivoFirewallID]
return firewallID
}
Original file line number Diff line number Diff line change
Expand Up @@ -18,17 +18,17 @@ import (

func main() {

civo.ApiURL = os.Getenv("CIVO_API_URL")
civo.ApiKey = os.Getenv("CIVO_API_KEY")
civo.APIURL = os.Getenv("CIVO_API_URL")
civo.APIKey = os.Getenv("CIVO_API_KEY")
civo.Region = os.Getenv("CIVO_REGION")
civo.ClusterID = os.Getenv("CIVO_CLUSTER_ID")

if civo.ApiURL == "" || civo.ApiKey == "" || civo.Region == "" || civo.ClusterID == "" {
if civo.APIURL == "" || civo.APIKey == "" || civo.Region == "" || civo.ClusterID == "" {
fmt.Println("CIVO_API_URL, CIVO_API_KEY, CIVO_REGION, CIVO_CLUSTER_ID environment variables must be set")
os.Exit(1)
}

klog.Infof("Starting ccm with CIVO_API_URL: %s, CIVO_REGION: %s, CIVO_CLUSTER_ID: %s", civo.ApiURL, civo.Region, civo.ClusterID)
klog.Infof("Starting ccm with CIVO_API_URL: %s, CIVO_REGION: %s, CIVO_CLUSTER_ID: %s", civo.APIURL, civo.Region, civo.ClusterID)

opts, err := options.NewCloudControllerManagerOptions()
if err != nil {
Expand Down
2 changes: 2 additions & 0 deletions cloud-controller-manager/pkg/utils/instance.go
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,7 @@ func civoInstanceFromID(clusterID, instanceID string, c civogo.Clienter) (civogo
return *instance, nil
}

// CivoInstanceFromProviderID finds civo instance by clusterID and providerID
func CivoInstanceFromProviderID(providerID, clusterID string, c civogo.Clienter) (civogo.Instance, error) {
civoInstanceID, err := civoInstanceIDFromProviderID(providerID)
if err != nil {
Expand All @@ -45,6 +46,7 @@ func CivoInstanceFromProviderID(providerID, clusterID string, c civogo.Clienter)
return civoInstance, nil
}

// CivoInstanceFromName finds civo instance by clusterID and name
func CivoInstanceFromName(clusterID, instanceName string, c civogo.Clienter) (civogo.Instance, error) {
instance, err := c.FindKubernetesClusterInstance(clusterID, instanceName)
if err != nil {
Expand Down
Loading

0 comments on commit 92d9fcb

Please sign in to comment.