blob: 5c5d018246c2fb11691746692dc96a419cec9f84 [file]
//go:build e2e
/*
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
package e2e
import (
"context"
"fmt"
"net"
"strconv"
"strings"
"testing"
corev1 "k8s.io/api/core/v1"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
)
func TestLB_CreateSingleTCPPort(t *testing.T) {
f := NewFramework(t)
svc := f.CreateLBService(nil)
lbName := defaultLoadBalancerName(svc)
ingress := f.WaitForIngressIP(svc)
if ingress.IP == "" {
t.Fatalf("expected an ingress IP, got %+v", ingress)
}
if ip := net.ParseIP(ingress.IP); ip == nil {
t.Fatalf("ingress IP %q is not a valid IP", ingress.IP)
}
rules := f.WaitForLBRules(lbName, 1)
rule := rules[0]
wantName := fmt.Sprintf("%s-tcp-80", lbName)
if rule.Name != wantName {
t.Errorf("rule name = %q, want %q", rule.Name, wantName)
}
if rule.Algorithm != "roundrobin" {
t.Errorf("rule algorithm = %q, want roundrobin", rule.Algorithm)
}
if rule.Publicport != "80" {
t.Errorf("rule public port = %q, want 80", rule.Publicport)
}
current, err := f.K8s.CoreV1().Services(svc.Namespace).Get(context.Background(), svc.Name, metav1.GetOptions{})
if err != nil {
t.Fatalf("getting service: %v", err)
}
nodePort := strconv.Itoa(int(current.Spec.Ports[0].NodePort))
if rule.Privateport != nodePort {
t.Errorf("rule private port = %q, want NodePort %q", rule.Privateport, nodePort)
}
if rule.Publicip != ingress.IP {
t.Errorf("rule public IP = %q, want ingress IP %q", rule.Publicip, ingress.IP)
}
if !strings.Contains(rule.Cidrlist, "0.0.0.0/0") {
t.Errorf("rule cidrlist = %q, want it to contain 0.0.0.0/0", rule.Cidrlist)
}
// The isolated network offering includes the Firewall service.
f.Eventually(lbSyncTimeout, lbSyncInterval, "firewall rule for port 80",
func() (bool, error) {
fwRules, err := f.FirewallRules(rule.Publicipid)
if err != nil {
return false, err
}
for _, fw := range fwRules {
if fw.Startport == 80 && fw.Endport == 80 && strings.EqualFold(fw.Protocol, "tcp") {
return true, nil
}
}
return false, nil
})
}
func TestLB_MultiPort(t *testing.T) {
f := NewFramework(t)
svc := f.CreateLBService(func(s *corev1.Service) {
s.Spec.Ports = []corev1.ServicePort{
{Name: "http", Port: 80, Protocol: corev1.ProtocolTCP},
{Name: "https", Port: 443, Protocol: corev1.ProtocolTCP},
}
})
lbName := defaultLoadBalancerName(svc)
f.WaitForIngressIP(svc)
rules := f.WaitForLBRules(lbName, 2)
if rules[0].Publicipid != rules[1].Publicipid {
t.Errorf("expected both rules to share a public IP, got %q and %q",
rules[0].Publicipid, rules[1].Publicipid)
}
ports := map[string]bool{}
for _, r := range rules {
ports[r.Publicport] = true
}
if !ports["80"] || !ports["443"] {
t.Errorf("expected rules for ports 80 and 443, got %v", ports)
}
}
func TestLB_NodeMembership(t *testing.T) {
f := NewFramework(t)
svc := f.CreateLBService(nil)
lbName := defaultLoadBalancerName(svc)
f.WaitForIngressIP(svc)
rules := f.WaitForLBRules(lbName, 1)
// kubeadm labels the control plane exclude-from-external-load-balancers, so only workers join the rule.
wantIDs := map[string]bool{}
for _, node := range f.Nodes() {
if _, excluded := node.Labels["node.kubernetes.io/exclude-from-external-load-balancers"]; excluded {
continue
}
vm, err := f.VMByName(node.Name)
if err != nil || vm == nil {
t.Fatalf("looking up VM for node %s: %v", node.Name, err)
}
wantIDs[vm.Id] = true
}
if len(wantIDs) == 0 {
t.Fatal("no candidate worker nodes found")
}
f.Eventually(lbSyncTimeout, lbSyncInterval, "load balancer rule instances to match worker VMs",
func() (bool, error) {
p := f.CS.LoadBalancer.NewListLoadBalancerRuleInstancesParams(rules[0].Id)
resp, err := f.CS.LoadBalancer.ListLoadBalancerRuleInstances(p)
if err != nil {
return false, err
}
gotIDs := map[string]bool{}
for _, inst := range resp.LoadBalancerRuleInstances {
gotIDs[inst.Id] = true
}
if len(gotIDs) != len(wantIDs) {
return false, fmt.Errorf("got %d instances, want %d", len(gotIDs), len(wantIDs))
}
for id := range wantIDs {
if !gotIDs[id] {
return false, fmt.Errorf("VM %s missing from rule instances", id)
}
}
return true, nil
})
}
// A rule left without hosts, for example because assigning them failed after
// the rule was created, gets them back on the next sync instead of waiting for
// a node change.
func TestLB_HostsRestoredOnResync(t *testing.T) {
f := NewFramework(t)
svc := f.CreateLBService(nil)
lbName := defaultLoadBalancerName(svc)
f.WaitForIngressIP(svc)
rules := f.WaitForLBRules(lbName, 1)
var assigned []string
f.Eventually(lbSyncTimeout, lbSyncInterval, "hosts to be assigned to the rule",
func() (bool, error) {
p := f.CS.LoadBalancer.NewListLoadBalancerRuleInstancesParams(rules[0].Id)
resp, err := f.CS.LoadBalancer.ListLoadBalancerRuleInstances(p)
if err != nil {
return false, err
}
assigned = assigned[:0]
for _, inst := range resp.LoadBalancerRuleInstances {
assigned = append(assigned, inst.Id)
}
return len(assigned) > 0, nil
})
remove := f.CS.LoadBalancer.NewRemoveFromLoadBalancerRuleParams(rules[0].Id)
remove.SetVirtualmachineids(assigned)
if _, err := f.CS.LoadBalancer.RemoveFromLoadBalancerRule(remove); err != nil {
t.Fatalf("removing hosts from rule %s: %v", rules[0].Id, err)
}
if got, err := f.RuleInstanceCount(rules[0].Id); err != nil || got != 0 {
t.Fatalf("rule still has %d hosts after removal (err %v)", got, err)
}
forceReconcile(f, svc, lbName)
f.Eventually(lbSyncTimeout, lbSyncInterval, "the sync to restore the rule's hosts",
func() (bool, error) {
got, err := f.RuleInstanceCount(rules[0].Id)
if err != nil {
return false, err
}
if got != len(assigned) {
return false, fmt.Errorf("rule has %d hosts, want %d", got, len(assigned))
}
return true, nil
})
}
func TestLB_PortChange(t *testing.T) {
f := NewFramework(t)
svc := f.CreateLBService(nil)
lbName := defaultLoadBalancerName(svc)
f.WaitForIngressIP(svc)
f.WaitForLBRules(lbName, 1)
f.UpdateService(svc, func(s *corev1.Service) {
s.Spec.Ports[0].Port = 8080
})
f.Eventually(lbSyncTimeout, lbSyncInterval, "rule for port 8080 to replace port 80",
func() (bool, error) {
rules, err := f.LBRules(lbName)
if err != nil {
return false, err
}
return len(rules) == 1 && rules[0].Publicport == "8080", nil
})
}
func TestLB_Delete(t *testing.T) {
f := NewFramework(t)
svc := f.CreateLBService(nil)
lbName := defaultLoadBalancerName(svc)
f.WaitForIngressIP(svc)
rules := f.WaitForLBRules(lbName, 1)
publicIPID := rules[0].Publicipid
f.DeleteServiceAndWait(svc)
if remaining, err := f.LBRules(lbName); err != nil || len(remaining) != 0 {
t.Errorf("expected no remaining rules, got %d (err %v)", len(remaining), err)
}
f.Eventually(lbSyncTimeout, lbSyncInterval, "public IP to be released",
func() (bool, error) {
ip, err := f.PublicIP(publicIPID)
if err != nil {
return false, err
}
return ip == nil || ip.Allocated == "", nil
})
}