Skip to content
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
8 changes: 7 additions & 1 deletion AppInfrastructure/Public/Dobby/IDobbyProxy.h
Original file line number Diff line number Diff line change
Expand Up @@ -192,12 +192,18 @@ class IDobbyProxy : public AICommon::Notifier<IDobbyProxyEvents>
}

public:
// Listener for STOPPED events — no exit code, backward-compatible.
typedef std::function<void(int32_t, const std::string&, IDobbyProxyEvents::ContainerState, const void*)> StateChangeListener;

virtual int registerListener(const StateChangeListener &listener, const void* cbParams) = 0;
// Listener for STOPPED_WITH_STATUS events — includes exit code.
typedef std::function<void(int32_t, const std::string&, IDobbyProxyEvents::ContainerState, int32_t, const void*)> StateChangeWithStatusListener;

virtual int registerListener(const StateChangeListener &listener, const void* cbParams) = 0;
virtual void unregisterListener(int tag) = 0;

virtual int registerListenerWithStatus(const StateChangeWithStatusListener &listener, const void* cbParams) = 0;
virtual void unregisterListenerWithStatus(int tag) = 0;


#if (AI_BUILD_TYPE == AI_DEBUG)

Expand Down
15 changes: 12 additions & 3 deletions client/lib/include/DobbyProxy.h
Original file line number Diff line number Diff line change
Expand Up @@ -125,8 +125,10 @@ class DobbyProxy : public IDobbyProxy
int getContainerState(int32_t cd) const override;

int registerListener(const StateChangeListener &listener, const void* cbParams) override;
void unregisterListener(int id) override;

void unregisterListener(int tag) override;
int registerListenerWithStatus(const StateChangeWithStatusListener &listener, const void* cbParams) override;
void unregisterListenerWithStatus(int id) override;

std::string getContainerInfo(int32_t descriptor) const override;

Expand Down Expand Up @@ -155,6 +157,7 @@ class DobbyProxy : public IDobbyProxy
private:
void onContainerStartedEvent(const AI_IPC::VariantList& args);
void onContainerStoppedEvent(const AI_IPC::VariantList& args);
void onContainerStoppedWithStatusEvent(const AI_IPC::VariantList& args);
void onContainerHibernatedEvent(const AI_IPC::VariantList& args);
void onContainerAwokenEvent(const AI_IPC::VariantList& args);

Expand All @@ -173,6 +176,7 @@ class DobbyProxy : public IDobbyProxy
private:
std::string mContainerStartedSignal;
std::string mContainerStoppedSignal;
std::string mContainerStoppedWithStatusSignal;

private:
std::thread mStateChangeThread;
Expand All @@ -181,19 +185,20 @@ class DobbyProxy : public IDobbyProxy

struct StateChangeEvent
{
enum Type { Terminate, ContainerStarted, ContainerStopped, ContainerHibernated, ContainerAwoken };
enum Type { Terminate, ContainerStarted, ContainerStopped, ContainerStoppedWithStatus, ContainerHibernated, ContainerAwoken };

explicit StateChangeEvent(Type type_)
: type(type_), descriptor(-1)
{ }
Comment thread
B-Larsen marked this conversation as resolved.

StateChangeEvent(Type type_, int32_t descriptor_, const std::string& name_)
: type(type_), descriptor(descriptor_), name(name_)
: type(type_), descriptor(descriptor_), name(name_), exitCode(-1)
{ }

Type type;
int32_t descriptor;
std::string name;
int32_t exitCode;
};

std::deque<StateChangeEvent> mStateChangeQueue;
Expand All @@ -202,6 +207,10 @@ class DobbyProxy : public IDobbyProxy
AICommon::IDGenerator<8> mListenerIdGen;
std::map<int, std::pair<StateChangeListener, const void*>> mListeners;

std::mutex mStatusListenersLock;
AICommon::IDGenerator<8> mStatusListenerIdGen;
std::map<int, std::pair<StateChangeWithStatusListener, const void*>> mStatusListeners;

};


Expand Down
101 changes: 89 additions & 12 deletions client/lib/source/DobbyProxy.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@

#include <Logging.h>

#include <sys/wait.h>
#include <thread>


Expand Down Expand Up @@ -89,10 +90,16 @@
const AI_IPC::SignalHandler startedHandler(std::bind(&DobbyProxy::onContainerStartedEvent, this, std::placeholders::_1));
mContainerStartedSignal = mIpcService->registerSignalHandler(startedSignal, startedHandler);

// Subscribe to plain STOPPED to serve StateChangeListener clients (no exit code).
const AI_IPC::Signal stoppedSignal(objectName, DOBBY_CTRL_INTERFACE, DOBBY_CTRL_EVENT_STOPPED);
const AI_IPC::SignalHandler stoppedHandler(std::bind(&DobbyProxy::onContainerStoppedEvent, this, std::placeholders::_1));
mContainerStoppedSignal = mIpcService->registerSignalHandler(stoppedSignal, stoppedHandler);

// Subscribe to STOPPED_WITH_STATUS to serve StateChangeWithStatusListener clients (with exit code).
const AI_IPC::Signal stoppedWithStatusSignal(objectName, DOBBY_CTRL_INTERFACE, DOBBY_CTRL_EVENT_STOPPED_WITH_STATUS);
const AI_IPC::SignalHandler stoppedWithStatusHandler(std::bind(&DobbyProxy::onContainerStoppedWithStatusEvent, this, std::placeholders::_1));
mContainerStoppedWithStatusSignal = mIpcService->registerSignalHandler(stoppedWithStatusSignal, stoppedWithStatusHandler);

const AI_IPC::Signal hibernatedSignal(objectName, DOBBY_CTRL_INTERFACE, DOBBY_CTRL_EVENT_HIBERNATED);
const AI_IPC::SignalHandler hibernatedHandler(std::bind(&DobbyProxy::onContainerHibernatedEvent, this, std::placeholders::_1));
mContainerStartedSignal = mIpcService->registerSignalHandler(hibernatedSignal, hibernatedHandler);
Expand All @@ -101,7 +108,7 @@
const AI_IPC::SignalHandler awokenHandler(std::bind(&DobbyProxy::onContainerAwokenEvent, this, std::placeholders::_1));
mContainerStartedSignal = mIpcService->registerSignalHandler(awokenSignal, awokenHandler);
Comment thread
B-Larsen marked this conversation as resolved.

if (mContainerStartedSignal.empty() || mContainerStoppedSignal.empty())
if (mContainerStartedSignal.empty() || mContainerStoppedSignal.empty() || mContainerStoppedWithStatusSignal.empty())
{
AI_LOG_ERROR("failed to register dbus signal listeners");
}
Expand All @@ -127,6 +134,9 @@
if (!mContainerStoppedSignal.empty())
mIpcService->unregisterHandler(mContainerStoppedSignal);

if (!mContainerStoppedWithStatusSignal.empty())
mIpcService->unregisterHandler(mContainerStoppedWithStatusSignal);

// flush the ipc service to guarantee the signal handlers aren't going to
// be called after we're done
mIpcService->flush();
Expand Down Expand Up @@ -196,6 +206,36 @@
mListenerIdGen.put(id);
}

int DobbyProxy::registerListenerWithStatus(const StateChangeWithStatusListener &listener, const void* cbParams)
{
std::lock_guard<std::mutex> locker(mStatusListenersLock);

int id = mStatusListenerIdGen.get();
if (id < 0)
{
AI_LOG_ERROR("too many status listeners installed");
return -1;
}

mStatusListeners.emplace(id, std::make_pair(listener, cbParams));
return id;
}

void DobbyProxy::unregisterListenerWithStatus(int id)
{
std::lock_guard<std::mutex> locker(mStatusListenersLock);

auto it = mStatusListeners.find(id);
if (it == mStatusListeners.end())
{
AI_LOG_ERROR("no status listener installed with id %d", id);
return;
}

mStatusListeners.erase(it);
mStatusListenerIdGen.put(id);
}

// -----------------------------------------------------------------------------
/**
* @brief Called when a org.rdk.dobby.ctrl1.Started event is received from
Expand Down Expand Up @@ -244,7 +284,7 @@
{
AI_LOG_FN_ENTRY();

// the event should container two args; container descriptor and id
// STOPPED carries two args: container descriptor and id (no exit code).
int32_t descriptor;
std::string id;

Expand All @@ -255,7 +295,6 @@
}
else
{
// ping off an event
std::lock_guard<std::mutex> locker(mStateChangeLock);
mStateChangeQueue.emplace_back(StateChangeEvent::ContainerStopped, descriptor, id);
mStateChangeCond.notify_all();
Expand All @@ -264,6 +303,33 @@
AI_LOG_FN_EXIT();
}

void DobbyProxy::onContainerStoppedWithStatusEvent(const AI_IPC::VariantList& args)
{
AI_LOG_FN_ENTRY();

// STOPPED_WITH_STATUS carries three args: descriptor, id, raw waitpid status.
int32_t descriptor;
std::string id;
int32_t rawStatus;

if (!AI_IPC::parseVariantList<int32_t, std::string, int32_t>(args, &descriptor, &id, &rawStatus))
{
AI_LOG_ERROR("failed to read all args from %s.%s signal",
DOBBY_CTRL_INTERFACE, DOBBY_CTRL_EVENT_STOPPED_WITH_STATUS);
}
else
{
StateChangeEvent ev(StateChangeEvent::ContainerStoppedWithStatus, descriptor, id);
ev.exitCode = WIFEXITED(rawStatus) ? WEXITSTATUS(rawStatus) : -1;

std::lock_guard<std::mutex> locker(mStateChangeLock);
mStateChangeQueue.push_back(ev);
Comment thread
B-Larsen marked this conversation as resolved.
Dismissed
mStateChangeCond.notify_all();
}

AI_LOG_FN_EXIT();
}

// -----------------------------------------------------------------------------
/**
* @brief Called when a org.rdk.dobby.ctrl1.Hibernated event is received from
Expand Down Expand Up @@ -1498,16 +1564,27 @@
notify(&IDobbyProxyEvents::containerStateChanged,
event.descriptor, event.name, state);

// need to hold the lock before searching
std::lock_guard<std::mutex> listenerLocker(mListenersLock);

// check if we have any listener interested in this service
for (const std::pair<const int, std::pair<StateChangeListener, const void*>>& handler : mListeners)
if (event.type == StateChangeEvent::ContainerStoppedWithStatus)
{
// Notify status listeners (include exit code)
std::lock_guard<std::mutex> listenerLocker(mStatusListenersLock);
for (const auto& handler : mStatusListeners)
{
const StateChangeWithStatusListener& cb = handler.second.first;
if (cb)
cb(event.descriptor, event.name, state, event.exitCode, handler.second.second);
}
}
else
{
const StateChangeListener& callback = handler.second.first;
const void* cbParams = handler.second.second;
if (callback)
callback(event.descriptor, event.name, state, cbParams);
// Notify standard listeners (no exit code)
std::lock_guard<std::mutex> listenerLocker(mListenersLock);
for (const auto& handler : mListeners)
{
const StateChangeListener& cb = handler.second.first;
if (cb)
cb(event.descriptor, event.name, state, handler.second.second);
}
}
}

Expand Down
2 changes: 2 additions & 0 deletions tests/L1_testing/mocks/IIpcService.h
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,7 @@ class IIpcServiceImpl {
virtual std::string registerSignalHandler(const Signal& signal, const SignalHandler& handler) = 0;
virtual bool unregisterHandler(const std::string& regId) = 0;
virtual bool enableMonitor(const std::set<std::string>& matchRules, const MonitorHandler& handler) = 0;
virtual bool isServiceAvailable(const std::string& serviceName) const = 0;
virtual void flush() = 0;
};
class IIpcService {
Expand All @@ -57,6 +58,7 @@ static IIpcServiceImpl* impl;
std::string registerSignalHandler(const Signal& signal, const SignalHandler& handler);
bool unregisterHandler(const std::string& regId);
bool enableMonitor(const std::set<std::string>& matchRules, const MonitorHandler& handler);
bool isServiceAvailable(const std::string& serviceName) const;
void flush();
};

Expand Down
7 changes: 7 additions & 0 deletions tests/L1_testing/mocks/IIpcServiceMock.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -81,6 +81,13 @@ bool AI_IPC::IIpcService::enableMonitor(const std::set<std::string>& matchRules,
return impl->enableMonitor(matchRules, handler);
}

bool AI_IPC::IIpcService::isServiceAvailable(const std::string& serviceName) const
{
EXPECT_NE(impl, nullptr);

return impl->isServiceAvailable(serviceName);
}

void AI_IPC::IIpcService::flush()
{
EXPECT_NE(impl, nullptr);
Expand Down
1 change: 1 addition & 0 deletions tests/L1_testing/mocks/IIpcServiceMock.h
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,7 @@ class IpcServiceMock : public IIpcServiceImpl {
MOCK_METHOD(std::string, registerSignalHandler, (const Signal& signal, const SignalHandler& handler), (override));
MOCK_METHOD(bool, unregisterHandler, (const std::string& regId), (override));
MOCK_METHOD(bool, enableMonitor, (const std::set<std::string>& matchRules, const MonitorHandler& handler), (override));
MOCK_METHOD(bool, isServiceAvailable, (const std::string& serviceName), (const, override));
MOCK_METHOD(void, flush, (), (override));
};

Expand Down
1 change: 1 addition & 0 deletions tests/L1_testing/tests/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -19,4 +19,5 @@ add_subdirectory(DobbyUtilsTest)
add_subdirectory(DobbyTest)
add_subdirectory(DobbyManagerTest)
add_subdirectory(DobbySpecConfigTest)
add_subdirectory(DobbyProxyTest)

72 changes: 72 additions & 0 deletions tests/L1_testing/tests/DobbyProxyTest/CMakeLists.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,72 @@
# If not stated otherwise in this file or this component's LICENSE file the
# following copyright and licenses apply:
#
# Copyright 2024 Sky UK
#
# 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.

cmake_minimum_required(VERSION 3.7)
project(DobbyProxyL1Test)

set(CMAKE_CXX_STANDARD 14)

find_package(GTest REQUIRED)

include_directories(${GTEST_INCLUDE_DIRS})

add_library(DobbyProxyTestLib STATIC
../../../../client/lib/source/DobbyProxy.cpp
../../../../AppInfrastructure/Logging/source/Logging.cpp
../../mocks/IIpcServiceMock.cpp
../../mocks/IpcVariantList.cpp
../../mocks/IpcFileDescriptorMock.cpp
)

target_include_directories(DobbyProxyTestLib
PUBLIC
../../mocks
../../../../client/lib/include
../../../../AppInfrastructure/Public
../../../../AppInfrastructure/Common/include
../../../../AppInfrastructure/Logging/include
../../../../AppInfrastructure/IpcService/include
../../../../protocol/include
)

file(GLOB TESTS *.cpp
../../mocks/IpcVariantList.cpp
)
Comment thread
B-Larsen marked this conversation as resolved.

add_executable(${PROJECT_NAME} ${TESTS})

target_include_directories(${PROJECT_NAME}
PRIVATE
../../mocks
../../../../client/lib/include
../../../../AppInfrastructure/Public
../../../../AppInfrastructure/Common/include
../../../../AppInfrastructure/Logging/include
../../../../AppInfrastructure/IpcService/include
../../../../protocol/include
)

target_link_libraries(${PROJECT_NAME}
PRIVATE
DobbyProxyTestLib
GTest::gmock
GTest::GTest
GTest::Main
pthread
)

install(TARGETS ${PROJECT_NAME} DESTINATION bin)
Loading
Loading