...

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

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

     1use kube::ResourceExt;
     2use linkerd_policy_controller_k8s_api as k8s;
     3use linkerd_policy_test::{
     4    await_condition, await_route_status, create, find_route_condition, mk_route, update,
     5    with_temp_ns,
     6};
     7
     8#[tokio::test(flavor = "current_thread")]
     9async fn inbound_accepted_parent() {
    10    with_temp_ns(|client, ns| async move {
    11        // Create a test 'Server'
    12        let server_name = "test-accepted-server";
    13        let server = k8s::policy::Server {
    14            metadata: k8s::ObjectMeta {
    15                namespace: Some(ns.to_string()),
    16                name: Some(server_name.to_string()),
    17                ..Default::default()
    18            },
    19            spec: k8s::policy::ServerSpec {
    20                selector: k8s::policy::server::Selector::Pod(k8s::labels::Selector::from_iter(
    21                    Some(("app", server_name)),
    22                )),
    23                port: k8s::policy::server::Port::Name("http".to_string()),
    24                proxy_protocol: Some(k8s::policy::server::ProxyProtocol::Http1),
    25            },
    26        };
    27        let server = create(&client, server).await;
    28        let srv_ref = vec![k8s::policy::httproute::ParentReference {
    29            group: Some("policy.linkerd.io".to_string()),
    30            kind: Some("Server".to_string()),
    31            namespace: server.namespace(),
    32            name: server.name_unchecked(),
    33            section_name: None,
    34            port: None,
    35        }];
    36
    37        // Create a route that references the Server resource.
    38        let _route = create(&client, mk_route(&ns, "test-accepted-route", Some(srv_ref))).await;
    39        // Wait until route is updated with a status
    40        let statuses = await_route_status(&client, &ns, "test-accepted-route")
    41            .await
    42            .parents;
    43
    44        let route_status = statuses
    45            .clone()
    46            .into_iter()
    47            .find(|route_status| route_status.parent_ref.name == server_name)
    48            .expect("must have at least one parent status");
    49
    50        // Check status references to parent we have created
    51        assert_eq!(
    52            route_status.parent_ref.group.as_deref(),
    53            Some("policy.linkerd.io")
    54        );
    55        assert_eq!(route_status.parent_ref.kind.as_deref(), Some("Server"));
    56
    57        // Check status is accepted with a status of 'True'
    58        let cond = find_route_condition(&statuses, server_name)
    59            .expect("must have at least one 'Accepted' condition for accepted server");
    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 inbound_multiple_parents() {
    68    with_temp_ns(|client, ns| async move {
    69        // Exercise accepted test with a valid, and an invalid parent reference
    70        let srv_refs = vec![
    71            k8s::policy::httproute::ParentReference {
    72                group: Some("policy.linkerd.io".to_string()),
    73                kind: Some("Server".to_string()),
    74                namespace: Some(ns.clone()),
    75                name: "test-valid-server".to_string(),
    76                section_name: None,
    77                port: None,
    78            },
    79            k8s::policy::httproute::ParentReference {
    80                group: Some("policy.linkerd.io".to_string()),
    81                kind: Some("Server".to_string()),
    82                namespace: Some(ns.clone()),
    83                name: "test-invalid-server".to_string(),
    84                section_name: None,
    85                port: None,
    86            },
    87        ];
    88
    89        // Create only one of the parents
    90        let server = k8s::policy::Server {
    91            metadata: k8s::ObjectMeta {
    92                namespace: Some(ns.to_string()),
    93                name: Some("test-valid-server".to_string()),
    94                ..Default::default()
    95            },
    96            spec: k8s::policy::ServerSpec {
    97                selector: k8s::policy::server::Selector::Pod(k8s::labels::Selector::from_iter(
    98                    Some(("app", "test-valid-server")),
    99                )),
   100                port: k8s::policy::server::Port::Name("http".to_string()),
   101                proxy_protocol: Some(k8s::policy::server::ProxyProtocol::Http1),
   102            },
   103        };
   104        let _server = create(&client, server).await;
   105
   106        // Create a route that references both parents.
   107        let _route = create(
   108            &client,
   109            mk_route(&ns, "test-multiple-parents-route", Some(srv_refs)),
   110        )
   111        .await;
   112        // Wait until route is updated with a status
   113        let parent_status = await_route_status(&client, &ns, "test-multiple-parents-route")
   114            .await
   115            .parents;
   116
   117        // Find status for invalid parent and extract the condition
   118        let invalid_cond = find_route_condition(&parent_status, "test-invalid-server")
   119            .expect("must have at least one 'Accepted' condition set for invalid parent");
   120        // Route shouldn't be accepted
   121        assert_eq!(invalid_cond.status, "False");
   122        assert_eq!(invalid_cond.reason, "NoMatchingParent");
   123
   124        // Find status for valid parent and extract the condition
   125        let valid_cond = find_route_condition(&parent_status, "test-valid-server")
   126            .expect("must have at least one 'Accepted' condition set for valid parent");
   127        assert_eq!(valid_cond.status, "True");
   128        assert_eq!(valid_cond.reason, "Accepted")
   129    })
   130    .await
   131}
   132
   133#[tokio::test(flavor = "current_thread")]
   134async fn inbound_no_parent_ref_patch() {
   135    with_temp_ns(|client, ns| async move {
   136        // Create a test 'Server'
   137        let server_name = "test-accepted-server";
   138        let server = k8s::policy::Server {
   139            metadata: k8s::ObjectMeta {
   140                namespace: Some(ns.to_string()),
   141                name: Some(server_name.to_string()),
   142                ..Default::default()
   143            },
   144            spec: k8s::policy::ServerSpec {
   145                selector: k8s::policy::server::Selector::Pod(k8s::labels::Selector::from_iter(
   146                    Some(("app", server_name)),
   147                )),
   148                port: k8s::policy::server::Port::Name("http".to_string()),
   149                proxy_protocol: Some(k8s::policy::server::ProxyProtocol::Http1),
   150            },
   151        };
   152        let server = create(&client, server).await;
   153        let srv_ref = vec![k8s::policy::httproute::ParentReference {
   154            group: Some("policy.linkerd.io".to_string()),
   155            kind: Some("Server".to_string()),
   156            namespace: server.namespace(),
   157            name: server.name_unchecked(),
   158            section_name: None,
   159            port: None,
   160        }];
   161        // Create a route with a parent reference.
   162        let route = create(
   163            &client,
   164            mk_route(&ns, "test-no-parent-refs-route", Some(srv_ref)),
   165        )
   166        .await;
   167
   168        // Status may not be set straight away. To account for that, wrap a
   169        // status condition watcher in a timeout.
   170        let status = await_route_status(&client, &ns, "test-no-parent-refs-route").await;
   171        // If timeout has elapsed, then route did not receive a status patch
   172        assert!(
   173            status.parents.len() == 1,
   174            "HTTPRoute Status should have 1 parent status"
   175        );
   176
   177        // Update route to remove parent_refs
   178        let _route = update(&client, mk_route(&ns, "test-no-parent-refs-route", None)).await;
   179
   180        // Wait for the status to be updated to contain no parent statuses.
   181        await_condition::<k8s::policy::HttpRoute>(
   182            &client,
   183            &ns,
   184            &route.name_unchecked(),
   185            |obj: Option<&k8s::policy::HttpRoute>| -> bool {
   186                obj.and_then(|route| route.status.as_ref())
   187                    .is_some_and(|status| status.inner.parents.is_empty())
   188            },
   189        )
   190        .await
   191        .expect("HTTPRoute Status should have no parent status");
   192    })
   193    .await
   194}
   195
   196#[tokio::test(flavor = "current_thread")]
   197// Tests that inbound routes (routes attached to a `Server`) are properly
   198// reconciled when the parentReference changes. Additionally, tests that routes
   199// whose parentRefs do not exist are patched with an appropriate status.
   200async fn inbound_accepted_reconcile_no_parent() {
   201    with_temp_ns(|client, ns| async move {
   202        // Given a route with a nonexistent parentReference, we expect to have an
   203        // 'Accepted' condition with 'False' as a status.
   204        let server_name = "test-reconcile-inbound-server";
   205        let srv_ref = vec![k8s::policy::httproute::ParentReference {
   206            group: Some("policy.linkerd.io".to_string()),
   207            kind: Some("Server".to_string()),
   208            namespace: Some(ns.clone()),
   209            name: server_name.to_string(),
   210            section_name: None,
   211            port: None,
   212        }];
   213        let _route = create(
   214            &client,
   215            mk_route(&ns, "test-reconcile-inbound-route", Some(srv_ref)),
   216        )
   217        .await;
   218        let route_status = await_route_status(&client, &ns, "test-reconcile-inbound-route").await;
   219        let cond = find_route_condition(&route_status.parents, server_name)
   220            .expect("must have at least one 'Accepted' condition set for parent");
   221        // Test when parent ref does not exist we get Accepted { False }.
   222        assert_eq!(cond.status, "False");
   223        assert_eq!(cond.reason, "NoMatchingParent");
   224
   225        // Create the 'Server' that route references and expect it to be picked up
   226        // by the index. Consequently, route will have its status reconciled.
   227        let server = k8s::policy::Server {
   228            metadata: k8s::ObjectMeta {
   229                namespace: Some(ns.to_string()),
   230                name: Some(server_name.to_string()),
   231                ..Default::default()
   232            },
   233            spec: k8s::policy::ServerSpec {
   234                selector: k8s::policy::server::Selector::Pod(k8s::labels::Selector::from_iter(
   235                    Some(("app", server_name)),
   236                )),
   237                port: k8s::policy::server::Port::Name("http".to_string()),
   238                proxy_protocol: Some(k8s::policy::server::ProxyProtocol::Http1),
   239            },
   240        };
   241        create(&client, server).await;
   242
   243        // HTTPRoute may not be patched instantly, await the route condition
   244        // status becoming accepted.
   245        let _route_status = await_condition(
   246            &client,
   247            &ns,
   248            "test-reconcile-inbound-route",
   249            |obj: Option<&k8s::policy::httproute::HttpRoute>| -> bool {
   250                tracing::trace!(?obj, "got route status");
   251                let status = match obj.and_then(|route| route.status.as_ref()) {
   252                    Some(status) => status,
   253                    None => return false,
   254                };
   255                let cond = match find_route_condition(&status.inner.parents, server_name) {
   256                    Some(cond) => cond,
   257                    None => return false,
   258                };
   259                cond.status == "True" && cond.reason == "Accepted"
   260            },
   261        )
   262        .await
   263        .expect("must fetch route")
   264        .status
   265        .expect("route must contain a status representation");
   266    })
   267    .await;
   268}
   269
   270#[tokio::test(flavor = "current_thread")]
   271async fn inbound_accepted_reconcile_parent_delete() {
   272    with_temp_ns(|client, ns| async move {
   273        // Attach a route to a Server and expect the route to be patched with an
   274        // Accepted status.
   275        let server_name = "test-reconcile-delete-server";
   276        let server = k8s::policy::Server {
   277            metadata: k8s::ObjectMeta {
   278                namespace: Some(ns.to_string()),
   279                name: Some(server_name.to_string()),
   280                ..Default::default()
   281            },
   282            spec: k8s::policy::ServerSpec {
   283                selector: k8s::policy::server::Selector::Pod(k8s::labels::Selector::from_iter(
   284                    Some(("app", server_name)),
   285                )),
   286                port: k8s::policy::server::Port::Name("http".to_string()),
   287                proxy_protocol: Some(k8s::policy::server::ProxyProtocol::Http1),
   288            },
   289        };
   290        create(&client, server).await;
   291
   292        // Create parentReference and route
   293        let srv_ref = vec![k8s::policy::httproute::ParentReference {
   294            group: Some("policy.linkerd.io".to_string()),
   295            kind: Some("Server".to_string()),
   296            namespace: Some(ns.clone()),
   297            name: server_name.to_string(),
   298            section_name: None,
   299            port: None,
   300        }];
   301        let _route = create(
   302            &client,
   303            mk_route(&ns, "test-reconcile-delete-route", Some(srv_ref)),
   304        )
   305        .await;
   306        let route_status = await_route_status(&client, &ns, "test-reconcile-delete-route").await;
   307        let cond = find_route_condition(&route_status.parents, server_name)
   308            .expect("must have at least one 'Accepted' condition");
   309        assert_eq!(cond.status, "True");
   310        assert_eq!(cond.reason, "Accepted");
   311
   312        // Delete Server
   313        let api: kube::Api<k8s::policy::Server> = kube::Api::namespaced(client.clone(), &ns);
   314        api.delete(
   315            "test-reconcile-delete-server",
   316            &kube::api::DeleteParams::default(),
   317        )
   318        .await
   319        .expect("API delete request failed");
   320
   321        // HTTPRoute may not be patched instantly, await the route condition
   322        // becoming NoMatchingParent.
   323        let _route_status = await_condition(
   324            &client,
   325            &ns,
   326            "test-reconcile-delete-route",
   327            |obj: Option<&k8s::policy::httproute::HttpRoute>| -> bool {
   328                tracing::trace!(?obj, "got route status");
   329                let status = match obj.and_then(|route| route.status.as_ref()) {
   330                    Some(status) => status,
   331                    None => return false,
   332                };
   333                let cond = match find_route_condition(&status.inner.parents, server_name) {
   334                    Some(cond) => cond,
   335                    None => return false,
   336                };
   337                cond.status == "False" && cond.reason == "NoMatchingParent"
   338            },
   339        )
   340        .await
   341        .expect("must fetch route")
   342        .status
   343        .expect("route must contain a status representation");
   344    })
   345    .await;
   346}

View as plain text