Skip to content

[uss_qualifier] DSS0210,1b confirm subscription manager is synced #560

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -22,11 +22,41 @@ Verifies that all subscription CRUD operations performed on a single DSS instanc

[`PlanningAreaResource`](../../../../../resources/astm/f3548/v21/planning_area.py) describes the 3D volume in which subscriptions will be created.

### second_utm_auth

A `resources.communications.AuthAdapterResource` containing a second set of valid credentials for interacting with the DSS.

This second set of credentials is required to validate that the DSS properly synchronizes the manager of a subscription to other DSS instances.

The test designer should provide this second set of credentials when full testing of manager synchronization behavior is desired.

#### Credential requirements

In general, these test credentials may be in all points equal to the ones used by the `AuthAdapterResource` that is
provided to the `dss` resources above, except for the value contained in the `sub` claim of the token.

Note that most checks in this scenario will work if the second set of credentials is not provided.

##### Required scope

For the purpose of this scenario, the `second_utm_auth` resource must provide access to a token with at least the following scope:

* `utm.strategic_coordination`

##### Separate subject

Note that the subject (or 'sub' claim) of the token that will be obtained for this resource
MUST be different from the one of the `dss` resources mentioned above:
this will be verified at runtime, and the depending checks will not be run if this is not the case.

## Setup test case

### [Ensure clean workspace test step](../clean_workspace.md)

This step ensures that no subscription with the known test ID exists in the DSS.
This step ensures that no subscriptions with the known test IDs exist in the DSS.

This includes the main test subscription used in this test, as well as the extra subscription
used for testing the `manager` field sync, if the test is configured to test for it.

## Subscription Synchronization test case

Expand Down Expand Up @@ -110,6 +140,8 @@ Verify that the version of the subscription returned by every DSS is as expected

This test step attempts to mutate the subscription on every secondary DSS instance (that is, instances through which the subscription has not been created) to confirm that such mutations are properly propagated to every DSS.

Note that this step is repeated for every secondary DSS instance.

#### 🛑 Subscription can be mutated on secondary DSS check

If the secondary DSS does not allow the subscription to be mutated, either the secondary DSS or the primary DSS are in violation of one or both of the following requirements:
Expand All @@ -127,6 +159,20 @@ If it does not, it might be in violation of **[astm.f3548.v21.DSS0005,5](../../.

Confirm that the secondary DSS handles the update properly.

#### [Validate subscription](../fragments/sub/validate/correctness.md)

Verify that the subscription returned by the DSS is properly formatted and contains the correct content.

#### [Validate version is updated by mutation](../fragments/sub/validate/mutated.md)

Verify that the version of the subscription is updated after the mutation on the secondary.

### Verify mutation on all secondaries test step

This step verifies that the mutation of the subscription on a secondary instance, from the previous step, is properly propagated to every other DSS.

Note that this step is repeated for every secondary DSS instance.

#### [Subscription is synchronized](../fragments/sub/sync.md)

Confirm that the subscription that was just mutated is properly synchronized across all DSS instances.
Expand All @@ -139,13 +185,30 @@ Confirms that the subscription that was just mutated can be retrieved from any D

Verify that the subscription returned by the DSS is properly formatted and contains the correct content.

#### [Validate version is updated by mutation](../fragments/sub/validate/mutated.md)
#### [Validate version is as expected when read](../fragments/sub/validate/non_mutated.md)

Verify that the version of the subscription returned by the DSS the subscription was mutated through has been updated.
Verify that when we are reading the subscription without mutating it, the version is as expected.

#### [Validate new version is synced](../fragments/sub/validate/non_mutated.md)
### Create subscription with different credentials test step

Verify that the new version of the subscription has been propagated.
If the second set of credentials is provided, this test step will create a subscription using these credentials,
in order to prepare the next step that checks manager synchronization.

#### [Create subscription](../fragments/sub/crud/create.md)

Verify that a subscription can be created on the primary DSS using the separate set of credentials.

### Verify manager synchronization test step

If the second set of credentials is provided, checks that the manager of a subscription is properly synchronized across all DSS instances.

This is done by verifying that the main credentials are not able to delete the subscription via any of the secondary DSS instances.

#### ⚠️ Subscription deletion with different non-managing credentials on secondary DSS fails check

If the subscription can be deleted by a client which did not create it, via a DSS instance to which the subscription was synced
following its creation on the primary DSS, either one of the primary DSS or the DSS that accepted the deletion failed to properly broadcast, respectively take into account, the manage of the subscription,
and therefore violates **[astm.f3548.v21.DSS0210,1b](../../../../../requirements/astm/f3548/v21.md)**.

### Delete subscription on primary test step

Expand Down Expand Up @@ -207,3 +270,8 @@ either one of the primary DSS or the DSS that returned the subscription is in vi
As a result, the DSS pool under test is failing to meet **[astm.f3548.v21.DSS0020](../../../../../requirements/astm/f3548/v21.md)**.

## [Cleanup](../clean_workspace.md)

This step ensures that no subscriptions with the known test IDs remain in the DSS by deleting them at this point if they do exist.

This includes the main test subscription used in this test, as well as the extra subscription
used for testing the `manager` field sync, if the test is configured to test for it.
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@
from monitoring.uss_qualifier.resources.astm.f3548.v21.planning_area import (
SubscriptionParams,
)
from monitoring.uss_qualifier.resources.communications import AuthAdapterResource
from monitoring.uss_qualifier.resources.interuss.id_generator import IDGeneratorResource
from monitoring.uss_qualifier.scenarios.astm.utm.dss import test_step_fragments
from monitoring.uss_qualifier.scenarios.astm.utm.dss.validators import (
Expand All @@ -45,9 +46,15 @@ class SubscriptionSynchronization(TestScenario):
"""

SUB_TYPE = register_resource_type(379, "Subscription")
ACL_SUB_TYPE = register_resource_type(
381, "Subscription with different credentials"
)

_dss: DSSInstance

# Separate DSS client for testing manager synchronization
_dss_separate_creds: Optional[DSSInstance]

_dss_read_instances: List[DSSInstance]

# Base identifier for the subscriptions that will be created
Expand All @@ -56,6 +63,11 @@ class SubscriptionSynchronization(TestScenario):
# Extra sub IDs for testing only deletions
_ids_for_deletion: List[SubscriptionID]

# Extra sub id for testing manager sync
_acl_sub_id: SubscriptionID
# The extra subscription for testing manager sync
_current_acl_sub: Subscription

# Base parameters used for subscription creation
_sub_params: SubscriptionParams

Expand All @@ -71,6 +83,7 @@ def __init__(
other_instances: DSSInstancesResource,
id_generator: IDGeneratorResource,
planning_area: PlanningAreaResource,
second_utm_auth: Optional[AuthAdapterResource] = None,
):
"""
Args:
Expand Down Expand Up @@ -105,6 +118,7 @@ def __init__(
for i in range(1, len(self._dss_read_instances) + 1)
]

self._acl_sub_id = id_generator.id_factory.make_id(self.ACL_SUB_TYPE)
self._planning_area = planning_area.specification

# Build a ready-to-use 4D volume with no specified time for searching
Expand Down Expand Up @@ -144,6 +158,14 @@ def __init__(
notify_for_constraints=False,
)

if second_utm_auth is not None:
# Build a second DSSWrapper identical to the first but with the other auth adapter
self._dss_separate_creds = self._dss.with_different_auth(
second_utm_auth, scopes_primary
)
else:
self._dss_separate_creds = None

def run(self, context: ExecutionContext):

# Check that we actually have at least one other DSS to test against:
Expand All @@ -154,28 +176,39 @@ def run(self, context: ExecutionContext):
return

self.begin_test_scenario(context)
self._setup_case()
self._step_setup_case()
self.begin_test_case("Subscription Synchronization")

self.begin_test_step("Create subscription validation")
self._step_create_subscriptions()
self.end_test_step()

self.begin_test_step("Query newly created subscription")
self._query_secondaries_and_compare(self._sub_params)
self._step_query_secondaries_and_compare(self._sub_params)
self.end_test_step()

self.begin_test_step("Mutate subscription broadcast")
self._step_mutate_subscriptions_broadcast_shift_time()
self.end_test_step()

self.begin_test_step("Query updated subscription")
self._query_secondaries_and_compare(self._sub_params)
self._step_query_secondaries_and_compare(self._sub_params)
self.end_test_step()

self.begin_test_step("Mutate subscription on secondaries")
self._step_mutate_subscriptions_secondaries_shift_time()
self.end_test_step()
if self._dss_separate_creds:
self.begin_test_step("Create subscription with different credentials")
self._step_create_sub_separate_creds()
self.end_test_step()
self.begin_test_step("Verify manager synchronization")
self._step_test_delete_sub_with_separate_creds()
self.end_test_step()
else:
self.record_note(
"manager_check",
"Skipping manager synchronization check: no extra credentials provided",
)

self._repeat_steps_mutate_subscriptions_secondaries_shift_time()

self.begin_test_step("Delete subscription on primary")
self._step_delete_sub()
Expand All @@ -192,7 +225,7 @@ def run(self, context: ExecutionContext):
self.end_test_case()
self.end_test_scenario()

def _setup_case(self):
def _step_setup_case(self):
self.begin_test_case("Setup")
# Multiple runs of the scenario seem to rely on the same instance of it:
# thus we need to reset the state of the scenario before running it.
Expand All @@ -214,6 +247,10 @@ def _ensure_test_sub_ids_do_not_exist(self):
test_step_fragments.cleanup_sub(self, self._dss, self._sub_id)
for sub_id in self._ids_for_deletion:
test_step_fragments.cleanup_sub(self, self._dss, sub_id)
if self._dss_separate_creds:
test_step_fragments.cleanup_sub(
self, self._dss_separate_creds, self._acl_sub_id
)

def _ensure_no_active_subs_exist(self):
test_step_fragments.cleanup_active_subs(
Expand Down Expand Up @@ -267,7 +304,9 @@ def _create_sub_with_params(

return newly_created.subscription

def _query_secondaries_and_compare(self, expected_sub_params: SubscriptionParams):
def _step_query_secondaries_and_compare(
self, expected_sub_params: SubscriptionParams
):
for secondary_dss in self._dss_read_instances:
self._validate_get_sub_from_secondary(
secondary_dss=secondary_dss,
Expand Down Expand Up @@ -578,26 +617,93 @@ def _mutate_subscription_with_dss(
# Update the parameters we used for that subscription
self._sub_params = new_params

def _step_create_sub_separate_creds(self):
"""Create a subscription on the main DSS with the separate credentials"""
params = self._sub_params.copy()
params.sub_id = self._acl_sub_id
with self.check(
"Create subscription query succeeds", [self._primary_pid]
) as check:
acl_sub = self._dss_separate_creds.upsert_subscription(
**params,
)
self.record_query(acl_sub)
if not acl_sub.success:
check.record_failed(
"Subscription creation with separate credentials failed",
details=f"Subscription creation failed with status code {acl_sub.status_code} when attempted "
f"with separate credentials: {acl_sub.error_message}",
query_timestamps=[acl_sub.request.timestamp],
)
self._current_acl_sub = acl_sub.subscription

def _step_test_delete_sub_with_separate_creds(self):
"""Check we can't delete the subscription created with separate credentials with the main credentials.
This is to confirm that the manager of the subscription is properly synced.
Note that if the separate credentials are for the same subject as the main one, the checks are skipped.
"""

if not self._credentials_are_different():
self.record_note(
"manager_check",
"Skipping manager synchronization check: "
"separate credentials have the same subscriber as the main ones.",
)
return

# For each secondary dss, try to delete the subscription using the main credentials:
for secondary_dss in self._dss_read_instances:
deleted_sub = secondary_dss.delete_subscription(
sub_id=self._acl_sub_id, sub_version=self._current_acl_sub.version
)
self.record_query(deleted_sub)
with self.check(
"Subscription deletion with different non-managing credentials on secondary DSS fails",
[secondary_dss.participant_id],
) as check:
if deleted_sub.status_code != 403:
check.record_failed(
"Subscription deletion with main credentials did not fail",
details=f"Subscription deletion with main credentials did not fail with the expected "
f"status code of 403; instead returned {deleted_sub.status_code}",
query_timestamps=[deleted_sub.request.timestamp],
)

def _credentials_are_different(self):
"""
Checks the auth adapters for the subscription (jwt 'sub' field) they used and returns False if they are the same.
Note that both adapters need to have been used at least once before this check can be performed,
otherwise they have no token available.
"""
return (
self._dss_separate_creds.client.auth_adapter.get_sub()
!= self._dss.client.auth_adapter.get_sub()
)

def _step_mutate_subscriptions_broadcast_shift_time(self):
"""Mutate the subscription on the primary DSS by adding 10 seconds to its start and end times"""

sp = self._sub_params
new_params = sp.shift_time(timedelta(seconds=10))
self._mutate_subscription_with_dss(self._dss, new_params, is_primary=True)

def _step_mutate_subscriptions_secondaries_shift_time(self):
def _repeat_steps_mutate_subscriptions_secondaries_shift_time(self):
"""Mutate the subscription on every secondary DSS by adding 10 seconds to its start and end times,
then checking on every DSS that the response is valid and corresponds to the expected parameters."""

for secondary_dss in self._dss_read_instances:
# Mutate the subscription on the secondary DSS
self.begin_test_step("Mutate subscription on secondaries")
self._mutate_subscription_with_dss(
secondary_dss,
self._sub_params.shift_time(timedelta(seconds=10)),
is_primary=False,
)
self.end_test_step()
self.begin_test_step("Verify mutation on all secondaries")
# Check that the mutation was propagated to every DSS:
self._query_secondaries_and_compare(self._sub_params)
self._step_query_secondaries_and_compare(self._sub_params)
self.end_test_step()

def _delete_sub_from_dss(
self,
Expand Down
1 change: 1 addition & 0 deletions monitoring/uss_qualifier/suites/astm/utm/dss_probing.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -56,6 +56,7 @@ actions:
other_instances: all_dss_instances
id_generator: id_generator
planning_area: planning_area
second_utm_auth: second_utm_auth
- test_scenario:
scenario_type: scenarios.astm.utm.dss.CRDBAccess
resources:
Expand Down