Skip to content

[PINS] Add ConsumerNotifier for PINS response path #547

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Closed
wants to merge 5 commits into from
Closed
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
2 changes: 1 addition & 1 deletion azure-pipelines.yml
Original file line number Diff line number Diff line change
Expand Up @@ -39,7 +39,7 @@ stages:
sudo apt-get install -y python3-pip
sudo pip3 install pytest
sudo apt-get install -y python
sudo apt-get install cmake libgtest-dev
sudo apt-get install cmake libgtest-dev libgmock-dev
cd /usr/src/gtest && sudo cmake . && sudo make
displayName: "Install dependencies"
- script: |
Expand Down
41 changes: 41 additions & 0 deletions common/consumernotifier.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
#include "consumernotifier.h"

#include "select.h"
#include "selectable.h"

namespace swss
{

ConsumerNotifier::ConsumerNotifier(const std::string &notifier_channel_name, DBConnector *db_connector)
{
notifier_channel_name_ = std::string{notifier_channel_name};
notification_consumer_ = std::make_unique<NotificationConsumer>(db_connector, notifier_channel_name_);
}

bool ConsumerNotifier::WaitForNotificationAndPop(std::string &op, std::string &data,
std::vector<FieldValueTuple> &values, int64_t timeout_ms)
{
int message_available = notification_consumer_->peek();
if (message_available == -1)
{
return false;
}

// Wait for notification only when the queue is empty.
if (message_available == 0)
{
Select s;
s.addSelectable(notification_consumer_.get());
Selectable *sel;

int error = s.select(&sel, timeout_ms);
if (error != Select::OBJECT)
{
return false;
}
}
notification_consumer_->pop(op, data, values);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This logic is applicable to any Selectable or Select, which contains multiple Selectable. Could you move it to their class?

return true;
}

} // namespace swss
27 changes: 27 additions & 0 deletions common/consumernotifier.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
#pragma once

#include <memory>
#include <vector>

#include "consumernotifierinterface.h"
#include "dbconnector.h"
#include "notificationconsumer.h"

namespace swss
{

// Wrapper class to use swss::NotificationConsumer.
class ConsumerNotifier : public ConsumerNotifierInterface
{
public:
ConsumerNotifier(const std::string &notifier_channel_name, DBConnector *db_connector);

bool WaitForNotificationAndPop(std::string &op, std::string &data, std::vector<FieldValueTuple> &values,
int64_t timeout_ms = 60000LL) override;

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Robert's proposal

protected:
  ConsumerNotifier();  // used for testing

Alternative:

  • Provide a fake/mock DBConnector as a subclass and use that; need to create a testing only constructor there

private:
std::unique_ptr<NotificationConsumer> notification_consumer_;
std::string notifier_channel_name_;
};

} // namespace swss
23 changes: 23 additions & 0 deletions common/consumernotifierinterface.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
#pragma once

#include <vector>

#include "table.h"

namespace swss
{

// Wrapper class to use swss::NotificationConsumer.
class ConsumerNotifierInterface
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

ConsumerNotifierInterface

I feel the *Interface classes just mark some functions as virtual. Can we remove all the classes?

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We can, but the other issue these interface classes address is the base class constructors requiring a redis db to be active. If we remove this do you have any objections to having a protected constructor that doesn't initialize a redis connection?

{
public:
virtual ~ConsumerNotifierInterface() = default;

// Waits for notifications to be posted on the response channel,
// this goes into a blocking wait (default timeout of 60 seconds) until a
// response is received and then uses swss::pop to get the response values.
virtual bool WaitForNotificationAndPop(std::string &op, std::string &data, std::vector<FieldValueTuple> &values,
int64_t timeout_ms = 60000LL) = 0;
};

} // namespace swss
14 changes: 14 additions & 0 deletions common/dbconnector.cpp
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
#include <string.h>
#include <stdint.h>
#include <iterator>
#include <vector>
#include <unistd.h>
#include <errno.h>
Expand Down Expand Up @@ -750,6 +751,19 @@ int64_t DBConnector::decr(const string &key)
return r.getContext()->integer;
}

#ifndef SWIG
std::unordered_map<std::string, std::string> DBConnector::hgetall(
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

hgetall

The template version is a general solution for all STL container types. Why we need to specialize it to unordered_map?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@rhalstea what do you think?

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The templates didn't work with the virtual interfaces.

Where this is needed in the P4RT App could be updated to the Table class. We can remove this once that happens, but that change may be difficult with the MVP timelines. However, internally we do want it planned for the next push.

const std::string &key){
return hgetall<std::unordered_map<std::string, std::string>>(key);
}
#endif

void DBConnector::hmset(
const std::string &key,
const std::vector<std::pair<std::string, std::string>> &values) {
hmset(key, values.begin(), values.end());
}

shared_ptr<string> DBConnector::get(const string &key)
{
RedisCommand sget;
Expand Down
19 changes: 14 additions & 5 deletions common/dbconnector.h
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@
#include <mutex>

#include <hiredis/hiredis.h>
#include "dbconnectorinterface.h"
#include "rediscommand.h"
#include "redisreply.h"
#define EMPTY_NAMESPACE std::string()
Expand Down Expand Up @@ -137,7 +138,7 @@ class RedisContext
redisContext *m_conn;
};

class DBConnector : public RedisContext
class DBConnector : public RedisContext, public DBConnectorInterface
{
public:
static constexpr const char *DEFAULT_UNIXSOCKET = "/var/run/redis/redis.sock";
Expand Down Expand Up @@ -174,7 +175,7 @@ class DBConnector : public RedisContext

PubSub *pubsub();

int64_t del(const std::string &key);
int64_t del(const std::string &key) override;

#ifdef SWIG
// SWIG interface file (.i) globally rename map C++ `del` to python `delete`,
Expand All @@ -187,7 +188,7 @@ class DBConnector : public RedisContext
%}
#endif

bool exists(const std::string &key);
bool exists(const std::string &key) override;

int64_t hdel(const std::string &key, const std::string &field);

Expand All @@ -199,11 +200,14 @@ class DBConnector : public RedisContext
ReturnType hgetall(const std::string &key);

#ifndef SWIG
std::unordered_map<std::string, std::string> hgetall(
Copy link
Contributor

@bocon13 bocon13 Nov 12, 2021

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Qi's suggestion: make this a standalone function in sonic-pins; first parameter is DBConnector; implementation calls the template version.

const std::string &key) override;

template <typename OutputIterator>
void hgetall(const std::string &key, OutputIterator result);
#endif

std::vector<std::string> keys(const std::string &key);
std::vector<std::string> keys(const std::string &key) override;

std::pair<int, std::vector<std::string>> scan(int cursor = 0, const char *match = "", uint32_t count = 10);

Expand All @@ -217,9 +221,14 @@ class DBConnector : public RedisContext

void hmset(const std::unordered_map<std::string, std::vector<std::pair<std::string, std::string>>>& multiHash);

void hmset(const std::string &key,
const std::vector<std::pair<std::string, std::string>> &values)
override;

std::shared_ptr<std::string> get(const std::string &key);

std::shared_ptr<std::string> hget(const std::string &key, const std::string &field);
std::shared_ptr<std::string> hget(const std::string &key,
const std::string &field) override;

bool hexists(const std::string &key, const std::string &field);

Expand Down
30 changes: 30 additions & 0 deletions common/dbconnectorinterface.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
#pragma once
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Qi concerned about performance penalty for virtual functions. Can this be done without this interface and virtual functions?

If this is needed for new feature, then it's okay. But we shouldn't incur a performance penalty just for testing.

Copy link
Contributor

@bocon13 bocon13 Nov 9, 2021

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Update: See comment on main PR conversation regarding virtual functions: #547 (comment)


#include <map>
#include <memory>
#include <vector>

namespace swss
{

class DBConnectorInterface
{
public:
virtual ~DBConnectorInterface() = default;

virtual int64_t del(const std::string &key) = 0;

virtual void del(const std::vector<std::string> &keys) = 0;

virtual bool exists(const std::string &key) = 0;

virtual std::unordered_map<std::string, std::string> hgetall(const std::string &glob) = 0;

virtual std::vector<std::string> keys(const std::string &glob) = 0;

virtual void hmset(const std::string &key, const std::vector<std::pair<std::string, std::string>> &values) = 0;

virtual std::shared_ptr<std::string> hget(const std::string &key, const std::string &field) = 0;
};

} // namespace swss
5 changes: 5 additions & 0 deletions common/producerstatetable.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -358,4 +358,9 @@ void ProducerStateTable::apply_temp_view()
m_tempViewActive = false;
}

std::string ProducerStateTable::get_table_name() const
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This function does not add value, can you remove?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We use this in the P4RT server application.

@rhalstea what do you think?

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It's useful at various points in P4RT App to know the table name. Particularly around logging. The function was added here to make it available as part of the interface.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It's already defined in TableBase::getTableName(), and TableBase could be treated as an interface.

{
return getTableName();
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

return

Please follow the code format convention in existing code. Such as 1. use 4-space indentation. 2. m_ prefix for non-public member variables.

}

}
11 changes: 8 additions & 3 deletions common/producerstatetable.h
Original file line number Diff line number Diff line change
@@ -1,12 +1,14 @@
#pragma once

#include <memory>

#include "producerstatetableinterface.h"
#include "table.h"
#include "redispipeline.h"

namespace swss {

class ProducerStateTable : public TableBase, public TableName_KeySet
class ProducerStateTable : public TableBase, public TableName_KeySet, public ProducerStateTableInterface
{
public:
ProducerStateTable(DBConnector *db, const std::string &tableName);
Expand All @@ -18,11 +20,11 @@ class ProducerStateTable : public TableBase, public TableName_KeySet
virtual void set(const std::string &key,
const std::vector<FieldValueTuple> &values,
const std::string &op = SET_COMMAND,
const std::string &prefix = EMPTY_PREFIX);
const std::string &prefix = EMPTY_PREFIX) override;

virtual void del(const std::string &key,
const std::string &op = DEL_COMMAND,
const std::string &prefix = EMPTY_PREFIX);
const std::string &prefix = EMPTY_PREFIX) override;

#ifdef SWIG
// SWIG interface file (.i) globally rename map C++ `del` to python `delete`,
Expand All @@ -44,6 +46,9 @@ class ProducerStateTable : public TableBase, public TableName_KeySet
void create_temp_view();

void apply_temp_view();

std::string get_table_name() const override;

private:
bool m_buffered;
bool m_pipeowned;
Expand Down
25 changes: 25 additions & 0 deletions common/producerstatetableinterface.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
#pragma once

#include <memory>
#include <vector>

#include "table.h"

namespace swss
{

class ProducerStateTableInterface
{
public:
virtual ~ProducerStateTableInterface() = default;

virtual void set(const std::string &key, const std::vector<FieldValueTuple> &values,
const std::string &op = SET_COMMAND, const std::string &prefix = EMPTY_PREFIX) = 0;

virtual void del(const std::string &key, const std::string &op = DEL_COMMAND,
const std::string &prefix = EMPTY_PREFIX) = 0;

virtual std::string get_table_name() const = 0;
};

} // namespace swss
2 changes: 1 addition & 1 deletion tests/Makefile.am
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ DBGFLAGS = -g -DNDEBUG
endif

CFLAGS_GTEST =
LDADD_GTEST = -L/usr/src/gtest -lgtest -lgtest_main
LDADD_GTEST = -L/usr/src/gtest -lgtest -lgtest_main -lgmock -lgmock_main

tests_SOURCES = redis_ut.cpp \
redis_piped_ut.cpp \
Expand Down
20 changes: 20 additions & 0 deletions tests/fakes/fake_consumer_notifier.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
#include "swss/fakes/fake_consumer_notifier.h"

#include "glog/logging.h"

namespace swss
{

FakeConsumerNotifier::FakeConsumerNotifier(FakeSonicDbTable *app_db_table) : app_db_table_(app_db_table)
{
LOG_IF(FATAL, app_db_table == nullptr) << "FakeSonicDbTable cannot be nullptr.";
}

bool FakeConsumerNotifier::WaitForNotificationAndPop(std::string &op, std::string &data, SonicDbEntry &values,
int64_t timeout_ms)
{
app_db_table_->GetNextNotification(op, data, values);
return true;
}

} // namespace swss
36 changes: 36 additions & 0 deletions tests/fakes/fake_consumer_notifier.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
#pragma once

#include <vector>

#include "absl/status/status.h"
#include "swss/consumernotifierinterface.h"
#include "swss/fakes/fake_sonic_db_table.h"

namespace swss
{

// Fakes the OrchAgent response path behavior for AppDb table entries.
//
// Every write into an AppDb table is handled by the OrchAgent. The write can
// either succeed or fail. In the latter case a failed StatusCode should be
// returned by the fake.
class FakeConsumerNotifier final : public ConsumerNotifierInterface
{
public:
explicit FakeConsumerNotifier(FakeSonicDbTable *app_db_table);

// Not copyable or moveable.
FakeConsumerNotifier(const FakeConsumerNotifier &) = delete;
FakeConsumerNotifier &operator=(const FakeConsumerNotifier &) = delete;

// Faked methods.
bool WaitForNotificationAndPop(std::string &op, std::string &data, SonicDbEntry &values,
int64_t timeout_ms = 60000LL) override;

private:
// The AppDb table maintains a list of notifications that this fake can
// request.
FakeSonicDbTable *app_db_table_; // No ownership.
};

} // namespace swss
Loading