@@ -6,7 +6,7 @@ use ic_config::message_routing::{MAX_STREAM_MESSAGES, TARGET_STREAM_SIZE_BYTES};
66use ic_error_types:: RejectCode ;
77use ic_limits:: SYSTEM_SUBNET_STREAM_MSG_LIMIT ;
88use ic_management_canister_types_private:: Method ;
9- use ic_registry_routing_table:: { CanisterIdRange , RoutingTable } ;
9+ use ic_registry_routing_table:: { CanisterIdRange , CanisterMigrations , RoutingTable } ;
1010use ic_registry_subnet_type:: SubnetType ;
1111use ic_replicated_state:: testing:: {
1212 CanisterQueuesTesting , ReplicatedStateTesting , SystemStateTesting ,
@@ -18,7 +18,9 @@ use ic_test_utilities_metrics::{
1818 nonzero_values,
1919} ;
2020use ic_test_utilities_state:: { new_canister_state, register_callback} ;
21- use ic_test_utilities_types:: ids:: { SUBNET_27 , SUBNET_42 , canister_test_id, user_test_id} ;
21+ use ic_test_utilities_types:: ids:: {
22+ SUBNET_3 , SUBNET_4 , SUBNET_5 , SUBNET_27 , SUBNET_42 , canister_test_id, user_test_id,
23+ } ;
2224use ic_test_utilities_types:: messages:: RequestBuilder ;
2325use ic_types:: messages:: {
2426 CallbackId , MAX_INTER_CANISTER_PAYLOAD_IN_BYTES_U64 , NO_DEADLINE , Payload , Refund ,
@@ -1216,6 +1218,211 @@ fn build_streams_with_oversized_payloads() {
12161218 } ) ;
12171219}
12181220
1221+ /// Local subnet is splitting: a canister is migrating from local subnet to
1222+ /// subnet B.
1223+ #[ test]
1224+ fn test_observe_misrouted_messages_on_splitting_subnet ( ) {
1225+ with_test_replica_logger ( |log| {
1226+ let ( stream_builder, mut state, metrics_registry) = new_fixture ( & log) ;
1227+
1228+ // Subnets and canisters.
1229+ const REMOTE_SUBNET_B : SubnetId = SUBNET_4 ; // Destination of migration.
1230+ const REMOTE_SUBNET_Z : SubnetId = SUBNET_5 ; // Other subnet, no migration.
1231+
1232+ let local_canister = canister_test_id ( 100 ) ;
1233+ // Migrating but not yet migrated canister.
1234+ let migrating_canister = canister_test_id ( 200 ) ;
1235+ // Already migrated canister.
1236+ let migrated_canister = canister_test_id ( 300 ) ;
1237+ let canister_on_b = canister_test_id ( 400 ) ;
1238+ let canister_on_z = canister_test_id ( 500 ) ;
1239+
1240+ // Routing table: `migrating_canister` is still hosted by the local subnet;
1241+ // `migrated_canister` has already migrated from the local subnet to B.
1242+ state. metadata . network_topology . routing_table = Arc :: new (
1243+ RoutingTable :: try_from ( btreemap ! {
1244+ CanisterIdRange { start: local_canister, end: local_canister } => LOCAL_SUBNET ,
1245+ CanisterIdRange { start: migrating_canister, end: migrating_canister } => LOCAL_SUBNET ,
1246+ CanisterIdRange { start: migrated_canister, end: migrated_canister } => REMOTE_SUBNET_B ,
1247+ CanisterIdRange { start: canister_on_b, end: canister_on_b } => REMOTE_SUBNET_B ,
1248+ CanisterIdRange { start: canister_on_z, end: canister_on_z } => REMOTE_SUBNET_Z ,
1249+ } )
1250+ . unwrap ( ) ,
1251+ ) ;
1252+
1253+ // Canister migrations: both `migrating_canister` and `migrated_canister` are
1254+ // migrating from the local subnet to B.
1255+ state. metadata . network_topology . canister_migrations = Arc :: new (
1256+ CanisterMigrations :: try_from ( btreemap ! {
1257+ CanisterIdRange { start: migrating_canister, end: migrating_canister } => vec![ LOCAL_SUBNET , REMOTE_SUBNET_B ] ,
1258+ CanisterIdRange { start: migrated_canister, end: migrated_canister } => vec![ LOCAL_SUBNET , REMOTE_SUBNET_B ] ,
1259+ } )
1260+ . unwrap ( ) ,
1261+ ) ;
1262+
1263+ let message_to = |receiver : CanisterId | {
1264+ RequestBuilder :: default ( )
1265+ . sender ( local_canister)
1266+ . receiver ( receiver)
1267+ . build ( )
1268+ . into ( )
1269+ } ;
1270+ let message = |sender : CanisterId , receiver : CanisterId | {
1271+ RequestBuilder :: default ( )
1272+ . sender ( sender)
1273+ . receiver ( receiver)
1274+ . build ( )
1275+ . into ( )
1276+ } ;
1277+
1278+ // Loopback stream, with messages to and from all canisters hosted by subnet A
1279+ // at any given time. The 3 messages to/from `migrated_canister` are misrouted.
1280+ let mut loopback_stream = Stream :: default ( ) ;
1281+ loopback_stream. push ( message_to ( local_canister) ) ;
1282+ loopback_stream. push ( message_to ( migrated_canister) ) ;
1283+ loopback_stream. push ( message_to ( migrating_canister) ) ;
1284+ loopback_stream. push ( message ( migrated_canister, local_canister) ) ;
1285+ loopback_stream. push ( message ( migrated_canister, migrated_canister) ) ;
1286+ loopback_stream. push ( message ( migrating_canister, local_canister) ) ;
1287+ loopback_stream. push ( message ( migrating_canister, migrating_canister) ) ;
1288+
1289+ // Stream to subnet B, with messages to all canisters potentially hosted by
1290+ // subnet B at any given time.
1291+ //
1292+ // The 2 messages from the migrated canister and the 2 messages to the migrating
1293+ // canister are misrouted.
1294+ let mut stream_to_subnet_b = Stream :: default ( ) ;
1295+ stream_to_subnet_b. push ( message_to ( canister_on_b) ) ;
1296+ stream_to_subnet_b. push ( message_to ( migrated_canister) ) ;
1297+ stream_to_subnet_b. push ( message_to ( migrating_canister) ) ;
1298+ stream_to_subnet_b. push ( message ( migrated_canister, canister_on_b) ) ;
1299+ stream_to_subnet_b. push ( message ( migrated_canister, migrated_canister) ) ;
1300+ stream_to_subnet_b. push ( message ( migrating_canister, canister_on_b) ) ;
1301+ stream_to_subnet_b. push ( message ( migrating_canister, migrating_canister) ) ;
1302+
1303+ // Stream to subnet Z: one message from each currently or previously hosted
1304+ // canister. The message from `migrated_canister` is misrouted.
1305+ let mut stream_to_subnet_z = Stream :: default ( ) ;
1306+ stream_to_subnet_z. push ( message_to ( canister_on_z) ) ;
1307+ stream_to_subnet_z. push ( message ( migrated_canister, canister_on_z) ) ;
1308+ stream_to_subnet_z. push ( message ( migrating_canister, canister_on_z) ) ;
1309+
1310+ state. modify_streams ( |streams| {
1311+ * streams = btreemap ! {
1312+ LOCAL_SUBNET => loopback_stream,
1313+ REMOTE_SUBNET_B => stream_to_subnet_b,
1314+ REMOTE_SUBNET_Z => stream_to_subnet_z,
1315+ }
1316+ } ) ;
1317+
1318+ // Act.
1319+ stream_builder. observe_misrouted_messages ( & state) ;
1320+
1321+ // Assert.
1322+ assert_eq ! (
1323+ metric_vec( & [
1324+ ( & [ ( LABEL_REMOTE , & LOCAL_SUBNET . to_string( ) ) ] , 3 ) ,
1325+ ( & [ ( LABEL_REMOTE , & REMOTE_SUBNET_B . to_string( ) ) ] , 4 ) ,
1326+ ( & [ ( LABEL_REMOTE , & REMOTE_SUBNET_Z . to_string( ) ) ] , 1 )
1327+ ] ) ,
1328+ fetch_int_gauge_vec( & metrics_registry, METRIC_STREAM_MISROUTED_MESSAGES )
1329+ ) ;
1330+ } ) ;
1331+ }
1332+
1333+ /// A canister is migrating between remote subnets A and B.
1334+ #[ test]
1335+ fn test_observe_misrouted_messages_on_third_party_subnet ( ) {
1336+ with_test_replica_logger ( |log| {
1337+ let ( stream_builder, mut state, metrics_registry) = new_fixture ( & log) ;
1338+
1339+ // Subnets and canisters.
1340+ const REMOTE_SUBNET_A : SubnetId = SUBNET_3 ; // Source of migration.
1341+ const REMOTE_SUBNET_B : SubnetId = SUBNET_4 ; // Destination of migration.
1342+ const REMOTE_SUBNET_Z : SubnetId = SUBNET_5 ; // Other subnet, no migration.
1343+
1344+ let local_canister = canister_test_id ( 100 ) ;
1345+ let canister_on_a = canister_test_id ( 200 ) ;
1346+ let migrated_canister = canister_test_id ( 300 ) ;
1347+ let migrating_canister = canister_test_id ( 400 ) ;
1348+ let canister_on_b = canister_test_id ( 500 ) ;
1349+ let canister_on_z = canister_test_id ( 600 ) ;
1350+
1351+ // Routing table: `migrating_canister` is still hosted by subnet A;
1352+ // `migrated_canister` has already migrated from A to B.
1353+ state. metadata . network_topology . routing_table = Arc :: new (
1354+ RoutingTable :: try_from ( btreemap ! {
1355+ CanisterIdRange { start: local_canister, end: local_canister } => LOCAL_SUBNET ,
1356+ CanisterIdRange { start: canister_on_a, end: canister_on_a } => REMOTE_SUBNET_A ,
1357+ CanisterIdRange { start: migrating_canister, end: migrating_canister } => REMOTE_SUBNET_A ,
1358+ CanisterIdRange { start: migrated_canister, end: migrated_canister } => REMOTE_SUBNET_B ,
1359+ CanisterIdRange { start: canister_on_b, end: canister_on_b } => REMOTE_SUBNET_B ,
1360+ CanisterIdRange { start: canister_on_z, end: canister_on_z } => REMOTE_SUBNET_Z ,
1361+ } )
1362+ . unwrap ( ) ,
1363+ ) ;
1364+
1365+ // Canister migrations: both `migrating_canister` and `migrated_canister` are
1366+ // migrating from A to B.
1367+ state. metadata . network_topology . canister_migrations = Arc :: new (
1368+ CanisterMigrations :: try_from ( btreemap ! {
1369+ CanisterIdRange { start: migrated_canister, end: migrated_canister } => vec![ REMOTE_SUBNET_A , REMOTE_SUBNET_B ] ,
1370+ CanisterIdRange { start: migrating_canister, end: migrating_canister } => vec![ REMOTE_SUBNET_A , REMOTE_SUBNET_B ] ,
1371+ } )
1372+ . unwrap ( ) ,
1373+ ) ;
1374+
1375+ let message_to = |receiver : CanisterId | {
1376+ RequestBuilder :: default ( )
1377+ . sender ( local_canister)
1378+ . receiver ( receiver)
1379+ . build ( )
1380+ . into ( )
1381+ } ;
1382+
1383+ // Stream to subnet A with messages to all the canisters it hosted at one time
1384+ // or another. Only the message to `migrated_canister` is misrouted.
1385+ let mut stream_to_subnet_a = Stream :: default ( ) ;
1386+ stream_to_subnet_a. push ( message_to ( canister_on_a) ) ;
1387+ stream_to_subnet_a. push ( message_to ( migrated_canister) ) ;
1388+ stream_to_subnet_a. push ( message_to ( migrating_canister) ) ;
1389+
1390+ // Stream to subnet B with messages to all the canisters it could potentially
1391+ // have hosted. Only the message to `migrating_canister` is misrouted.
1392+ let mut stream_to_subnet_b = Stream :: default ( ) ;
1393+ stream_to_subnet_b. push ( message_to ( canister_on_b) ) ;
1394+ stream_to_subnet_b. push ( message_to ( migrated_canister) ) ;
1395+ stream_to_subnet_b. push ( message_to ( migrating_canister) ) ;
1396+
1397+ // Contents of both the loopback stream and the stream to subnet Z. It enqueues
1398+ // one misrouted message (e.g. due to a manual canister migration). Not counted
1399+ // because neither the local subnet nor Z are on any migration trace.
1400+ let mut other_stream = Stream :: default ( ) ;
1401+ other_stream. push ( message_to ( canister_on_a) ) ;
1402+
1403+ state. modify_streams ( |streams| {
1404+ * streams = btreemap ! {
1405+ REMOTE_SUBNET_A => stream_to_subnet_a,
1406+ REMOTE_SUBNET_B => stream_to_subnet_b,
1407+ REMOTE_SUBNET_Z => other_stream. clone( ) ,
1408+ LOCAL_SUBNET => other_stream,
1409+ }
1410+ } ) ;
1411+
1412+ // Act.
1413+ stream_builder. observe_misrouted_messages ( & state) ;
1414+
1415+ // Assert.
1416+ assert_eq ! (
1417+ metric_vec( & [
1418+ ( & [ ( LABEL_REMOTE , & REMOTE_SUBNET_A . to_string( ) ) ] , 1 ) ,
1419+ ( & [ ( LABEL_REMOTE , & REMOTE_SUBNET_B . to_string( ) ) ] , 1 )
1420+ ] ) ,
1421+ fetch_int_gauge_vec( & metrics_registry, METRIC_STREAM_MISROUTED_MESSAGES )
1422+ ) ;
1423+ } ) ;
1424+ }
1425+
12191426/// Sets up the `StreamHandlerImpl`, `ReplicatedState` and `MetricsRegistry` to
12201427/// be used by a test using specific stream limits.
12211428fn new_fixture_with_limits (
0 commit comments