github.com/juju/juju@v0.0.0-20240430160146-1752b71fcf00/caas/kubernetes/provider/ingress_test.go (about)

     1  // Copyright 2019 Canonical Ltd.
     2  // Licensed under the AGPLv3, see LICENCE file for details.
     3  
     4  package provider_test
     5  
     6  import (
     7  	jc "github.com/juju/testing/checkers"
     8  	"go.uber.org/mock/gomock"
     9  	gc "gopkg.in/check.v1"
    10  	apps "k8s.io/api/apps/v1"
    11  	appsv1 "k8s.io/api/apps/v1"
    12  	core "k8s.io/api/core/v1"
    13  	networkingv1 "k8s.io/api/networking/v1"
    14  	networkingv1beta1 "k8s.io/api/networking/v1beta1"
    15  	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
    16  	"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
    17  	"k8s.io/apimachinery/pkg/runtime"
    18  	k8stypes "k8s.io/apimachinery/pkg/types"
    19  	"k8s.io/apimachinery/pkg/util/intstr"
    20  	k8sversion "k8s.io/apimachinery/pkg/version"
    21  	"k8s.io/utils/pointer"
    22  
    23  	"github.com/juju/juju/caas"
    24  	"github.com/juju/juju/caas/kubernetes/provider"
    25  	k8sspecs "github.com/juju/juju/caas/kubernetes/provider/specs"
    26  	"github.com/juju/juju/core/config"
    27  	"github.com/juju/juju/core/resources"
    28  	"github.com/juju/juju/core/status"
    29  	"github.com/juju/juju/testing"
    30  )
    31  
    32  func (s *K8sBrokerSuite) assertIngressResources(c *gc.C, ingressResources []k8sspecs.K8sIngress, expectedErrString string, assertCalls ...any) {
    33  	basicPodSpec := getBasicPodspec()
    34  	basicPodSpec.ProviderPod = &k8sspecs.K8sPodSpec{
    35  		KubernetesResources: &k8sspecs.KubernetesResources{
    36  			IngressResources: ingressResources,
    37  		},
    38  	}
    39  	workloadSpec, err := provider.PrepareWorkloadSpec(
    40  		"app-name", "app-name", basicPodSpec, resources.DockerImageDetails{RegistryPath: "operator/image-path"},
    41  	)
    42  	c.Assert(err, jc.ErrorIsNil)
    43  	podSpec := provider.Pod(workloadSpec).PodSpec
    44  
    45  	numUnits := int32(2)
    46  	statefulSetArg := &appsv1.StatefulSet{
    47  		ObjectMeta: metav1.ObjectMeta{
    48  			Name:   "app-name",
    49  			Labels: map[string]string{"app.kubernetes.io/managed-by": "juju", "app.kubernetes.io/name": "app-name"},
    50  			Annotations: map[string]string{
    51  				"app.juju.is/uuid":               "appuuid",
    52  				"controller.juju.is/id":          testing.ControllerTag.Id(),
    53  				"charm.juju.is/modified-version": "0",
    54  			},
    55  		},
    56  		Spec: appsv1.StatefulSetSpec{
    57  			Replicas: &numUnits,
    58  			Selector: &metav1.LabelSelector{
    59  				MatchLabels: map[string]string{"app.kubernetes.io/name": "app-name"},
    60  			},
    61  			RevisionHistoryLimit: pointer.Int32Ptr(0),
    62  			Template: core.PodTemplateSpec{
    63  				ObjectMeta: metav1.ObjectMeta{
    64  					Labels: map[string]string{"app.kubernetes.io/name": "app-name"},
    65  					Annotations: map[string]string{
    66  						"apparmor.security.beta.kubernetes.io/pod": "runtime/default",
    67  						"seccomp.security.beta.kubernetes.io/pod":  "docker/default",
    68  						"controller.juju.is/id":                    testing.ControllerTag.Id(),
    69  						"charm.juju.is/modified-version":           "0",
    70  					},
    71  				},
    72  				Spec: podSpec,
    73  			},
    74  			PodManagementPolicy: apps.ParallelPodManagement,
    75  			ServiceName:         "app-name-endpoints",
    76  		},
    77  	}
    78  
    79  	serviceArg := *basicServiceArg
    80  	serviceArg.Spec.Type = core.ServiceTypeClusterIP
    81  
    82  	assertCalls = append(
    83  		[]any{
    84  			s.mockStatefulSets.EXPECT().Get(gomock.Any(), "juju-operator-app-name", metav1.GetOptions{}).
    85  				Return(nil, s.k8sNotFoundError()),
    86  		},
    87  		assertCalls...,
    88  	)
    89  
    90  	ociImageSecret := s.getOCIImageSecret(c, nil)
    91  	if expectedErrString == "" {
    92  		// no error expected, so continue to check following assertions.
    93  		assertCalls = append(assertCalls, []any{
    94  			s.mockSecrets.EXPECT().Create(gomock.Any(), ociImageSecret, metav1.CreateOptions{}).
    95  				Return(ociImageSecret, nil),
    96  			s.mockServices.EXPECT().Get(gomock.Any(), "app-name", metav1.GetOptions{}).
    97  				Return(nil, s.k8sNotFoundError()),
    98  			s.mockServices.EXPECT().Update(gomock.Any(), &serviceArg, metav1.UpdateOptions{}).
    99  				Return(nil, s.k8sNotFoundError()),
   100  			s.mockServices.EXPECT().Create(gomock.Any(), &serviceArg, metav1.CreateOptions{}).
   101  				Return(nil, nil),
   102  			s.mockServices.EXPECT().Get(gomock.Any(), "app-name-endpoints", metav1.GetOptions{}).
   103  				Return(nil, s.k8sNotFoundError()),
   104  			s.mockServices.EXPECT().Update(gomock.Any(), basicHeadlessServiceArg, metav1.UpdateOptions{}).
   105  				Return(nil, s.k8sNotFoundError()),
   106  			s.mockServices.EXPECT().Create(gomock.Any(), basicHeadlessServiceArg, metav1.CreateOptions{}).
   107  				Return(nil, nil),
   108  			s.mockStatefulSets.EXPECT().Get(gomock.Any(), "app-name", metav1.GetOptions{}).
   109  				Return(statefulSetArg, nil),
   110  			s.mockStatefulSets.EXPECT().Create(gomock.Any(), statefulSetArg, metav1.CreateOptions{}).
   111  				Return(nil, nil),
   112  		}...)
   113  	}
   114  	gomock.InOrder(assertCalls...)
   115  
   116  	params := &caas.ServiceParams{
   117  		PodSpec: basicPodSpec,
   118  		Deployment: caas.DeploymentParams{
   119  			DeploymentType: caas.DeploymentStateful,
   120  		},
   121  		ImageDetails: resources.DockerImageDetails{RegistryPath: "operator/image-path"},
   122  		ResourceTags: map[string]string{"juju-controller-uuid": testing.ControllerTag.Id()},
   123  	}
   124  	err = s.broker.EnsureService("app-name", func(_ string, _ status.Status, e string, _ map[string]interface{}) error {
   125  		c.Logf("EnsureService error -> %q", e)
   126  		return nil
   127  	}, params, 2, config.ConfigAttributes{
   128  		"kubernetes-service-loadbalancer-ip": "10.0.0.1",
   129  		"kubernetes-service-externalname":    "ext-name",
   130  	})
   131  	if expectedErrString != "" {
   132  		c.Assert(err, gc.ErrorMatches, expectedErrString)
   133  	} else {
   134  		c.Assert(err, jc.ErrorIsNil)
   135  	}
   136  }
   137  
   138  func (s *K8sBrokerSuite) TestEnsureServiceIngressResourcesCreateV1Beta1(c *gc.C) {
   139  	ctrl := s.setupController(c)
   140  	defer ctrl.Finish()
   141  
   142  	gomock.InOrder(
   143  		s.mockDiscovery.EXPECT().ServerVersion().Return(&k8sversion.Info{
   144  			Major: "1", Minor: "18",
   145  		}, nil),
   146  	)
   147  
   148  	ingress1Rule1 := networkingv1beta1.IngressRule{
   149  		IngressRuleValue: networkingv1beta1.IngressRuleValue{
   150  			HTTP: &networkingv1beta1.HTTPIngressRuleValue{
   151  				Paths: []networkingv1beta1.HTTPIngressPath{
   152  					{
   153  						Path: "/testpath",
   154  						Backend: networkingv1beta1.IngressBackend{
   155  							ServiceName: "test",
   156  							ServicePort: intstr.IntOrString{IntVal: 80},
   157  						},
   158  					},
   159  				},
   160  			},
   161  		},
   162  	}
   163  	ingress1 := k8sspecs.K8sIngress{
   164  		Meta: k8sspecs.Meta{
   165  			Name: "test-ingress",
   166  			Labels: map[string]string{
   167  				"foo": "bar",
   168  			},
   169  			Annotations: map[string]string{
   170  				"nginx.ingress.kubernetes.io/rewrite-target": "/",
   171  			},
   172  		},
   173  		Spec: k8sspecs.K8sIngressSpec{
   174  			Version: k8sspecs.K8sIngressV1Beta1,
   175  			SpecV1Beta1: networkingv1beta1.IngressSpec{
   176  				Rules: []networkingv1beta1.IngressRule{ingress1Rule1},
   177  			},
   178  		},
   179  	}
   180  
   181  	ingressResources := []k8sspecs.K8sIngress{ingress1}
   182  	ingress := &networkingv1beta1.Ingress{
   183  		ObjectMeta: metav1.ObjectMeta{
   184  			Name:   "test-ingress",
   185  			Labels: map[string]string{"app.kubernetes.io/managed-by": "juju", "app.kubernetes.io/name": "app-name", "foo": "bar"},
   186  			Annotations: map[string]string{
   187  				"nginx.ingress.kubernetes.io/rewrite-target": "/",
   188  				"controller.juju.is/id":                      "deadbeef-1bad-500d-9000-4b1d0d06f00d",
   189  			},
   190  		},
   191  		Spec: networkingv1beta1.IngressSpec{
   192  			Rules: []networkingv1beta1.IngressRule{ingress1Rule1},
   193  		},
   194  	}
   195  	s.assertIngressResources(
   196  		c, ingressResources, "",
   197  		s.mockIngressV1Beta1.EXPECT().Create(gomock.Any(), ingress, metav1.CreateOptions{}).Return(ingress, nil),
   198  	)
   199  }
   200  
   201  func (s *K8sBrokerSuite) TestEnsureServiceIngressResourcesUpdateV1Beta1(c *gc.C) {
   202  	ctrl := s.setupController(c)
   203  	defer ctrl.Finish()
   204  
   205  	gomock.InOrder(
   206  		s.mockDiscovery.EXPECT().ServerVersion().Return(&k8sversion.Info{
   207  			Major: "1", Minor: "18",
   208  		}, nil),
   209  	)
   210  
   211  	ingress1Rule1 := networkingv1beta1.IngressRule{
   212  		IngressRuleValue: networkingv1beta1.IngressRuleValue{
   213  			HTTP: &networkingv1beta1.HTTPIngressRuleValue{
   214  				Paths: []networkingv1beta1.HTTPIngressPath{
   215  					{
   216  						Path: "/testpath",
   217  						Backend: networkingv1beta1.IngressBackend{
   218  							ServiceName: "test",
   219  							ServicePort: intstr.IntOrString{IntVal: 80},
   220  						},
   221  					},
   222  				},
   223  			},
   224  		},
   225  	}
   226  	ingress1 := k8sspecs.K8sIngress{
   227  		Meta: k8sspecs.Meta{
   228  			Name: "test-ingress",
   229  			Labels: map[string]string{
   230  				"foo": "bar",
   231  			},
   232  			Annotations: map[string]string{
   233  				"nginx.ingress.kubernetes.io/rewrite-target": "/",
   234  			},
   235  		},
   236  		Spec: k8sspecs.K8sIngressSpec{
   237  			Version: k8sspecs.K8sIngressV1Beta1,
   238  			SpecV1Beta1: networkingv1beta1.IngressSpec{
   239  				Rules: []networkingv1beta1.IngressRule{ingress1Rule1},
   240  			},
   241  		},
   242  	}
   243  
   244  	ingressResources := []k8sspecs.K8sIngress{ingress1}
   245  	ingress := &networkingv1beta1.Ingress{
   246  		ObjectMeta: metav1.ObjectMeta{
   247  			Name:   "test-ingress",
   248  			Labels: map[string]string{"app.kubernetes.io/managed-by": "juju", "app.kubernetes.io/name": "app-name", "foo": "bar"},
   249  			Annotations: map[string]string{
   250  				"nginx.ingress.kubernetes.io/rewrite-target": "/",
   251  				"controller.juju.is/id":                      "deadbeef-1bad-500d-9000-4b1d0d06f00d",
   252  			},
   253  		},
   254  		Spec: networkingv1beta1.IngressSpec{
   255  			Rules: []networkingv1beta1.IngressRule{ingress1Rule1},
   256  		},
   257  	}
   258  	data, err := runtime.Encode(unstructured.UnstructuredJSONScheme, ingress)
   259  	c.Assert(err, jc.ErrorIsNil)
   260  	s.assertIngressResources(
   261  		c, ingressResources, "",
   262  		s.mockIngressV1Beta1.EXPECT().Create(gomock.Any(), ingress, metav1.CreateOptions{}).Return(nil, s.k8sAlreadyExistsError()),
   263  		s.mockIngressV1Beta1.EXPECT().Get(gomock.Any(), "test-ingress", metav1.GetOptions{}).Return(ingress, nil),
   264  		s.mockIngressV1Beta1.EXPECT().
   265  			Patch(gomock.Any(), ingress.GetName(), k8stypes.StrategicMergePatchType, data, metav1.PatchOptions{FieldManager: "juju"}).
   266  			Return(ingress, nil),
   267  	)
   268  }
   269  
   270  func (s *K8sBrokerSuite) TestEnsureServiceIngressResourcesUpdateConflictWithExistingNonJujuManagedIngressV1Beta1(c *gc.C) {
   271  	ctrl := s.setupController(c)
   272  	defer ctrl.Finish()
   273  
   274  	gomock.InOrder(
   275  		s.mockDiscovery.EXPECT().ServerVersion().Return(&k8sversion.Info{
   276  			Major: "1", Minor: "18",
   277  		}, nil),
   278  	)
   279  
   280  	ingress1Rule1 := networkingv1beta1.IngressRule{
   281  		IngressRuleValue: networkingv1beta1.IngressRuleValue{
   282  			HTTP: &networkingv1beta1.HTTPIngressRuleValue{
   283  				Paths: []networkingv1beta1.HTTPIngressPath{
   284  					{
   285  						Path: "/testpath",
   286  						Backend: networkingv1beta1.IngressBackend{
   287  							ServiceName: "test",
   288  							ServicePort: intstr.IntOrString{IntVal: 80},
   289  						},
   290  					},
   291  				},
   292  			},
   293  		},
   294  	}
   295  	ingress1 := k8sspecs.K8sIngress{
   296  		Meta: k8sspecs.Meta{
   297  			Name: "test-ingress",
   298  			Labels: map[string]string{
   299  				"foo": "bar",
   300  			},
   301  			Annotations: map[string]string{
   302  				"nginx.ingress.kubernetes.io/rewrite-target": "/",
   303  			},
   304  		},
   305  
   306  		Spec: k8sspecs.K8sIngressSpec{
   307  			Version: k8sspecs.K8sIngressV1Beta1,
   308  			SpecV1Beta1: networkingv1beta1.IngressSpec{
   309  				Rules: []networkingv1beta1.IngressRule{ingress1Rule1},
   310  			},
   311  		},
   312  	}
   313  
   314  	ingressResources := []k8sspecs.K8sIngress{ingress1}
   315  
   316  	getIngress := func() *networkingv1beta1.Ingress {
   317  		return &networkingv1beta1.Ingress{
   318  			ObjectMeta: metav1.ObjectMeta{
   319  				Name:   "test-ingress",
   320  				Labels: map[string]string{"app.kubernetes.io/managed-by": "juju", "app.kubernetes.io/name": "app-name", "foo": "bar"},
   321  				Annotations: map[string]string{
   322  					"nginx.ingress.kubernetes.io/rewrite-target": "/",
   323  					"controller.juju.is/id":                      "deadbeef-1bad-500d-9000-4b1d0d06f00d",
   324  				},
   325  			},
   326  			Spec: networkingv1beta1.IngressSpec{
   327  				Rules: []networkingv1beta1.IngressRule{ingress1Rule1},
   328  			},
   329  		}
   330  	}
   331  	ingress := getIngress()
   332  	existingNonJujuManagedIngress := getIngress()
   333  	existingNonJujuManagedIngress.SetLabels(map[string]string{})
   334  	s.assertIngressResources(
   335  		c, ingressResources, `creating or updating ingress resources: ensuring ingress "test-ingress" with version "v1beta1": existing ingress "test-ingress" found which does not belong to "app-name"`,
   336  		s.mockIngressV1Beta1.EXPECT().Create(gomock.Any(), ingress, gomock.Any()).Return(nil, s.k8sAlreadyExistsError()),
   337  		s.mockIngressV1Beta1.EXPECT().Get(gomock.Any(), "test-ingress", metav1.GetOptions{}).Return(existingNonJujuManagedIngress, nil),
   338  	)
   339  }
   340  
   341  func (s *K8sBrokerSuite) TestEnsureServiceIngressResourcesCreateV1(c *gc.C) {
   342  	ctrl := s.setupController(c)
   343  	defer ctrl.Finish()
   344  
   345  	gomock.InOrder(
   346  		s.mockDiscovery.EXPECT().ServerVersion().Return(&k8sversion.Info{
   347  			Major: "1", Minor: "22",
   348  		}, nil),
   349  	)
   350  
   351  	ingress1Rule1 := networkingv1.IngressRule{
   352  		IngressRuleValue: networkingv1.IngressRuleValue{
   353  			HTTP: &networkingv1.HTTPIngressRuleValue{
   354  				Paths: []networkingv1.HTTPIngressPath{
   355  					{
   356  						Path: "/testpath",
   357  						Backend: networkingv1.IngressBackend{
   358  							Resource: &core.TypedLocalObjectReference{
   359  								APIGroup: pointer.StringPtr("k8s.example.com"),
   360  								Kind:     "StorageBucket",
   361  								Name:     "icon-assets",
   362  							},
   363  						},
   364  					},
   365  				},
   366  			},
   367  		},
   368  	}
   369  	ingress1 := k8sspecs.K8sIngress{
   370  		Meta: k8sspecs.Meta{
   371  			Name: "test-ingress",
   372  			Labels: map[string]string{
   373  				"foo": "bar",
   374  			},
   375  			Annotations: map[string]string{
   376  				"nginx.ingress.kubernetes.io/rewrite-target": "/",
   377  			},
   378  		},
   379  		Spec: k8sspecs.K8sIngressSpec{
   380  			Version: k8sspecs.K8sIngressV1,
   381  			SpecV1: networkingv1.IngressSpec{
   382  				Rules: []networkingv1.IngressRule{ingress1Rule1},
   383  			},
   384  		},
   385  	}
   386  
   387  	ingressResources := []k8sspecs.K8sIngress{ingress1}
   388  	ingress := &networkingv1.Ingress{
   389  		ObjectMeta: metav1.ObjectMeta{
   390  			Name: "test-ingress",
   391  			Labels: map[string]string{
   392  				"foo":                          "bar",
   393  				"app.kubernetes.io/name":       "app-name",
   394  				"app.kubernetes.io/managed-by": "juju",
   395  			},
   396  			Annotations: map[string]string{
   397  				"nginx.ingress.kubernetes.io/rewrite-target": "/",
   398  				"controller.juju.is/id":                      "deadbeef-1bad-500d-9000-4b1d0d06f00d",
   399  			},
   400  		},
   401  		Spec: networkingv1.IngressSpec{
   402  			Rules: []networkingv1.IngressRule{ingress1Rule1},
   403  		},
   404  	}
   405  	s.assertIngressResources(
   406  		c, ingressResources, "",
   407  		s.mockIngressV1.EXPECT().Create(gomock.Any(), ingress, gomock.Any()).Return(ingress, nil),
   408  	)
   409  }
   410  
   411  func (s *K8sBrokerSuite) TestEnsureServiceIngressResourcesUpdateV1(c *gc.C) {
   412  	ctrl := s.setupController(c)
   413  	defer ctrl.Finish()
   414  
   415  	gomock.InOrder(
   416  		s.mockDiscovery.EXPECT().ServerVersion().Return(&k8sversion.Info{
   417  			Major: "1", Minor: "22",
   418  		}, nil),
   419  	)
   420  
   421  	ingress1Rule1 := networkingv1.IngressRule{
   422  		IngressRuleValue: networkingv1.IngressRuleValue{
   423  			HTTP: &networkingv1.HTTPIngressRuleValue{
   424  				Paths: []networkingv1.HTTPIngressPath{
   425  					{
   426  						Path: "/testpath",
   427  						Backend: networkingv1.IngressBackend{
   428  							Resource: &core.TypedLocalObjectReference{
   429  								APIGroup: pointer.StringPtr("k8s.example.com"),
   430  								Kind:     "StorageBucket",
   431  								Name:     "icon-assets",
   432  							},
   433  						},
   434  					},
   435  				},
   436  			},
   437  		},
   438  	}
   439  	ingress1 := k8sspecs.K8sIngress{
   440  		Meta: k8sspecs.Meta{
   441  			Name: "test-ingress",
   442  			Labels: map[string]string{
   443  				"foo": "bar",
   444  			},
   445  			Annotations: map[string]string{
   446  				"nginx.ingress.kubernetes.io/rewrite-target": "/",
   447  			},
   448  		},
   449  		Spec: k8sspecs.K8sIngressSpec{
   450  			Version: k8sspecs.K8sIngressV1,
   451  			SpecV1: networkingv1.IngressSpec{
   452  				Rules: []networkingv1.IngressRule{ingress1Rule1},
   453  			},
   454  		},
   455  	}
   456  
   457  	ingressResources := []k8sspecs.K8sIngress{ingress1}
   458  	ingress := &networkingv1.Ingress{
   459  		ObjectMeta: metav1.ObjectMeta{
   460  			Name: "test-ingress",
   461  			Labels: map[string]string{
   462  				"foo":                          "bar",
   463  				"app.kubernetes.io/name":       "app-name",
   464  				"app.kubernetes.io/managed-by": "juju",
   465  			},
   466  			Annotations: map[string]string{
   467  				"nginx.ingress.kubernetes.io/rewrite-target": "/",
   468  				"controller.juju.is/id":                      "deadbeef-1bad-500d-9000-4b1d0d06f00d",
   469  			},
   470  		},
   471  		Spec: networkingv1.IngressSpec{
   472  			Rules: []networkingv1.IngressRule{ingress1Rule1},
   473  		},
   474  	}
   475  	data, err := runtime.Encode(unstructured.UnstructuredJSONScheme, ingress)
   476  	c.Assert(err, jc.ErrorIsNil)
   477  	s.assertIngressResources(
   478  		c, ingressResources, "",
   479  		s.mockIngressV1.EXPECT().Create(gomock.Any(), ingress, gomock.Any()).Return(nil, s.k8sAlreadyExistsError()),
   480  		s.mockIngressV1.EXPECT().Get(gomock.Any(), "test-ingress", metav1.GetOptions{}).Return(ingress, nil),
   481  		s.mockIngressV1.EXPECT().
   482  			Patch(gomock.Any(), ingress.GetName(), k8stypes.StrategicMergePatchType, data, metav1.PatchOptions{FieldManager: "juju"}).
   483  			Return(ingress, nil),
   484  	)
   485  }
   486  
   487  func (s *K8sBrokerSuite) TestEnsureServiceIngressResourcesUpdateConflictWithExistingNonJujuManagedIngressV1(c *gc.C) {
   488  	ctrl := s.setupController(c)
   489  	defer ctrl.Finish()
   490  
   491  	gomock.InOrder(
   492  		s.mockDiscovery.EXPECT().ServerVersion().Return(&k8sversion.Info{
   493  			Major: "1", Minor: "22",
   494  		}, nil),
   495  	)
   496  
   497  	ingress1Rule1 := networkingv1.IngressRule{
   498  		IngressRuleValue: networkingv1.IngressRuleValue{
   499  			HTTP: &networkingv1.HTTPIngressRuleValue{
   500  				Paths: []networkingv1.HTTPIngressPath{
   501  					{
   502  						Path: "/testpath",
   503  						Backend: networkingv1.IngressBackend{
   504  							Resource: &core.TypedLocalObjectReference{
   505  								APIGroup: pointer.StringPtr("k8s.example.com"),
   506  								Kind:     "StorageBucket",
   507  								Name:     "icon-assets",
   508  							},
   509  						},
   510  					},
   511  				},
   512  			},
   513  		},
   514  	}
   515  	ingress1 := k8sspecs.K8sIngress{
   516  		Meta: k8sspecs.Meta{
   517  			Name: "test-ingress",
   518  			Labels: map[string]string{
   519  				"foo": "bar",
   520  			},
   521  			Annotations: map[string]string{
   522  				"nginx.ingress.kubernetes.io/rewrite-target": "/",
   523  			},
   524  		},
   525  
   526  		Spec: k8sspecs.K8sIngressSpec{
   527  			Version: k8sspecs.K8sIngressV1,
   528  			SpecV1: networkingv1.IngressSpec{
   529  				Rules: []networkingv1.IngressRule{ingress1Rule1},
   530  			},
   531  		},
   532  	}
   533  
   534  	ingressResources := []k8sspecs.K8sIngress{ingress1}
   535  
   536  	getIngress := func() *networkingv1.Ingress {
   537  		return &networkingv1.Ingress{
   538  			ObjectMeta: metav1.ObjectMeta{
   539  				Name: "test-ingress",
   540  				Labels: map[string]string{
   541  					"foo":                          "bar",
   542  					"app.kubernetes.io/name":       "app-name",
   543  					"app.kubernetes.io/managed-by": "juju",
   544  				},
   545  				Annotations: map[string]string{
   546  					"nginx.ingress.kubernetes.io/rewrite-target": "/",
   547  					"controller.juju.is/id":                      "deadbeef-1bad-500d-9000-4b1d0d06f00d",
   548  				},
   549  			},
   550  			Spec: networkingv1.IngressSpec{
   551  				Rules: []networkingv1.IngressRule{ingress1Rule1},
   552  			},
   553  		}
   554  	}
   555  	ingress := getIngress()
   556  	existingNonJujuManagedIngress := getIngress()
   557  	existingNonJujuManagedIngress.SetLabels(map[string]string{})
   558  	s.assertIngressResources(
   559  		c, ingressResources, `creating or updating ingress resources: ensuring ingress "test-ingress" with version "v1": existing ingress "test-ingress" found which does not belong to "app-name"`,
   560  		s.mockIngressV1.EXPECT().Create(gomock.Any(), ingress, gomock.Any()).Return(nil, s.k8sAlreadyExistsError()),
   561  		s.mockIngressV1.EXPECT().Get(gomock.Any(), "test-ingress", metav1.GetOptions{}).Return(existingNonJujuManagedIngress, nil),
   562  	)
   563  }
   564  
   565  func (s *K8sBrokerSuite) TestEnsureServiceIngressResourcesUpdateConflictWithIngressCreatedByJujuExpose(c *gc.C) {
   566  	ctrl := s.setupController(c)
   567  	defer ctrl.Finish()
   568  
   569  	gomock.InOrder(
   570  		s.mockDiscovery.EXPECT().ServerVersion().Return(&k8sversion.Info{
   571  			Major: "1", Minor: "22",
   572  		}, nil),
   573  	)
   574  
   575  	ingress1Rule1 := networkingv1beta1.IngressRule{
   576  		IngressRuleValue: networkingv1beta1.IngressRuleValue{
   577  			HTTP: &networkingv1beta1.HTTPIngressRuleValue{
   578  				Paths: []networkingv1beta1.HTTPIngressPath{
   579  					{
   580  						Path: "/testpath",
   581  						Backend: networkingv1beta1.IngressBackend{
   582  							ServiceName: "test",
   583  							ServicePort: intstr.IntOrString{IntVal: 80},
   584  						},
   585  					},
   586  				},
   587  			},
   588  		},
   589  	}
   590  	ingress1 := k8sspecs.K8sIngress{
   591  		Meta: k8sspecs.Meta{
   592  			Name: "app-name",
   593  			Labels: map[string]string{
   594  				"foo": "bar",
   595  			},
   596  			Annotations: map[string]string{
   597  				"nginx.ingress.kubernetes.io/rewrite-target": "/",
   598  			},
   599  		},
   600  		Spec: k8sspecs.K8sIngressSpec{
   601  			Version: k8sspecs.K8sIngressV1Beta1,
   602  			SpecV1Beta1: networkingv1beta1.IngressSpec{
   603  				Rules: []networkingv1beta1.IngressRule{ingress1Rule1},
   604  			},
   605  		},
   606  	}
   607  
   608  	ingressResources := []k8sspecs.K8sIngress{ingress1}
   609  	s.assertIngressResources(
   610  		c, ingressResources, `creating or updating ingress resources: ingress name "app-name" is reserved for juju expose not valid`,
   611  	)
   612  }
   613  
   614  func (s *K8sBrokerSuite) TestEnsureServiceIngressResourcesV1Beta1OnV1Cluster(c *gc.C) {
   615  	ctrl := s.setupController(c)
   616  	defer ctrl.Finish()
   617  
   618  	gomock.InOrder(
   619  		s.mockDiscovery.EXPECT().ServerVersion().Return(&k8sversion.Info{
   620  			Major: "1", Minor: "19",
   621  		}, nil),
   622  	)
   623  
   624  	ingress1Rule1 := networkingv1beta1.IngressRule{
   625  		IngressRuleValue: networkingv1beta1.IngressRuleValue{
   626  			HTTP: &networkingv1beta1.HTTPIngressRuleValue{
   627  				Paths: []networkingv1beta1.HTTPIngressPath{
   628  					{
   629  						Path: "/testpath",
   630  						Backend: networkingv1beta1.IngressBackend{
   631  							ServiceName: "test",
   632  							ServicePort: intstr.IntOrString{IntVal: 80},
   633  						},
   634  					},
   635  				},
   636  			},
   637  		},
   638  	}
   639  	ingress1 := k8sspecs.K8sIngress{
   640  		Meta: k8sspecs.Meta{
   641  			Name: "test-ingress",
   642  		},
   643  		Spec: k8sspecs.K8sIngressSpec{
   644  			Version: k8sspecs.K8sIngressV1Beta1,
   645  			SpecV1Beta1: networkingv1beta1.IngressSpec{
   646  				Rules: []networkingv1beta1.IngressRule{ingress1Rule1},
   647  			},
   648  		},
   649  	}
   650  
   651  	pathType := networkingv1.PathTypeImplementationSpecific
   652  	ingressResources := []k8sspecs.K8sIngress{ingress1}
   653  	ingress := &networkingv1.Ingress{
   654  		ObjectMeta: metav1.ObjectMeta{
   655  			Name:   "test-ingress",
   656  			Labels: map[string]string{"app.kubernetes.io/managed-by": "juju", "app.kubernetes.io/name": "app-name"},
   657  			Annotations: map[string]string{
   658  				"controller.juju.is/id": "deadbeef-1bad-500d-9000-4b1d0d06f00d",
   659  			},
   660  		},
   661  		Spec: networkingv1.IngressSpec{
   662  			Rules: []networkingv1.IngressRule{{
   663  				IngressRuleValue: networkingv1.IngressRuleValue{
   664  					HTTP: &networkingv1.HTTPIngressRuleValue{
   665  						Paths: []networkingv1.HTTPIngressPath{
   666  							{
   667  								Path:     "/testpath",
   668  								PathType: &pathType,
   669  								Backend: networkingv1.IngressBackend{
   670  									Service: &networkingv1.IngressServiceBackend{
   671  										Name: "test",
   672  										Port: networkingv1.ServiceBackendPort{Number: 80},
   673  									},
   674  								},
   675  							},
   676  						},
   677  					},
   678  				},
   679  			}},
   680  		},
   681  	}
   682  	s.assertIngressResources(
   683  		c, ingressResources, "",
   684  		s.mockIngressV1.EXPECT().Create(gomock.Any(), ingress, metav1.CreateOptions{}).Return(ingress, nil),
   685  	)
   686  }
   687  
   688  func (s *K8sBrokerSuite) TestEnsureServiceIngressResourcesV1OnV1BetaCluster(c *gc.C) {
   689  	ctrl := s.setupController(c)
   690  	defer ctrl.Finish()
   691  
   692  	gomock.InOrder(
   693  		s.mockDiscovery.EXPECT().ServerVersion().Return(&k8sversion.Info{
   694  			Major: "1", Minor: "18",
   695  		}, nil),
   696  	)
   697  
   698  	ingress1Rule1 := networkingv1.IngressRule{
   699  		IngressRuleValue: networkingv1.IngressRuleValue{
   700  			HTTP: &networkingv1.HTTPIngressRuleValue{
   701  				Paths: []networkingv1.HTTPIngressPath{
   702  					{
   703  						Path: "/testpath",
   704  						Backend: networkingv1.IngressBackend{
   705  							Resource: &core.TypedLocalObjectReference{
   706  								APIGroup: pointer.StringPtr("k8s.example.com"),
   707  								Kind:     "StorageBucket",
   708  								Name:     "icon-assets",
   709  							},
   710  						},
   711  					},
   712  				},
   713  			},
   714  		},
   715  	}
   716  	ingress1 := k8sspecs.K8sIngress{
   717  		Meta: k8sspecs.Meta{
   718  			Name: "test-ingress",
   719  		},
   720  		Spec: k8sspecs.K8sIngressSpec{
   721  			Version: k8sspecs.K8sIngressV1,
   722  			SpecV1: networkingv1.IngressSpec{
   723  				Rules: []networkingv1.IngressRule{ingress1Rule1},
   724  			},
   725  		},
   726  	}
   727  
   728  	ingressResources := []k8sspecs.K8sIngress{ingress1}
   729  	ingress := &networkingv1beta1.Ingress{
   730  		ObjectMeta: metav1.ObjectMeta{
   731  			Name:   "test-ingress",
   732  			Labels: map[string]string{"app.kubernetes.io/managed-by": "juju", "app.kubernetes.io/name": "app-name"},
   733  			Annotations: map[string]string{
   734  				"controller.juju.is/id": "deadbeef-1bad-500d-9000-4b1d0d06f00d",
   735  			},
   736  		},
   737  		Spec: networkingv1beta1.IngressSpec{
   738  			Rules: []networkingv1beta1.IngressRule{{
   739  				IngressRuleValue: networkingv1beta1.IngressRuleValue{
   740  					HTTP: &networkingv1beta1.HTTPIngressRuleValue{
   741  						Paths: []networkingv1beta1.HTTPIngressPath{
   742  							{
   743  								Path: "/testpath",
   744  								Backend: networkingv1beta1.IngressBackend{
   745  									Resource: &core.TypedLocalObjectReference{
   746  										APIGroup: pointer.StringPtr("k8s.example.com"),
   747  										Kind:     "StorageBucket",
   748  										Name:     "icon-assets",
   749  									},
   750  								},
   751  							},
   752  						},
   753  					},
   754  				},
   755  			}},
   756  		},
   757  	}
   758  	s.assertIngressResources(
   759  		c, ingressResources, "",
   760  		s.mockIngressV1Beta1.EXPECT().Create(gomock.Any(), ingress, metav1.CreateOptions{}).Return(ingress, nil),
   761  	)
   762  }