-
Notifications
You must be signed in to change notification settings - Fork 150
authz
Test gNSI API behaviors and gRPC authorization policy behaviors.
-
test_infra_id
: the SPIFFE-ID that is used by test infra clients.
Configure the DUT to enable the following services (that are using gRPC) are up, and use mTLS for authentication:
- gNMI
- gNOI
- gNSI
- gRIBI
NOTE: the support of SPIFFE-ID should NOT require explicitly pre-configured local users in the DUT config (for the purpose of 1:1 mapping of each SPIFFE-ID to a local user).
Prepare the following certs with the specified SPIFFE ID. Cert format details can be found in SPIFFE PR
-
cert_user_admin
withspiffe://test-abc.foo.bar/xyz/admin
-
cert_user_deny_all
withspiffe://test-abc.foo.bar/xyz/deny-all
-
cert_gribi_modify
withspiffe://test-abc.foo.bar/xyz/gribi-modify
-
cert_gnmi_set
withspiffe://test-abc.foo.bar/xyz/gnmi-set
-
cert_gnoi_time
withspiffe://test-abc.foo.bar/xyz/gnoi-time
-
cert_gnoi_ping
withspiffe://test-abc.foo.bar/xyz/gnoi-ping
-
cert_gnsi_probe
withspiffe://test-abc.foo.bar/xyz/gnsi-probe
-
cert_read_only
withspiffe://test-abc.foo.bar/xyz/read-only
NOTE: unless specifically mentioned, the rule allow-test-infra
MUST be attached to all the policies, so that the test or the test infra is not blocked from the device.
{
"name": "allow-test-infra",
"source": {
"principals": [
"<test_infra_id>"
]
},
"request": {}
},
Prepare the following gRPC authorization policies.
{
"name": "policy-everyone-can-gnmi-not-gribi",
"allow_rules": [
{
"name": "everyone-can-gnmi-get",
"source": {},
"request": {
"paths": [
"/gnmi.gNMI/Get"
]
}
}
],
"deny_rules": [
{
"name": "no-one-can-gribi-get",
"request": {
"paths": [
"/gribi.gRIBI/Get"
]
}
}
]
}
{
"name": "policy-everyone-can-gribi-not-gnmi",
"allow_rules": [
{
"name": "everyone-can-gribi",
"source": {
"principals": [
"*"
]
},
"request": {
"paths": [
"/gribi.gRIBI/*"
]
}
}
],
"deny_rules": [
{
"name": "no-one-can-gnmi",
"source": {
"principals": [
"*"
]
},
"request": {
"paths": [
"/gribi.gNMI/*"
]
}
}
]
}
{
"name": "policy-invalid-no-allow-rules",
"deny_rules": [
{
"name": "no-one-can-gribi",
"request": {
"paths": [
"/gribi.gRIBI/*"
]
}
}
]
}
{
"name": "policy-gribi-get",
"allow_rules": [
{
"name": "gribi-get",
"source": {
"principals": [
"spiffe://test-abc.foo.bar/xyz/read-only"
]
},
"request": {
"paths": ["/gribi.gRIBI/Get"]
}
}
]
}
{
"name": "policy-gnmi-get",
"allow_rules": [
{
"name": "gnmi-get",
"source": {
"principals": [
"spiffe://test-abc.foo.bar/xyz/read-only"
]
},
"request": {
"paths": ["/gnmi.gNMI/Get"]
}
}
]
}
The following table describes policy policy-normal-1
:
Cert | gRIBI.Modify | gRIBI.Get | gNMI.Set | gNMI.Get | gNOI.Time | gNOI.Ping | gNSI.Rotate | gNSI.Get | gNSI.Probe |
---|---|---|---|---|---|---|---|---|---|
cert_user_admin | allow | allow | allow | allow | allow | allow | allow | allow | allow |
cert_user_deny_all | deny | deny | deny | deny | deny | deny | deny | deny | deny |
cert_gribi_modify | allow | allow | deny | deny | deny | deny | deny | deny | deny |
cert_gnmi_set | deny | deny | allow | allow | deny | deny | deny | deny | deny |
cert_gnoi_time | deny | deny | deny | deny | allow | deny | deny | deny | deny |
cert_gnoi_ping | deny | deny | deny | deny | deny | allow | deny | deny | deny |
cert_gnsi_probe | deny | deny | deny | deny | deny | deny | deny | deny | allow |
cert_read_only | deny | allow | deny | allow | deny | deny | deny | allow | deny |
{
"name": "policy-normal-1",
"allow_rules": [
{
"name": "gribi-modify",
"source": {
"principals": [
"spiffe://test-abc.foo.bar/xyz/admin",
"spiffe://test-abc.foo.bar/xyz/gribi-modify"
]
},
"request": {
"paths": ["/gribi.gRIBI/*"]
}
},
{
"name": "gnmi-set",
"source": {
"principals": [
"spiffe://test-abc.foo.bar/xyz/admin",
"spiffe://test-abc.foo.bar/xyz/gnmi-set"
]
},
"request": {
"paths": ["/gnmi.gNMI/*"]
}
},
{
"name": "gnoi-time",
"source": {
"principals": [
"spiffe://test-abc.foo.bar/xyz/admin",
"spiffe://test-abc.foo.bar/xyz/gnoi-time"
]
},
"request": {
"paths": ["/gnoi.system.System/Time"]
}
},
{
"name": "gnoi-ping",
"source": {
"principals": [
"spiffe://test-abc.foo.bar/xyz/admin",
"spiffe://test-abc.foo.bar/xyz/gnoi-ping"
]
},
"request": {
"paths": ["/gnoi.system.System/Ping"]
}
},
{
"name": "gnsi-set",
"source": {
"principals": [
"spiffe://test-abc.foo.bar/xyz/admin"
]
},
"request": {
"paths": ["/gnsi.authz.v1.Authz/*"]
}
},
{
"name": "gnsi-probe",
"source": {
"principals": [
"spiffe://test-abc.foo.bar/xyz/gnsi-probe"
]
},
"request": {
"paths": ["/gnsi.authz.v1.Authz/Probe"]
}
},
{
"name": "read-only",
"source": {
"principals": [
"spiffe://test-abc.foo.bar/xyz/read-only"
]
},
"request": {
"paths": [
"/gnmi.gNMI/Get",
"/gribi.gRIBI/Get",
"/gnsi.authz.v1.Authz/Get"
]
}
}
],
"deny_rules": [
{
"name": "deny-all-user-can-do-nothing",
"source": {
"principals": [
"spiffe://test-abc.foo.bar/xyz/deny_all"
]
},
"request": {
"paths": ["/*"]
}
}
]
}
NOTE: regarding gNMI OC validation:
- Everytime a gRPC call (including gNSI calls themselves) is allowed or denied, the following OC leaves should be validated:
-
/system/grpc-servers/grpc-server/authz-policy-counters/rpcs/rpc[name]/state/name
is the matched request path, e.g. "/gribi.gRIBI/Get" -
/system/grpc-servers/grpc-server/authz-policy-counters/rpcs/rpc/rpc[name]/state/access-accepts
increments if the rpc call is allowed. -
/system/grpc-servers/grpc-server/authz-policy-counters/rpcs/rpc/rpc[name]/state/access-rejects
increments if the rpc call is denied. -
/system/grpc-servers/grpc-server/authz-policy-counters/rpcs/rpc/rpc[name]/state/last-access-accept
reflects the timestamp of the method call. -
/system/grpc-servers/grpc-server/authz-policy-counters/rpcs/rpc/rpc[name]/state/last-access-reject
reflects the timestamp of the method call.
-
- Everytime a valid policy is pushed (even it's not finalized), the following OC leaves should be validated:
-
/system/grpc-servers/grpc-server/state/authz-policy-version
=UploadRequest.version
in the API proto. -
/system/grpc-servers/grpc-server/state/authz-policy-created-on
=UploadRequest.created_on
(in terms of represented time).
-
- Everytime a valid policy is automatically rolled back, the following OC leaves should be validated:
-
/system/grpc-servers/grpc-server/state/authz-policy-version
=UploadRequest.version
of the previous request (the one rollback to). -
/system/grpc-servers/grpc-server/state/authz-policy-created-on
=UploadRequest.created_on
of the previous request (the one rollback to).
-
- An invalid policy should not trigger the following OC leaf updates:
/system/grpc-servers/grpc-server/state/authz-policy-version
/system/grpc-servers/grpc-server/state/authz-policy-created-on
For each of the scenarios in this section, we need to exercise the following 3 actions to get the authorization results:
-
gNSI.Probe
afterUploadResponse
message but before theFinalizeRequest
message. -
gNSI.Probe
after theRotateAuthzRequest
call finished. -
The actual corresponding service client calls, after the
RotateAuthzRequest
call finished. -
Authz-1.1, "Test empty source"
- Use
gNSI.Rotate
method to push policypolicy-everyone-can-gnmi-not-gribi
, withcreate_on
=100
andversion
=policy-everyone-can-gnmi-not-gribi_v1
. - Ensure all results match per the following:
-
cert_user_admin
is allowed to issuegNMI.Get
method. -
cert_user_admin
is denied to issuegRIBI.Get
method.
-
- Use
-
Authz-1.2, "Test empty request"
- Use
gNSI.Rotate
method to push and finalize policypolicy-everyone-can-gribi-not-gnmi
, withcreate_on
=100
andversion
=policy-everyone-can-gribi-not-gnmi_v1
. - Ensure all results match per the following:
-
cert_user_deny_all
is denied to issuegNMI.Get
method. -
cert_user_admin
is allowed to issuegRIBI.Get
method.
-
- Use
-
Authz-1.3, "Test that there can only be one policy"
- Use
gNSI.Rotate
method to push and finalize policypolicy-gribi-get
, withcreate_on
=100
andversion
=policy-gribi-get_v1
. - Ensure all results match per the following:
-
cert_read_only
is allowed to issuegRIBI.Get
method. -
cert_read_only
is denied to issuegNMI.Get
method.
-
- Use
gNSI.Rotate
method to push and finalize policypolicy-gnmi-get
. - Ensure all results changed to the following:
-
cert_read_only
is denied to issuegRIBI.Get
method. -
cert_read_only
is allowed to issuegNMI.Get
method.
-
- Use
-
Authz-1.4, "Test normal policy"
- Use
gNSI.Rotate
method to push and finalize policypolicy-normal-1
, withcreate_on
=100
andversion
=policy-normal-1_v1
. - Ensure all results match per the above table for policy
policy-normal-1
.
- Use
-
TODO: Authz-1.5, "Test principle prefix and suffix match"
- Test the behavior of prefix and suffix match on principles
-
Authz-2.1, "Test only one rotation request at a time"
- Use
gNSI.Rotate
method to push policypolicy-everyone-can-gnmi-not-gribi
, but don't finalize it yet. - Initial another
gNSI.Rotate
method to push policypolicy-everyone-can-gribi-not-gnmi
, and expect to receive anUNAVAILABLE
gRPC error. - Ensure all actual client authorization result stays as per the following:
-
cert_user_admin
is allowed to issuegNMI.Get
method. -
cert_user_admin
is denied to issuegRIBI.Get
method.
-
- Use
-
Authz-2.2, "Test rollback when connection closed"
- Use
gNSI.Rotate
method to push and finalize policypolicy-gribi-get
. - Ensure
gNSI.Probe
result matches the following:-
cert_read_only
is allowed to issuegRIBI.Get
method. -
cert_read_only
is denied to issuegNMI.Get
method.
-
- Use
gNSI.Rotate
method to push policypolicy-gnmi-get
, but don't finalize it yet. - Ensure
gNSI.Probe
result matches the following:-
cert_read_only
is denied to issuegRIBI.Get
method. -
cert_read_only
is allowed to issuegNMI.Get
method.
-
- Close the gRPC session.
- Ensure
gNSI.Probe
result changed back to the following:-
cert_read_only
is allowed to issuegRIBI.Get
method. -
cert_read_only
is denied to issuegNMI.Get
method.
-
- Use
-
Authz-2.3, "Test rollback on invalid policy"
- Use
gNSI.Rotate
method to push and finalize policypolicy-gribi-get
. - Ensure
gNSI.Probe
result matches the following:-
cert_read_only
is allowed to issuegRIBI.Get
method. -
cert_read_only
is denied to issuegNMI.Get
method.
-
- Use
gNSI.Rotate
method to push policypolicy-invalid-no-allow-rules
, expect an error message and closed gRPC session. - Ensure
gNSI.Probe
result remains as the following:-
cert_read_only
is allowed to issuegRIBI.Get
method. -
cert_read_only
is denied to issuegNMI.Get
method.
-
- Use
-
Authz-2.4, "Test force_overwrite when the version does not change"
- Use
gNSI.Rotate
method to push and finalize policypolicy-gribi-get
. - Use
gNSI.Rotate
method to try to push policypolicy-gnmi-get
with version value not changed. Expect error message and closed gRPC session. - Validate that actual client authorization result stays as the following:
-
cert_read_only
is allowed to issuegRIBI.Get
method. -
cert_read_only
is denied to issuegNMI.Get
method.
-
- Use
gNSI.Rotate
method to try to push policypolicy-gnmi-get
with version value, butforce_overwrite
set to true. Expect no error message, and the push can be finalized. - Ensure actual client authorization results are changed to the following:
-
cert_read_only
is denied to issuegRIBI.Get
method. -
cert_read_only
is allowed to issuegNMI.Get
method.
-
- Use
- Use
gNSI.Rotate
method to push and finalize policypolicy-gribi-get
. - Wait for 30s, intial
gNSI.Get
and validate the value ofversion
,created_on
and gRPC policy content does not change.
- Use
gNSI.Rotate
method to push and finalize policypolicy-normal-1
. - Reboot the device.
- Reconnect to the device, issue
gNSI.Get
andgNMI.Get
and validate the value ofversion
,created_on
and gRPC policy content does not change. - Ensure actual corresponding clients are authorized per the the above table for policy
policy-normal-1
.
The below yaml defines the OC paths intended to be covered by this test. OC paths used for test setup are not listed here.
TODO(OCRPC): Record is not complete
rpcs:
gnsi:
authz.v1.Authz.Get:
-
Home
- Test Plans
- ACCTZ-1.1: Record Subscribe Full
- ACCTZ-2.1: Record Subscribe Partial
- ACCTZ-3.1: Record Subscribe Non-gRPC
- ACCTZ-4.1: Record History Truncation
- ACCTZ-4.2: Record Payload Truncation
- Authz: General Authz (1-4) tests
- CNTR-1: Basic container lifecycle via
gnoi.Containerz
. - CNTR-2: Container network connectivity tests
- Credentialz-1: Password console login
- Credentialz-2: SSH Password Login Disallowed
- Credentialz-3: Host Certificates
- Credentialz-4: SSH Public Key Authentication
- Credentialz-5: Hiba Authentication
- DP-1.2: QoS policy feature config
- DP-1.3: QoS ECN feature config
- DP-1.4: QoS Interface Output Queue Counters
- DP-1.7: One strict priority queue traffic test
- DP-1.8: Two strict priority queue traffic test
- DP-1.9: WRR traffic test
- DP-1.10: Mixed strict priority and WRR traffic test
- DP-1.11: Bursty traffic test
- DP-1.14: QoS basic test
- example-0.1: Topology Test
- FP-1.1: Power admin DOWN/UP Test
- gNMI-1.1: cli Origin
- gNMI-1.2: Benchmarking: Full Configuration Replace
- gNMI-1.3: Benchmarking: Drained Configuration Convergence Time
- gNMI-1.4: Telemetry: Inventory
- gNMI-1.5: Telemetry: Port Speed Test
- gNMI-1.8: Configuration Metadata-only Retrieve and Replace
- gNMI-1.9: Get requests
- gNMI-1.10: Telemetry: Basic Check
- gNMI-1.11: Telemetry: Interface Packet Counters
- gNMI-1.12: Mixed OpenConfig/CLI Origin
- gNMI-1.13: Optics Telemetry, Instant, threshold, and miscellaneous static info
- gNMI-1.14: OpenConfig metadata consistency during large config push
- gNMI-1.15: Set Requests
- gNMI-1.16: fabric redundancy test
- gNMI-1.17: Controller Card redundancy test
- gNMI-1.18: gNMI subscribe with sample mode for backplane capacity counters
- gNMI-1.19: ConfigPush after Control Card switchover
- gNMI-1.20: Telemetry: Optics Thresholds
- gNMI-1.21: Integrated Circuit Hardware Resource Utilization Test
- gNMI-1.22: Controller card port attributes
- gNMI-1.27: gNMI Sample Mode Test
- GNMI-2: gnmi_subscriptionlist_test
- gNOI-2.1: Packet-based Link Qualification
- gNOI-3.1: Complete Chassis Reboot
- gNOI-3.2: Per-Component Reboot
- gNOI-3.3: Supervisor Switchover
- gNOI-3.4: Chassis Reboot Status and Reboot Cancellation
- gNOI-4.1: Software Upgrade
- gNOI-5.1: Ping Test
- gNOI-5.2: Traceroute Test
- gNOI-5.3: Copying Debug Files
- gNOI-6.1: Factory Reset
- Health-1.1: Generic Health Check
- Health-1.2: Healthz component status paths
- MGT-1: Management HA solution test
- MTU-1.3: Large IP Packet Transmission
- OC-1.2: Default Address Families
- OC-26.1: Network Time Protocol (NTP)
- P4RT-1.1: Base P4RT Functionality
- P4RT-1.2: P4RT Daemon Failure
- P4RT-2.1: P4RT Election
- P4RT-2.2: P4RT Metadata Validation
- P4RT-3.1: Google Discovery Protocol: PacketIn
- P4RT-3.2: Google Discovery Protocol: PacketOut
- P4RT-3.21: Google Discovery Protocol: PacketOut with LAG
- P4RT-5.1: Traceroute: PacketIn
- P4RT-5.2: Traceroute Packetout
- P4RT-5.3: Traceroute: PacketIn With VRF Selection
- P4RT-6.1: Required Packet I/O rate: Performance
- P4RT-7.1: LLDP: PacketIn
- P4RT-7.2: LLDP: PacketOut
- Replay-1.0: Record/replay presession test
- Replay-1.1: Record/replay diff command trees test
- Replay-1.2: P4RT Replay Test
- RT-1.1: Base BGP Session Parameters
- RT-1.2: BGP Policy & Route Installation
- RT-1.3: BGP Route Propagation
- RT-1.4: BGP Graceful Restart
- RT-1.5: BGP Prefix Limit
- RT-1.7: Local BGP Test
- RT-1.10: BGP Keepalive and HoldTimer Configuration Test
- RT-1.11: BGP remove private AS
- RT-1.12: BGP always compare MED
- RT-1.14: BGP Long-Lived Graceful Restart
- RT-1.19: BGP 2-Byte and 4-Byte ASN support
- RT-1.21: BGP TCP MSS and PMTUD
- RT-1.23: BGP AFI SAFI OC DEFAULTS
- RT-1.24: BGP 2-Byte and 4-Byte ASN support with policy
- RT-1.25: Management network-instance default static route
- RT-1.26: Basic static route support
- RT-1.27: Static route to BGP redistribution
- RT-1.28: BGP to IS-IS redistribution
- RT-1.29: BGP chained import/export policy attachment
- RT-1.30: BGP nested import/export policy attachment
- RT-1.32: BGP policy actions - MED, LocPref, prepend, flow-control
- RT-1.33: BGP Policy with prefix-set matching
- RT-1.34: BGP route-distance configuration
- RT-1.51: BGP multipath ECMP
- RT-1.52: BGP multipath UCMP support with Link Bandwidth Community
- RT-1.53: prefix-list test
- RT-1.54: BGP Override AS-path split-horizon
- RT-1.55: BGP session mode (active/passive)
- RT-2.1: Base IS-IS Process and Adjacencies
- RT-2.2: IS-IS LSP Updates
- RT-2.6: IS-IS Hello-Padding enabled at interface level
- RT-2.7: IS-IS Passive is enabled at interface level
- RT-2.8: IS-IS metric style wide not enabled
- RT-2.9: IS-IS metric style wide enabled
- RT-2.10: IS-IS change LSP lifetime
- RT-2.11: IS-IS Passive is enabled at the area level
- RT-2.12: Static route to IS-IS redistribution
- RT-2.13: Weighted-ECMP for IS-IS
- RT-2.14: IS-IS Drain Test
- RT-2.15: IS-IS Graceful Restart Helper
- RT-3.1: Policy based VRF selection
- RT-3.2: Multiple <Protocol, DSCP> Rules for VRF Selection
- RT-4.10: AFTs Route Summary
- RT-4.11: AFTs Route Summary
- RT-5.1: Singleton Interface
- RT-5.2: Aggregate Interfaces
- RT-5.3: Aggregate Balancing
- RT-5.4: Aggregate Forwarding Viable
- RT-5.5: Interface hold-time
- RT-5.6: Interface Loopback mode
- RT-5.7: Aggregate Not Viable All
- RT-5.8: IPv6 Link Local
- RT-5.9: Disable IPv6 ND Router Arvetisment
- RT-5.10: IPv6 Link Local generated by SLAAC
- RT-6.1: Core LLDP TLV Population
- RT-7.1: BGP default policies
- RT-7.2: BGP Policy Community Set
- RT-7.3: BGP Policy AS Path Set
- RT-7.4: BGP Policy AS Path Set and Community Set
- RT-7.5: BGP Policy - Match and Set Link Bandwidth Community
- RT-7.8: BGP Policy Match Standard Community and Add Community Import/Export Policy
- RT-7.11: BGP Policy - Import/Export Policy Action Using Multiple Criteria
- RT-14.2: GRIBI Route Test
- SEC-3.1: Authentication
- SFLOW-1: sFlow Configuration and Sampling
- System-1: System testing
- TE-1.1: Static ARP
- TE-1.2: My Station MAC
- TE-2.1: gRIBI IPv4 Entry
- TE-2.2: gRIBI IPv4 Entry With Aggregate Ports
- TE-3.1: Base Hierarchical Route Installation
- TE-3.2: Traffic Balancing According to Weights
- TE-3.3: Hierarchical weight resolution
- TE-3.5: Ordering: ACK Received
- TE-3.6: ACK in the Presence of Other Routes
- TE-3.7: Base Hierarchical NHG Update
- TE-3.31: Hierarchical weight resolution with PBF
- TE-4.1: Base Leader Election
- TE-4.2: Persistence Mode
- TE-5.1: gRIBI Get RPC
- TE-6.1: Route Removal via Flush
- TE-6.2: Route Removal In Non Default VRF
- TE-8.1: DUT Daemon Failure
- TE-8.2: Supervisor Failure
- TE-9.2: MPLS based forwarding Static LSP
- TE-9.3: FIB FAILURE DUE TO HARDWARE RESOURCE EXHAUST
- TE-9: gRIBI MPLS Compliance
- TE-10: gRIBI MPLS Forwarding
- TE-11.1: Backup NHG: Single NH
- TE-11.2: Backup NHG: Multiple NH
- TE-11.3: Backup NHG: Actions
- TE-11.21: Backup NHG: Multiple NH with PBF
- TE-11.31: Backup NHG: Actions with PBF
- TE-13.1: gRIBI route ADD during Failover
- TE-13.2: gRIBI route DELETE during Failover
- TE-14.1: gRIBI Scaling
- TE-14.2: encap and decap scale
- TE-15.1: gRIBI Compliance
- TE-16.1: basic encapsulation tests
- TE-16.2: encapsulation FRR scenarios
- TE-16.3: encapsulation FRR scenarios
- TE-17.1: VRF selection policy driven TE
- TR-6.1: Remote Syslog feature config
- TRANSCEIVER-1: Telemetry: 400ZR Chromatic Dispersion(CD) telemetry values streaming
- TRANSCEIVER-3: Telemetry: 400ZR Optics firmware version streaming
- TRANSCEIVER-4: Telemetry: 400ZR RX input and TX output power telemetry values streaming.
- TRANSCEIVER-5: Configuration: 400ZR channel frequency, output TX launch power and operational mode setting.
- TRANSCEIVER-6: Telemetry: 400ZR Optics performance metrics (pm) streaming.
- TRANSCEIVER-7: Telemetry: 400ZR Optics inventory info streaming
- TRANSCEIVER-8: Telemetry: 400ZR Optics module temperature streaming.
- TRANSCEIVER-9: Telemetry: 400ZR TX laser bias current telemetry values streaming.
- TRANSCEIVER-10: Telemetry: 400ZR Optics FEC(Forward Error Correction) Uncorrectable Frames Streaming.
- TRANSCEIVER-11: Telemetry: 400ZR Optics logical channels provisioning and related telemetry.
- TRANSCEIVER-12: Telemetry: 400ZR Transceiver Supply Voltage streaming.
- TRANSCEIVER-13: Configuration: 400ZR Transceiver Low Power Mode Setting.
- TUN-1.4: Interface based IPv6 GRE Encapsulation
- TUN-1.9: GRE inner packet DSCP
- Test Plans