[asterisk-scf-commits] asterisk-scf/integration/file_session_gateway.git branch "initial_development" updated.
Commits to the Asterisk SCF project code repositories
asterisk-scf-commits at lists.digium.com
Wed Nov 30 09:34:59 CST 2011
branch "initial_development" has been updated
via 8e0d0f4e904a813389480705f724858b7eac505b (commit)
via 09f8ea4796b816882fd51edeb022e915d5ce337f (commit)
from 377beed4fec4f9bc266ac7932142d275703dae03 (commit)
Summary of changes:
CMakeLists.txt | 7 +-
config/FileSessionGatewayConfigurator.py | 382 +++++++++++++++
config/FileSessionGatewayTest.conf | 6 +
.../FileSessionReplicationIf.ice | 166 ++++---
slice/CMakeLists.txt | 18 +
src/CMakeLists.txt | 13 +-
src/Component.cpp | 188 +++++++-
src/{ReplicationContext.h => ComponentDefs.h} | 33 +-
src/ComponentFeature.h | 8 +-
src/Configuration.cpp | 62 +++-
src/CookieManager.cpp | 142 ++++++
src/CookieManager.h | 143 ++++++
src/Endpoint.cpp | 203 +++++++--
src/Endpoint.h | 31 ++-
src/EndpointLocator.cpp | 188 ++++----
src/EndpointLocator.h | 58 ++-
src/{Config.h => EndpointSpecification.h} | 67 ++--
src/FileSessionGatewayComponent.h | 4 +
src/ReplicatedObject.cpp | 127 +++++
src/ReplicatedObject.h | 56 +++
src/{ReplicationContext.h => ReplicationAdapter.h} | 33 +-
src/ReplicationListener.cpp | 136 ++++++
src/ReplicationListener.h | 15 +-
src/Replicator.h | 26 +-
src/ReplicatorListener.h | 40 --
src/Session.cpp | 496 +++++++++++++++-----
src/Session.h | 92 ++++-
test/CMakeLists.txt | 33 ++
test/TestCookiesIf.ice | 51 ++
test/TestSessionObject.cpp | 459 ++++++++++++++++++
30 files changed, 2808 insertions(+), 475 deletions(-)
create mode 100755 config/FileSessionGatewayConfigurator.py
create mode 100644 config/FileSessionGatewayTest.conf
create mode 100644 slice/CMakeLists.txt
copy src/{ReplicationContext.h => ComponentDefs.h} (65%)
create mode 100644 src/CookieManager.cpp
create mode 100644 src/CookieManager.h
copy src/{Config.h => EndpointSpecification.h} (67%)
create mode 100644 src/ReplicatedObject.cpp
create mode 100644 src/ReplicatedObject.h
copy src/{ReplicationContext.h => ReplicationAdapter.h} (56%)
create mode 100644 src/ReplicationListener.cpp
delete mode 100644 src/ReplicatorListener.h
create mode 100644 test/CMakeLists.txt
create mode 100644 test/TestCookiesIf.ice
create mode 100644 test/TestSessionObject.cpp
- Log -----------------------------------------------------------------
commit 8e0d0f4e904a813389480705f724858b7eac505b
Author: Brent Eagles <beagles at digium.com>
Date: Tue Nov 29 16:15:44 2011 -0330
Fixups for replication and corresponding tests.
diff --git a/config/FileSessionGatewayTest.conf b/config/FileSessionGatewayTest.conf
new file mode 100644
index 0000000..a193178
--- /dev/null
+++ b/config/FileSessionGatewayTest.conf
@@ -0,0 +1,6 @@
+#
+# Configuration file for the FileSessionGatewaySessionTest test driver.
+#
+
+LocatorService.Proxy=LocatorService:tcp -p 4411
+LocatorServiceManagement.Proxy=LocatorServiceManagement:tcp -p 4422
diff --git a/slice/AsteriskSCF/Replication/FileSessionGateway/FileSessionReplicationIf.ice b/slice/AsteriskSCF/Replication/FileSessionGateway/FileSessionReplicationIf.ice
index 2ab5b9a..8d7e781 100644
--- a/slice/AsteriskSCF/Replication/FileSessionGateway/FileSessionReplicationIf.ice
+++ b/slice/AsteriskSCF/Replication/FileSessionGateway/FileSessionReplicationIf.ice
@@ -114,7 +114,7 @@ class MediaSessionState extends SessionState
class SessionListenerUpdate extends SessionState
{
- AsteriskSCF::SessionCommunications::V1::SessionListener* listener;
+ AsteriskSCF::SessionCommunications::V1::SessionListenerSeq listeners;
};
class BridgeState extends SessionState
diff --git a/src/ReplicatedObject.cpp b/src/ReplicatedObject.cpp
new file mode 100644
index 0000000..fd0ed0e
--- /dev/null
+++ b/src/ReplicatedObject.cpp
@@ -0,0 +1,127 @@
+/*
+ * Asterisk SCF -- An open-source communications framework.
+ *
+ * Copyright (C) 2010, Digium, Inc.
+ *
+ * See http://www.asterisk.org for more information about
+ * the Asterisk SCF project. Please do not directly contact
+ * any of the maintainers of this project for assistance;
+ * the project provides a web site, mailing lists and IRC
+ * channels for your use.
+ *
+ * This program is free software, distributed under the terms of
+ * the GNU General Public License Version 2. See the LICENSE.txt file
+ * at the top of the source tree.
+ */
+
+#include "ReplicatedObject.h"
+#include <string>
+
+using namespace AsteriskSCF::FileSessionGtw;
+using namespace std;
+
+ReplicatedObject::ReplicatedObject(const std::string& key) :
+ mKey(key)
+{
+ //
+ // Nothing to do here.
+ //
+}
+
+ReplicatedObject::ReplicatedObject(const std::string& key, const ReplicatorSmartProxy& replicator) :
+ mKey(key),
+ mReplicator(replicator)
+{
+}
+
+void ReplicatedObject::updateReplicator(const ReplicatorSmartProxy& replicator)
+{
+ //
+ // Assignment of instances of SmartProxy are supposed to be thread safe.
+ //
+ mReplicator = replicator;
+ onProxyUpdate();
+}
+
+void ReplicatedObject::update(const AsteriskSCF::Replication::FileSessionGateway::V1::StateItemPtr& stateUpdate)
+{
+ AsteriskSCF::Replication::FileSessionGateway::V1::StateItemSeq seq;
+ seq.push_back(stateUpdate);
+ update(seq);
+}
+
+void ReplicatedObject::update(const AsteriskSCF::Replication::FileSessionGateway::V1::StateItemSeq& updates)
+{
+ if (mReplicator)
+ {
+ try
+ {
+ mReplicator->setState(updates);
+ }
+ catch (const AsteriskSCF::Core::Discovery::V1::ServiceNotFound&)
+ {
+ //
+ // Really should not happen.
+ //
+ }
+ }
+}
+
+void ReplicatedObject::remove(const Ice::StringSeq& items)
+{
+ if (mReplicator)
+ {
+ try
+ {
+ mReplicator->removeState(items);
+ }
+ catch (const AsteriskSCF::Core::Discovery::V1::ServiceNotFound&)
+ {
+ //
+ // Really should not happen.
+ //
+ }
+ }
+}
+
+void ReplicatedObject::remove(const string& key)
+{
+ Ice::StringSeq items;
+ items.push_back(key);
+ remove(items);
+}
+
+void ReplicatedObject::remove(const AsteriskSCF::Replication::FileSessionGateway::V1::StateItemSeq& itemsToRemove)
+{
+ if (mReplicator)
+ {
+ try
+ {
+ mReplicator->removeStateForItems(itemsToRemove);
+ }
+ catch (const AsteriskSCF::Core::Discovery::V1::ServiceNotFound&)
+ {
+ //
+ // Really should not happen.
+ //
+ }
+ }
+}
+
+std::string ReplicatedObject::getKey()
+{
+ //
+ // mKey is immutable and does not require a lock.
+ //
+ return mKey;
+}
+
+void ReplicatedObject::onProxyUpdate()
+{
+ //
+ // Default implementation does nothing. Derived objects may want to push the new proxy to children,
+ // log change, etc.
+ //
+}
+
+
diff --git a/src/ReplicatedObject.h b/src/ReplicatedObject.h
index 15942b4..0b164c9 100644
--- a/src/ReplicatedObject.h
+++ b/src/ReplicatedObject.h
@@ -33,8 +33,8 @@ namespace FileSessionGtw
class ReplicatedObject
{
public:
- ReplicatedObject();
- ReplicatedObject(const ReplicatorSmartProxy& replicator);
+ ReplicatedObject(const std::string& id);
+ ReplicatedObject(const std::string& id, const ReplicatorSmartProxy& replicator);
virtual ~ReplicatedObject() {}
void updateReplicator(const ReplicatorSmartProxy& replicator);
void update(const AsteriskSCF::Replication::FileSessionGateway::V1::StateItemPtr& stateUpdate);
@@ -42,8 +42,11 @@ public:
void remove(const Ice::StringSeq& items);
void remove(const std::string& item);
void remove(const AsteriskSCF::Replication::FileSessionGateway::V1::StateItemSeq& itemsToRemove);
+
+ std::string getKey();
protected:
+ std::string mKey;
ReplicatorSmartProxy mReplicator;
virtual void onProxyUpdate();
diff --git a/src/Session.cpp b/src/Session.cpp
index 04d8fbe..b579062 100644
--- a/src/Session.cpp
+++ b/src/Session.cpp
@@ -75,6 +75,7 @@ public:
const string& endpointKey,
const Ice::ObjectAdapterPtr& adapter,
const Logger& logger) :
+ SessionImpl(id),
mServiceLocator(locator),
mAdapter(adapter),
mPublicProxy(myPublicProxy),
@@ -112,6 +113,7 @@ public:
stateUpdate =
createUpdate<AsteriskSCF::Replication::FileSessionGateway::V1::SessionListenerUpdatePtr>(
mId + ".listener." + idString);
+ copyListeners(stateUpdate->listeners);
}
}
if (stateUpdate)
@@ -292,7 +294,7 @@ public:
{
try
{
- string removalKey;
+ Replication::BridgeStatePtr stateUpdate;
{
UniqueLock lock(mLock);
stateCheckNoLock();
@@ -301,18 +303,22 @@ public:
throw NotBridged();
}
Ice::CommunicatorPtr c = mAdapter->getCommunicator();
- removalKey = mId + ".bridge";
+ stateUpdate = createUpdate<Replication::BridgeStatePtr>(mId + ".bridge");
mBridge = BridgePrx();
string idString = c->identityToString(listener->ice_getIdentity());
Listeners::iterator iter = mListeners.find(idString);
if (iter != mListeners.end())
{
mListeners.erase(iter);
+ stateUpdate->listener = listener;
}
}
- if (!removalKey.empty())
+ //
+ // removing a bridge is not replicated by "removal", but a change in state values.
+ //
+ if (stateUpdate)
{
- remove(removalKey);
+ update(stateUpdate);
}
cb->ice_response();
}
@@ -410,25 +416,34 @@ public:
cb->ice_response(AsteriskSCF::SessionCommunications::PartyIdentification::V1::ConnectedLinePtr());
}
+ bool isDestroyed()
+ {
+ SharedLock lock(mLock);
+ return mDestroyed;
+ }
+
void destroy()
{
UniqueLock lock(mLock);
mDestroyed = true;
}
- ReplicationAdapterPtr getReplicationAdapter();
-
- bool isDestroyed()
+ string getId()
{
SharedLock lock(mLock);
- return mDestroyed;
+ return mId;
}
- string getId()
+ ReplicationAdapterPtr getReplicationAdapter();
+
+ AsteriskSCF::SessionCommunications::V1::SessionListenerSeq getSessionListeners()
{
+ AsteriskSCF::SessionCommunications::V1::SessionListenerSeq result;
SharedLock lock(mLock);
- return mId;
+ copyListeners(result);
+ return result;
}
+
void cookiesSet(const SessionCookies&)
{
@@ -486,23 +501,24 @@ public:
mCookieManager->resetCookies(cookie->cookies);
}
- void addListener(const Replication::SessionListenerUpdatePtr& listener)
+ void setListeners(const Replication::SessionListenerUpdatePtr& listenerUpdate)
{
- string idString = mAdapter->getCommunicator()->identityToString(listener->listener->ice_getIdentity());
UniqueLock lock(mLock);
- mListeners[idString] = listener->listener;
- mLogger(Trace) << "(Replication: " << mId << "): adding listener " << idString;
+ mListeners.clear();
+ for (AsteriskSCF::SessionCommunications::V1::SessionListenerSeq::const_iterator iter =
+ listenerUpdate->listeners.begin(); iter != listenerUpdate->listeners.end(); ++iter)
+ {
+ if (!(*iter))
+ {
+ continue;
+ }
+ string idString = mAdapter->getCommunicator()->identityToString((*iter)->ice_getIdentity());
+ mListeners[idString] = (*iter);
+ }
}
- void removeListener(const Replication::SessionListenerUpdatePtr& listener)
+ void setMediaSessionId(const Replication::MediaSessionIdStatePtr&)
{
- string idString = mAdapter->getCommunicator()->identityToString(listener->listener->ice_getIdentity());
- UniqueLock lock(mLock);
- Listeners::iterator iter = mListeners.find(idString);
- if (iter != mListeners.end())
- {
- mLogger(Trace) << "(Replication:" << mId << "): removing listener " << idString;
- }
}
void setMediaSession(const Replication::MediaSessionStatePtr&)
@@ -522,8 +538,21 @@ public:
{
}
- void setSessionControllerId(const Replication::SessionControllerIdState&)
+ void setSessionControllerId(const Replication::SessionControllerIdStatePtr&)
+ {
+ }
+
+ //
+ // This *could* be private, but it's been made public to facilitate testing.
+ //
+ template <typename T>
+ T createUpdate(const string& itemKey)
{
+ T result = new typename T::element_type;
+ result->key = itemKey;
+ result->endpointItemKey = mEndpointKey;
+ result->sessionId = mId;
+ return result;
}
private:
@@ -578,46 +607,78 @@ private:
return info;
}
- template <typename T>
- T createUpdate(const string& itemKey)
+ void copyListeners(AsteriskSCF::SessionCommunications::V1::SessionListenerSeq& seq)
{
- T result = new typename T::element_type;
- result->key = itemKey;
- result->endpointItemKey = mEndpointKey;
- result->sessionId = mId;
- return result;
+ seq.clear();
+ for (Listeners::const_iterator iter = mListeners.begin(); iter != mListeners.end(); ++iter)
+ {
+ seq.push_back(iter->second);
+ }
}
};
typedef IceUtil::Handle<SessionServant> SessionServantPtr;
+#define CHECK_AND_DISPATCH_UPDATE(T, Object, Method, Update) \
+ { \
+ T v = T::dynamicCast(Update); \
+ if (v) \
+ { \
+ Object->Method(v); \
+ return; \
+ } \
+ }
+
+
class SessionReplicationAdapter : public ReplicationAdapter
{
public:
- SessionReplicationAdapter(const SessionServantPtr& sessionServant) :
- mSession(sessionServant)
+ SessionReplicationAdapter(const SessionServantPtr& sessionServant,
+ const Logger& logger) :
+ mSession(sessionServant),
+ mLogger(logger)
{
+ assert(mSession);
}
- void update(const AsteriskSCF::Replication::FileSessionGateway::V1::StateItemPtr&)
- {
- //
- // XXX:
- //
+ void update(const AsteriskSCF::Replication::FileSessionGateway::V1::StateItemPtr& update)
+ {
+ CHECK_AND_DISPATCH_UPDATE(Replication::MediaSessionStatePtr, mSession, setMediaSession, update);
+ CHECK_AND_DISPATCH_UPDATE(Replication::SessionControllerIdStatePtr, mSession, setSessionControllerId, update);
+ CHECK_AND_DISPATCH_UPDATE(Replication::SourcesStatePtr, mSession, setSourcesState, update);
+ CHECK_AND_DISPATCH_UPDATE(Replication::SinksStatePtr, mSession, setSinks, update);
+ CHECK_AND_DISPATCH_UPDATE(Replication::StreamsStatePtr, mSession, setStreams, update);
+ CHECK_AND_DISPATCH_UPDATE(Replication::MediaSessionIdStatePtr, mSession, setMediaSessionId, update);
+ CHECK_AND_DISPATCH_UPDATE(Replication::SessionListenerUpdatePtr, mSession, setListeners, update);
+ CHECK_AND_DISPATCH_UPDATE(Replication::BridgeStatePtr, mSession, setBridge, update);
+ CHECK_AND_DISPATCH_UPDATE(Replication::CookieStatePtr, mSession, setCookies, update);
+ Replication::SessionStatePtr sessionUpdate = Replication::SessionStatePtr::dynamicCast(update);
+ if (!sessionUpdate)
+ {
+ mLogger(Error) << "A non-session replication update was routed to a session object";
+ return;
+ }
}
void destroy()
{
+ mSession->destroy();
}
private:
SessionServantPtr mSession;
+ Logger mLogger;
};
ReplicationAdapterPtr SessionServant::getReplicationAdapter()
{
- return ReplicationAdapterPtr(new SessionReplicationAdapter(this));
+ return ReplicationAdapterPtr(new SessionReplicationAdapter(this, mLogger));
+}
+
+SessionImpl::SessionImpl(const string& id) :
+ ReplicatedObject(id)
+{
}
SessionImplPtr SessionImpl::create(
diff --git a/src/Session.h b/src/Session.h
index 57393bf..32f88a6 100644
--- a/src/Session.h
+++ b/src/Session.h
@@ -32,24 +32,75 @@ namespace AsteriskSCF
namespace FileSessionGtw
{
+/**
+ *
+ * Base class for the FileSessionGateway's session implementation. This
+ * class defines methods that are used by the other objects in the
+ * FileSessionGateway.
+ *
+ */
class SessionImpl : public AsteriskSCF::SessionCommunications::V1::Session, public ReplicatedObject
{
public:
- virtual bool isDestroyed() = 0;
+ SessionImpl(const std::string& id);
- virtual std::string getId() = 0;
+ /**
+ *
+ * Check to see if the object has been marked as "destroyed". A
+ * destroyed object is pending deletion but some state is still
+ * available for further internal processing.
+ *
+ * @returns true if the object has been destroyed.
+ *
+ */
+ virtual bool isDestroyed() = 0;
+ /**
+ *
+ * Mark this object as destroyed.
+ *
+ */
virtual void destroy() = 0;
+ /**
+ *
+ * Get the internal id. This id must be relatively unique (can be
+ * repeated in a component's lifetime, but two objects of the same type
+ * may not share the same id at the same time).
+ *
+ * @returns a string containing the current id.
+ *
+ */
+ virtual std::string getId() = 0;
+
+ /**
+ *
+ * Get the replication adapter for this object. To be used by
+ * replication responders to update this object.
+ *
+ * @returns a replication adapter instance for this session instance. Note: this may be a new instance
+ * each time it is called so pointer comparisons to determine if an adapter "belongs" to a particular
+ * object is not supported.
+ *
+ */
virtual ReplicationAdapterPtr getReplicationAdapter() = 0;
/**
*
- * @param adapter The Ice object adapter that objects instantiated by the
- * calls on the Session object should be activated on.
- *
- * @param myProxy The proxy that can be used to access to this object.
+ * Get the session listeners that are currently registered with this session.
+ *
+ * @returns a sequence of session listener proxies.
+ *
+ */
+ virtual AsteriskSCF::SessionCommunications::V1::SessionListenerSeq getSessionListeners() = 0;
+
+ /**
+ *
+ * Create a new instance of a session object.
+ *
+ * TODO: document parameters.
+ *
*/
static IceUtil::Handle<SessionImpl> create(
const AsteriskSCF::Core::Discovery::V1::ServiceLocatorPrx& locator,
@@ -60,6 +111,13 @@ public:
const Ice::ObjectAdapterPtr& adapter,
const AsteriskSCF::System::Logging::Logger& logger);
+ /**
+ *
+ * Create a replica of a session object.
+ *
+ * TODO: document parameters.
+ *
+ */
static IceUtil::Handle<SessionImpl> create(
const AsteriskSCF::Core::Discovery::V1::ServiceLocatorPrx& locator,
const AsteriskSCF::Replication::FileSessionGateway::V1::SessionDefinitionStatePtr& sessionState,
diff --git a/test/TestSessionObject.cpp b/test/TestSessionObject.cpp
index 6a1784b..44ed6f5 100644
--- a/test/TestSessionObject.cpp
+++ b/test/TestSessionObject.cpp
@@ -39,9 +39,57 @@ using namespace std;
using namespace boost::unit_test;
using namespace AsteriskSCF::FileSessionGtw;
+//namespace Replication = AsteriskSCF::Replication::FileSessionGateway::V1;
+
+struct IndicationRecord
+{
+ AsteriskSCF::SessionCommunications::V1::SessionPrx source;
+ AsteriskSCF::SessionCommunications::V1::IndicationPtr indication;
+ AsteriskSCF::SessionCommunications::V1::SessionCookies cookies;
+};
+typedef vector<IndicationRecord> Indications;
+
+class TestSessionListener : public AsteriskSCF::SessionCommunications::V1::SessionListener
+{
+public:
+ TestSessionListener()
+ {
+ }
+
+ void indicated(const AsteriskSCF::SessionCommunications::V1::SessionPrx& source,
+ const AsteriskSCF::SessionCommunications::V1::IndicationPtr& indication,
+ const AsteriskSCF::SessionCommunications::V1::SessionCookies& cookies,
+ const Ice::Current&)
+ {
+ UniqueLock lock(mLock);
+ IndicationRecord record;
+ record.source = source;
+ record.indication = indication;
+ record.cookies = cookies;
+ mIndicationLog.push_back(record);
+ }
+
+ Indications getLog()
+ {
+ SharedLock lock(mLock);
+ return mIndicationLog;
+ }
+
+ void clearLog()
+ {
+ UniqueLock lock(mLock);
+ mIndicationLog.clear();
+ }
+
+private:
+ boost::shared_mutex mLock;
+ Indications mIndicationLog;
+};
+typedef IceUtil::Handle<TestSessionListener> TestSessionListenerPtr;
+
/**
*
- * Simple test to validate the CookieManager helper class.
+ * Simple test suite to validate the CookieManager helper class.
*
*/
typedef boost::shared_ptr<AsteriskSCF::SessionCommunications::V1::SessionCookies> SessionCookiesPtr;
@@ -54,6 +102,9 @@ public:
{
}
+ //
+ // Test listener to verify listener updates. Simply takes a smart pointer to a cookie sequence.
+ //
class TestListener : public CookieManagerListener
{
public:
@@ -86,7 +137,10 @@ public:
SessionCookiesPtr mCookiesRef;
};
typedef boost::shared_ptr<TestListener> TestListenerPtr;
-
+
+ //
+ // These could be broken into separate tests.
+ //
void testCookieOps()
{
SessionCookiesPtr cookieCapture(new AsteriskSCF::SessionCommunications::V1::SessionCookies);
@@ -214,15 +268,146 @@ public:
mObjectAdapter(adapter),
mLogger(logger)
{
+ cerr << mCommunicator->getProperties()->getProperty("LocatorService.Proxy") << endl;
+ mLocator = AsteriskSCF::Core::Discovery::V1::ServiceLocatorPrx::uncheckedCast(
+ mCommunicator->propertyToProxy("LocatorService.Proxy"));
+ //
+ // Note: BOOST test framework methods cannot be used here yet since the framework
+ // isn't initialized yet.
+ //
+ }
+
+ //
+ // Exercise the methods declared in Session.h.
+ //
+ void implementationInterfaceTests()
+ {
+ //
+ // Exercise the implementation interfaces defined in Session.h
+ //
+ SessionImplPtr session = createTestSessionObject();
+ BOOST_REQUIRE(!session->isDestroyed());
+ BOOST_REQUIRE(session->getId() == "TestSession");
+ AsteriskSCF::FileSessionGtw::ReplicationAdapterPtr replicationAdapter =
+ session->getReplicationAdapter();
+ BOOST_REQUIRE(replicationAdapter);
+ session->destroy();
+ BOOST_REQUIRE(session->isDestroyed());
+
+ //
+ // Note: the replication adapter itself is tested elsewhere.
+ //
+
+ //
+ // Reminder: if the test is modified to register anything with the
+ // Ice runtime, it should be removed now.
+ //
+ }
+
+ //
+ // Exercise the session's replication adapter.
+ //
+ void testReplicationAdapter()
+ {
+ BOOST_REQUIRE(mLocator);
+ SessionServantPtr session = createTestSessionObject();
+ AsteriskSCF::FileSessionGtw::ReplicationAdapterPtr replicationAdapter =
+ session->getReplicationAdapter();
+ BOOST_REQUIRE(replicationAdapter);
+
+ //
+ // How this test works is we create examples of state replications
+ // and feed them to the replication adapter. We can then example
+ // the state of session to determine the success or failure fo the
+ // replication.
+ //
+ AsteriskSCF::SessionCommunications::V1::BridgePrx bridgePrx =
+ AsteriskSCF::SessionCommunications::V1::BridgePrx::uncheckedCast(
+ mObjectAdapter->createProxy(mCommunicator->stringToIdentity("TestBridge")));
+ TestSessionListenerPtr listener = new TestSessionListener;
+ Ice::Identity listenerId = mCommunicator->stringToIdentity("TestSessionListener");
+ AsteriskSCF::SessionCommunications::V1::SessionListenerPrx listenerPrx =
+ AsteriskSCF::SessionCommunications::V1::SessionListenerPrx::uncheckedCast(mObjectAdapter->add(listener, listenerId));
+
+ Ice::ObjectPrx objPrx = mObjectAdapter->add(session, mCommunicator->stringToIdentity("TestSession"));
+
+ //
+ // Since the session objects implement some methods as AMD, we need
+ // to create a non collocation optimized proxy.
+ //
+ AsteriskSCF::SessionCommunications::V1::SessionPrx sessionPrx =
+ AsteriskSCF::SessionCommunications::V1::SessionPrx::uncheckedCast(objPrx->ice_collocationOptimized(false));
+
+ try
+ {
+ Replication::BridgeStatePtr bridgeUpdate =
+ session->createUpdate<Replication::BridgeStatePtr>(session->getId() + ".bridge");
+ bridgeUpdate->bridgeProxy = bridgePrx;
+ bridgeUpdate->listener = listenerPrx;
+
+ replicationAdapter->update(bridgeUpdate);
+
+ AsteriskSCF::SessionCommunications::V1::BridgePrx returnedBridge = sessionPrx->getBridge();
+ BOOST_CHECK(returnedBridge->ice_getIdentity() == bridgePrx->ice_getIdentity());
+
+ AsteriskSCF::SessionCommunications::V1::SessionListenerSeq listeners = session->getSessionListeners();
+ BOOST_CHECK(listeners.size() == 1);
+ BOOST_CHECK(listeners[0]->ice_getIdentity() == listenerId);
+
+ bridgeUpdate->bridgeProxy = AsteriskSCF::SessionCommunications::V1::BridgePrx();
+ replicationAdapter->update(bridgeUpdate);
+ returnedBridge = sessionPrx->getBridge();
+ BOOST_CHECK(!returnedBridge);
+ listeners = session->getSessionListeners();
+ BOOST_CHECK(listeners.empty());
+ }
+ catch (const std::exception& ex)
+ {
+ mLogger(Error) << __FILE__ << ':' << __LINE__ << " - " << "Unexpected " << ex.what();
+ BOOST_REQUIRE(false);
+ }
+ catch (...)
+ {
+ mLogger(Error) << __FILE__ << ':' << __LINE__ << " - " << "Unexpected unknown exception.";
+ BOOST_REQUIRE(false);
+ }
+ try
+ {
+ mObjectAdapter->remove(listenerId);
+ }
+ catch (...)
+ {
+ BOOST_REQUIRE(false);
+ }
}
- void fooTest()
+ //
+ // Helper function for creating a test session object.
+ //
+ SessionServantPtr createTestSessionObject()
{
- BOOST_REQUIRE(true);
+ BOOST_REQUIRE(mLocator);
+ AsteriskSCF::SessionCommunications::V1::SessionPrx testProxy =
+ AsteriskSCF::SessionCommunications::V1::SessionPrx::uncheckedCast(
+ mObjectAdapter->createProxy(mCommunicator->stringToIdentity("TestSession")));
+ AsteriskSCF::SessionCommunications::V1::SessionEndpointPrx testEndpoint =
+ AsteriskSCF::SessionCommunications::V1::SessionEndpointPrx::uncheckedCast(
+ mObjectAdapter->createProxy(mCommunicator->stringToIdentity("TestEndpoint")));
+
+ //
+ // Exercise the implementation interfaces defined in Session.h
+ //
+ SessionServantPtr session = SessionServantPtr::dynamicCast(
+ SessionImpl::create(mLocator, testProxy, testEndpoint, "TestSession", "TestEndpoint", mObjectAdapter,
+ mLogger));
+ BOOST_REQUIRE(session);
+ return session;
}
+
private:
Ice::CommunicatorPtr mCommunicator;
Ice::ObjectAdapterPtr mObjectAdapter;
+ AsteriskSCF::Core::Discovery::V1::ServiceLocatorPrx mLocator;
Logger mLogger;
};
@@ -232,7 +417,10 @@ boost::shared_ptr<CookieManagerTests> cookieManagerSuite;
bool init_unit_test()
{
framework::master_test_suite().add(BOOST_TEST_CASE(
- boost::bind(&SessionObjectTests::fooTest, sessionObjectSuite)));
+ boost::bind(&SessionObjectTests::implementationInterfaceTests, sessionObjectSuite)));
+ framework::master_test_suite().add(BOOST_TEST_CASE(
+ boost::bind(&SessionObjectTests::testReplicationAdapter, sessionObjectSuite)));
+
framework::master_test_suite().add(BOOST_TEST_CASE(
boost::bind(&CookieManagerTests::testCookieOps, cookieManagerSuite)));
@@ -249,6 +437,7 @@ int main(int argc, char** argv)
Ice::ObjectAdapterPtr objectAdapter = communicator->createObjectAdapterWithEndpoints(
"UnitTestAdapter", communicator->getProperties()->getPropertyWithDefault(
"UnitTestAdapter.Endpoints", "default"));
+ objectAdapter->activate();
//
// Initialize test suite objects.
//
commit 09f8ea4796b816882fd51edeb022e915d5ce337f
Author: Brent Eagles <beagles at digium.com>
Date: Mon Nov 28 06:32:06 2011 -0330
Lots of additions, reworkings, refactoring and some test code.
diff --git a/CMakeLists.txt b/CMakeLists.txt
index c049ee8..dd29684 100755
--- a/CMakeLists.txt
+++ b/CMakeLists.txt
@@ -1,8 +1,11 @@
astscf_project(FileSessionGateway 3.4)
-add_subdirectory(src)
+add_subdirectory(slice)
+# add_subdirectory(src)
+add_subdirectory(test)
+
#
-#if(BUILD_TESTING)
+#(BUILD_TESTING)
#
# add_subdirectory(test)
#endif()
diff --git a/config/FileSessionGatewayConfigurator.py b/config/FileSessionGatewayConfigurator.py
new file mode 100755
index 0000000..3da565e
--- /dev/null
+++ b/config/FileSessionGatewayConfigurator.py
@@ -0,0 +1,382 @@
+#!/usr/bin/env python
+
+#
+# Asterisk SCF -- An open-source communications framework.
+#
+# Copyright (C) 2011, Digium, Inc.
+#
+# See http://www.asterisk.org for more information about
+# the Asterisk SCF project. Please do not directly contact
+# any of the maintainers of this project for assistance;
+# the project provides a web site, mailing lists and IRC
+# channels for your use.
+#
+# This program is free software, distributed under the terms of
+# the GNU General Public License Version 2. See the LICENSE.txt file
+# at the top of the source tree.
+#
+
+# FileSessionGateway configurator
+
+# Bring in the common configuration infrastructure
+import ConfigParser, Ice, Configurator, sys, os, traceback
+
+# Load our component specific configuration definitions
+Ice.loadSlice("--underscore -I\"" + os.environ["ASTSCF_HOME"] + "\"" + " -I" + Ice.getSliceDir() + " --all ../slice/AsteriskSCF/Configuration/FileSessionGateway/FileSessionGatewayConfigurationIf.ice")
+import AsteriskSCF.Configuration.FileSessionGateway.V1
+
+# Add our own visitor implementations for the sections we support
+class SipSectionVisitors(Configurator.SectionVisitors):
+ def visit_general(self, config, section):
+ group = AsteriskSCF.Configuration.SipSessionManager.V1.SipGeneralGroup()
+ group.configurationItems = { }
+ self.groups.append(group)
+
+ def visit_transport_udp(self, config, section):
+ group = AsteriskSCF.Configuration.SipSessionManager.V1.SipUDPTransportGroup()
+ group.name = section
+ group.configurationItems = { }
+
+ mapper = Configurator.OptionMapper()
+ item = AsteriskSCF.Configuration.SipSessionManager.V1.SipHostItem()
+ mapper.map('host', item, 'host', 'address', config.get, None)
+ mapper.map('port', item, 'port', 'address', config.getint, 5060)
+ for option in config.options(section):
+ mapper.execute(group, section, option)
+
+ mapper.finish(group)
+
+ self.groups.append(group)
+
+ def visit_transport_tcp(self, config, section):
+ group = AsteriskSCF.Configuration.SipSessionManager.V1.SipTCPTransportGroup()
+ group.name = section
+ group.configurationItems = { }
+
+ mapper = Configurator.OptionMapper()
+ item = AsteriskSCF.Configuration.SipSessionManager.V1.SipHostItem()
+ mapper.map('host', item, 'host', 'address', config.get, None)
+ mapper.map('port', item, 'port', 'address', config.getint, 5060)
+ for option in config.options(section):
+ mapper.execute(group, section, option)
+
+ mapper.finish(group)
+
+ self.groups.append(group)
+
+ def visit_transport_tls(self, config, section):
+ group = AsteriskSCF.Configuration.SipSessionManager.V1.SipTLSTransportGroup()
+ group.name = section
+ group.configurationItems = { }
+
+ mapper = Configurator.OptionMapper()
+ item = AsteriskSCF.Configuration.SipSessionManager.V1.SipHostItem()
+ mapper.map('host', item, 'host', 'address', config.get, None)
+ mapper.map('port', item, 'port', 'address', config.getint, 5060)
+ for option in config.options(section):
+ mapper.execute(group, section, option)
+
+ mapper.finish(group)
+
+ self.groups.append(group)
+
+ def visit_transport_stun(self, config, section):
+ group = AsteriskSCF.Configuration.SipSessionManager.V1.SipSTUNTransportGroup()
+ group.name = section
+ group.configurationItems = {}
+ mapper = Configurator.OptionMapper()
+
+ item = AsteriskSCF.Configuration.SipSessionManager.V1.SipSignalingSTUNHostItem()
+ mapper.map('stunhost', item, 'address', 'stunServer', config.get, None)
+ mapper.map('stunport', item, 'port', 'stunServer', config.getint, 3478)
+
+ item = AsteriskSCF.Configuration.SipSessionManager.V1.SipHostItem()
+ mapper.map('baseaddress', item, 'host', 'baseAddress', config.get, None)
+ mapper.map('baseport', item, 'port', 'baseAddress', config.getint, 4512)
+
+ for option in config.options(section):
+ mapper.execute(group, section, option)
+
+ mapper.finish(group)
+
+ self.groups.append(group)
+
+ def visit_registration(self, config, section):
+
+ class RegistrationContactHandler:
+ def __init__(self, config):
+ self.config = config
+ def get(self, section, option):
+ setting = config.get(section, option)
+ contacts = setting.split(',')
+ ret = []
+
+ for contact in contacts:
+ uri, slash, expiration = contact.partition('/')
+ info = AsteriskSCF.Configuration.SipSessionManager.V1.ContactInfo()
+ info.contactURI = uri
+ info.expiration = int(expiration)
+ ret.append(info)
+
+ return ret
+
+ group = AsteriskSCF.Configuration.SipSessionManager.V1.SipRegistrationGroup()
+ group.name = section
+ group.configurationItems = {}
+
+ mapper = Configurator.OptionMapper()
+
+ item = AsteriskSCF.Configuration.SipSessionManager.V1.SipClientRegistrationItem()
+
+ mapper.map('aor', item, 'aor', 'registration', config.get, None)
+ mapper.map('expiration', item, 'defaultExpiration', 'registration', config.getint, AsteriskSCF.Configuration.SipSessionManager.V1.DefaultRegistrationExpirationSeconds)
+ handler = RegistrationContactHandler(config)
+ mapper.map('contacts', item, 'contacts', 'registration', handler.get, None)
+
+ for option in config.options(section):
+ mapper.execute(group, section, option)
+
+ mapper.finish(group)
+
+ self.groups.append(group)
+
+ def visit_identity(self, config, section):
+ group = AsteriskSCF.Configuration.SipSessionManager.V1.IdentityGroup()
+ group.name = section
+ group.configurationItems = { }
+
+ mapper = Configurator.OptionMapper()
+
+ item = AsteriskSCF.Configuration.SipSessionManager.V1.IdentityItem()
+ # map(option, object, item, item_name, method, default)
+ mapper.map('name', item, 'name', 'id', config.get, None)
+ mapper.map('number', item, 'number', 'id', config.get, None)
+
+ for option in config.options(section):
+ mapper.execute(group, section, option)
+
+ mapper.finish(group)
+
+ self.groups.append(group)
+
+ def visit_endpoint(self, config, section):
+ group = AsteriskSCF.Configuration.SipSessionManager.V1.SipEndpointGroup()
+ group.name = section
+ group.configurationItems = { }
+
+ mapper = Configurator.OptionMapper()
+
+ mapper.map('routing', AsteriskSCF.Configuration.SipSessionManager.V1.SipRoutingItem(), 'routingServiceName', 'routingService', config.get, None)
+
+ item = AsteriskSCF.Configuration.SipSessionManager.V1.IdentityItem()
+ mapper.map('name', item, 'name', 'identity', config.get, None)
+ mapper.map('number', item, 'number', 'identity', config.get, None)
+
+ # Alternate form of setting id is a list of references to IdentityGroup objects.
+ try:
+ ids = config.get(section, 'ids')
+ idList = ids.split(',')
+ for id in idList:
+ item = AsteriskSCF.Configuration.SipSessionManager.V1.IdentityGroupRef()
+ item.identityGroupName = id
+ group.configurationItems[id] = item
+ except ConfigParser.NoOptionError:
+ # It's legit to omit the ids option from this section.
+ pass
+
+ item = AsteriskSCF.Configuration.SipSessionManager.V1.SipSourceTransportAddressItem()
+ mapper.map('sourcehost', item, 'host', 'sourceaddress', config.get, None)
+ mapper.map('sourceport', item, 'port', 'sourceaddress', config.getint, 5060)
+
+ item = AsteriskSCF.Configuration.SipSessionManager.V1.SipTargetDestinationAddressItem()
+ mapper.map('targethost', item, 'host', 'targetaddress', config.get, None)
+ mapper.map('targetport', item, 'port', 'targetaddress', config.getint, 5060)
+
+ item = AsteriskSCF.Configuration.SipSessionManager.V1.SipSignalingNATItem()
+ mapper.map('enablestun', item, 'stun', 'enableSTUN', config.get, None)
+
+ item = AsteriskSCF.Configuration.SipSessionManager.V1.SipMediaNATItem()
+ mapper.map('enablertpoverice', item, 'enableICE', 'enableRTPICE', config.get, None)
+ mapper.map('enableturn', item, 'enableTURN', 'enableRTPICE', config.get, None)
+
+ item = AsteriskSCF.Configuration.SipSessionManager.V1.SRTPCryptoItem()
+
+ class CryptoKeyHandler:
+ def __init__(self, config, keyItem):
+ self.config = config
+ self.item = keyItem
+
+ def getSuite(self, section, item):
+ self.item.suite = self.config.get(section, item)
+
+ def getKey(self, section, item):
+ self.item.cryptoKey = self.config.get(section, item)
+
+ item.cryptoKeys = [ AsteriskSCF.Configuration.SipSessionManager.V1.SRTPCryptoKey() ]
+ mapper.map('enableauth', item, 'enableAuthentication', 'srtpCryptoSettings', config.get, None)
+ mapper.map('enableencryption', item, 'enableEncryption', 'srtpCryptoSettings', config.get, None)
+ handler = CryptoKeyHandler(config, item.cryptoKeys[0])
+ mapper.map('ciphersuite', item, 'suite', 'srtpCryptoSettings', handler.getSuite, None)
+ mapper.map('cryptokey', item, 'cryptoKey', 'srtpCryptoSettings', handler.getKey, None)
+
+ class AllowableCallDirectionTransformer():
+ def __init__(self, config):
+ self.config = config
+ def get(self, section, item):
+ if self.config.get(section, item) == 'inbound':
+ return AsteriskSCF.Configuration.SipSessionManager.V1.SipAllowableCallDirection.Inbound
+ elif self.config.get(section, item) == 'outbound':
+ return AsteriskSCF.Configuration.SipSessionManager.V1.SipAllowableCallDirection.Outbound
+ elif self.config.get(section, item) == 'both':
+ return AsteriskSCF.Configuration.SipSessionManager.V1.SipAllowableCallDirection.Both
+ elif self.config.get(section, item) == 'none':
+ return AsteriskSCF.Configuration.SipSessionManager.V1.SipAllowableCallDirection.Disabled
+
+ transformer = AllowableCallDirectionTransformer(config)
+
+ mapper.map('direction', AsteriskSCF.Configuration.SipSessionManager.V1.SipAllowableCallDirectionItem(), 'callDirection', 'callDirection', transformer.get, None)
+
+ mapper.map('securetransport', AsteriskSCF.Configuration.SipSessionManager.V1.SipEndpointTransportItem(), 'secureTransport', 'transport', transformer.get, None)
+
+ item = AsteriskSCF.Configuration.SipSessionManager.V1.SipRTPMediaServiceItem()
+ mapper.map('rtpoveripv6', item, 'requireIPv6', 'mediaservice', config.getboolean, None)
+
+ item = AsteriskSCF.Configuration.SipSessionManager.V1.DirectMediaItem()
+ mapper.map('directmedia', item, 'enabled', 'directmedia', config.getboolean, None)
+
+ item = AsteriskSCF.Configuration.SipSessionManager.V1.SipUDPTLMediaServiceItem()
+ mapper.map('udptloveripv6', item, 'requireIPv6', 'udptlmediaservice', config.getboolean, None)
+ mapper.map('udptloverice', item, 'enableICE', 'udptlmediaservice', config.getboolean, None)
+ mapper.map('udptlwithturn', item, 'enableTURN', 'udptlmediaservice', config.getboolean, None)
+
+ item = AsteriskSCF.Configuration.SipSessionManager.V1.SipCryptoCertificateItem()
+ mapper.map('certificateauthorityfile', item, 'certificateAuthority', 'cryptocert', config.get, None)
+ mapper.map('certificatefile', item, 'certificate', 'cryptocert', config.get, None)
+ mapper.map('privatekeyfile', item, 'privateKey', 'cryptocert', config.get, None)
+ mapper.map('privatekeypassword', item, 'privateKeyPassword', 'cryptocert', config.get, None)
+
+ item = AsteriskSCF.Configuration.SipSessionManager.V1.SipCryptoRequirementsItem()
+ mapper.map('requireverifiedserver', item, 'requireVerifiedServer', 'cryptorequirements', config.getboolean, None)
+ mapper.map('requireverifiedclient', item, 'requireVerifiedClient', 'cryptorequirements', config.getboolean, None)
+ mapper.map('requireclientcertificate', item, 'requireClientCertificate', 'cryptorequirements', config.getboolean, None)
+
+ item = AsteriskSCF.Configuration.SipSessionManager.V1.SipCryptoItem()
+ mapper.map('supportedciphers', item, 'supportedCiphers', 'crypto', config.get, None)
+ mapper.map('tlsservername', item, 'serverName', 'crypto', config.get, None)
+ mapper.map('tlstimeout', item, 'timeout', 'crypto', config.getint, None)
+
+ class TLSProtocolMethodTransformer():
+ def __init__(self, config):
+ self.config = config
+ def get(self, section, item):
+ if self.config.get(section, item) == 'unspecified':
+ return AsteriskSCF.Configuration.SipSessionManager.V1.TLSProtocolMethod.PROTOCOLMETHODUNSPECIFIED
+ elif self.config.get(section, item) == 'tlsv1':
+ return AsteriskSCF.Configuration.SipSessionManager.V1.TLSProtocolMethod.PROTOCOLMETHODTLSV1
+ elif self.config.get(section, item) == 'sslv2':
+ return AsteriskSCF.Configuration.SipSessionManager.V1.TLSProtocolMethod.PROTOCOLMETHODTSSLV2
+ elif self.config.get(section, item) == 'sslv3':
+ return AsteriskSCF.Configuration.SipSessionManager.V1.TLSProtocolMethod.PROTOCOLMETHODSSLV3
+ elif self.config.get(section, item) == 'sslv23':
+ return AsteriskSCF.Configuration.SipSessionManager.V1.TLSProtocolMethod.PROTOCOLMETHODSSLV23
+
+ transformer = TLSProtocolMethodTransformer(config)
+ mapper.map('tlsprotocolmethod', item, 'protocolMethod', 'crypto', transformer.get, None)
+
+ class DTMFMethodTransformer():
+ def __init__(self, config):
+ self.config = config
+ def get(self, section, item):
+ if self.config.get(section, item) == 'info':
+ return AsteriskSCF.Configuration.SipSessionManager.V1.SipDTMFOption.INFO
+ if self.config.get(section, item) == 'rfc4733' or self.config.get(section.item) == 'rfc2833':
+ return AsteriskSCF.Configuration.SipSessionManager.V1.SipDTMFOption.RFC4733
+ if self.config.get(section, item) == 'inband':
+ return AsteriskSCF.Configuration.SipSessionManager.V1.SipDTMFOption.Inband
+
+ item = AsteriskSCF.Configuration.SipSessionManager.V1.SipDTMFItem()
+ dtmfTransformer = DTMFMethodTransformer(config)
+ mapper.map('dtmfmethod', item, 'dtmf', 'dtmfmethod', dtmfTransformer.get, AsteriskSCF.Configuration.SipSessionManager.V1.SipDTMFOption.RFC4733)
+
+ for option in config.options(section):
+ mapper.execute(group, section, option)
+ mapper.finish(group)
+
+ try:
+ formats = config.get(section, 'formats')
+ configuredFormats = formats.split(',')
+ for format in configuredFormats:
+ item = AsteriskSCF.Configuration.SipSessionManager.V1.SipMediaFormatItem()
+
+ front, found, rest = format.partition('/')
+ if found:
+ item.name = front
+ else:
+ rest = front
+
+ front, found, rest = rest.partition('@')
+ if found:
+ if item.name:
+ item.sampleRate = front
+ else:
+ item.name = front
+ else:
+ rest = front
+
+ front, found, rest = rest.partition(';')
+ if found:
+ if item.name:
+ item.frameSize = front
+ else:
+ item.name = front
+ else:
+ rest = front
+
+ item.formatSpecific = [ ]
+
+ if not item.name:
+ item.name = format
+
+ if item.name:
+ while rest:
+ front, found, rest = rest.partition('&')
+ item.formatSpecific.append(front)
+
+ group.configurationItems[format] = item
+ except:
+ print 'No configured formats for endpoint ' + section
+
+ try:
+ registrations = config.get(section, 'registrations')
+ registrationList = registrations.split(',')
+ for reg in registrationList:
+ item = AsteriskSCF.Configuration.SipSessionManager.V1.SipRegistrationGroupRef()
+ item.registrationGroupName = reg
+ group.configurationItems[reg] = item
+ except:
+ pass
+
+ self.groups.append(group)
+
+ def visit_unsupported(self, config, section):
+ if config.get(section, 'type') == 'transport-udp':
+ self.visit_transport_udp(config, section)
+ elif config.get(section, 'type') == 'transport-tcp':
+ self.visit_transport_tcp(config, section)
+ elif config.get(section, 'type') == 'transport-tls':
+ self.visit_transport_tls(config, section)
+ elif config.get(section, 'type') == 'endpoint':
+ self.visit_endpoint(config, section)
+ elif config.get(section, 'type') == 'registration':
+ self.visit_registration(config, section)
+ elif config.get(section, 'type') == 'identity':
+ self.visit_identity(config, section)
+
+# In order to do service locator based lookup we need to pass in a params object
+serviceLocatorParams = AsteriskSCF.Core.Discovery.V1.ServiceLocatorParams()
+serviceLocatorParams.category = AsteriskSCF.Configuration.SipSessionManager.V1.ConfigurationDiscoveryCategory
+serviceLocatorParams.service = 'default'
+
+# Make a configurator application and let it run
+app = Configurator.ConfiguratorApp('Sip.config', SipSectionVisitors(), None, serviceLocatorParams)
+sys.exit(app.main(sys.argv))
diff --git a/slice/AsteriskSCF/Replication/FileSessionGateway/FileSessionReplicationIf.ice b/slice/AsteriskSCF/Replication/FileSessionGateway/FileSessionReplicationIf.ice
index fc9d718..2ab5b9a 100644
--- a/slice/AsteriskSCF/Replication/FileSessionGateway/FileSessionReplicationIf.ice
+++ b/slice/AsteriskSCF/Replication/FileSessionGateway/FileSessionReplicationIf.ice
@@ -36,69 +36,109 @@ module FileSessionGateway
["suppress"]
module V1
{
- const string StateReplicatorComponentCategory = "FileSessionReplicatorComponent";
- const string StateReplicatorDiscoveryCategory = "FileSessionReplicator";
-
- class StateItem
- {
- string key;
- string sessionId;
- };
-
- sequence<StateItem> StateItemSeq;
-
- interface StateReplicatorListener
- {
- void stateRemoved(Ice::StringSeq itemKeys);
- void stateRemovedForItems(StateItemSeq items);
- void stateSet(StateItemSeq items);
- };
-
- interface StateReplicator
- {
- void addListener(StateReplicatorListener *listener);
- void removeListener(StateReplicatorListener *listener);
- void setState (StateItemSeq items);
- void removeState(Ice::StringSeq items);
- void removeStateForItems(StateItemSeq items);
- idempotent StateItemSeq getState(Ice::StringSeq iteKeys);
- idempotent StateItemSeq getAllState();
- };
-
- sequence<AsteriskSCF::Media::File::V1::FileSession*> MediaSessionSeq;
-
- class SessionState extends StateItem
- {
- string endpointName;
-
- Ice::Identity sessionObjectId;
- Ice::Identity mediaSessionObjectId;
- Ice::Identity sessionControllerObjectId;
-
- AsteriskSCF::Media::V1::StreamSourceSeq sources;
- AsteriskSCF::Media::V1::StreamSinkSeq sinks;
- AsteriskSCF::Media::V1::StreamInformationDict streams;
-
- MediaSessionSeq rtpMediaSessions;
-
- AsteriskSCF::SessionCommunications::V1::SessionListenerSeq listeners;
-
- AsteriskSCF::SessionCommunications::V1::Bridge *bridge;
-
- AsteriskSCF::SessionCommunications::V1::SessionCookieDict cookies;
- };
-
- class DefaultSessionListenerItem extends StateItem
- {
- string endpointName;
- AsteriskSCF::SessionCommunications::V1::SessionListener* listener;
- };
-
- class DefaultSessionCookieItem extends StateItem
- {
- string endpointName;
- AsteriskSCF::SessionCommunications::V1::SessionCookie cookie;
- };
+
+const string StateReplicatorComponentCategory = "FileSessionReplicatorComponent";
+const string StateReplicatorDiscoveryCategory = "FileSessionReplicator";
+
+class StateItem
+{
+ string key;
+};
+
+sequence<StateItem> StateItemSeq;
+
+interface StateReplicatorListener
+{
+ void stateRemoved(Ice::StringSeq itemKeys);
+ void stateRemovedForItems(StateItemSeq items);
+ void stateSet(StateItemSeq items);
+};
+
+interface StateReplicator
+{
+ void addListener(StateReplicatorListener *listener);
+ void removeListener(StateReplicatorListener *listener);
+ void setState (StateItemSeq items);
+ void removeState(Ice::StringSeq items);
+ void removeStateForItems(StateItemSeq items);
+ idempotent StateItemSeq getState(Ice::StringSeq iteKeys);
+ idempotent StateItemSeq getAllState();
+};
+
+sequence<AsteriskSCF::Media::File::V1::FileSession*> MediaSessionSeq;
+
+class SessionDefinitionState extends StateItem
+{
+ string endpointName;
+ string endpointItemKey;
+ string sessionId;
+ Ice::Identity sessionObjectId;
+ AsteriskSCF::SessionCommunications::V1::Session* publishedProxy;
+};
+
+class SessionState extends StateItem
+{
+ string endpointItemKey;
+ string sessionId;
+};
+
+class MediaSessionIdState extends SessionState
+{
+ Ice::Identity mediaSessionObjectId;
+};
+
+class SessionControllerIdState extends SessionState
+{
+ Ice::Identity sessionControllerObjectId;
+};
+
+class SourcesState extends SessionState
+{
+ AsteriskSCF::Media::V1::StreamSourceSeq sources;
+};
+
+class SinksState extends SessionState
+{
+ AsteriskSCF::Media::V1::StreamSinkSeq sinks;
+};
+
+class StreamsState extends SessionState
+{
+ AsteriskSCF::Media::V1::StreamInformationDict streams;
+};
+
+class MediaSessionState extends SessionState
+{
+ MediaSessionSeq rtpMediaSessions;
+};
+
+class SessionListenerUpdate extends SessionState
+{
+ AsteriskSCF::SessionCommunications::V1::SessionListener* listener;
+};
+
+class BridgeState extends SessionState
+{
+ AsteriskSCF::SessionCommunications::V1::Bridge* bridgeProxy;
+ AsteriskSCF::SessionCommunications::V1::SessionListener* listener;
+};
+
+class CookieState extends SessionState
+{
+ AsteriskSCF::SessionCommunications::V1::SessionCookies cookies;
+};
+
+class DefaultSessionListenerItem extends StateItem
+{
+ string endpointName;
+ AsteriskSCF::SessionCommunications::V1::SessionListener* listener;
+};
+
+class DefaultSessionCookieItem extends StateItem
+{
+ string endpointName;
+ AsteriskSCF::SessionCommunications::V1::SessionCookie cookie;
+};
}; /* module V1 */
diff --git a/slice/CMakeLists.txt b/slice/CMakeLists.txt
new file mode 100644
index 0000000..90e1e47
--- /dev/null
+++ b/slice/CMakeLists.txt
@@ -0,0 +1,18 @@
+set(LIBNAME "AstScfFileSessionGatewayAPI")
+
+astscf_slice_collection(GLOBAL
+ NAME FILESESSIONGATEWAY
+ PATH "${CMAKE_CURRENT_SOURCE_DIR}"
+ HEADERS
+ "${CMAKE_CURRENT_BINARY_DIR}/${LIBNAME}/slice-FILESESSIONGATEWAY"
+ LIBRARY ${LIBNAME}
+ )
+
+astscf_slice_include_collection(FILESESSIONGATEWAY)
+
+astscf_component_init(${LIBNAME})
+astscf_component_add_slices(${LIBNAME} FILESESSIONGATEWAY GLOB_RECURSE
+ "AsteriskSCF/*.ice")
+astscf_component_add_slice_collection_libraries(${LIBNAME} ASTSCF)
+astscf_component_build_library(${LIBNAME})
+astscf_slice_collection_install(FILESESSIONGATEWAY)
diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt
index 144f394..0244db1 100644
--- a/src/CMakeLists.txt
+++ b/src/CMakeLists.txt
@@ -12,8 +12,12 @@ astscf_component_add_files(FileSessionGateway
EndpointLocator.h
EndpointLocator.cpp
LoggerF.h
- ReplicationContext.h
+ CookieManager.cpp
+ CookieManager.h
+ ReplicatedObject.cpp
+ ReplicatedObject.h
Replication.h
+ Replicator.h
ReplicationListener.h
Session.h
Session.cpp
@@ -21,15 +25,10 @@ astscf_component_add_files(FileSessionGateway
astscf_component_add_files(FileSessionGateway Component.cpp)
astscf_component_add_files(FileSessionGateway Configuration.cpp)
astscf_component_add_files(FileSessionGateway Configuration.h)
-astscf_component_add_slices(FileSessionGateway PROJECT
- AsteriskSCF/FileSessionGateway/FileSessionGatewayIf.ice
- AsteriskSCF/Configuration/FileSessionGateway/FileSessionGatewayConfigurationIf.ice
- AsteriskSCF/Replication/FileSessionGateway/FileSessionReplicationIf.ice
- )
astscf_component_add_ice_libraries(FileSessionGateway IceStorm)
astscf_component_add_boost_libraries(FileSessionGateway core)
-astscf_component_add_slice_collection_libraries(FileSessionGateway ASTSCF)
+astscf_component_add_slice_collection_libraries(FileSessionGateway FILESESSIONGATEWAY ASTSCF)
astscf_component_build_icebox(FileSessionGateway)
target_link_libraries(FileSessionGateway astscf-ice-util-cpp logging-client)
astscf_component_install(FileSessionGateway)
diff --git a/src/Component.cpp b/src/Component.cpp
index 5013318..1c10863 100644
--- a/src/Component.cpp
+++ b/src/Component.cpp
@@ -13,7 +13,16 @@
* the GNU General Public License Version 2. See the LICENSE.txt file
* at the top of the source tree.
*/
+
+#include "EndpointLocator.h"
+#include "FileSessionGatewayComponent.h"
+#include "Config.h"
+#include "Replicator.h"
+#include "ReplicationListener.h"
+#include "ComponentDefs.h"
+
#include <AsteriskSCF/Component/Component.h>
+
#include <IceUtil/UUID.h>
#include <boost/thread.hpp>
@@ -28,18 +37,8 @@
#include <AsteriskSCF/Logger/IceLogger.h>
#include <AsteriskSCF/logger.h>
-#include "EndpointLocator.h"
-// #include "ReplicationListener.h"
-// #include "ReplicationContext.h"
-#include "FileSessionGatewayComponent.h"
-#include "Config.h"
-
-using namespace AsteriskSCF::Core::Routing::V1;
-using namespace AsteriskSCF::Core::Discovery::V1;
using namespace AsteriskSCF::System::Component::V1;
using namespace AsteriskSCF::System::Logging;
-using namespace AsteriskSCF::SessionCommunications::V1;
-using namespace AsteriskSCF::System::Configuration::V1;
using namespace std;
namespace AsteriskSCF
@@ -71,29 +70,198 @@ public:
manageBackplaneService(wrapServiceForRegistration(proxy, category));
}
+ void onActivated()
+ {
+ mEndpointLocatorFeature->getLocator()->updateReplicator(mReplicator);
+ }
+
+ void onStandby()
+ {
+ //
+ // Nil'ing out the replicator object will stop an
+ // further replication.
+ //
+ mEndpointLocatorFeature->getLocator()->updateReplicator(ReplicatorSmartProxy());
+ }
+
void createReplicationStateListeners()
{
+ //
+ // Listener is created in startListening for now.. a little different
+ // than other implementations. It is implemented this way mostly to
+ // experiment with an alternate approach to handling the listener
+ // lifetime and moving to and from the standby state.
+ //
}
void stopListeningToStateReplicators()
{
+ boost::mutex::scoped_lock lock(mReplicationStateMutex);
+ if (!mReceivingReplicationUpdates)
+ {
+ //
+ // We aren't listening, so we can just exit early.
+ //
+ return;
+ }
+ mReceivingReplicationUpdates = false;
+
+ //
+ // This will cause the object to be "destroyed" with respect to the
+ // object adapter. Since the components affected by the replication
+ // listeners are passive relative to the listener (ie. they don't know
+ // about the listener, the listener knows about them .. sort of)
+ //
+ mReplicationListener->destroy();
+ mReplicationListener = 0;
}
void listenToStateReplicators()
{
+ if (getReplicationContext()->getState() != AsteriskSCF::Replication::STANDBY_IN_REPLICA_GROUP)
+ {
+ //
+ // The code is setup so it does two locks. While suboptimal it
+ // prevents us having to have an RPC while holding a lock. This
+ // isn't a performance critical method so this should be ok.
+ //
+ {
+ //
+ // Check to see if we are already wired up and if so, exit early.
+ //
+ boost::mutex::scoped_lock lock(mReplicationStateMutex);
+ if (mReceivingReplicationUpdates)
+ {
+ return;
+ }
+ }
+
+ //
+ // This is a little different that how some of the other services
+ // are setup because the listener is perpetual in those other
+ // implemenations. In this case, we are trying something a bit
+ // different and only creating the listener when it's needed.
+ //
+ try
+ {
+ AsteriskSCF::Replication::FileSessionGateway::V1::StateReplicatorListenerPtr servant =
+ AsteriskSCF::FileSessionGtw::ReplicationListener::create(mEndpointLocatorFeature->getLocator(),
+ getServiceAdapter(), mLogger);
+ mReplicatorListenerProxy =
+ AsteriskSCF::Replication::FileSessionGateway::V1::StateReplicatorListenerPrx::uncheckedCast(
+ getServiceAdapter()->addWithUUID(servant));
+ try
+ {
+ mReplicatorListenerProxy =
+ AsteriskSCF::Replication::FileSessionGateway::V1::StateReplicatorListenerPrx::uncheckedCast(
+ mReplicatorListenerProxy->ice_oneway());
+ }
+ catch (const Ice::NoEndpointException&)
+ {
+ //
+ // If this exception is thrown it simply means that oneways
+ // cannot be supported, mReplicatorListenerProxy's original
+ // value will not be affected.
+ //
+ }
+ }
+ catch (const std::exception& ex)
+ {
+ mLogger(Error) << "Unable to instantiate replication listener in " << getName() << ": " << ex.what();
+ }
+ catch (...)
+ {
+ mLogger(Error) << "Unable to instantiate replication listener in " << getName() << " (unknown exception).";
+ }
+
+ //
+ // Find the replication service.
+ //
+ try
+ {
+ assert(mReplicator);
+ mReplicator->addListener(mReplicatorListenerProxy);
+ boost::mutex::scoped_lock lock(mReplicationStateMutex);
+ mReceivingReplicationUpdates = true;
+ }
+ catch (const std::exception& ex)
+ {
+ mLogger(Error) << "Unable to add listener to replication server: " << ex.what();
+ throw;
+ }
+ }
}
void createPrimaryServices()
{
+ mEndpointLocatorFeature = createEndpointLocatorFeature(getName(), getServiceAdapter());
}
void findRemoteServices()
{
+ //
+ // If we are in a situation where replication is supported/required, locate the
+ // replicator.
+ //
+ if (getReplicationContext()->getState() != AsteriskSCF::Replication::ACTIVE_STANDALONE)
+ {
+ AsteriskSCF::Core::Discovery::V1::ServiceLocatorParamsPtr replicatorParams =
+ new AsteriskSCF::Core::Discovery::V1::ServiceLocatorParams;
+ replicatorParams->category = AsteriskSCF::Replication::FileSessionGateway::V1::StateReplicatorDiscoveryCategory;
+ replicatorParams->service = getCommunicator()->getProperties()->getPropertyWithDefault(
+ getName() + ".StateReplicatorService", "default");
+ replicatorParams->id = getCommunicator()->getProperties()->getPropertyWithDefault(
+ getName() + ".StateReplicatorId", "");
+ try
+ {
+ mReplicator = AsteriskSCF::FileSessionGtw::ReplicatorSmartProxy(getServiceLocator(), replicatorParams,
+ mLogger);
+ }
+ catch (const AsteriskSCF::Core::Discovery::V1::ServiceNotFound&)
+ {
+ mLogger(Error) << getName() << ": unable to get replicator from service locator. Please check configuration.";
+ throw;
+ }
+ }
}
void preparePrimaryServicesForDiscovery()
{
+ mEndpointLocatorProxy = AsteriskSCF::Core::Routing::V1::EndpointLocatorPrx::uncheckedCast(
+ mEndpointLocatorFeature->activate(getServiceAdapter()));
+ }
+
+ void registerWithRemoteRemoteServices()
+ {
+ if (mEndpointLocatorFeature)
+ {
+ AsteriskSCF::Core::Discovery::V1::ServiceLocatorParamsPtr routerParams =
+ new AsteriskSCF::Core::Discovery::V1::ServiceLocatorParams;
+ routerParams->category = AsteriskSCF::Core::Routing::V1::RoutingServiceLocatorRegistryDiscoveryCategory;
+ routerParams->service =getCommunicator()->getProperties()->getPropertyWithDefault(getName() +
+ ".EndpointLocatorRegistry", "default");
+ mEndpointLocatorFeature->makeReady(getServiceLocator(), routerParams);
+ }
+ }
+
+ void unregisterFromRemoteServices()
+ {
+ if (mEndpointLocatorFeature)
+ {
+ mEndpointLocatorFeature->suspend();
+ }
}
+
+private:
+ AsteriskSCF::FileSessionGtw::ReplicatorSmartProxy mReplicator;
+ AsteriskSCF::Replication::FileSessionGateway::V1::StateReplicatorListenerPrx mReplicatorListenerProxy;
+ AsteriskSCF::FileSessionGtw::ReplicationListenerPtr mReplicationListener;
+
+ boost::mutex mReplicationStateMutex;
+ bool mReceivingReplicationUpdates;
+
+ EndpointLocatorFeaturePtr mEndpointLocatorFeature;
+ AsteriskSCF::Core::Routing::V1::EndpointLocatorPrx mEndpointLocatorProxy;
};
}; // end FileSessionGtw
diff --git a/src/ComponentDefs.h b/src/ComponentDefs.h
new file mode 100644
index 0000000..4e4116f
--- /dev/null
+++ b/src/ComponentDefs.h
@@ -0,0 +1,41 @@
+/*
+ * Asterisk SCF -- An open-source communications framework.
+ *
+ * Copyright (C) 2010, Digium, Inc.
+ *
+ * See http://www.asterisk.org for more information about
+ * the Asterisk SCF project. Please do not directly contact
+ * any of the maintainers of this project for assistance;
+ * the project provides a web site, mailing lists and IRC
+ * channels for your use.
+ *
+ * This program is free software, distributed under the terms of
+ * the GNU General Public License Version 2. See the LICENSE.txt file
+ * at the top of the source tree.
+ */
+
+#pragma once
+
+#include <boost/thread/shared_mutex.hpp>
+#include <boost/thread/locks.hpp>
+#include <AsteriskSCF/Discovery/SmartProxy.h>
+#include <AsteriskSCF/Replication/FileSessionGateway/FileSessionReplicationIf.h>
+
+namespace AsteriskSCF
+{
+namespace FileSessionGtw
+{
+
+/**
+ * Some simple typedefs to abbreviate code a bit. Arguably makes it
+ * a bit more readable too.
+ */
+typedef boost::unique_lock<boost::shared_mutex> UniqueLock;
+typedef boost::shared_lock<boost::shared_mutex> SharedLock;
+
+typedef AsteriskSCF::Discovery::SmartProxy<AsteriskSCF::Replication::FileSessionGateway::V1::StateReplicatorPrx>
+ ReplicatorSmartProxy;
+
+} /* End of namespace FileSessionGtw */
+} /* End of namespace AsteriskSCF */
+
diff --git a/src/ComponentFeature.h b/src/ComponentFeature.h
index 1359881..ad5c820 100644
--- a/src/ComponentFeature.h
+++ b/src/ComponentFeature.h
@@ -44,7 +44,13 @@ public:
/**
* make final preparations.
*/
- virtual void makeReady(const AsteriskSCF::Core::Discovery::V1::ServiceLocatorPrx& locator) = 0;
+ virtual void makeReady(const AsteriskSCF::Core::Discovery::V1::ServiceLocatorPrx& locator,
+ const AsteriskSCF::Core::Discovery::V1::ServiceLocatorParamsPtr& params) = 0;
+
+ /**
+ *
+ */
+ virtual void suspend() = 0;
};
typedef IceUtil::Handle<ComponentFeature> ComponentFeaturePtr;
diff --git a/src/Configuration.cpp b/src/Configuration.cpp
index a6d5fd2..6457046 100644
--- a/src/Configuration.cpp
+++ b/src/Configuration.cpp
@@ -17,6 +17,8 @@
#include "Configuration.h"
#include "Config.h"
+#include "EndpointLocator.h"
+
#include <AsteriskSCF/Configuration/FileSessionGateway/FileSessionGatewayConfigurationIf.h>
#include <AsteriskSCF/logger.h>
#include <AsteriskSCF/System/Component/ConfigurationIf.h>
@@ -34,23 +36,61 @@ namespace
class ConfigurationImpl : public AsteriskSCF::System::Configuration::V1::ConfigurationService
{
public:
- ConfigurationImpl()
+ ConfigurationImpl(const EndpointLocatorImplPtr& endpointLocator) :
+ mEndpointLocator(endpointLocator)
{
}
- ConfigurationGroupSeq getConfiguration(const ConfigurationGroupSeq&, const Ice::Current&)
+ ConfigurationGroupSeq getConfiguration(const ConfigurationGroupSeq& groups, const Ice::Current&)
{
- return ConfigurationGroupSeq();
+ ConfigurationGroupSeq result;
+ for (ConfigurationGroupSeq::const_iterator iter = groups.begin(); iter != groups.end(); ++iter)
+ {
+ //
+ // There aren't a lot of different types, so the visitor is a little overkill.
+ //
+ EndpointGroupPtr endpointGroup = EndpointGroupPtr::dynamicCast(*iter);
+ if (endpointGroup)
+ {
+ }
+ }
+ return result;
}
ConfigurationGroupSeq getConfigurationAll(const ConfigurationGroupSeq&, const Ice::Current&)
{
- return ConfigurationGroupSeq();
+ ConfigurationGroupSeq result;
+ for (ConfigurationGroupSeq::const_iterator iter = groups.begin(); iter != groups.end(); ++iter)
+ {
+ //
+ // There aren't a lot of different types, so the visitor is a little overkill.
+ //
+ EndpointGroupPtr endpointGroup = EndpointGroupPtr::dynamicCast(*iter);
+ result.push_back(endpointGroup);
+ if (endpointGroup)
+ {
+ EndpointGroupPtr group = new EndpointGroup;
+ group->id = mEndpointLocator->getId();
+ EndpointImplSeq impls = mEndpointLocator->getEndpoints();
+ for (EndpointImplSeq::const_iterator endpointIter = impls.begin(); endpointIter != impls.end(); ++impls)
+ {
+ EndpointSpecificationPtr spec = (*endpointIter)->getSpecification();
+ //
+ // XXX translate spec to configuration information.
+ //
+ }
+ }
+ }
+ return result;
}
ConfigurationGroupSeq getConfigurationGroups(const Ice::Current&)
{
- return ConfigurationGroupSeq();
+ ConfigurationGroupSeq groups;
+ EndpointGroupPtr group = new EndpointGroup;
+ group->id = mEndpointLocator->getId();
+ groups.push_back(group);
+ return groups;
}
void setConfiguration(const ConfigurationGroupSeq&, const Ice::Current&)
@@ -64,6 +104,8 @@ namespace
void removeConfigurationGroups(const ConfigurationGroupSeq&, const Ice::Current&)
{
}
+ private:
+ EndpointLocatorImplPtr mEndpointLocator;
};
typedef IceUtil::Handle<ConfigurationImpl> ConfigurationImplPtr;
@@ -104,9 +146,17 @@ namespace
}
}
- void makeReady(const AsteriskSCF::Core::Discovery::V1::ServiceLocatorPrx& /* locator */)
+ void makeReady(const AsteriskSCF::Core::Discovery::V1::ServiceLocatorPrx& /* locator */,
+ const AsteriskSCF::Core::Discovery::V1::ServiceLocatorParamsPtr& /* params */)
+ {
+ // XXX
+ }
+
+ void suspend()
{
+ //
// XXX
+ //
}
private:
diff --git a/src/CookieManager.cpp b/src/CookieManager.cpp
new file mode 100644
index 0000000..701d7c9
--- /dev/null
+++ b/src/CookieManager.cpp
@@ -0,0 +1,142 @@
+/*
+ * Asterisk SCF -- An open-source communications framework.
+ *
+ * Copyright (C) 2010, Digium, Inc.
+ *
+ * See http://www.asterisk.org for more information about
+ * the Asterisk SCF project. Please do not directly contact
+ * any of the maintainers of this project for assistance;
+ * the project provides a web site, mailing lists and IRC
+ * channels for your use.
+ *
+ * This program is free software, distributed under the terms of
+ * the GNU General Public License Version 2. See the LICENSE.txt file
+ * at the top of the source tree.
+ */
+
+#include "CookieManager.h"
+
+using namespace AsteriskSCF::SessionCommunications::V1;
+
+namespace AsteriskSCF
+{
+namespace FileSessionGtw
+{
+
+class DummyCookieListener : public CookieManagerListener
+{
+public:
+ void cookiesSet(const SessionCookies&)
+ {
+ //
+ // No op.
+ //
+ }
+
+ void cookiesRemoved(const SessionCookies&)
+ {
+ //
+ // No op.
+ //
+ }
+};
+
+}
+}
+
+using namespace AsteriskSCF::FileSessionGtw;
+
+CookieManager::CookieManager(const CookieManagerListenerPtr& listener) :
+ mListener(listener)
+{
+ //
+ // If no listener is given, one will be provided..
+ //
+ if (!listener)
+ {
+ mListener.reset(new DummyCookieListener);
+ }
+}
+
+void CookieManager::setCookies(const SessionCookies& cookies)
+{
+ {
+ UniqueLock lock(mLock);
+ for (SessionCookies::const_iterator lookingFor = cookies.begin();
+ lookingFor != cookies.end(); ++lookingFor)
+ {
+ bool found = false;
+ for (SessionCookies::iterator myCookie = mCookies.begin();
+ myCookie != mCookies.end(); ++myCookie)
+ {
+ //
+ // Cookies of matching id's are replaced, not simply appended
+ // to the cookie sequence.
+ //
+ if ((*lookingFor)->ice_id() == (*myCookie)->ice_id())
+ {
+ *myCookie = *lookingFor;
+ found = true;
+ break;
+ }
+ }
+ if (!found)
... 2591 lines suppressed ...
--
asterisk-scf/integration/file_session_gateway.git
More information about the asterisk-scf-commits
mailing list