Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Issue 4837: chore: main_test.go unit-tests #6314

Draft
wants to merge 1 commit into
base: main
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
110 changes: 66 additions & 44 deletions cmd/nginx-ingress/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -68,13 +68,32 @@

buildOS := os.Getenv("BUILD_OS")

config, kubeClient := mustCreateConfigAndKubeClient()
mustValidateKubernetesVersionInfo(kubeClient)
mustValidateIngressClass(kubeClient)
config, err := mustGetClientConfig()
if err != nil {
glog.Fatalf("error creating client configuration: %v", err)

Check warning on line 73 in cmd/nginx-ingress/main.go

View check run for this annotation

Codecov / codecov/patch

cmd/nginx-ingress/main.go#L71-L73

Added lines #L71 - L73 were not covered by tests
}

checkNamespaces(kubeClient)
dynClient, err := mustCreateDynamicClient(config)
if err != nil {
glog.Fatal(err)

Check warning on line 78 in cmd/nginx-ingress/main.go

View check run for this annotation

Codecov / codecov/patch

cmd/nginx-ingress/main.go#L76-L78

Added lines #L76 - L78 were not covered by tests
}
confClient, err := mustCreateConfigClient(config)
if err != nil {
glog.Fatal(err)

Check warning on line 82 in cmd/nginx-ingress/main.go

View check run for this annotation

Codecov / codecov/patch

cmd/nginx-ingress/main.go#L80-L82

Added lines #L80 - L82 were not covered by tests
}

kubeClient, err := mustGetKubeClient(config)
if err != nil {
glog.Fatalf("Failed to create client: %v.", err)

Check warning on line 87 in cmd/nginx-ingress/main.go

View check run for this annotation

Codecov / codecov/patch

cmd/nginx-ingress/main.go#L85-L87

Added lines #L85 - L87 were not covered by tests
}
if err := mustConfirmMinimumK8sVersionCriteria(kubeClient); err != nil {
glog.Fatal(err)

Check warning on line 90 in cmd/nginx-ingress/main.go

View check run for this annotation

Codecov / codecov/patch

cmd/nginx-ingress/main.go#L89-L90

Added lines #L89 - L90 were not covered by tests
}
if err := mustValidateIngressClass(kubeClient); err != nil {
glog.Fatal(err)

Check warning on line 93 in cmd/nginx-ingress/main.go

View check run for this annotation

Codecov / codecov/patch

cmd/nginx-ingress/main.go#L92-L93

Added lines #L92 - L93 were not covered by tests
}

dynClient, confClient := createCustomClients(config)
checkNamespaces(kubeClient)

Check warning on line 96 in cmd/nginx-ingress/main.go

View check run for this annotation

Codecov / codecov/patch

cmd/nginx-ingress/main.go#L96

Added line #L96 was not covered by tests

constLabels := map[string]string{"class": *ingressClass}

Expand Down Expand Up @@ -259,9 +278,8 @@
}
}

func mustCreateConfigAndKubeClient() (*rest.Config, *kubernetes.Clientset) {
var config *rest.Config
var err error
// This function returns a k8s client object configuration
func mustGetClientConfig() (config *rest.Config, err error) {
if *proxyURL != "" {
config, err = clientcmd.NewNonInteractiveDeferredLoadingClientConfig(
&clientcmd.ClientConfigLoadingRules{},
Expand All @@ -270,53 +288,55 @@
Server: *proxyURL,
},
}).ClientConfig()
if err != nil {
glog.Fatalf("error creating client configuration: %v", err)
}
} else {
if config, err = rest.InClusterConfig(); err != nil {
glog.Fatalf("error creating client configuration: %v", err)
}
config, err = rest.InClusterConfig()

Check warning on line 292 in cmd/nginx-ingress/main.go

View check run for this annotation

Codecov / codecov/patch

cmd/nginx-ingress/main.go#L292

Added line #L292 was not covered by tests
}

kubeClient, err := kubernetes.NewForConfig(config)
if err != nil {
glog.Fatalf("Failed to create client: %v.", err)
}
return config, err
}

return config, kubeClient
// This returns a k8s client with the provided client config for interacting with the k8s API
func mustGetKubeClient(config *rest.Config) (kubeClient *kubernetes.Clientset, err error) {
kubeClient, err = kubernetes.NewForConfig(config)
return kubeClient, err

Check warning on line 301 in cmd/nginx-ingress/main.go

View check run for this annotation

Codecov / codecov/patch

cmd/nginx-ingress/main.go#L299-L301

Added lines #L299 - L301 were not covered by tests
}

// mustValidateKubernetesVersionInfo calls internally os.Exit if
// the k8s version can not be retrieved or the version is not supported.
func mustValidateKubernetesVersionInfo(kubeClient kubernetes.Interface) {
func mustConfirmMinimumK8sVersionCriteria(kubeClient kubernetes.Interface) (err error) {
k8sVersion, err := k8s.GetK8sVersion(kubeClient)
if err != nil {
glog.Fatalf("error retrieving k8s version: %v", err)
return fmt.Errorf("error retrieving k8s version: %w", err)

Check warning on line 307 in cmd/nginx-ingress/main.go

View check run for this annotation

Codecov / codecov/patch

cmd/nginx-ingress/main.go#L307

Added line #L307 was not covered by tests
}
glog.Infof("Kubernetes version: %v", k8sVersion)

minK8sVersion, err := util_version.ParseGeneric("1.22.0")
if err != nil {
glog.Fatalf("unexpected error parsing minimum supported version: %v", err)
return fmt.Errorf("unexpected error parsing minimum supported version: %w", err)

Check warning on line 313 in cmd/nginx-ingress/main.go

View check run for this annotation

Codecov / codecov/patch

cmd/nginx-ingress/main.go#L313

Added line #L313 was not covered by tests
}

if !k8sVersion.AtLeast(minK8sVersion) {
glog.Fatalf("Versions of Kubernetes < %v are not supported, please refer to the documentation for details on supported versions and legacy controller support.", minK8sVersion)
return fmt.Errorf("versions of kubernetes < %v are not supported, please refer to the documentation for details on supported versions and legacy controller support", minK8sVersion)
}
return err
}

// mustValidateIngressClass calls internally os.Exit
// and terminates the program if the ingress class is not valid.
func mustValidateIngressClass(kubeClient kubernetes.Interface) {
// An Ingress resource can target a specific Ingress controller instance.
// This is useful when running multiple ingress controllers in the same cluster.
// Targeting an Ingress controller means only a specific controller should handle/implement the ingress resource.
// This can be done using either the IngressClassName field or the ingress.class annotation
// This function confirms that the Ingress resource is meant to be handled by NGINX Ingress Controller.
// Otherwise an error is returned to the caller
// This is defined in the const k8s.IngressControllerName
func mustValidateIngressClass(kubeClient kubernetes.Interface) (err error) {
ingressClassRes, err := kubeClient.NetworkingV1().IngressClasses().Get(context.TODO(), *ingressClass, meta_v1.GetOptions{})
if err != nil {
glog.Fatalf("Error when getting IngressClass %v: %v", *ingressClass, err)
return fmt.Errorf("error when getting IngressClass %v: %w", *ingressClass, err)
}

if ingressClassRes.Spec.Controller != k8s.IngressControllerName {
glog.Fatalf("IngressClass with name %v has an invalid Spec.Controller %v; expected %v", ingressClassRes.Name, ingressClassRes.Spec.Controller, k8s.IngressControllerName)
return fmt.Errorf("ingressClass with name %v has an invalid Spec.Controller %v; expected %v", ingressClassRes.Name, ingressClassRes.Spec.Controller, k8s.IngressControllerName)

Check warning on line 336 in cmd/nginx-ingress/main.go

View check run for this annotation

Codecov / codecov/patch

cmd/nginx-ingress/main.go#L336

Added line #L336 was not covered by tests
}

return err
}

func checkNamespaces(kubeClient kubernetes.Interface) {
Expand Down Expand Up @@ -349,29 +369,31 @@
}
}

func createCustomClients(config *rest.Config) (dynamic.Interface, k8s_nginx.Interface) {
var dynClient dynamic.Interface
var err error
if *appProtectDos || *appProtect || *ingressLink != "" {
dynClient, err = dynamic.NewForConfig(config)
if err != nil {
glog.Fatalf("Failed to create dynamic client: %v.", err)
}
}
var confClient k8s_nginx.Interface
func mustCreateConfigClient(config *rest.Config) (configClient k8s_nginx.Interface, err error) {
if *enableCustomResources {
confClient, err = k8s_nginx.NewForConfig(config)
configClient, err = k8s_nginx.NewForConfig(config)
if err != nil {
glog.Fatalf("Failed to create a conf client: %v", err)
return configClient, fmt.Errorf("failed to create a conf client: %w", err)

Check warning on line 376 in cmd/nginx-ingress/main.go

View check run for this annotation

Codecov / codecov/patch

cmd/nginx-ingress/main.go#L376

Added line #L376 was not covered by tests
}

// required for emitting Events for VirtualServer
err = conf_scheme.AddToScheme(scheme.Scheme)
if err != nil {
glog.Fatalf("Failed to add configuration types to the scheme: %v", err)
return configClient, fmt.Errorf("failed to add configuration types to the scheme: %w", err)

Check warning on line 382 in cmd/nginx-ingress/main.go

View check run for this annotation

Codecov / codecov/patch

cmd/nginx-ingress/main.go#L382

Added line #L382 was not covered by tests
}
}
return configClient, err
}

// Creates a new dynamic client or returns an error
func mustCreateDynamicClient(config *rest.Config) (dynClient dynamic.Interface, err error) {
if *appProtectDos || *appProtect || *ingressLink != "" {
dynClient, err = dynamic.NewForConfig(config)
if err != nil {
return dynClient, fmt.Errorf("failed to create dynamic client: %w", err)

Check warning on line 393 in cmd/nginx-ingress/main.go

View check run for this annotation

Codecov / codecov/patch

cmd/nginx-ingress/main.go#L389-L393

Added lines #L389 - L393 were not covered by tests
}
}
return dynClient, confClient
return dynClient, err

Check warning on line 396 in cmd/nginx-ingress/main.go

View check run for this annotation

Codecov / codecov/patch

cmd/nginx-ingress/main.go#L396

Added line #L396 was not covered by tests
}

func createPlusClient(nginxPlus bool, useFakeNginxManager bool, nginxManager nginx.Manager) *client.NginxClient {
Expand Down
137 changes: 137 additions & 0 deletions cmd/nginx-ingress/main_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,137 @@
package main

import (
"fmt"
"testing"

"github.com/nginxinc/kubernetes-ingress/internal/k8s"
networkingv1 "k8s.io/api/networking/v1"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
apiVersion "k8s.io/apimachinery/pkg/version"
fakeDisc "k8s.io/client-go/discovery/fake"
"k8s.io/client-go/kubernetes"
"k8s.io/client-go/kubernetes/fake"
)

func TestCreateConfigClient(t *testing.T) {
*enableCustomResources = true
{
*proxyURL = "localhost"
config, err := mustGetClientConfig()
if err != nil {
t.Errorf("Failed to get client config: %v", err)
}

// This code block tests the working scenario
{
_, err := mustCreateConfigClient(config)
if err != nil {
t.Errorf("Failed to create client config: %v", err)
}
}
}
}

func TestMinimumK8sVersion(t *testing.T) {
// Create a fake client -
// WARNING: NewSimpleClientset is deprecated
clientset := fake.NewSimpleClientset()

// Override the ServerVersion method on the fake Discovery client
discoveryClient, ok := clientset.Discovery().(*fakeDisc.FakeDiscovery)
if !ok {
fmt.Println("couldn't convert Discovery() to *FakeDiscovery")
}

// This test block is when the correct/expected k8s version is returned
{
correctVersion := &apiVersion.Info{
Major: "1", Minor: "22", GitVersion: "v1.22.2",
}
discoveryClient.FakedServerVersion = correctVersion

// Get the server version as a sanity check
_, err := discoveryClient.ServerVersion()
if err != nil {
t.Fatalf("Failed to get server version: %v", err)
}

// Verify if the mocked server version is as expected.
if err := mustConfirmMinimumK8sVersionCriteria(clientset); err != nil {
t.Fatalf("Error in checking minimum k8s version: %v", err)
}
}

// This test block is when the incorrect/unexpected k8s version is returned
// i.e. not the min supported version
{
wrongVersion := &apiVersion.Info{
Major: "1", Minor: "19", GitVersion: "v1.19.2",
}
discoveryClient.FakedServerVersion = wrongVersion

// Get the server version as a sanity check
_, err := discoveryClient.ServerVersion()
if err != nil {
t.Fatalf("Failed to get server version: %v", err)
}

// Verify if the mocked server version returns an error as we are testing for < 1.22 (v1.19.2).
if err := mustConfirmMinimumK8sVersionCriteria(clientset); err == nil {
t.Fatalf("Expected an error when checking minimum k8s version but got none: %v", err)
}
}
}

// Test valid (nginx) and invalid (other) ingress classes
func TestValidateIngressClass(t *testing.T) {
// Define an IngressClass
{
ingressClass := &networkingv1.IngressClass{
ObjectMeta: metav1.ObjectMeta{
Name: "nginx",
},
Spec: networkingv1.IngressClassSpec{
Controller: k8s.IngressControllerName,
},
}
// Create a fake client
clientset := fake.NewSimpleClientset(ingressClass)

validData := []struct {
clientset kubernetes.Interface
}{
{
clientset: clientset,
},
}

if err := mustValidateIngressClass(validData[0].clientset); err != nil {
t.Fatalf("error in ingress class, error: %v", err)
}
}

// Test invalid case
{
ingressClass := &networkingv1.IngressClass{
ObjectMeta: metav1.ObjectMeta{
Name: "not-nginx",
},
Spec: networkingv1.IngressClassSpec{
Controller: "www.example.com/ingress-controller",
},
}
clientset := fake.NewSimpleClientset(ingressClass)
inValidData := []struct {
clientset kubernetes.Interface
}{
{
clientset: clientset,
},
}

if err := mustValidateIngressClass(inValidData[0].clientset); err == nil {
t.Fatalf("validateIngressClass() returned no error for invalid input, error: %v", err)
}
}
}
Loading