Browse Source

common module-side API for commands and configuration

git-svn-id: svn://bind10.isc.org/svn/bind10/branches/jelte-datadef@394 e5f2f494-b856-4b98-b285-d166d9295462
Jelte Jansen 15 years ago
parent
commit
e0f80ec2f7

+ 58 - 54
src/bin/parkinglot/ccsession.cc

@@ -71,23 +71,48 @@ CommandSession::read_data_definition(const std::string& filename) {
     file.close();
 }
 
-CommandSession::CommandSession() :
+CommandSession::CommandSession(std::string module_name,
+                               std::string spec_file_name,
+                               ISC::Data::ElementPtr(*config_handler)(ISC::Data::ElementPtr new_config),
+                               ISC::Data::ElementPtr(*command_handler)(ISC::Data::ElementPtr command)
+                              ) :
+    module_name_(module_name),
     session_(ISC::CC::Session())
 {
+    config_handler_ = config_handler;
+    command_handler_ = command_handler;
+
     try {
         // todo: workaround, let boss wait until msgq is started
         // and remove sleep here
         sleep(1);
+
+        ElementPtr answer, env;
+
         session_.establish();
-        session_.subscribe("ParkingLot", "*");
+        session_.subscribe(module_name, "*");
         session_.subscribe("Boss", "*", "meonly");
         session_.subscribe("ConfigManager", "*", "meonly");
         session_.subscribe("statistics", "*", "meonly");
-        read_data_definition("parkinglot.spec");
+        read_data_definition(spec_file_name);
         sleep(1);
-        ElementPtr cmd = Element::create_from_string("{ \"config_manager\": 1}");
-        // why does the msgq seem to kill this msg?
+        // send the data specification
         session_.group_sendmsg(data_definition_.getDefinition(), "ConfigManager");
+        session_.group_recvmsg(env, answer, false);
+        
+        // get any stored configuration from the manager
+        if (config_handler_) {
+            ElementPtr cmd = Element::create_from_string("{ \"command\": [ \"get_config\", \"" + module_name + "\" ] }");
+            session_.group_sendmsg(cmd, "ConfigManager");
+            session_.group_recvmsg(env, answer, false);
+            cout << "[XX] got config: " << endl << answer->str() << endl;
+            // replace string_value and "0" with int_value and 0 with new cc after merge */
+            if (answer->contains("result") && answer->get("result")->get(0)->string_value() == "0") {
+                config_handler(answer->get("result")->get(1));
+            } else {
+                cout << "[XX] no result in answer" << endl;
+            }
+        }
     } catch (...) {
         throw std::runtime_error("SessionManager: failed to open sessions");
     }
@@ -99,58 +124,37 @@ CommandSession::getSocket()
     return (session_.getSocket());
 }
 
-std::pair<std::string, ElementPtr>
-CommandSession::getCommand(int counter) {
-    ElementPtr cmd, routing, data, ep;
-    string s;
-    session_.group_recvmsg(routing, data, false);
-    string channel = routing->get("group")->string_value();
-
-    if (channel == "statistics") {
-        cmd = data->get("command");
-        if (cmd != NULL && cmd->string_value() == "getstat") {
-            struct timeval now;
-            ElementPtr resp = Element::create(std::map<std::string,
-                                              ElementPtr>());
-            gettimeofday(&now, NULL);
-            resp->set("sent", Element::create(now.tv_sec +
-                                              (double)now.tv_usec / 1000000));
-            resp->set("counter", Element::create(counter));
-            session_.group_sendmsg(resp, "statistics");
+int
+CommandSession::check_command()
+{
+    cout << "[XX] check for command" << endl;
+    ElementPtr cmd, routing, data;
+    if (session_.group_recvmsg(routing, data, true)) {
+        /* ignore result messages (in case we're out of sync, to prevent
+         * pingpongs */
+        if (!data->get_type() == Element::map || data->contains("result")) {
+            return 0;
         }
-    } else {
-        cout << "[parkinglot] saw message: " << data << endl;
-        // todo: common interface for config updates?
-        cmd = data->get("config_update");
-        if (cmd != NULL) {
-            return std::pair<string, ElementPtr>("config_update", cmd);
+        cout << "[XX] got something!" << endl << data->str() << endl;
+        ElementPtr answer;
+        if (data->contains("config_update")) {
+            if (config_handler_) {
+                // handle config update
+                answer = config_handler_(data->get("config_update"));
+            } else {
+                answer = Element::create_from_string("{ \"result\": [0] }");
+            }
         }
-        // todo: common interface for command handling
-        cmd = data->get("command");
-        // the format is defined partly by convention;
-        // { "command": [ "module", "command", args... ]
-        // args is defined in the .spec file
-        // we could do checking here as well if we want
-        if (cmd != NULL && cmd->get(1)->string_value() == "print_message") {
-            cout << "[parkinglot] " << cmd->get(2)->string_value() << endl;
-            ElementPtr answer = Element::create_from_string("{ \"result\": [0] }");
-            session_.reply(routing, answer);
+        if (data->contains("command")) {
+            if (command_handler_) {
+                answer = command_handler_(data->get("command"));
+            } else {
+                answer = Element::create_from_string("{ \"result\": [0] }");
+            }
         }
+        session_.reply(routing, answer);
     }
-
-    return std::pair<string, ElementPtr>("unknown", ElementPtr());
+    
+    return 0;
 }
 
-// should be replaced by the general config-getter in cc setup
-std::vector<std::string>
-CommandSession::getZones() {
-    ElementPtr cmd, result, env;
-    std::vector<std::string> zone_names;
-    cmd = Element::create_from_string("{ \"command\": [ \"zone\", \"list\" ] }");
-    session_.group_sendmsg(cmd, "ConfigManager");
-    session_.group_recvmsg(env, result, false);
-    BOOST_FOREACH(ElementPtr zone_name, result->get("result")->list_value()) {
-        zone_names.push_back(zone_name->string_value());
-    }
-    return zone_names;
-}

+ 50 - 5
src/bin/parkinglot/ccsession.h

@@ -25,16 +25,61 @@
 
 class CommandSession {
 public:
-    CommandSession();
+    /**
+     * Initialize a config/command session
+     * @param module_name: The name of this module. This is not a
+     *                     reference because we expect static strings
+     *                     to be passed here.
+     * @param spec_file_name: The name of the file containing the data
+     *                        definition.
+     */
+    CommandSession(std::string module_name, std::string spec_file_name,
+                   ISC::Data::ElementPtr(*config_handler)(ISC::Data::ElementPtr new_config) = NULL,
+                   ISC::Data::ElementPtr(*command_handler)(ISC::Data::ElementPtr command) = NULL
+                  );
     int getSocket();
-    std::pair<std::string, ISC::Data::ElementPtr> getCommand(int counter);
-    std::vector<std::string> getZones();
+
+    /**
+     * Check if there is a command or config change on the command
+     * session. If so, the appropriate handler is called if set.
+     * If not set, a default answer is returned.
+     * This is a non-blocking read; if there is nothing this function
+     * will return 0.
+     */
+    int check_command();
+
+    /**
+     * The config handler function should expect an ElementPtr containing
+     * the full configuration where non-default values have been set.
+     * Later we might want to think about more granular control
+     * (i.e. this does not scale to for instance lists containing
+     * 100000 zones, where the whole list is passed every time a single
+     * thing changes)
+     */
+    void set_config_handler(ISC::Data::ElementPtr(*config_handler)(ISC::Data::ElementPtr new_config)) { config_handler_ = config_handler; };
+
+    /**
+     * Set a command handler; the function that is passed takes an
+     * ElementPtr, pointing to a list element, containing
+     * [ module_name, command_name, arg1, arg2, ... ]
+     * The returned ElementPtr should look like
+     * { "result": [ return_value, result_value ] }
+     * result value here is optional and depends on the command
+     *
+     * This protocol is very likely to change.
+     */
+    void set_command_handler(ISC::Data::ElementPtr(*command_handler)(ISC::Data::ElementPtr command)) { command_handler_ = command_handler; };
+    
 private:
-	void read_data_definition(const std::string& filename);
-	
+    void read_data_definition(const std::string& filename);
+    
+    std::string module_name_;
     ISC::CC::Session session_;
     ISC::Data::DataDefinition data_definition_;
     ISC::Data::ElementPtr config_;
+
+    ISC::Data::ElementPtr(*config_handler_)(ISC::Data::ElementPtr new_config);
+    ISC::Data::ElementPtr(*command_handler_)(ISC::Data::ElementPtr command);
 };
 
 #endif // __CCSESSION_H

+ 53 - 9
src/bin/parkinglot/main.cc

@@ -39,10 +39,18 @@
 
 #include "common.h"
 
+#include <boost/foreach.hpp>
+
 using namespace std;
 
-const string PROGRAM = "parkinglot";
-const int DNSPORT = 53;
+const string PROGRAM = "ParkingLot";
+const string SPECFILE = "parkinglot.spec";
+const int DNSPORT = 5300;
+
+/* need global var for config/command handlers.
+ * todo: turn this around, and put handlers in the parkinglot
+ * class itself */
+ParkingLot plot = ParkingLot(DNSPORT);
 
 static void
 usage() {
@@ -50,6 +58,38 @@ usage() {
     exit(1);
 }
 
+ISC::Data::ElementPtr
+my_config_handler(ISC::Data::ElementPtr config)
+{
+    cout << "[XX] Handle config: " << endl << config->str() << endl;
+    if (config->contains("zones")) {
+        plot.clear_zones();
+        BOOST_FOREACH(ISC::Data::ElementPtr zone_el, config->get("zones")->list_value()) {
+            plot.serve(zone_el->string_value());
+        }
+    }
+    if (config->contains("port")) {
+        // todo: what to do with port change. restart automatically?
+        // ignore atm
+    }
+    return ISC::Data::Element::create_from_string("{ \"result\": [0] }");
+}
+
+ISC::Data::ElementPtr
+my_command_handler(ISC::Data::ElementPtr command)
+{
+    ISC::Data::ElementPtr answer = ISC::Data::Element::create_from_string("{ \"result\": [0] }");
+
+    cout << "[XX] Handle command: " << endl << command->str() << endl;
+    if (command->get(1)->string_value() == "print_message") {
+        cout << command->get(2)->string_value() << endl;
+        /* let's add that message to our answer as well */
+        answer->get("result")->add(command->get(2));
+    }
+
+    return answer;
+}
+
 int
 main(int argc, char* argv[]) {
     int ch;
@@ -70,18 +110,20 @@ main(int argc, char* argv[]) {
         usage();
 
     // initialize parking lot
-    ParkingLot plot(port);
+    //plot = ParkingLot(port);
 
     // initialize command channel
-    CommandSession cs;
-
+    CommandSession cs = CommandSession(PROGRAM, SPECFILE, my_config_handler, my_command_handler);
+    //cs.set_config_handler(my_config_handler);
+    //cs.set_command_handler(my_command_handler);
+    
     // main server loop
     fd_set fds;
     int ps = plot.getSocket();
     int ss = cs.getSocket();
-    BOOST_FOREACH(std::string zone, cs.getZones()) {
+    /*BOOST_FOREACH(std::string zone, cs.getZones()) {
         plot.serve(zone);
-    }
+    }*/
     int nfds = max(ps, ss) + 1;
     int counter = 0;
 
@@ -100,9 +142,11 @@ main(int argc, char* argv[]) {
             plot.processMessage();
         }
 
+        /* isset not really necessary, but keep it for now */
         if (FD_ISSET(ss, &fds)) {
-            pair<string, ISC::Data::ElementPtr> cmd = cs.getCommand(counter);
-            plot.command(cmd);
+            cs.check_command();
+            //pair<string, ISC::Data::ElementPtr> cmd = cs.getCommand(counter);
+            //plot.command(cmd);
         }
     }
 

+ 1 - 0
src/bin/parkinglot/parkinglot.h

@@ -28,6 +28,7 @@ public:
     void processMessage();
     void command(std::pair<std::string,ISC::Data::ElementPtr>);
     void serve(std::string zone_name);
+    void clear_zones() { zones.clear_zones(); };
     
 private:
     isc::dns::Rdata::RdataPtr ns1, ns2, ns3, a, aaaa, soa;