Skip to content

Commit 7f0fd6d

Browse files
Merge pull request #1204 from Miciah/NE-1994-test-e2e-new-test-for-Istio-manual-deployment
NE-1994: Add E2E test for Istio manual deployment
2 parents 288f96c + e2eb4f0 commit 7f0fd6d

File tree

1 file changed

+127
-1
lines changed

1 file changed

+127
-1
lines changed

test/e2e/gateway_api_test.go

Lines changed: 127 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ import (
88
"fmt"
99
"strings"
1010
"testing"
11+
"time"
1112

1213
"github.com/openshift/api/features"
1314
operatorclient "github.com/openshift/cluster-ingress-operator/pkg/operator/client"
@@ -18,8 +19,10 @@ import (
1819
"k8s.io/apimachinery/pkg/api/errors"
1920
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
2021
"k8s.io/apimachinery/pkg/types"
22+
"k8s.io/apimachinery/pkg/util/wait"
2123
"k8s.io/apiserver/pkg/storage/names"
2224
"k8s.io/client-go/rest"
25+
"k8s.io/utils/ptr"
2326

2427
"sigs.k8s.io/controller-runtime/pkg/client"
2528
"sigs.k8s.io/controller-runtime/pkg/client/config"
@@ -96,9 +99,10 @@ func TestGatewayAPI(t *testing.T) {
9699
t.Run("testGatewayAPIResources", testGatewayAPIResources)
97100
if gatewayAPIControllerEnabled {
98101
t.Run("testGatewayAPIObjects", testGatewayAPIObjects)
102+
t.Run("testGatewayAPIManualDeployment", testGatewayAPIManualDeployment)
99103
t.Run("testGatewayAPIIstioInstallation", testGatewayAPIIstioInstallation)
100104
} else {
101-
t.Log("Gateway API Controller not enabled, skipping testGatewayAPIObjects and testGatewayAPIIstioInstallation")
105+
t.Log("Gateway API Controller not enabled, skipping controller tests")
102106
}
103107
t.Run("testGatewayAPIResourcesProtection", testGatewayAPIResourcesProtection)
104108
}
@@ -191,6 +195,128 @@ func testGatewayAPIObjects(t *testing.T) {
191195
}
192196
}
193197

198+
// testGatewayAPIManualDeployment verifies that Istio's "manual deployment"
199+
// feature is not enabled (see
200+
// <https://istio.io/latest/docs/tasks/traffic-management/ingress/gateway-api/#manual-deployment>).
201+
// We only want Istio to allow "automated deployment" (see
202+
// <https://istio.io/latest/docs/tasks/traffic-management/ingress/gateway-api/#automated-deployment>).
203+
//
204+
// When manual deployment is enabled, then Istio allows a gateway to use an
205+
// existing service (for example, another gateway's service) by specifying that
206+
// service in spec.addresses. When a gateway using manual deployment specifies
207+
// another gateway's service, the resulting behavior is effectively the same
208+
// behavior as Gateway API's concept of gateway listener "merging" (see
209+
// <https://github.com/kubernetes-sigs/gateway-api/blob/v1.2.1/apis/v1/gateway_types.go#L181-L182>).
210+
//
211+
// Gateway listener merging is underspecified in Gateway API and is not
212+
// consistently implemented among Gateway API implementations, and so we do not
213+
// want to allow it or any similar behavior (such as Istio's "manual
214+
// deployment") until such a time as it is well defined, standard behavior.
215+
// Instead, for the time being, we expect Istio to provision a service for a
216+
// gateway ("automated deployment"), even if the gateway specifies some existing
217+
// service in spec.addresses.
218+
func testGatewayAPIManualDeployment(t *testing.T) {
219+
gatewayClass, err := createGatewayClass("openshift-default", "openshift.io/gateway-controller/v1")
220+
if err != nil {
221+
t.Fatalf("Failed to create gatewayclass: %v", err)
222+
}
223+
224+
gatewayName := types.NamespacedName{
225+
Name: "manual-deployment",
226+
Namespace: "openshift-ingress",
227+
}
228+
// Use the router's internal service in order to ensure that the
229+
// referent exists. Using an existing service isn't strictly necessary
230+
// in order to verify that Istio does not use manual deployment; if
231+
// manual deployment *is* enabled, Istio rejects the gateway if it
232+
// points to a non-existent referent. However, using an existing
233+
// service more closely reflects the way that manual deployment *would*
234+
// be used if it were allowed.
235+
const existingServiceHostname = "router-internal-default.openshift-ingress.svc.cluster.local"
236+
gateway := gatewayapiv1.Gateway{
237+
ObjectMeta: metav1.ObjectMeta{
238+
Name: gatewayName.Name,
239+
Namespace: gatewayName.Namespace,
240+
},
241+
Spec: gatewayapiv1.GatewaySpec{
242+
GatewayClassName: gatewayapiv1.ObjectName(gatewayClass.Name),
243+
Addresses: []gatewayapiv1.GatewayAddress{{
244+
Type: ptr.To(gatewayapiv1.HostnameAddressType),
245+
Value: existingServiceHostname,
246+
}},
247+
Listeners: []gatewayapiv1.Listener{{
248+
Name: "http",
249+
Hostname: ptr.To(gatewayapiv1.Hostname(fmt.Sprintf("*.manual-deployment.%s", dnsConfig.Spec.BaseDomain))),
250+
Port: 80,
251+
Protocol: "HTTP",
252+
}},
253+
},
254+
}
255+
t.Logf("Creating gateway %q...", gatewayName)
256+
if err := kclient.Create(context.Background(), &gateway); err != nil {
257+
t.Fatalf("Failed to create gateway %v: %v", gatewayName, err)
258+
}
259+
t.Cleanup(func() {
260+
if err := kclient.Delete(context.Background(), &gateway); err != nil {
261+
if !errors.IsNotFound(err) {
262+
t.Errorf("Failed to delete gateway %v: %v", gatewayName, err)
263+
}
264+
}
265+
})
266+
267+
interval, timeout := 5*time.Second, 1*time.Minute
268+
t.Logf("Polling for up to %v to verify that the gateway is accepted...", timeout)
269+
if err := wait.PollUntilContextTimeout(context.Background(), interval, timeout, false, func(context context.Context) (bool, error) {
270+
if err := kclient.Get(context, gatewayName, &gateway); err != nil {
271+
t.Logf("Failed to get gateway %v: %v; retrying...", gatewayName, err)
272+
273+
return false, nil
274+
}
275+
276+
for _, condition := range gateway.Status.Conditions {
277+
if condition.Type == string(gatewayapiv1.GatewayConditionAccepted) {
278+
t.Logf("Found %q status condition: %+v", gatewayapiv1.GatewayConditionAccepted, condition)
279+
280+
if condition.Status == metav1.ConditionTrue {
281+
return true, nil
282+
}
283+
}
284+
}
285+
286+
t.Logf("Observed that gateway %v is not yet accepted; retrying...", gatewayName)
287+
288+
return false, nil
289+
}); err != nil {
290+
t.Errorf("Failed to observe the expected condition for gateway %v: %v", gatewayName, err)
291+
}
292+
293+
serviceName := types.NamespacedName{
294+
Name: fmt.Sprintf("%s-%s", gateway.Name, gatewayClass.Name),
295+
Namespace: gateway.Namespace,
296+
}
297+
var service corev1.Service
298+
t.Logf("Polling for up to %v to verify that service %q is created...", timeout, serviceName)
299+
if err := wait.PollUntilContextTimeout(context.Background(), interval, timeout, false, func(context context.Context) (bool, error) {
300+
if err := kclient.Get(context, serviceName, &service); err != nil {
301+
t.Logf("Failed to get service %s: %v; retrying...", serviceName, err)
302+
303+
return false, nil
304+
}
305+
306+
// Just verify that the service is created. No need to verify
307+
// that a load balancer is provisioned. Indeed, provisioning
308+
// will likely fail because Istio copies the address hostname to
309+
// the service spec.loadBalancerIP field, which at least some
310+
// cloud provider implementations reject.
311+
312+
t.Logf("Found service %q", serviceName)
313+
314+
return true, nil
315+
}); err != nil {
316+
t.Errorf("Failed to observe the expected condition for service %v: %v", serviceName, err)
317+
}
318+
}
319+
194320
// testGatewayAPIResourcesProtection verifies that the ingress operator's Validating Admission Policy
195321
// denies admission requests attempting to modify Gateway API CRDs on behalf of a user
196322
// who is not the ingress operator's service account.

0 commit comments

Comments
 (0)