...

Text file src/github.com/linkerd/linkerd2/policy-test/tests/outbound_http_route_status.rs

Documentation: github.com/linkerd/linkerd2/policy-test/tests

     1use k8s::Condition;
     2use k8s_gateway_api::{ParentReference, RouteParentStatus, RouteStatus};
     3use k8s_openapi::chrono::Utc;
     4use kube::ResourceExt;
     5use linkerd_policy_controller_core::POLICY_CONTROLLER_NAME;
     6use linkerd_policy_controller_k8s_api as k8s;
     7use linkerd_policy_test::{
     8    await_condition, await_route_status, create, find_route_condition, mk_route, with_temp_ns,
     9};
    10
    11#[tokio::test(flavor = "current_thread")]
    12async fn accepted_parent() {
    13    with_temp_ns(|client, ns| async move {
    14        // Create a parent Service
    15        let svc_name = "test-service";
    16        let svc = k8s::Service {
    17            metadata: k8s::ObjectMeta {
    18                namespace: Some(ns.clone()),
    19                name: Some(svc_name.to_string()),
    20                ..Default::default()
    21            },
    22            spec: Some(k8s::ServiceSpec {
    23                type_: Some("ClusterIP".to_string()),
    24                ports: Some(vec![k8s::ServicePort {
    25                    port: 80,
    26                    ..Default::default()
    27                }]),
    28                ..Default::default()
    29            }),
    30            ..k8s::Service::default()
    31        };
    32        let svc = create(&client, svc).await;
    33        let svc_ref = vec![k8s::policy::httproute::ParentReference {
    34            group: Some("core".to_string()),
    35            kind: Some("Service".to_string()),
    36            namespace: svc.namespace(),
    37            name: svc.name_unchecked(),
    38            section_name: None,
    39            port: Some(80),
    40        }];
    41
    42        // Create a route that references the Service resource.
    43        let _route = create(&client, mk_route(&ns, "test-route", Some(svc_ref))).await;
    44        // Wait until route is updated with a status
    45        let statuses = await_route_status(&client, &ns, "test-route").await.parents;
    46
    47        let route_status = statuses
    48            .clone()
    49            .into_iter()
    50            .find(|route_status| route_status.parent_ref.name == svc_name)
    51            .expect("must have at least one parent status");
    52
    53        // Check status references to parent we have created
    54        assert_eq!(route_status.parent_ref.group.as_deref(), Some("core"));
    55        assert_eq!(route_status.parent_ref.kind.as_deref(), Some("Service"));
    56
    57        // Check status is accepted with a status of 'True'
    58        let cond = find_route_condition(&statuses, svc_name)
    59            .expect("must have at least one 'Accepted' condition for accepted servuce");
    60        assert_eq!(cond.status, "True");
    61        assert_eq!(cond.reason, "Accepted")
    62    })
    63    .await;
    64}
    65
    66#[tokio::test(flavor = "current_thread")]
    67async fn no_cluster_ip() {
    68    with_temp_ns(|client, ns| async move {
    69        // Create a parent Service
    70        let svc = k8s::Service {
    71            metadata: k8s::ObjectMeta {
    72                namespace: Some(ns.clone()),
    73                name: Some("test-service".to_string()),
    74                ..Default::default()
    75            },
    76            spec: Some(k8s::ServiceSpec {
    77                cluster_ip: Some("None".to_string()),
    78                type_: Some("ClusterIP".to_string()),
    79                ports: Some(vec![k8s::ServicePort {
    80                    port: 80,
    81                    ..Default::default()
    82                }]),
    83                ..Default::default()
    84            }),
    85            ..k8s::Service::default()
    86        };
    87        let svc = create(&client, svc).await;
    88        let svc_ref = vec![k8s::policy::httproute::ParentReference {
    89            group: Some("core".to_string()),
    90            kind: Some("Service".to_string()),
    91            namespace: svc.namespace(),
    92            name: svc.name_unchecked(),
    93            section_name: None,
    94            port: Some(80),
    95        }];
    96
    97        // Create a route that references the Service resource.
    98        let _route = create(&client, mk_route(&ns, "test-route", Some(svc_ref))).await;
    99        // Wait until route is updated with a status
   100        let status = await_route_status(&client, &ns, "test-route").await;
   101        let cond = find_route_condition(&status.parents, "test-service")
   102            .expect("must have at least one 'Accepted' condition set for parent");
   103        // Parent with no ClusterIP should not match.
   104        assert_eq!(cond.status, "False");
   105        assert_eq!(cond.reason, "NoMatchingParent");
   106    })
   107    .await;
   108}
   109
   110#[tokio::test(flavor = "current_thread")]
   111async fn external_name() {
   112    with_temp_ns(|client, ns| async move {
   113        // Create a parent Service
   114        let svc = k8s::Service {
   115            metadata: k8s::ObjectMeta {
   116                namespace: Some(ns.clone()),
   117                name: Some("test-service".to_string()),
   118                ..Default::default()
   119            },
   120            spec: Some(k8s::ServiceSpec {
   121                type_: Some("ExternalName".to_string()),
   122                external_name: Some("linkerd.io".to_string()),
   123                ports: Some(vec![k8s::ServicePort {
   124                    port: 80,
   125                    ..Default::default()
   126                }]),
   127                ..Default::default()
   128            }),
   129            ..k8s::Service::default()
   130        };
   131        let svc = create(&client, svc).await;
   132        let svc_ref = vec![k8s::policy::httproute::ParentReference {
   133            group: Some("core".to_string()),
   134            kind: Some("Service".to_string()),
   135            namespace: svc.namespace(),
   136            name: svc.name_unchecked(),
   137            section_name: None,
   138            port: Some(80),
   139        }];
   140
   141        // Create a route that references the Service resource.
   142        let _route = create(&client, mk_route(&ns, "test-route", Some(svc_ref))).await;
   143        // Wait until route is updated with a status
   144        let status = await_route_status(&client, &ns, "test-route").await;
   145        let cond = find_route_condition(&status.parents, "test-service")
   146            .expect("must have at least one 'Accepted' condition set for parent");
   147        // Parent with ExternalName should not match.
   148        assert_eq!(cond.status, "False");
   149        assert_eq!(cond.reason, "NoMatchingParent");
   150    })
   151    .await;
   152}
   153
   154#[tokio::test(flavor = "current_thread")]
   155async fn multiple_statuses() {
   156    with_temp_ns(|client, ns| async move {
   157        // Create a parent Service
   158        let svc_name = "test-service";
   159        let svc = k8s::Service {
   160            metadata: k8s::ObjectMeta {
   161                namespace: Some(ns.clone()),
   162                name: Some(svc_name.to_string()),
   163                ..Default::default()
   164            },
   165            spec: Some(k8s::ServiceSpec {
   166                type_: Some("ClusterIP".to_string()),
   167                ports: Some(vec![k8s::ServicePort {
   168                    port: 80,
   169                    ..Default::default()
   170                }]),
   171                ..Default::default()
   172            }),
   173            ..k8s::Service::default()
   174        };
   175        let svc = create(&client, svc).await;
   176        let svc_ref = vec![k8s::policy::httproute::ParentReference {
   177            group: Some("core".to_string()),
   178            kind: Some("Service".to_string()),
   179            namespace: svc.namespace(),
   180            name: svc.name_unchecked(),
   181            section_name: None,
   182            port: Some(80),
   183        }];
   184
   185        // Create a route that references the Service resource.
   186        let _route = create(&client, mk_route(&ns, "test-route", Some(svc_ref))).await;
   187
   188        // Patch a status onto the HttpRoute.
   189        let value = serde_json::json!({
   190            "apiVersion": "policy.linkerd.io",
   191                "kind": "HTTPRoute",
   192                "name": "test-route",
   193                "status": k8s::policy::httproute::HttpRouteStatus {
   194                    inner: RouteStatus {
   195                        parents: vec![RouteParentStatus {
   196                            conditions: vec![Condition {
   197                                last_transition_time: k8s::Time(Utc::now()),
   198                                message: "".to_string(),
   199                                observed_generation: None,
   200                                reason: "Accepted".to_string(),
   201                                status: "True".to_string(),
   202                                type_: "Accepted".to_string(),
   203                            }],
   204                            controller_name: "someone/else".to_string(),
   205                            parent_ref: ParentReference {
   206                                group: Some("gateway.networking.k8s.io".to_string()),
   207                                name: "foo".to_string(),
   208                                kind: Some("Gateway".to_string()),
   209                                namespace: Some("bar".to_string()),
   210                                port: None,
   211                                section_name: None,
   212                            },
   213                        }],
   214                    },
   215                },
   216        });
   217        let patch = k8s::Patch::Merge(value);
   218        let patch_params = k8s::PatchParams::apply("someone/else");
   219        let api = k8s::Api::<k8s::policy::HttpRoute>::namespaced(client.clone(), &ns);
   220        api.patch_status("test-route", &patch_params, &patch)
   221            .await
   222            .expect("failed to patch status");
   223
   224        await_condition(
   225            &client,
   226            &ns,
   227            "test-route",
   228            |obj: Option<&k8s::policy::HttpRoute>| -> bool {
   229                obj.and_then(|route| route.status.as_ref())
   230                    .map(|status| {
   231                        let statuses = &status.inner.parents;
   232
   233                        let other_status_found = statuses
   234                            .iter()
   235                            .any(|route_status| route_status.controller_name == "someone/else");
   236
   237                        let linkerd_status_found = statuses.iter().any(|route_status| {
   238                            route_status.controller_name == POLICY_CONTROLLER_NAME
   239                        });
   240
   241                        other_status_found && linkerd_status_found
   242                    })
   243                    .unwrap_or(false)
   244            },
   245        )
   246        .await
   247        .expect("must have both statuses");
   248    })
   249    .await;
   250}

View as plain text