Skip to content

Commit

Permalink
Issue 4837: chore: main_test.go unit-tests
Browse files Browse the repository at this point in the history
* Added test for mustCreateConfigClient, mustValidateIngressClass, mustConfirmMinimumK8sVersionCriteria
  • Loading branch information
mrajagopal committed Aug 30, 2024
1 parent a41fc13 commit f58aa90
Show file tree
Hide file tree
Showing 2 changed files with 203 additions and 44 deletions.
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 @@ func main() {

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 main() {
}
}

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 @@ func mustCreateConfigAndKubeClient() (*rest.Config, *kubernetes.Clientset) {
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 checkNamespaceExists(kubeClient kubernetes.Interface, namespaces []string)
}
}

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)
}
}
}

0 comments on commit f58aa90

Please sign in to comment.