From b8d65761b33728dc1fe8bc12777dd83e07fa2ba7 Mon Sep 17 00:00:00 2001
From: Reiner Hauser <Reiner.Hauser@cern.ch>
Date: Wed, 4 Nov 2015 14:46:41 +0000
Subject: [PATCH] Allow to dynamically allocate a unique multicast address.

The OKS value of the multicast address is allowed to be * instead of
a real IP address. In this case the following procedure is used to allocate a
unique MC address across all partitions. The network/netmask part of the string
should still be defined, e.g. */10.149.0.0/255.255.0.0.

The allocate_multicast_address() method of NameService should be called by a
single application in the partition (typically the HLT supervisor). It will
first iterate over all existing IS objects of type 'MultiCastGroup' in the
RunParams IS server of the initial partition. If it finds an entry which
matches its own partition, the multicast address from this object will be
used. This means that MC groups will be stable as long as the initial partition
is up and running.

If the method does not find an entry, it will try to insert a new object
which contains the multicast address as a string in its name. This guarantees
the uniqueness of the object and therefore address. The method will loop over
a range of pre-defined multicast address until insertion succeeds. If the range
is exhausted an exception CannotAllocateMulticast is thrown.

As a final step either the static or dynamically allocated address and network
are published in the local partition in all DFConfig IS servers as an object
with the fixed name 'MultiCastAddress'.

The lookup_multicast_address() method simply looks up the latter object in its
appropriate DFConfig IS server and returns it.

Note that the IS object contains both the partition name and the network part, but
the latter should always be taken straight from OKS for purposes of configuration.
The IS object simply contains it as auxilliary information.
---
 asyncmsg/NameService.h       |  37 +++++++++++-
 schema/MsgInfo_is.schema.xml |  17 ++++--
 src/MultiCastGroup.h         | 105 +++++++++++++++++++++++++++++++++++
 src/MultiCastGroupNamed.h    | 105 +++++++++++++++++++++++++++++++++++
 src/NameService.cxx          |  84 ++++++++++++++++++++++++++++
 5 files changed, 342 insertions(+), 6 deletions(-)
 create mode 100644 src/MultiCastGroup.h
 create mode 100644 src/MultiCastGroupNamed.h

diff --git a/asyncmsg/NameService.h b/asyncmsg/NameService.h
index 26fbabb..170abbb 100644
--- a/asyncmsg/NameService.h
+++ b/asyncmsg/NameService.h
@@ -41,6 +41,13 @@ namespace daq {
                            ((std::string)m_name)
                            )
 
+    ERS_DECLARE_ISSUE_BASE(asyncmsg,
+                           CannotAllocateMulticast,
+                           Issue,
+                           "Cannot allocate multicast address",
+                           ERS_EMPTY,
+                           ERS_EMPTY)
+
     namespace asyncmsg {
 
         
@@ -106,16 +113,42 @@ namespace daq {
 
             /** \brief Resolve a single specific application.
              *
-             * \param[in] Resolves exactly one specific remote applications.
+             * \param[in] name Resolves exactly one specific remote applications with this name.
              *
              * \throws daq::asyncmsg::CannotResolve, IS exceptions
              */
             boost::asio::ip::tcp::endpoint resolve(const std::string& name) const;
 
-            /** \brief A helper method to parse a string of the form network/netmask.
+            /**
+             * \brief Allocate the multicast address for the partition.
+             *
+             * \param[in] addr The string representation of the address; can be a full IP address or '*'.
+             * \param[in] network The string representation of the network to use for multicast.
+             * \returns The allocated multicast address.
+             * \throws daq::asyncmsg::CannotAllocateMulticast, daq::asyncmsg::InvalidISServer
+             *
+             * Only one application per partition should call this. If the 'addr' parameter contains
+             * a '*', the multicast address will be dynamically allocated in such a way that it
+             * is unique per installation and across partitions (e.g. in Point 1).
+             */
+            std::string allocate_multicast_address(const std::string& addr, const std::string& network);
+
+            /** \brief Find the multicast address for the partition.
              * 
+             * \returns A string containing the IP addresses usable by boost::asio.
+             *
+             * \throws daq::asyncmsg::CannotResolve
+             *
+             * Applications who need the multicast address of the partition should call this with the 
+             * 'addr' taken from OKS. If 'addr' is '*' the multicast address will be dynamically looked up.
              */
+            std::string lookup_multicast_address(const std::string& addr) const;
+
+            /** typedef for readability, a 2-tuple of IP addresses representing network/netmask. */
             typedef std::tuple<boost::asio::ip::address,boost::asio::ip::address> Network;
+
+            /** \brief A helper method to parse a string of the form network/netmask.
+             */
             static Network parse_address_network(const std::string& network);
 
             /** \brief A helper method to find the local interface matching netmask.
diff --git a/schema/MsgInfo_is.schema.xml b/schema/MsgInfo_is.schema.xml
index 7876424..a91496c 100644
--- a/schema/MsgInfo_is.schema.xml
+++ b/schema/MsgInfo_is.schema.xml
@@ -9,7 +9,6 @@
   <!ATTLIST info
       name CDATA #REQUIRED
       type CDATA #REQUIRED
-      num-of-includes CDATA #REQUIRED
       num-of-items CDATA #REQUIRED
       oks-format CDATA #FIXED "schema"
       oks-version CDATA #REQUIRED
@@ -50,7 +49,6 @@
       range CDATA ""
       format (dec|hex|oct) "dec"
       is-multi-value (yes|no) "no"
-      multi-value-implementation (list|vector) "list"
       init-value CDATA ""
       is-not-null (yes|no) "no"
   >
@@ -64,7 +62,6 @@
       is-composite (yes|no) #REQUIRED
       is-exclusive (yes|no) #REQUIRED
       is-dependent (yes|no) #REQUIRED
-      multi-value-implementation (list|vector) "list"
   >
   <!ELEMENT method (method-implementation*)>
   <!ATTLIST method
@@ -81,7 +78,12 @@
 
 <oks-schema>
 
-<info name="" type="" num-of-includes="0" num-of-items="1" oks-format="schema" oks-version="oks-06-06-07 built &quot;May  2 2013&quot;" created-by="rhauser" created-on="msu-pc7.cern.ch" creation-time="20130502T084342" last-modified-by="rhauser" last-modified-on="msu-pc7.cern.ch" last-modification-time="20130502T084544"/>
+<info name="" type="" num-of-items="2" oks-format="schema" oks-version="oks-06-09-02 built &quot;Nov  2 2015&quot;" created-by="rhauser" created-on="msu-pc7.cern.ch" creation-time="20130502T084342" last-modified-by="rhauser" last-modified-on="msu-pc7.cern.ch" last-modification-time="20151102T125421"/>
+
+<include>
+ <file path="is/is.xml"/>
+</include>
+
 
  <class name="MsgInfo">
   <superclass name="Info"/>
@@ -90,4 +92,11 @@
   <attribute name="Port" description="Port number" type="u16" is-not-null="yes"/>
  </class>
 
+ <class name="MultiCastGroup">
+  <superclass name="Info"/>
+  <attribute name="Partition" description="The name of the partition where this multicast group is used." type="string" is-not-null="yes"/>
+  <attribute name="MulticastAddress" description="The address of the multicast group. It must be in the 224.*.*.* range." type="string" range="224\.[0-9]+\.[0-9]+\.[0-9]+" init-value="224.200.100.100" is-not-null="yes"/>
+  <attribute name="MulticastNetwork" description="The network address where multicast packages are being sent. This determines the outgoing interface and should be the network part of the " type="string" range="[0-9]+\.[0-9]+\.[0-9]+\.[0-9]+(/[0-9]+\.[0-9]+\.[0-9]+\.[0-9]+)?" init-value="10.149.0.0" is-not-null="yes"/>
+ </class>
+
 </oks-schema>
diff --git a/src/MultiCastGroup.h b/src/MultiCastGroup.h
new file mode 100644
index 0000000..ea75556
--- /dev/null
+++ b/src/MultiCastGroup.h
@@ -0,0 +1,105 @@
+#ifndef MULTICASTGROUP_H
+#define MULTICASTGROUP_H
+
+#include <is/info.h>
+
+#include <string>
+#include <ostream>
+
+
+// <<BeginUserCode>>
+
+// <<EndUserCode>>
+/**
+ * 
+ * @author  generated by the IS tool
+ * @version 04/11/15
+ */
+
+class MultiCastGroup : public ISInfo {
+public:
+
+    /**
+     * The name of the partition where this multicast group is used.
+     */
+    std::string                   Partition;
+
+    /**
+     * The address of the multicast group. It must be in the 224.*.*.* range.
+     */
+    std::string                   MulticastAddress;
+
+    /**
+     * The network address where multicast packages are being sent. This determines the outgoing interface and should be the network part of the 
+     */
+    std::string                   MulticastNetwork;
+
+
+    static const ISType & type() {
+	static const ISType type_ = MultiCastGroup( ).ISInfo::type();
+	return type_;
+    }
+
+    virtual std::ostream & print( std::ostream & out ) const {
+	ISInfo::print( out );
+	out << std::endl;
+	out << "Partition: " << Partition << "\t// The name of the partition where this multicast group is used." << std::endl;
+	out << "MulticastAddress: " << MulticastAddress << "\t// The address of the multicast group. It must be in the 224.*.*.* range." << std::endl;
+	out << "MulticastNetwork: " << MulticastNetwork << "\t// The network address where multicast packages are being sent. This determines the outgoing interface and should be the network part of the ";
+	return out;
+    }
+
+    MultiCastGroup( )
+      : ISInfo( "MultiCastGroup" )
+    {
+	initialize();
+    }
+
+    ~MultiCastGroup(){
+
+// <<BeginUserCode>>
+
+// <<EndUserCode>>
+    }
+
+protected:
+    MultiCastGroup( const std::string & type )
+      : ISInfo( type )
+    {
+	initialize();
+    }
+
+    void publishGuts( ISostream & out ){
+	out << Partition << MulticastAddress << MulticastNetwork;
+    }
+
+    void refreshGuts( ISistream & in ){
+	in >> Partition >> MulticastAddress >> MulticastNetwork;
+    }
+
+private:
+    void initialize()
+    {
+	MulticastAddress = "224.200.100.100";
+	MulticastNetwork = "10.149.0.0";
+
+// <<BeginUserCode>>
+
+// <<EndUserCode>>
+    }
+
+
+// <<BeginUserCode>>
+
+// <<EndUserCode>>
+};
+
+// <<BeginUserCode>>
+
+// <<EndUserCode>>
+inline std::ostream & operator<<( std::ostream & out, const MultiCastGroup & info ) {
+    info.print( out );
+    return out;
+}
+
+#endif // MULTICASTGROUP_H
diff --git a/src/MultiCastGroupNamed.h b/src/MultiCastGroupNamed.h
new file mode 100644
index 0000000..300b238
--- /dev/null
+++ b/src/MultiCastGroupNamed.h
@@ -0,0 +1,105 @@
+#ifndef MULTICASTGROUPNAMED_H
+#define MULTICASTGROUPNAMED_H
+
+#include <is/namedinfo.h>
+
+#include <string>
+#include <ostream>
+
+
+// <<BeginUserCode>>
+
+// <<EndUserCode>>
+/**
+ * 
+ * @author  generated by the IS tool
+ * @version 04/11/15
+ */
+
+class MultiCastGroupNamed : public ISNamedInfo {
+public:
+
+    /**
+     * The name of the partition where this multicast group is used.
+     */
+    std::string                   Partition;
+
+    /**
+     * The address of the multicast group. It must be in the 224.*.*.* range.
+     */
+    std::string                   MulticastAddress;
+
+    /**
+     * The network address where multicast packages are being sent. This determines the outgoing interface and should be the network part of the 
+     */
+    std::string                   MulticastNetwork;
+
+
+    static const ISType & type() {
+	static const ISType type_ = MultiCastGroupNamed( IPCPartition(), "" ).ISInfo::type();
+	return type_;
+    }
+
+    virtual std::ostream & print( std::ostream & out ) const {
+	ISNamedInfo::print( out );
+	out << std::endl;
+	out << "Partition: " << Partition << "\t// The name of the partition where this multicast group is used." << std::endl;
+	out << "MulticastAddress: " << MulticastAddress << "\t// The address of the multicast group. It must be in the 224.*.*.* range." << std::endl;
+	out << "MulticastNetwork: " << MulticastNetwork << "\t// The network address where multicast packages are being sent. This determines the outgoing interface and should be the network part of the ";
+	return out;
+    }
+
+    MultiCastGroupNamed( const IPCPartition & partition, const std::string & name )
+      : ISNamedInfo( partition, name, "MultiCastGroup" )
+    {
+	initialize();
+    }
+
+    ~MultiCastGroupNamed(){
+
+// <<BeginUserCode>>
+
+// <<EndUserCode>>
+    }
+
+protected:
+    MultiCastGroupNamed( const IPCPartition & partition, const std::string & name, const std::string & type )
+      : ISNamedInfo( partition, name, type )
+    {
+	initialize();
+    }
+
+    void publishGuts( ISostream & out ){
+	out << Partition << MulticastAddress << MulticastNetwork;
+    }
+
+    void refreshGuts( ISistream & in ){
+	in >> Partition >> MulticastAddress >> MulticastNetwork;
+    }
+
+private:
+    void initialize()
+    {
+	MulticastAddress = "224.200.100.100";
+	MulticastNetwork = "10.149.0.0";
+
+// <<BeginUserCode>>
+
+// <<EndUserCode>>
+    }
+
+
+// <<BeginUserCode>>
+
+// <<EndUserCode>>
+};
+
+// <<BeginUserCode>>
+
+// <<EndUserCode>>
+inline std::ostream & operator<<( std::ostream & out, const MultiCastGroupNamed & info ) {
+    info.print( out );
+    return out;
+}
+
+#endif // MULTICASTGROUPNAMED_H
diff --git a/src/NameService.cxx b/src/NameService.cxx
index b0763e1..a1c1b3e 100644
--- a/src/NameService.cxx
+++ b/src/NameService.cxx
@@ -3,10 +3,13 @@
 #include "transport/Interface.h"
 
 #include "MsgInfo.h"
+#include "MultiCastGroup.h"
+#include "MultiCastGroupNamed.h"
 
 #include "is/infostream.h"
 #include "is/infodictionary.h"
 #include "is/serveriterator.h"
+#include "is/infoiterator.h"
 
 #include <algorithm>
 
@@ -346,5 +349,86 @@ namespace daq {
             return boost::asio::ip::address();
         }
 
+        std::string NameService::allocate_multicast_address(const std::string& addr, const std::string& network)
+        {
+            MultiCastGroup group;
+            ISInfoDictionary  dict(m_partition);
+
+            if(addr == "*") {
+
+                // Find potential entry in initial partition
+                ISInfoIterator it(IPCPartition(), "RunParams", MultiCastGroup::type());
+                bool found = false;
+
+                while(it++) {
+                    it.value(group);
+                    if(group.Partition == m_partition.name()) {
+                        // found our partition.
+                        found = true;
+                        break;
+                    }
+                }
+
+                if(!found) {
+                    // allocate a new group.
+
+                    unsigned short    index = 1;
+                    std::string       name_prefix = "RunParams.MultiCast-";
+
+                    group.Partition = m_partition.name();
+                    group.MulticastAddress = std::string("224.100.100.") + boost::lexical_cast<std::string>(index);
+                    group.MulticastNetwork = network;
+
+                    while(!found && index < 256) {
+                        
+                        try {
+                            dict.insert(name_prefix + group.MulticastAddress, group);
+                            found = true;
+                        } catch(...) {
+                            // try next
+                            index++;
+                        }
+                    }
+                }
+                    
+                if(!found) {
+                    // A dynamic address was requested, but we could not allocate one...
+                    throw CannotAllocateMulticast(ERS_HERE);
+                }
+            }    
+
+            ISServerIterator servers(m_partition, m_is_server + ".*");
+            if(servers.entries() == 0) {
+                throw InvalidISServer(ERS_HERE,m_is_server);
+            }
+            while(servers++) {
+                // may throw
+                try {
+                    dict.checkin(std::string(servers.name()) + ".MultiCastAddress", group);
+                } catch(ers::Issue& reason) {
+                    throw InvalidISServer(ERS_HERE, servers.name(), reason);
+                }
+            }
+            
+            return group.MulticastAddress;
+        }
+        
+        std::string NameService::lookup_multicast_address(const std::string& addr) const
+        {
+            if(addr == "*") {
+
+                MultiCastGroupNamed group(m_partition, m_is_server + ".MultiCastAddress");
+                
+                try {
+                    group.checkout();
+                    return group.MulticastAddress;
+                    
+                } catch (ers::Issue& ex) {
+                    throw CannotResolve(ERS_HERE, addr, ex);
+                }
+            }
+
+            return addr;
+        }
     }
 }
-- 
GitLab