From 2c522cb50a2860a3dda949e6b7ff70114955d9d9 Mon Sep 17 00:00:00 2001 From: Wang Qixiang <43193572+wqx6@users.noreply.github.com> Date: Tue, 5 Mar 2024 12:40:19 +0800 Subject: [PATCH] Add records of session establishment for subscription resumption (#31755) * Add records of session establishment for subscription resumption * Restyled by clang-format * review changes * Schedule subscription resumption when failing to establish the session in SubscriptionResumptionSessionEstablisher * Add option to set subscription timeout resumption retry interval seconds for Linux app Add cirque test for subscription resumption timeout * Restyled by clang-format * Restyled by autopep8 * Restyled by isort * fix CI building * Add test to the test list * add subscription resumption restries number to SubscriptionInfo struct * review changes * make resumption retries persistent * Restyled by clang-format * ci build fixes * try to fix cirque test --------- Co-authored-by: Restyled.io --- examples/platform/linux/AppMain.cpp | 7 +- examples/platform/linux/Options.cpp | 11 +- examples/platform/linux/Options.h | 3 +- scripts/tests/cirque_tests.sh | 1 + src/app/InteractionModelEngine.cpp | 14 +- src/app/InteractionModelEngine.h | 20 ++- .../SimpleSubscriptionResumptionStorage.cpp | 16 +++ src/app/SimpleSubscriptionResumptionStorage.h | 1 + ...bscriptionResumptionSessionEstablisher.cpp | 33 ++++- src/app/SubscriptionResumptionStorage.h | 3 + .../subscription_resumption_timeout_test.py | 125 ++++++++++++++++ .../SubscriptionResumptionTimeoutTest.py | 136 ++++++++++++++++++ 12 files changed, 361 insertions(+), 9 deletions(-) create mode 100755 src/controller/python/test/test_scripts/subscription_resumption_timeout_test.py create mode 100755 src/test_driver/linux-cirque/SubscriptionResumptionTimeoutTest.py diff --git a/examples/platform/linux/AppMain.cpp b/examples/platform/linux/AppMain.cpp index 71701f74aaedd6..b92d76e613e618 100644 --- a/examples/platform/linux/AppMain.cpp +++ b/examples/platform/linux/AppMain.cpp @@ -570,7 +570,12 @@ void ChipLinuxAppMainLoop(AppMainLoopImplementation * impl) chip::app::InteractionModelEngine::GetInstance()->SetHandlerCapacityForSubscriptions( LinuxDeviceOptions::GetInstance().subscriptionCapacity); chip::app::InteractionModelEngine::GetInstance()->SetForceHandlerQuota(true); -#endif +#if CHIP_CONFIG_PERSIST_SUBSCRIPTIONS && CHIP_CONFIG_SUBSCRIPTION_TIMEOUT_RESUMPTION + // Set subscription time resumption retry interval seconds + chip::app::InteractionModelEngine::GetInstance()->SetSubscriptionTimeoutResumptionRetryIntervalSeconds( + LinuxDeviceOptions::GetInstance().subscriptionResumptionRetryIntervalSec); +#endif // CHIP_CONFIG_PERSIST_SUBSCRIPTIONS && CHIP_CONFIG_SUBSCRIPTION_TIMEOUT_RESUMPTION +#endif // CONFIG_BUILD_FOR_HOST_UNIT_TEST // Now that the server has started and we are done with our startup logging, // log our discovery/onboarding information again so it's not lost in the diff --git a/examples/platform/linux/Options.cpp b/examples/platform/linux/Options.cpp index 8398c728762bc9..3017e77901adef 100644 --- a/examples/platform/linux/Options.cpp +++ b/examples/platform/linux/Options.cpp @@ -90,7 +90,10 @@ enum #if CONFIG_BUILD_FOR_HOST_UNIT_TEST kDeviceOption_SubscriptionCapacity = 0x1024, #endif - kDeviceOption_WiFiSupports5g = 0x1025 + kDeviceOption_WiFiSupports5g = 0x1025, +#if CONFIG_BUILD_FOR_HOST_UNIT_TEST + kDeviceOption_SubscriptionResumptionRetryIntervalSec = 0x1026, +#endif }; constexpr unsigned kAppUsageLength = 64; @@ -151,6 +154,7 @@ OptionDef sDeviceOptionDefs[] = { #endif #if CONFIG_BUILD_FOR_HOST_UNIT_TEST { "subscription-capacity", kArgumentRequired, kDeviceOption_SubscriptionCapacity }, + { "subscription-resumption-retry-interval", kArgumentRequired, kDeviceOption_SubscriptionResumptionRetryIntervalSec }, #endif {} }; @@ -280,6 +284,8 @@ const char * sDeviceOptionHelp = #if CONFIG_BUILD_FOR_HOST_UNIT_TEST " --subscription-capacity\n" " Max number of subscriptions the device will allow\n" + " --subscription-resumption-retry-interval\n" + " subscription timeout resumption retry interval in seconds\n" #endif "\n"; @@ -547,6 +553,9 @@ bool HandleOption(const char * aProgram, OptionSet * aOptions, int aIdentifier, case kDeviceOption_SubscriptionCapacity: LinuxDeviceOptions::GetInstance().subscriptionCapacity = static_cast(atoi(aValue)); break; + case kDeviceOption_SubscriptionResumptionRetryIntervalSec: + LinuxDeviceOptions::GetInstance().subscriptionResumptionRetryIntervalSec = static_cast(atoi(aValue)); + break; #endif default: PrintArgError("%s: INTERNAL ERROR: Unhandled option: %s\n", aProgram, aName); diff --git a/examples/platform/linux/Options.h b/examples/platform/linux/Options.h index ca8082b5dcdc54..5063d03993b4eb 100644 --- a/examples/platform/linux/Options.h +++ b/examples/platform/linux/Options.h @@ -73,7 +73,8 @@ struct LinuxDeviceOptions uint16_t rpcServerPort = 33000; #endif #if CONFIG_BUILD_FOR_HOST_UNIT_TEST - int32_t subscriptionCapacity = CHIP_IM_MAX_NUM_SUBSCRIPTIONS; + int32_t subscriptionCapacity = CHIP_IM_MAX_NUM_SUBSCRIPTIONS; + int32_t subscriptionResumptionRetryIntervalSec = -1; #endif static LinuxDeviceOptions & GetInstance(); }; diff --git a/scripts/tests/cirque_tests.sh b/scripts/tests/cirque_tests.sh index 0f9fe22e9cdde0..67bf0f2de63af5 100755 --- a/scripts/tests/cirque_tests.sh +++ b/scripts/tests/cirque_tests.sh @@ -51,6 +51,7 @@ CIRQUE_TESTS=( "CommissioningWindowTest" "SubscriptionResumptionTest" "SubscriptionResumptionCapacityTest" + "SubscriptionResumptionTimeoutTest" ) BOLD_GREEN_TEXT="\033[1;32m" diff --git a/src/app/InteractionModelEngine.cpp b/src/app/InteractionModelEngine.cpp index 8e4b5a80e3df7d..68ab4525d083e4 100644 --- a/src/app/InteractionModelEngine.cpp +++ b/src/app/InteractionModelEngine.cpp @@ -389,7 +389,11 @@ void InteractionModelEngine::OnDone(ReadHandler & apReadObj) mReportingEngine.ResetReadHandlerTracker(&apReadObj); mReadHandlers.ReleaseObject(&apReadObj); + TryToResumeSubscriptions(); +} +void InteractionModelEngine::TryToResumeSubscriptions() +{ #if CHIP_CONFIG_PERSIST_SUBSCRIPTIONS && CHIP_CONFIG_SUBSCRIPTION_TIMEOUT_RESUMPTION if (!mSubscriptionResumptionScheduled && HasSubscriptionsToResume()) { @@ -398,8 +402,10 @@ void InteractionModelEngine::OnDone(ReadHandler & apReadObj) mpExchangeMgr->GetSessionManager()->SystemLayer()->StartTimer( System::Clock::Seconds32(timeTillNextSubscriptionResumptionSecs), ResumeSubscriptionsTimerCallback, this); mNumSubscriptionResumptionRetries++; + ChipLogProgress(InteractionModel, "Schedule subscription resumption when failing to establish session, Retries: %" PRIu32, + mNumSubscriptionResumptionRetries); } -#endif // CHIP_CONFIG_PERSIST_SUBSCRIPTIONS +#endif // CHIP_CONFIG_PERSIST_SUBSCRIPTIONS && CHIP_CONFIG_SUBSCRIPTION_TIMEOUT_RESUMPTION } Status InteractionModelEngine::OnInvokeCommandRequest(Messaging::ExchangeContext * apExchangeContext, @@ -1990,6 +1996,12 @@ void InteractionModelEngine::ResumeSubscriptionsTimerCallback(System::Layer * ap #if CHIP_CONFIG_PERSIST_SUBSCRIPTIONS && CHIP_CONFIG_SUBSCRIPTION_TIMEOUT_RESUMPTION uint32_t InteractionModelEngine::ComputeTimeSecondsTillNextSubscriptionResumption() { +#if CONFIG_BUILD_FOR_HOST_UNIT_TEST + if (mSubscriptionResumptionRetrySecondsOverride > 0) + { + return static_cast(mSubscriptionResumptionRetrySecondsOverride); + } +#endif if (mNumSubscriptionResumptionRetries > CHIP_CONFIG_SUBSCRIPTION_TIMEOUT_RESUMPTION_MAX_FIBONACCI_STEP_INDEX) { return CHIP_CONFIG_SUBSCRIPTION_TIMEOUT_RESUMPTION_MAX_RETRY_INTERVAL_SECS; diff --git a/src/app/InteractionModelEngine.h b/src/app/InteractionModelEngine.h index 1c05767299dfbb..22ec1fb758153a 100644 --- a/src/app/InteractionModelEngine.h +++ b/src/app/InteractionModelEngine.h @@ -354,6 +354,19 @@ class InteractionModelEngine : public Messaging::UnsolicitedMessageHandler, // void SetForceHandlerQuota(bool forceHandlerQuota) { mForceHandlerQuota = forceHandlerQuota; } +#if CHIP_CONFIG_PERSIST_SUBSCRIPTIONS && CHIP_CONFIG_SUBSCRIPTION_TIMEOUT_RESUMPTION + // + // Override the subscription timeout resumption retry interval seconds. The default retry interval will be + // 300s + GetFibonacciForIndex(retry_times) * 300s, which is too long for unit-tests. + // + // If -1 is passed in, no override is instituted and default behavior resumes. + // + void SetSubscriptionTimeoutResumptionRetryIntervalSeconds(int32_t seconds) + { + mSubscriptionResumptionRetrySecondsOverride = seconds; + } +#endif + // // When testing subscriptions using the high-level APIs in src/controller/ReadInteraction.h, // they don't provide for the ability to shut down those subscriptions after they've been established. @@ -392,6 +405,8 @@ class InteractionModelEngine : public Messaging::UnsolicitedMessageHandler, void OnDone(CommandHandler & apCommandObj) override; void OnDone(ReadHandler & apReadObj) override; + void TryToResumeSubscriptions(); + ReadHandler::ApplicationCallback * GetAppCallback() override { return mpReadHandlerApplicationCallback; } InteractionModelEngine * GetInteractionModelEngine() override { return this; } @@ -637,7 +652,10 @@ class InteractionModelEngine : public Messaging::UnsolicitedMessageHandler, // enforce such check based on the configured size. This flag is used for unit tests only, there is another compare time flag // CHIP_CONFIG_IM_FORCE_FABRIC_QUOTA_CHECK for stress tests. bool mForceHandlerQuota = false; -#endif +#if CHIP_CONFIG_PERSIST_SUBSCRIPTIONS && CHIP_CONFIG_SUBSCRIPTION_TIMEOUT_RESUMPTION + int mSubscriptionResumptionRetrySecondsOverride = -1; +#endif // CHIP_CONFIG_PERSIST_SUBSCRIPTIONS && CHIP_CONFIG_SUBSCRIPTION_TIMEOUT_RESUMPTION +#endif // CONFIG_BUILD_FOR_HOST_UNIT_TEST #if CHIP_CONFIG_PERSIST_SUBSCRIPTIONS && CHIP_CONFIG_SUBSCRIPTION_TIMEOUT_RESUMPTION bool HasSubscriptionsToResume(); diff --git a/src/app/SimpleSubscriptionResumptionStorage.cpp b/src/app/SimpleSubscriptionResumptionStorage.cpp index f6f96978d5ba9d..e233c18af9218a 100644 --- a/src/app/SimpleSubscriptionResumptionStorage.cpp +++ b/src/app/SimpleSubscriptionResumptionStorage.cpp @@ -46,6 +46,7 @@ constexpr TLV::Tag SimpleSubscriptionResumptionStorage::kClusterIdTag; constexpr TLV::Tag SimpleSubscriptionResumptionStorage::kAttributeIdTag; constexpr TLV::Tag SimpleSubscriptionResumptionStorage::kEventIdTag; constexpr TLV::Tag SimpleSubscriptionResumptionStorage::kEventPathTypeTag; +constexpr TLV::Tag SimpleSubscriptionResumptionStorage::kResumptionRetriesTag; SimpleSubscriptionResumptionStorage::SimpleSubscriptionInfoIterator::SimpleSubscriptionInfoIterator( SimpleSubscriptionResumptionStorage & storage) : @@ -252,6 +253,18 @@ CHIP_ERROR SimpleSubscriptionResumptionStorage::Load(uint16_t subscriptionIndex, } ReturnErrorOnFailure(reader.ExitContainer(eventsListType)); +#if CHIP_CONFIG_SUBSCRIPTION_TIMEOUT_RESUMPTION + // If the reader cannot get resumption retries, set it to 0 for subscriptionInfo + if (reader.Next(kResumptionRetriesTag) == CHIP_NO_ERROR) + { + ReturnErrorOnFailure(reader.Get(subscriptionInfo.mResumptionRetries)); + } + else + { + subscriptionInfo.mResumptionRetries = 0; + } +#endif // CHIP_CONFIG_SUBSCRIPTION_TIMEOUT_RESUMPTION + ReturnErrorOnFailure(reader.ExitContainer(subscriptionContainerType)); return CHIP_NO_ERROR; @@ -307,6 +320,9 @@ CHIP_ERROR SimpleSubscriptionResumptionStorage::Save(TLV::TLVWriter & writer, Su ReturnErrorOnFailure(writer.EndContainer(eventContainerType)); } ReturnErrorOnFailure(writer.EndContainer(eventsListType)); +#if CHIP_CONFIG_SUBSCRIPTION_TIMEOUT_RESUMPTION + ReturnErrorOnFailure(writer.Put(kResumptionRetriesTag, subscriptionInfo.mResumptionRetries)); +#endif // CHIP_CONFIG_SUBSCRIPTION_TIMEOUT_RESUMPTION ReturnErrorOnFailure(writer.EndContainer(subscriptionContainerType)); diff --git a/src/app/SimpleSubscriptionResumptionStorage.h b/src/app/SimpleSubscriptionResumptionStorage.h index 7cd6a0a98b49d8..729f6f96f23b54 100644 --- a/src/app/SimpleSubscriptionResumptionStorage.h +++ b/src/app/SimpleSubscriptionResumptionStorage.h @@ -132,6 +132,7 @@ class SimpleSubscriptionResumptionStorage : public SubscriptionResumptionStorage static constexpr TLV::Tag kAttributeIdTag = TLV::ContextTag(13); static constexpr TLV::Tag kEventIdTag = TLV::ContextTag(14); static constexpr TLV::Tag kEventPathTypeTag = TLV::ContextTag(16); + static constexpr TLV::Tag kResumptionRetriesTag = TLV::ContextTag(17); PersistentStorageDelegate * mStorage; ObjectPool mSubscriptionInfoIterators; diff --git a/src/app/SubscriptionResumptionSessionEstablisher.cpp b/src/app/SubscriptionResumptionSessionEstablisher.cpp index f0d53566ad6b28..3c6b3969512c89 100644 --- a/src/app/SubscriptionResumptionSessionEstablisher.cpp +++ b/src/app/SubscriptionResumptionSessionEstablisher.cpp @@ -50,6 +50,9 @@ SubscriptionResumptionSessionEstablisher::ResumeSubscription( mSubscriptionInfo.mMinInterval = subscriptionInfo.mMinInterval; mSubscriptionInfo.mMaxInterval = subscriptionInfo.mMaxInterval; mSubscriptionInfo.mFabricFiltered = subscriptionInfo.mFabricFiltered; +#if CHIP_CONFIG_SUBSCRIPTION_TIMEOUT_RESUMPTION + mSubscriptionInfo.mResumptionRetries = subscriptionInfo.mResumptionRetries; +#endif // Copy the Attribute Paths and Event Paths if (subscriptionInfo.mAttributePaths.AllocatedSize() > 0) { @@ -100,6 +103,15 @@ void SubscriptionResumptionSessionEstablisher::HandleDeviceConnected(void * cont return; } readHandler->OnSubscriptionResumed(sessionHandle, *establisher); +#if CHIP_CONFIG_SUBSCRIPTION_TIMEOUT_RESUMPTION + // Reset the resumption retries to 0 if subscription is resumed + subscriptionInfo.mResumptionRetries = 0; + auto * subscriptionResumptionStorage = InteractionModelEngine::GetInstance()->GetSubscriptionResumptionStorage(); + if (subscriptionResumptionStorage) + { + subscriptionResumptionStorage->Save(subscriptionInfo); + } +#endif // CHIP_CONFIG_SUBSCRIPTION_TIMEOUT_RESUMPTION } void SubscriptionResumptionSessionEstablisher::HandleDeviceConnectionFailure(void * context, const ScopedNodeId & peerId, @@ -109,12 +121,25 @@ void SubscriptionResumptionSessionEstablisher::HandleDeviceConnectionFailure(voi SubscriptionResumptionStorage::SubscriptionInfo & subscriptionInfo = establisher->mSubscriptionInfo; ChipLogError(DataManagement, "Failed to establish CASE for subscription-resumption with error '%" CHIP_ERROR_FORMAT "'", error.Format()); - // If the device fails to establish the session, the subscriber might be offline and its subscription read client will - // be deleted when the device reconnect to the subscriber. This subscription will be never used again. So clean up - // the persistent subscription information storage. auto * subscriptionResumptionStorage = InteractionModelEngine::GetInstance()->GetSubscriptionResumptionStorage(); - if (subscriptionResumptionStorage) + if (!subscriptionResumptionStorage) + { + ChipLogError(DataManagement, "Failed to get subscription resumption storage"); + return; + } +#if CHIP_CONFIG_SUBSCRIPTION_TIMEOUT_RESUMPTION + if (subscriptionInfo.mResumptionRetries <= CHIP_CONFIG_SUBSCRIPTION_TIMEOUT_RESUMPTION_MAX_FIBONACCI_STEP_INDEX) + { + InteractionModelEngine::GetInstance()->TryToResumeSubscriptions(); + subscriptionInfo.mResumptionRetries++; + subscriptionResumptionStorage->Save(subscriptionInfo); + } + else +#endif // CHIP_CONFIG_SUBSCRIPTION_TIMEOUT_RESUMPTION { + // If the device fails to establish the session several times, the subscriber might be offline and its subscription + // read client will be deleted when the device reconnects to the subscriber. This subscription will be never used again. + // Clean up the persistent subscription information storage. subscriptionResumptionStorage->Delete(subscriptionInfo.mNodeId, subscriptionInfo.mFabricIndex, subscriptionInfo.mSubscriptionId); } diff --git a/src/app/SubscriptionResumptionStorage.h b/src/app/SubscriptionResumptionStorage.h index 19342599f36bbc..74158ecfd395f8 100644 --- a/src/app/SubscriptionResumptionStorage.h +++ b/src/app/SubscriptionResumptionStorage.h @@ -73,6 +73,9 @@ class SubscriptionResumptionStorage NodeId mNodeId; FabricIndex mFabricIndex; SubscriptionId mSubscriptionId; +#if CHIP_CONFIG_SUBSCRIPTION_TIMEOUT_RESUMPTION + uint32_t mResumptionRetries; +#endif uint16_t mMinInterval; uint16_t mMaxInterval; bool mFabricFiltered; diff --git a/src/controller/python/test/test_scripts/subscription_resumption_timeout_test.py b/src/controller/python/test/test_scripts/subscription_resumption_timeout_test.py new file mode 100755 index 00000000000000..1f6411f63699c3 --- /dev/null +++ b/src/controller/python/test/test_scripts/subscription_resumption_timeout_test.py @@ -0,0 +1,125 @@ +#!/usr/bin/env python3 + +# +# Copyright (c) 2024 Project CHIP Authors +# All rights reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# + +# Commissioning test. + +import os +import sys +from optparse import OptionParser + +from base import BaseTestHelper, FailIfNot, TestFail, TestTimeout, logger + +TEST_DISCRIMINATOR = 3840 +TEST_SETUPPIN = 20202021 + +TEST_ENDPOINT_ID = 0 + + +def main(): + optParser = OptionParser() + optParser.add_option( + "-t", + "--timeout", + action="store", + dest="testTimeout", + default=90, + type='int', + help="The program will return with timeout after specified seconds.", + metavar="", + ) + optParser.add_option( + "-a", + "--address", + action="store", + dest="deviceAddress", + default='', + type='str', + help="Address of the device", + metavar="", + ) + optParser.add_option( + "--nodeid", + action="store", + dest="nodeid", + default=1, + type=int, + help="The Node ID issued to the device", + metavar="" + ) + optParser.add_option( + "--discriminator", + action="store", + dest="discriminator", + default=TEST_DISCRIMINATOR, + type=int, + help="Discriminator of the device", + metavar="" + ) + optParser.add_option( + "--setuppin", + action="store", + dest="setuppin", + default=TEST_SETUPPIN, + type=int, + help="Setup PIN of the device", + metavar="" + ) + optParser.add_option( + "-p", + "--paa-trust-store-path", + action="store", + dest="paaTrustStorePath", + default='', + type='str', + help="Path that contains valid and trusted PAA Root Certificates.", + metavar="" + ) + + (options, remainingArgs) = optParser.parse_args(sys.argv[1:]) + + timeoutTicker = TestTimeout(options.testTimeout) + timeoutTicker.start() + + test = BaseTestHelper( + nodeid=112233, paaTrustStorePath=options.paaTrustStorePath, testCommissioner=True) + + FailIfNot( + test.TestOnNetworkCommissioning(options.discriminator, options.setuppin, options.nodeid, options.deviceAddress), + "Failed on on-network commissioing") + try: + test.devCtrl.ZCLSubscribeAttribute("BasicInformation", "NodeLabel", options.nodeid, TEST_ENDPOINT_ID, 1, 2, + keepSubscriptions=True, autoResubscribe=False) + except Exception as ex: + TestFail(f"Failed to subscribe attribute: {ex}") + + timeoutTicker.stop() + + logger.info("Test finished") + + # TODO: Python device controller cannot be shutdown clean sometimes and will block on AsyncDNSResolverSockets shutdown. + # Call os._exit(0) to force close it. + os._exit(0) + + +if __name__ == "__main__": + try: + main() + except Exception as ex: + logger.exception(ex) + TestFail("Exception occurred when running tests.") diff --git a/src/test_driver/linux-cirque/SubscriptionResumptionTimeoutTest.py b/src/test_driver/linux-cirque/SubscriptionResumptionTimeoutTest.py new file mode 100755 index 00000000000000..d916504cfb1e9a --- /dev/null +++ b/src/test_driver/linux-cirque/SubscriptionResumptionTimeoutTest.py @@ -0,0 +1,136 @@ +#!/usr/bin/env python3 +""" +Copyright (c) 2024 Project CHIP Authors + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + +http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +""" + +import logging +import os +import sys +import time + +from helper.CHIPTestBase import CHIPVirtualHome + +""" +Subscription Resumption Timeout Test to validate that the device will keep resuming subscription +when it receives no status report from the controller. +Steps for this test: + 1. Subcription an attribute on the controller + 2. Shutdown the controller + 3. Verify that the server app with keep resuming the subscription +""" + +logger = logging.getLogger('SubscriptionResumptionTest') +logger.setLevel(logging.INFO) + +sh = logging.StreamHandler() +sh.setFormatter( + logging.Formatter( + '%(asctime)s [%(name)s] %(levelname)s %(message)s')) +logger.addHandler(sh) + +CHIP_PORT = 5540 + +CIRQUE_URL = "http://localhost:5000" +CHIP_REPO = os.path.join(os.path.abspath( + os.path.dirname(__file__)), "..", "..", "..") +TEST_EXTPANID = "fedcba9876543210" +TEST_DISCRIMINATOR = 3840 +MATTER_DEVELOPMENT_PAA_ROOT_CERTS = "credentials/development/paa-root-certs" +TEST_END_DEVICE_APP = "chip-all-clusters-app" + +DEVICE_CONFIG = { + 'device0': { + 'type': 'MobileDevice', + 'base_image': '@default', + 'capability': ['TrafficControl', 'Mount'], + 'rcp_mode': True, + 'docker_network': 'Ipv6', + 'traffic_control': {'latencyMs': 25}, + "mount_pairs": [[CHIP_REPO, CHIP_REPO]], + }, + 'device1': { + 'type': 'CHIPEndDevice', + 'base_image': '@default', + 'capability': ['Thread', 'TrafficControl', 'Mount'], + 'rcp_mode': True, + 'docker_network': 'Ipv6', + 'traffic_control': {'latencyMs': 25}, + "mount_pairs": [[CHIP_REPO, CHIP_REPO]], + } +} + + +class TestSubscriptionResumptionTimeout(CHIPVirtualHome): + def __init__(self, device_config): + super().__init__(CIRQUE_URL, device_config) + self.logger = logger + + def setup(self): + self.initialize_home() + + def test_routine(self): + self.run_subscription_resumption_timeout_test() + + def run_subscription_resumption_timeout_test(self): + ethernet_ip = [device['description']['ipv6_addr'] for device in self.non_ap_devices + if device['type'] == 'CHIPEndDevice'][0] + server_ids = [device['id'] for device in self.non_ap_devices + if device['type'] == 'CHIPEndDevice'] + req_ids = [device['id'] for device in self.non_ap_devices + if device['type'] == 'MobileDevice'] + + server_device_id = server_ids[0] + self.execute_device_cmd( + server_device_id, + ("CHIPCirqueDaemon.py -- run gdb -batch -return-child-result -q -ex \"set pagination off\" " + "-ex run -ex \"thread apply all bt\" --args {} --thread --discriminator {} " + "--subscription-resumption-retry-interval 5").format( + os.path.join(CHIP_REPO, "out/debug/standalone", TEST_END_DEVICE_APP), TEST_DISCRIMINATOR)) + + self.reset_thread_devices(server_ids) + + req_device_id = req_ids[0] + + self.execute_device_cmd(req_device_id, "pip3 install {}".format(os.path.join( + CHIP_REPO, "out/debug/linux_x64_gcc/controller/python/chip_clusters-0.0-py3-none-any.whl"))) + self.execute_device_cmd(req_device_id, "pip3 install {}".format(os.path.join( + CHIP_REPO, "out/debug/linux_x64_gcc/controller/python/chip_core-0.0-cp37-abi3-linux_x86_64.whl"))) + self.execute_device_cmd(req_device_id, "pip3 install {}".format(os.path.join( + CHIP_REPO, "out/debug/linux_x64_gcc/controller/python/chip_repl-0.0-py3-none-any.whl"))) + + command = ("gdb -batch -return-child-result -q -ex run -ex \"thread apply all bt\" " + "--args python3 {} -t 300 -a {} --paa-trust-store-path {}").format( + os.path.join( + CHIP_REPO, "src/controller/python/test/test_scripts/subscription_resumption_timeout_test.py"), ethernet_ip, + os.path.join(CHIP_REPO, MATTER_DEVELOPMENT_PAA_ROOT_CERTS)) + ret = self.execute_device_cmd(req_device_id, command) + + self.assertEqual(ret['return_code'], '0', + "Test failed: non-zero return code") + + # Wait for some time so that the sever will try to resume the subscription for several times + time.sleep(120) + + # Check the device can resume subscriptions + self.logger.info("checking device log for {}".format( + self.get_device_pretty_id(server_device_id))) + self.assertTrue(self.sequenceMatch(self.get_device_log(server_device_id).decode('utf-8'), [ + "Schedule subscription resumption when failing to establish session, Retries: 1", + "Schedule subscription resumption when failing to establish session, Retries: 2"]), + "SubscriptionResumption test failed: cannot find matching string from device {}".format(server_device_id)) + + +if __name__ == "__main__": + sys.exit(TestSubscriptionResumptionTimeout(DEVICE_CONFIG).run_test())