package controller import ( "testing" corev1 "k8s.io/api/core/v1" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" mikrotikv1alpha1 "code.brunner.ninja/feedc0de/mikrotik-operator/api/v1alpha1" "code.brunner.ninja/feedc0de/mikrotik-operator/internal/routeros" ) func TestDesiredRules(t *testing.T) { router := &mikrotikv1alpha1.MikroTikRouter{ ObjectMeta: metav1.ObjectMeta{Name: "edge"}, Spec: mikrotikv1alpha1.MikroTikRouterSpec{AllowedNamespaces: []string{"games"}}, } service := corev1.Service{ ObjectMeta: metav1.ObjectMeta{ Namespace: "games", Name: "factorio", Annotations: map[string]string{ AnnotationRouter: "edge", AnnotationServicePort: "game", AnnotationDestinationPort: "40000", AnnotationInInterfaceList: "WAN", }, }, Spec: corev1.ServiceSpec{ Type: corev1.ServiceTypeLoadBalancer, Ports: []corev1.ServicePort{ {Name: "game", Port: 34197, Protocol: corev1.ProtocolUDP}, {Name: "rcon", Port: 27015, Protocol: corev1.ProtocolTCP}, }, }, Status: corev1.ServiceStatus{LoadBalancer: corev1.LoadBalancerStatus{Ingress: []corev1.LoadBalancerIngress{ {IP: "2001:db8::1"}, {IP: "192.168.0.17"}, }}}, } rules, preserve, issues := DesiredRules(router, []corev1.Service{service}) if len(issues) != 0 { t.Fatalf("unexpected issues: %#v", issues) } if len(preserve) != 0 { t.Fatalf("unexpected preserve set: %#v", preserve) } if len(rules) != 1 { t.Fatalf("got %d rules, want 1", len(rules)) } want := routeros.Rule{ Comment: routeros.ManagedComment("games", "factorio"), Protocol: "udp", DestinationPort: "40000", InInterfaceList: "WAN", ToAddress: "192.168.0.17", ToPort: "34197", } if rules[0] != want { t.Fatalf("rule = %#v, want %#v", rules[0], want) } } func TestDesiredRulesPreservesRuleWhileLoadBalancerIsPending(t *testing.T) { router := &mikrotikv1alpha1.MikroTikRouter{ ObjectMeta: metav1.ObjectMeta{Name: "edge"}, Spec: mikrotikv1alpha1.MikroTikRouterSpec{AllowedNamespaces: []string{"games"}}, } service := corev1.Service{ ObjectMeta: metav1.ObjectMeta{Namespace: "games", Name: "factorio", Annotations: map[string]string{AnnotationRouter: "edge"}}, Spec: corev1.ServiceSpec{Type: corev1.ServiceTypeLoadBalancer, Ports: []corev1.ServicePort{{Port: 34197, Protocol: corev1.ProtocolUDP}}}, } rules, preserve, issues := DesiredRules(router, []corev1.Service{service}) if len(rules) != 0 || len(issues) != 1 { t.Fatalf("rules=%#v issues=%#v", rules, issues) } if _, ok := preserve[routeros.ManagedComment("games", "factorio")]; !ok { t.Fatal("pending rule was not preserved") } } func TestDesiredRulesDoesNotHonorUnlistedNamespace(t *testing.T) { router := &mikrotikv1alpha1.MikroTikRouter{ ObjectMeta: metav1.ObjectMeta{Name: "edge"}, Spec: mikrotikv1alpha1.MikroTikRouterSpec{AllowedNamespaces: []string{"trusted"}}, } service := corev1.Service{ObjectMeta: metav1.ObjectMeta{ Namespace: "untrusted", Name: "bad", Annotations: map[string]string{AnnotationRouter: "edge"}, }} rules, preserve, issues := DesiredRules(router, []corev1.Service{service}) if len(rules) != 0 || len(preserve) != 0 || len(issues) != 0 { t.Fatalf("unlisted namespace affected reconciliation: rules=%#v preserve=%#v issues=%#v", rules, preserve, issues) } } func TestDesiredRulesSupportsNamedForwardingsAndRanges(t *testing.T) { router := &mikrotikv1alpha1.MikroTikRouter{ ObjectMeta: metav1.ObjectMeta{Name: "edge"}, Spec: mikrotikv1alpha1.MikroTikRouterSpec{AllowedNamespaces: []string{"*"}}, } service := corev1.Service{ ObjectMeta: metav1.ObjectMeta{ Namespace: "coturn", Name: "coturn", Annotations: map[string]string{ AnnotationRouter: "edge", AnnotationForwardings: `[ {"name":"turn","servicePort":"turn","dstAddressList":"WANs"}, {"name":"relay","servicePortRange":"49160-49162","dstAddressList":"WANs"} ]`, }, }, Spec: corev1.ServiceSpec{ Type: corev1.ServiceTypeLoadBalancer, Ports: []corev1.ServicePort{ {Name: "turn", Port: 3478, Protocol: corev1.ProtocolUDP}, {Name: "relay-49160", Port: 49160, Protocol: corev1.ProtocolUDP}, {Name: "relay-49161", Port: 49161, Protocol: corev1.ProtocolUDP}, {Name: "relay-49162", Port: 49162, Protocol: corev1.ProtocolUDP}, }, }, Status: corev1.ServiceStatus{LoadBalancer: corev1.LoadBalancerStatus{Ingress: []corev1.LoadBalancerIngress{{IP: "192.168.0.19"}}}}, } rules, preserve, issues := DesiredRules(router, []corev1.Service{service}) if len(issues) != 0 || len(preserve) != 0 { t.Fatalf("issues=%#v preserve=%#v", issues, preserve) } if len(rules) != 2 { t.Fatalf("got %d rules, want 2", len(rules)) } want := []routeros.Rule{ {Comment: routeros.ManagedForwardingComment("coturn", "coturn", "turn"), Protocol: "udp", DestinationPort: "3478", DestinationList: "WANs", ToAddress: "192.168.0.19", ToPort: "3478"}, {Comment: routeros.ManagedForwardingComment("coturn", "coturn", "relay"), Protocol: "udp", DestinationPort: "49160-49162", DestinationList: "WANs", ToAddress: "192.168.0.19", ToPort: "49160-49162"}, } for i := range want { if rules[i] != want[i] { t.Fatalf("rule[%d] = %#v, want %#v", i, rules[i], want[i]) } } } func TestDesiredRulesPreservesAllNamedRulesForInvalidJSON(t *testing.T) { router := &mikrotikv1alpha1.MikroTikRouter{ ObjectMeta: metav1.ObjectMeta{Name: "edge"}, Spec: mikrotikv1alpha1.MikroTikRouterSpec{AllowedNamespaces: []string{"*"}}, } service := corev1.Service{ObjectMeta: metav1.ObjectMeta{ Namespace: "default", Name: "traefik", Annotations: map[string]string{ AnnotationRouter: "edge", AnnotationForwardings: `[{broken]`, }, }} rules, preserve, issues := DesiredRules(router, []corev1.Service{service}) if len(rules) != 0 || len(issues) != 1 { t.Fatalf("rules=%#v issues=%#v", rules, issues) } if _, ok := preserve[routeros.ManagedComment("default", "traefik")+"/"]; !ok { t.Fatalf("named forwarding prefix was not preserved: %#v", preserve) } }