Skip to content

Commit

Permalink
[controller] add schedule migration API (openthread#2435)
Browse files Browse the repository at this point in the history
This commit adds the `ScheduleMigration` API to `ThreadHost` as well
as the implementation for NcpHost.

The commit also replaces the empty implementation in DBusNcp with the
real implementation by calling the API. The PR adds an expect test to
verify the result of a migration.
  • Loading branch information
Irving-cl authored Aug 21, 2024
1 parent 791828c commit 861ddf2
Show file tree
Hide file tree
Showing 10 changed files with 186 additions and 3 deletions.
6 changes: 4 additions & 2 deletions src/dbus/server/dbus_thread_object_ncp.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -148,8 +148,10 @@ void DBusThreadObjectNcp::ScheduleMigrationHandler(DBusRequest &aRequest)

SuccessOrExit(error = agent::ThreadHelper::ProcessDatasetForMigration(pendingOpDatasetTlvs, delayInMilli));

// TODO: Change to use Migrate API
error = OT_ERROR_NOT_IMPLEMENTED;
mHost.ScheduleMigration(pendingOpDatasetTlvs, [aRequest](otError aError, const std::string &aErrorInfo) mutable {
OT_UNUSED_VARIABLE(aErrorInfo);
aRequest.ReplyOtResult(aError);
});

exit:
if (error != OT_ERROR_NONE)
Expand Down
20 changes: 20 additions & 0 deletions src/ncp/ncp_host.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -114,6 +114,26 @@ void NcpHost::Leave(const AsyncResultReceiver &aReceiver)
task->Run();
}

void NcpHost::ScheduleMigration(const otOperationalDatasetTlvs &aPendingOpDatasetTlvs,
const AsyncResultReceiver aReceiver)
{
otDeviceRole role = GetDeviceRole();
otError error = OT_ERROR_NONE;
auto errorHandler = [aReceiver](otError aError, const std::string &aErrorInfo) { aReceiver(aError, aErrorInfo); };

VerifyOrExit(role != OT_DEVICE_ROLE_DISABLED && role != OT_DEVICE_ROLE_DETACHED, error = OT_ERROR_INVALID_STATE);

mNcpSpinel.DatasetMgmtSetPending(std::make_shared<otOperationalDatasetTlvs>(aPendingOpDatasetTlvs),
std::make_shared<AsyncTask>(errorHandler));

exit:
if (error != OT_ERROR_NONE)
{
mTaskRunner.Post(
[aReceiver, error](void) { aReceiver(error, "Cannot schedule migration when this device is detached"); });
}
}

void NcpHost::Process(const MainloopContext &aMainloop)
{
mSpinelDriver.Process(&aMainloop);
Expand Down
3 changes: 3 additions & 0 deletions src/ncp/ncp_host.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -88,6 +88,8 @@ class NcpHost : public MainloopProcessor, public ThreadHost, public NcpNetworkPr
// ThreadHost methods
void Join(const otOperationalDatasetTlvs &aActiveOpDatasetTlvs, const AsyncResultReceiver &aReceiver) override;
void Leave(const AsyncResultReceiver &aReceiver) override;
void ScheduleMigration(const otOperationalDatasetTlvs &aPendingOpDatasetTlvs,
const AsyncResultReceiver aReceiver) override;
CoprocessorType GetCoprocessorType(void) override { return OT_COPROCESSOR_NCP; }
const char *GetCoprocessorVersion(void) override;
const char *GetInterfaceName(void) const override { return mConfig.mInterfaceName; }
Expand All @@ -102,6 +104,7 @@ class NcpHost : public MainloopProcessor, public ThreadHost, public NcpNetworkPr
ot::Spinel::SpinelDriver &mSpinelDriver;
otPlatformConfig mConfig;
NcpSpinel mNcpSpinel;
TaskRunner mTaskRunner;
};

} // namespace Ncp
Expand Down
43 changes: 43 additions & 0 deletions src/ncp/ncp_spinel.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -110,6 +110,26 @@ void NcpSpinel::DatasetSetActiveTlvs(const otOperationalDatasetTlvs &aActiveOpDa
}
}

void NcpSpinel::DatasetMgmtSetPending(std::shared_ptr<otOperationalDatasetTlvs> aPendingOpDatasetTlvsPtr,
AsyncTaskPtr aAsyncTask)
{
otError error = OT_ERROR_NONE;
EncodingFunc encodingFunc = [this, aPendingOpDatasetTlvsPtr] {
return mEncoder.WriteData(aPendingOpDatasetTlvsPtr->mTlvs, aPendingOpDatasetTlvsPtr->mLength);
};

VerifyOrExit(mDatasetMgmtSetPendingTask == nullptr, error = OT_ERROR_BUSY);

SuccessOrExit(error = SetProperty(SPINEL_PROP_THREAD_MGMT_SET_PENDING_DATASET_TLVS, encodingFunc));
mDatasetMgmtSetPendingTask = aAsyncTask;

exit:
if (error != OT_ERROR_NONE)
{
mTaskRunner.Post([aAsyncTask, error] { aAsyncTask->SetResult(error, "Failed to set pending dataset!"); });
}
}

void NcpSpinel::Ip6SetEnabled(bool aEnable, AsyncTaskPtr aAsyncTask)
{
otError error = OT_ERROR_NONE;
Expand Down Expand Up @@ -327,6 +347,15 @@ void NcpSpinel::HandleValueIs(spinel_prop_key_t aKey, const uint8_t *aBuffer, ui
break;
}

case SPINEL_PROP_THREAD_MGMT_SET_PENDING_DATASET_TLVS:
{
spinel_status_t status = SPINEL_STATUS_OK;

SuccessOrExit(error = SpinelDataUnpack(aBuffer, aLength, SPINEL_DATATYPE_UINT_PACKED_S, &status));
CallAndClear(mDatasetMgmtSetPendingTask, ot::Spinel::SpinelStatusToOtError(status));
break;
}

default:
otbrLogWarning("Received uncognized key: %u", aKey);
break;
Expand Down Expand Up @@ -364,6 +393,20 @@ otbrError NcpSpinel::HandleResponseForPropSet(spinel_tid_t aTid,
CallAndClear(mThreadSetEnabledTask, OT_ERROR_NONE);
break;

case SPINEL_PROP_THREAD_MGMT_SET_PENDING_DATASET_TLVS:
if (aKey == SPINEL_PROP_LAST_STATUS)
{ // Failed case
spinel_status_t status = SPINEL_STATUS_OK;

SuccessOrExit(error = SpinelDataUnpack(aData, aLength, SPINEL_DATATYPE_UINT_PACKED_S, &status));
CallAndClear(mDatasetMgmtSetPendingTask, ot::Spinel::SpinelStatusToOtError(status));
}
else if (aKey != SPINEL_PROP_THREAD_MGMT_SET_PENDING_DATASET_TLVS)
{
ExitNow(error = OTBR_ERROR_INVALID_STATE);
}
break;

default:
VerifyOrExit(aKey == mWaitingKeyTable[aTid], error = OTBR_ERROR_INVALID_STATE);
break;
Expand Down
14 changes: 14 additions & 0 deletions src/ncp/ncp_spinel.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -122,6 +122,19 @@ class NcpSpinel
*/
void DatasetSetActiveTlvs(const otOperationalDatasetTlvs &aActiveOpDatasetTlvs, AsyncTaskPtr aAsyncTask);

/**
* This method instructs the NCP to send a MGMT_SET to set Thread Pending Operational Dataset.
*
* If this method is called again before the previous call completed, no action will be taken.
* The new receiver @p aAsyncTask will be set a result OT_ERROR_BUSY.
*
* @param[in] aPendingOpDatasetTlvsPtr A shared pointer to the pending operational dataset of the Thread network.
* @param[in] aAsyncTask A pointer to an async result to receive the result of this operation.
*
*/
void DatasetMgmtSetPending(std::shared_ptr<otOperationalDatasetTlvs> aPendingOpDatasetTlvsPtr,
AsyncTaskPtr aAsyncTask);

/**
* This method enableds/disables the IP6 on the NCP.
*
Expand Down Expand Up @@ -237,6 +250,7 @@ class NcpSpinel
PropsObserver *mPropsObserver;

AsyncTaskPtr mDatasetSetActiveTask;
AsyncTaskPtr mDatasetMgmtSetPendingTask;
AsyncTaskPtr mIp6SetEnabledTask;
AsyncTaskPtr mThreadSetEnabledTask;
AsyncTaskPtr mThreadDetachGracefullyTask;
Expand Down
9 changes: 9 additions & 0 deletions src/ncp/rcp_host.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -408,6 +408,15 @@ void RcpHost::Leave(const AsyncResultReceiver &aReceiver)
mTaskRunner.Post([aReceiver](void) { aReceiver(OT_ERROR_NOT_IMPLEMENTED, "Not implemented!"); });
}

void RcpHost::ScheduleMigration(const otOperationalDatasetTlvs &aPendingOpDatasetTlvs,
const AsyncResultReceiver aReceiver)
{
OT_UNUSED_VARIABLE(aPendingOpDatasetTlvs);

// TODO: Implement ScheduleMigration under RCP mode.
mTaskRunner.Post([aReceiver](void) { aReceiver(OT_ERROR_NOT_IMPLEMENTED, "Not implemented!"); });
}

/*
* Provide, if required an "otPlatLog()" function
*/
Expand Down
2 changes: 2 additions & 0 deletions src/ncp/rcp_host.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -220,6 +220,8 @@ class RcpHost : public MainloopProcessor, public ThreadHost, public OtNetworkPro
// Thread Control virtual methods
void Join(const otOperationalDatasetTlvs &aActiveOpDatasetTlvs, const AsyncResultReceiver &aRecevier) override;
void Leave(const AsyncResultReceiver &aRecevier) override;
void ScheduleMigration(const otOperationalDatasetTlvs &aPendingOpDatasetTlvs,
const AsyncResultReceiver aReceiver) override;

CoprocessorType GetCoprocessorType(void) override
{
Expand Down
10 changes: 10 additions & 0 deletions src/ncp/thread_host.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -133,6 +133,16 @@ class ThreadHost : virtual public NetworkProperties
*/
virtual void Leave(const AsyncResultReceiver &aRecevier) = 0;

/**
* This method migrates this device to the new network specified by @p aPendingOpDatasetTlvs.
*
* @param[in] aPendingOpDatasetTlvs A reference to the pending operational dataset of the Thread network.
* @param[in] aReceiver A receiver to get the async result of this operation.
*
*/
virtual void ScheduleMigration(const otOperationalDatasetTlvs &aPendingOpDatasetTlvs,
const AsyncResultReceiver aReceiver) = 0;

/**
* Returns the co-processor type.
*
Expand Down
80 changes: 80 additions & 0 deletions tests/scripts/expect/ncp_schedule_migration.exp
Original file line number Diff line number Diff line change
@@ -0,0 +1,80 @@
#!/usr/bin/expect -f
#
# Copyright (c) 2024, The OpenThread Authors.
# All rights reserved.
#
# Redistribution and use in source and binary forms, with or without
# modification, are permitted provided that the following conditions are met:
# 1. Redistributions of source code must retain the above copyright
# notice, this list of conditions and the following disclaimer.
# 2. Redistributions in binary form must reproduce the above copyright
# notice, this list of conditions and the following disclaimer in the
# documentation and/or other materials provided with the distribution.
# 3. Neither the name of the copyright holder nor the
# names of its contributors may be used to endorse or promote products
# derived from this software without specific prior written permission.
#
# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
# AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
# ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
# LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
# CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
# SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
# INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
# CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
# ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
# POSSIBILITY OF SUCH DAMAGE.
#

source "tests/scripts/expect/_common.exp"

# Dataset of the initial Thread network
set dataset "0e080000000000010000000300001435060004001fffe002087d61eb42cdc48d6a0708fd0d07fca1b9f0500510ba088fc2bd6c3b3897f7a10f58263ff3030f4f70656e5468726561642d353234660102524f04109dc023ccd447b12b50997ef68020f19e0c0402a0f7f8"
set dataset_dbus "0x0e,0x08,0x00,0x00,0x00,0x00,0x00,0x01,0x00,0x00,0x00,0x03,0x00,0x00,0x14,0x35,0x06,0x00,0x04,0x00,0x1f,0xff,0xe0,0x02,0x08,0x7d,0x61,0xeb,0x42,0xcd,0xc4,0x8d,0x6a,0x07,0x08,0xfd,0x0d,0x07,0xfc,0xa1,0xb9,0xf0,0x50,0x05,0x10,0xba,0x08,0x8f,0xc2,0xbd,0x6c,0x3b,0x38,0x97,0xf7,0xa1,0x0f,0x58,0x26,0x3f,0xf3,0x03,0x0f,0x4f,0x70,0x65,0x6e,0x54,0x68,0x72,0x65,0x61,0x64,0x2d,0x35,0x32,0x34,0x66,0x01,0x02,0x52,0x4f,0x04,0x10,0x9d,0xc0,0x23,0xcc,0xd4,0x47,0xb1,0x2b,0x50,0x99,0x7e,0xf6,0x80,0x20,0xf1,0x9e,0x0c,0x04,0x02,0xa0,0xf7,0xf8"

# Dataset of the Thread network to migrate to
# (Only updates active timestamp and panid, panid is set to 0x9999)
set dataset1 "0e080000000000020000000300001435060004001fffe002087d61eb42cdc48d6a0708fd0d07fca1b9f0500510ba088fc2bd6c3b3897f7a10f58263ff3030f4f70656e5468726561642d353234660102999904109dc023ccd447b12b50997ef68020f19e0c0402a0f7f8"
set dataset1_dbus "0x0e,0x08,0x00,0x00,0x00,0x00,0x00,0x02,0x00,0x00,0x00,0x03,0x00,0x00,0x14,0x35,0x06,0x00,0x04,0x00,0x1f,0xff,0xe0,0x02,0x08,0x7d,0x61,0xeb,0x42,0xcd,0xc4,0x8d,0x6a,0x07,0x08,0xfd,0x0d,0x07,0xfc,0xa1,0xb9,0xf0,0x50,0x05,0x10,0xba,0x08,0x8f,0xc2,0xbd,0x6c,0x3b,0x38,0x97,0xf7,0xa1,0x0f,0x58,0x26,0x3f,0xf3,0x03,0x0f,0x4f,0x70,0x65,0x6e,0x54,0x68,0x72,0x65,0x61,0x64,0x2d,0x35,0x32,0x34,0x66,0x01,0x02,0x99,0x99,0x04,0x10,0x9d,0xc0,0x23,0xcc,0xd4,0x47,0xb1,0x2b,0x50,0x99,0x7e,0xf6,0x80,0x20,0xf1,0x9e,0x0c,0x04,0x02,0xa0,0xf7,0xf8"

# Step 1. Start otbr-agent with a NCP and join the network by dbus join method
spawn_node 1 otbr $::env(EXP_OT_NCP_PATH)
sleep 1
spawn dbus-send --system --dest=io.openthread.BorderRouter.wpan0 --type=method_call --print-reply /io/openthread/BorderRouter/wpan0 io.openthread.BorderRouter.Join "array:byte:${dataset_dbus}"
expect eof

# Step 2. Wait 10 seconds, check if the otbr-agent has attached successfully
sleep 10
spawn dbus-send --system --dest=io.openthread.BorderRouter.wpan0 --print-reply --reply-timeout=1000 /io/openthread/BorderRouter/wpan0 org.freedesktop.DBus.Properties.Get string:io.openthread.BorderRouter string:DeviceRole
expect -re {leader} {
} timeout {
puts "timeout!"
exit 1
}
expect eof

# Step 3. Start a Thread node and create a Thread network
spawn_node 2 cli $::env(EXP_OT_CLI_PATH)

send "dataset set active ${dataset}\n"
expect_line "Done"
send "mode rn\n"
expect_line "Done"
send "ifconfig up\n"
expect_line "Done"
send "thread start\n"
expect_line "Done"
wait_for "state" "child"
expect_line "Done"

# Step 4. Call ScheduleMigration method to migrate to another Thread network after 30s
spawn dbus-send --system --dest=io.openthread.BorderRouter.wpan0 --type=method_call --print-reply /io/openthread/BorderRouter/wpan0 io.openthread.BorderRouter.ScheduleMigration "array:byte:${dataset1_dbus}" "uint32:0x7530"
expect eof

# Step 5. Wait 31 seconds, check if the otbr-agent has migrated successfully by checking child's panid
sleep 31
switch_node 2
send "panid\n"
expect_line "0x9999"
expect_line "Done"
Original file line number Diff line number Diff line change
Expand Up @@ -39,7 +39,7 @@ spawn_node 1 otbr $::env(EXP_OT_NCP_PATH)
sleep 1

spawn dbus-send --system --dest=io.openthread.BorderRouter.wpan0 --type=method_call --print-reply /io/openthread/BorderRouter/wpan0 io.openthread.BorderRouter.ScheduleMigration "array:byte:${dataset_valid}" "uint32:0x7530"
expect Error.NotImplemented
expect Error.InvalidState
expect eof

spawn dbus-send --system --dest=io.openthread.BorderRouter.wpan0 --type=method_call --print-reply /io/openthread/BorderRouter/wpan0 io.openthread.BorderRouter.ScheduleMigration "array:byte:${dataset_has_pending_timestamp}" "uint32:0x7530"
Expand Down

0 comments on commit 861ddf2

Please sign in to comment.