Browse Source

Merge branch 'master' into trac1140

Dima Volodin 13 years ago
parent
commit
9bbf7837ed

+ 1 - 1
ChangeLog

@@ -1,6 +1,6 @@
 278.	[doc]		jelte
 	Add logging configuration documentation to the guide.
-	(Trac #1011, git TODO)
+	(Trac #1011, git 2cc500af0929c1f268aeb6f8480bc428af70f4c4)
 
 277.	[func]		jerry
 	Implement the SRV rrtype according to RFC2782.

+ 18 - 0
src/bin/auth/auth.spec.pre.in

@@ -122,6 +122,24 @@
           }
         ]
       }
+    ],
+    "statistics": [
+      {
+        "item_name": "queries.tcp",
+        "item_type": "integer",
+        "item_optional": false,
+        "item_default": 0,
+        "item_title": "Queries TCP ",
+        "item_description": "A number of total query counts which all auth servers receive over TCP since they started initially"
+      },
+      {
+        "item_name": "queries.udp",
+        "item_type": "integer",
+        "item_optional": false,
+        "item_default": 0,
+        "item_title": "Queries UDP",
+        "item_description": "A number of total query counts which all auth servers receive over UDP since they started initially"
+      }
     ]
   }
 }

+ 11 - 0
src/bin/bind10/bob.spec

@@ -37,6 +37,17 @@
         "command_description": "List the running BIND 10 processes",
         "command_args": []
       }
+    ],
+    "statistics": [
+      {
+        "item_name": "boot_time",
+        "item_type": "string",
+        "item_optional": false,
+        "item_default": "1970-01-01T00:00:00Z",
+        "item_title": "Boot time",
+        "item_description": "A date time when bind10 process starts initially",
+        "item_format": "date-time"
+      }
     ]
   }
 }

+ 1 - 2
src/bin/stats/stats-schema.spec

@@ -54,8 +54,7 @@
         "item_optional": false,
         "item_default": 0.0,
         "item_title": "stats.Timestamp",
-        "item_description": "A current time stamp since epoch time (1970-01-01T00:00:00Z)",
-        "item_format": "second"
+        "item_description": "A current time stamp since epoch time (1970-01-01T00:00:00Z)"
       },
       {
         "item_name": "stats.lname",

+ 45 - 0
src/bin/stats/stats.spec

@@ -56,6 +56,51 @@
         "command_description": "Shut down the stats module",
         "command_args": []
       }
+    ],
+    "statistics": [
+      {
+        "item_name": "report_time",
+        "item_type": "string",
+        "item_optional": false,
+        "item_default": "1970-01-01T00:00:00Z",
+        "item_title": "Report time",
+        "item_description": "A date time when stats module reports",
+        "item_format": "date-time"
+      },
+      {
+        "item_name": "boot_time",
+        "item_type": "string",
+        "item_optional": false,
+        "item_default": "1970-01-01T00:00:00Z",
+        "item_title": "Boot time",
+        "item_description": "A date time when the stats module starts initially or when the stats module restarts",
+        "item_format": "date-time"
+      },
+      {
+        "item_name": "last_update_time",
+        "item_type": "string",
+        "item_optional": false,
+        "item_default": "1970-01-01T00:00:00Z",
+        "item_title": "Last update time",
+        "item_description": "The latest date time when the stats module receives from other modules like auth server or boss process and so on",
+        "item_format": "date-time"
+      },
+      {
+        "item_name": "timestamp",
+        "item_type": "real",
+        "item_optional": false,
+        "item_default": 0.0,
+        "item_title": "Timestamp",
+        "item_description": "A current time stamp since epoch time (1970-01-01T00:00:00Z)"
+      },
+      {
+        "item_name": "lname",
+        "item_type": "string",
+        "item_optional": false,
+        "item_default": "",
+        "item_title": "Local Name",
+        "item_description": "A localname of stats module given via CC protocol"
+       }
     ]
   }
 }

+ 89 - 0
src/bin/stats/tests/isc/config/ccsession.py

@@ -23,6 +23,7 @@ external module.
 
 import json
 import os
+import time
 from isc.cc.session import Session
 
 COMMAND_CONFIG_UPDATE = "config_update"
@@ -72,6 +73,9 @@ class ModuleSpecError(Exception):
 
 class ModuleSpec:
     def __init__(self, module_spec, check = True):
+        # check only confi_data for testing
+        if check and "config_data" in module_spec:
+            _check_config_spec(module_spec["config_data"])
         self._module_spec = module_spec
 
     def get_config_spec(self):
@@ -83,6 +87,91 @@ class ModuleSpec:
     def get_module_name(self):
         return self._module_spec['module_name']
 
+def _check_config_spec(config_data):
+    # config data is a list of items represented by dicts that contain
+    # things like "item_name", depending on the type they can have
+    # specific subitems
+    """Checks a list that contains the configuration part of the
+       specification. Raises a ModuleSpecError if there is a
+       problem."""
+    if type(config_data) != list:
+        raise ModuleSpecError("config_data is of type " + str(type(config_data)) + ", not a list of items")
+    for config_item in config_data:
+        _check_item_spec(config_item)
+
+def _check_item_spec(config_item):
+    """Checks the dict that defines one config item
+       (i.e. containing "item_name", "item_type", etc.
+       Raises a ModuleSpecError if there is an error"""
+    if type(config_item) != dict:
+        raise ModuleSpecError("item spec not a dict")
+    if "item_name" not in config_item:
+        raise ModuleSpecError("no item_name in config item")
+    if type(config_item["item_name"]) != str:
+        raise ModuleSpecError("item_name is not a string: " + str(config_item["item_name"]))
+    item_name = config_item["item_name"]
+    if "item_type" not in config_item:
+        raise ModuleSpecError("no item_type in config item")
+    item_type = config_item["item_type"]
+    if type(item_type) != str:
+        raise ModuleSpecError("item_type in " + item_name + " is not a string: " + str(type(item_type)))
+    if item_type not in ["integer", "real", "boolean", "string", "list", "map", "any"]:
+        raise ModuleSpecError("unknown item_type in " + item_name + ": " + item_type)
+    if "item_optional" in config_item:
+        if type(config_item["item_optional"]) != bool:
+            raise ModuleSpecError("item_default in " + item_name + " is not a boolean")
+        if not config_item["item_optional"] and "item_default" not in config_item:
+            raise ModuleSpecError("no default value for non-optional item " + item_name)
+    else:
+        raise ModuleSpecError("item_optional not in item " + item_name)
+    if "item_default" in config_item:
+        item_default = config_item["item_default"]
+        if (item_type == "integer" and type(item_default) != int) or \
+           (item_type == "real" and type(item_default) != float) or \
+           (item_type == "boolean" and type(item_default) != bool) or \
+           (item_type == "string" and type(item_default) != str) or \
+           (item_type == "list" and type(item_default) != list) or \
+           (item_type == "map" and type(item_default) != dict):
+            raise ModuleSpecError("Wrong type for item_default in " + item_name)
+    # TODO: once we have check_type, run the item default through that with the list|map_item_spec
+    if item_type == "list":
+        if "list_item_spec" not in config_item:
+            raise ModuleSpecError("no list_item_spec in list item " + item_name)
+        if type(config_item["list_item_spec"]) != dict:
+            raise ModuleSpecError("list_item_spec in " + item_name + " is not a dict")
+        _check_item_spec(config_item["list_item_spec"])
+    if item_type == "map":
+        if "map_item_spec" not in config_item:
+            raise ModuleSpecError("no map_item_sepc in map item " + item_name)
+        if type(config_item["map_item_spec"]) != list:
+            raise ModuleSpecError("map_item_spec in " + item_name + " is not a list")
+        for map_item in config_item["map_item_spec"]:
+            if type(map_item) != dict:
+                raise ModuleSpecError("map_item_spec element is not a dict")
+            _check_item_spec(map_item)
+    if 'item_format' in config_item and 'item_default' in config_item:
+        item_format = config_item["item_format"]
+        item_default = config_item["item_default"]
+        if not _check_format(item_default, item_format):
+            raise ModuleSpecError(
+                "Wrong format for " + str(item_default) + " in " + str(item_name))
+
+def _check_format(value, format_name):
+    """Check if specified value and format are correct. Return True if
+       is is correct."""
+    # TODO: should be added other format types if necessary
+    time_formats = { 'date-time' : "%Y-%m-%dT%H:%M:%SZ",
+                     'date'      : "%Y-%m-%d",
+                     'time'      : "%H:%M:%S" }
+    for fmt in time_formats:
+        if format_name == fmt:
+            try:
+                time.strptime(value, time_formats[fmt])
+                return True
+            except (ValueError, TypeError):
+                break
+    return False
+
 class ModuleCCSessionError(Exception):
     pass
 

+ 90 - 1
src/lib/config/module_spec.cc

@@ -1,4 +1,4 @@
-// Copyright (C) 2010  Internet Systems Consortium.
+// Copyright (C) 2010, 2011  Internet Systems Consortium.
 //
 // Permission to use, copy, modify, and distribute this software for any
 // purpose with or without fee is hereby granted, provided that the above
@@ -87,6 +87,61 @@ check_config_item_list(ConstElementPtr spec) {
     }
 }
 
+// checks whether the given element is a valid statistics specification
+// returns false if the specification is bad
+bool
+check_format(ConstElementPtr value, ConstElementPtr format_name) {
+    typedef std::map<std::string, std::string> format_types;
+    format_types time_formats;
+    // TODO: should be added other format types if necessary
+    time_formats.insert(
+        format_types::value_type("date-time", "%Y-%m-%dT%H:%M:%SZ") );
+    time_formats.insert(
+        format_types::value_type("date", "%Y-%m-%d") );
+    time_formats.insert(
+        format_types::value_type("time", "%H:%M:%S") );
+    BOOST_FOREACH (const format_types::value_type& f, time_formats) {
+        if (format_name->stringValue() == f.first) {
+            struct tm tm;
+            std::vector<char> buf(32);
+            memset(&tm, 0, sizeof(tm));
+            // reverse check
+            return (strptime(value->stringValue().c_str(),
+                             f.second.c_str(), &tm) != NULL
+                    && strftime(&buf[0], buf.size(),
+                                f.second.c_str(), &tm) != 0
+                    && strncmp(value->stringValue().c_str(),
+                               &buf[0], buf.size()) == 0);
+        }
+    }
+    return (false);
+}
+
+void check_statistics_item_list(ConstElementPtr spec);
+
+void
+check_statistics_item_list(ConstElementPtr spec) {
+    if (spec->getType() != Element::list) {
+        throw ModuleSpecError("statistics is not a list of elements");
+    }
+    BOOST_FOREACH(ConstElementPtr item, spec->listValue()) {
+        check_config_item(item);
+        // additional checks for statistics
+        check_leaf_item(item, "item_title", Element::string, true);
+        check_leaf_item(item, "item_description", Element::string, true);
+        check_leaf_item(item, "item_format", Element::string, false);
+        // checks name of item_format and validation of item_default
+        if (item->contains("item_format")
+            && item->contains("item_default")) {
+            if(!check_format(item->get("item_default"),
+                             item->get("item_format"))) {
+                throw ModuleSpecError(
+                    "item_default not valid type of item_format");
+            }
+        }
+    }
+}
+
 void
 check_command(ConstElementPtr spec) {
     check_leaf_item(spec, "command_name", Element::string, true);
@@ -116,6 +171,9 @@ check_data_specification(ConstElementPtr spec) {
     if (spec->contains("commands")) {
         check_command_list(spec->get("commands"));
     }
+    if (spec->contains("statistics")) {
+        check_statistics_item_list(spec->get("statistics"));
+    }
 }
 
 // checks whether the given element is a valid module specification
@@ -165,6 +223,15 @@ ModuleSpec::getConfigSpec() const {
     }
 }
 
+ConstElementPtr
+ModuleSpec::getStatisticsSpec() const {
+    if (module_specification->contains("statistics")) {
+        return (module_specification->get("statistics"));
+    } else {
+        return (ElementPtr());
+    }
+}
+
 const std::string
 ModuleSpec::getModuleName() const {
     return (module_specification->get("module_name")->stringValue());
@@ -186,6 +253,12 @@ ModuleSpec::validateConfig(ConstElementPtr data, const bool full) const {
 }
 
 bool
+ModuleSpec::validateStatistics(ConstElementPtr data, const bool full) const {
+    ConstElementPtr spec = module_specification->find("statistics");
+    return (validateSpecList(spec, data, full, ElementPtr()));
+}
+
+bool
 ModuleSpec::validateCommand(const std::string& command,
                              ConstElementPtr args,
                              ElementPtr errors) const
@@ -223,6 +296,14 @@ ModuleSpec::validateConfig(ConstElementPtr data, const bool full,
     return (validateSpecList(spec, data, full, errors));
 }
 
+bool
+ModuleSpec::validateStatistics(ConstElementPtr data, const bool full,
+                               ElementPtr errors) const
+{
+    ConstElementPtr spec = module_specification->find("statistics");
+    return (validateSpecList(spec, data, full, errors));
+}
+
 ModuleSpec
 moduleSpecFromFile(const std::string& file_name, const bool check)
                    throw(JSONError, ModuleSpecError)
@@ -343,6 +424,14 @@ ModuleSpec::validateItem(ConstElementPtr spec, ConstElementPtr data,
             }
         }
     }
+    if (spec->contains("item_format")) {
+        if (!check_format(data, spec->get("item_format"))) {
+            if (errors) {
+                errors->add(Element::create("Format mismatch"));
+            }
+            return (false);
+        }
+    }
     return (true);
 }
 

+ 22 - 1
src/lib/config/module_spec.h

@@ -1,4 +1,4 @@
-// Copyright (C) 2010  Internet Systems Consortium.
+// Copyright (C) 2010, 2011  Internet Systems Consortium.
 //
 // Permission to use, copy, modify, and distribute this software for any
 // purpose with or without fee is hereby granted, provided that the above
@@ -71,6 +71,12 @@ namespace isc { namespace config {
         ///                    part of the specification
         isc::data::ConstElementPtr getConfigSpec() const;
 
+        /// Returns the statistics part of the specification as an
+        /// ElementPtr
+        /// \return ElementPtr Shared pointer to the statistics
+        ///                    part of the specification
+        isc::data::ConstElementPtr getStatisticsSpec() const;
+
         /// Returns the full module specification as an ElementPtr
         /// \return ElementPtr Shared pointer to the specification
         isc::data::ConstElementPtr getFullSpec() const {
@@ -95,6 +101,17 @@ namespace isc { namespace config {
         bool validateConfig(isc::data::ConstElementPtr data,
                              const bool full = false) const;
 
+        // returns true if the given element conforms to this data
+        // statistics specification
+        /// Validates the given statistics data for this specification.
+        /// \param data The base \c Element of the data to check
+        /// \param full If true, all non-optional statistics parameters
+        /// must be specified.
+        /// \return true if the data conforms to the specification,
+        /// false otherwise.
+        bool validateStatistics(isc::data::ConstElementPtr data,
+                             const bool full = false) const;
+
         /// Validates the arguments for the given command
         ///
         /// This checks the command and argument against the
@@ -142,6 +159,10 @@ namespace isc { namespace config {
         bool validateConfig(isc::data::ConstElementPtr data, const bool full,
                              isc::data::ElementPtr errors) const;
 
+        /// errors must be of type ListElement
+        bool validateStatistics(isc::data::ConstElementPtr data, const bool full,
+                                isc::data::ElementPtr errors) const;
+
     private:
         bool validateItem(isc::data::ConstElementPtr spec,
                           isc::data::ConstElementPtr data,

File diff suppressed because it is too large
+ 2 - 2
src/lib/config/tests/ccsession_unittests.cc


+ 157 - 1
src/lib/config/tests/module_spec_unittests.cc

@@ -1,4 +1,4 @@
-// Copyright (C) 2009  Internet Systems Consortium, Inc. ("ISC")
+// Copyright (C) 2009, 2011  Internet Systems Consortium, Inc. ("ISC")
 //
 // Permission to use, copy, modify, and/or distribute this software for any
 // purpose with or without fee is hereby granted, provided that the above
@@ -18,6 +18,8 @@
 
 #include <fstream>
 
+#include <boost/foreach.hpp>
+
 #include <config/tests/data_def_unittests_config.h>
 
 using namespace isc::data;
@@ -57,6 +59,7 @@ TEST(ModuleSpec, ReadingSpecfiles) {
 
     dd = moduleSpecFromFile(specfile("spec2.spec"));
     EXPECT_EQ("[ { \"command_args\": [ { \"item_default\": \"\", \"item_name\": \"message\", \"item_optional\": false, \"item_type\": \"string\" } ], \"command_description\": \"Print the given message to stdout\", \"command_name\": \"print_message\" }, { \"command_args\": [  ], \"command_description\": \"Shut down BIND 10\", \"command_name\": \"shutdown\" } ]", dd.getCommandsSpec()->str());
+    EXPECT_EQ("[ { \"item_default\": \"1970-01-01T00:00:00Z\", \"item_description\": \"A dummy date time\", \"item_format\": \"date-time\", \"item_name\": \"dummy_time\", \"item_optional\": false, \"item_title\": \"Dummy Time\", \"item_type\": \"string\" } ]", dd.getStatisticsSpec()->str());
     EXPECT_EQ("Spec2", dd.getModuleName());
     EXPECT_EQ("", dd.getModuleDescription());
 
@@ -64,6 +67,11 @@ TEST(ModuleSpec, ReadingSpecfiles) {
     EXPECT_EQ("Spec25", dd.getModuleName());
     EXPECT_EQ("Just an empty module", dd.getModuleDescription());
     EXPECT_THROW(moduleSpecFromFile(specfile("spec26.spec")), ModuleSpecError);
+    EXPECT_THROW(moduleSpecFromFile(specfile("spec34.spec")), ModuleSpecError);
+    EXPECT_THROW(moduleSpecFromFile(specfile("spec35.spec")), ModuleSpecError);
+    EXPECT_THROW(moduleSpecFromFile(specfile("spec36.spec")), ModuleSpecError);
+    EXPECT_THROW(moduleSpecFromFile(specfile("spec37.spec")), ModuleSpecError);
+    EXPECT_THROW(moduleSpecFromFile(specfile("spec38.spec")), ModuleSpecError);
 
     std::ifstream file;
     file.open(specfile("spec1.spec").c_str());
@@ -71,6 +79,7 @@ TEST(ModuleSpec, ReadingSpecfiles) {
     EXPECT_EQ(dd.getFullSpec()->get("module_name")
                               ->stringValue(), "Spec1");
     EXPECT_TRUE(isNull(dd.getCommandsSpec()));
+    EXPECT_TRUE(isNull(dd.getStatisticsSpec()));
 
     std::ifstream file2;
     file2.open(specfile("spec8.spec").c_str());
@@ -114,6 +123,12 @@ TEST(ModuleSpec, SpecfileConfigData) {
                    "commands is not a list of elements");
 }
 
+TEST(ModuleSpec, SpecfileStatistics) {
+    moduleSpecError("spec36.spec", "item_default not valid type of item_format");
+    moduleSpecError("spec37.spec", "statistics is not a list of elements");
+    moduleSpecError("spec38.spec", "item_default not valid type of item_format");
+}
+
 TEST(ModuleSpec, SpecfileCommands) {
     moduleSpecError("spec17.spec",
                    "command_name missing in { \"command_args\": [ { \"item_default\": \"\", \"item_name\": \"message\", \"item_optional\": false, \"item_type\": \"string\" } ], \"command_description\": \"Print the given message to stdout\" }");
@@ -137,6 +152,17 @@ dataTest(const ModuleSpec& dd, const std::string& data_file_name) {
 }
 
 bool
+statisticsTest(const ModuleSpec& dd, const std::string& data_file_name) {
+    std::ifstream data_file;
+
+    data_file.open(specfile(data_file_name).c_str());
+    ConstElementPtr data = Element::fromJSON(data_file, data_file_name);
+    data_file.close();
+
+    return (dd.validateStatistics(data));
+}
+
+bool
 dataTestWithErrors(const ModuleSpec& dd, const std::string& data_file_name,
                       ElementPtr errors)
 {
@@ -149,6 +175,19 @@ dataTestWithErrors(const ModuleSpec& dd, const std::string& data_file_name,
     return (dd.validateConfig(data, true, errors));
 }
 
+bool
+statisticsTestWithErrors(const ModuleSpec& dd, const std::string& data_file_name,
+                      ElementPtr errors)
+{
+    std::ifstream data_file;
+
+    data_file.open(specfile(data_file_name).c_str());
+    ConstElementPtr data = Element::fromJSON(data_file, data_file_name);
+    data_file.close();
+
+    return (dd.validateStatistics(data, true, errors));
+}
+
 TEST(ModuleSpec, DataValidation) {
     ModuleSpec dd = moduleSpecFromFile(specfile("spec22.spec"));
 
@@ -175,6 +214,17 @@ TEST(ModuleSpec, DataValidation) {
     EXPECT_EQ("[ \"Unknown item value_does_not_exist\" ]", errors->str());
 }
 
+TEST(ModuleSpec, StatisticsValidation) {
+    ModuleSpec dd = moduleSpecFromFile(specfile("spec33.spec"));
+
+    EXPECT_TRUE(statisticsTest(dd, "data33_1.data"));
+    EXPECT_FALSE(statisticsTest(dd, "data33_2.data"));
+
+    ElementPtr errors = Element::createList();
+    EXPECT_FALSE(statisticsTestWithErrors(dd, "data33_2.data", errors));
+    EXPECT_EQ("[ \"Format mismatch\", \"Format mismatch\", \"Format mismatch\" ]", errors->str());
+}
+
 TEST(ModuleSpec, CommandValidation) {
     ModuleSpec dd = moduleSpecFromFile(specfile("spec2.spec"));
     ConstElementPtr arg = Element::fromJSON("{}");
@@ -220,3 +270,109 @@ TEST(ModuleSpec, NamedSetValidation) {
     EXPECT_FALSE(dataTest(dd, "data32_2.data"));
     EXPECT_FALSE(dataTest(dd, "data32_3.data"));
 }
+
+TEST(ModuleSpec, CheckFormat) {
+
+    const std::string json_begin = "{ \"module_spec\": { \"module_name\": \"Foo\", \"statistics\": [ { \"item_name\": \"dummy_time\", \"item_type\": \"string\", \"item_optional\": true, \"item_title\": \"Dummy Time\", \"item_description\": \"A dummy date time\"";
+    const std::string json_end = " } ] } }";
+    std::string item_default;
+    std::string item_format;
+    std::vector<std::string> specs;
+    ConstElementPtr el;
+
+    specs.clear();
+    item_default = "\"item_default\": \"2011-05-27T19:42:57Z\",";
+    item_format  = "\"item_format\": \"date-time\"";
+    specs.push_back("," + item_default + item_format);
+    item_default = "\"item_default\": \"2011-05-27\",";
+    item_format  = "\"item_format\": \"date\"";
+    specs.push_back("," + item_default + item_format);
+    item_default = "\"item_default\": \"19:42:57\",";
+    item_format  = "\"item_format\": \"time\"";
+    specs.push_back("," + item_default + item_format);
+
+    item_format  = "\"item_format\": \"date-time\"";
+    specs.push_back("," + item_format);
+    item_default = "";
+    item_format  = "\"item_format\": \"date\"";
+    specs.push_back("," + item_format);
+    item_default = "";
+    item_format  = "\"item_format\": \"time\"";
+    specs.push_back("," + item_format);
+
+    item_default = "\"item_default\": \"a\"";
+    specs.push_back("," + item_default);
+    item_default = "\"item_default\": \"b\"";
+    specs.push_back("," + item_default);
+    item_default = "\"item_default\": \"c\"";
+    specs.push_back("," + item_default);
+
+    item_format  = "\"item_format\": \"dummy\"";
+    specs.push_back("," + item_format);
+
+    specs.push_back("");
+
+    BOOST_FOREACH(std::string s, specs) {
+        el = Element::fromJSON(json_begin + s + json_end)->get("module_spec");
+        EXPECT_NO_THROW(ModuleSpec(el, true));
+    }
+
+    specs.clear();
+    item_default = "\"item_default\": \"2011-05-27T19:42:57Z\",";
+    item_format  = "\"item_format\": \"dummy\"";
+    specs.push_back("," + item_default + item_format);
+    item_default = "\"item_default\": \"2011-05-27\",";
+    item_format  = "\"item_format\": \"dummy\"";
+    specs.push_back("," + item_default + item_format);
+    item_default = "\"item_default\": \"19:42:57Z\",";
+    item_format  = "\"item_format\": \"dummy\"";
+    specs.push_back("," + item_default + item_format);
+
+    item_default = "\"item_default\": \"2011-13-99T99:99:99Z\",";
+    item_format  = "\"item_format\": \"date-time\"";
+    specs.push_back("," + item_default + item_format);
+    item_default = "\"item_default\": \"2011-13-99\",";
+    item_format  = "\"item_format\": \"date\"";
+    specs.push_back("," + item_default + item_format);
+    item_default = "\"item_default\": \"99:99:99Z\",";
+    item_format  = "\"item_format\": \"time\"";
+    specs.push_back("," + item_default + item_format);
+
+    item_default = "\"item_default\": \"1\",";
+    item_format  = "\"item_format\": \"date-time\"";
+    specs.push_back("," + item_default + item_format);
+    item_default = "\"item_default\": \"1\",";
+    item_format  = "\"item_format\": \"date\"";
+    specs.push_back("," + item_default + item_format);
+    item_default = "\"item_default\": \"1\",";
+    item_format  = "\"item_format\": \"time\"";
+    specs.push_back("," + item_default + item_format);
+
+    item_default = "\"item_default\": \"\",";
+    item_format  = "\"item_format\": \"date-time\"";
+    specs.push_back("," + item_default + item_format);
+    item_default = "\"item_default\": \"\",";
+    item_format  = "\"item_format\": \"date\"";
+    specs.push_back("," + item_default + item_format);
+    item_default = "\"item_default\": \"\",";
+    item_format  = "\"item_format\": \"time\"";
+    specs.push_back("," + item_default + item_format);
+
+    // wrong date-time-type format not ending with "Z"
+    item_default = "\"item_default\": \"2011-05-27T19:42:57\",";
+    item_format  = "\"item_format\": \"date-time\"";
+    specs.push_back("," + item_default + item_format);
+    // wrong date-type format ending with "T"
+    item_default = "\"item_default\": \"2011-05-27T\",";
+    item_format  = "\"item_format\": \"date\"";
+    specs.push_back("," + item_default + item_format);
+    // wrong time-type format ending with "Z"
+    item_default = "\"item_default\": \"19:42:57Z\",";
+    item_format  = "\"item_format\": \"time\"";
+    specs.push_back("," + item_default + item_format);
+
+    BOOST_FOREACH(std::string s, specs) {
+        el = Element::fromJSON(json_begin + s + json_end)->get("module_spec");
+        EXPECT_THROW(ModuleSpec(el, true), ModuleSpecError);
+    }
+}

+ 8 - 0
src/lib/config/tests/testdata/Makefile.am

@@ -25,6 +25,8 @@ EXTRA_DIST += data22_10.data
 EXTRA_DIST += data32_1.data
 EXTRA_DIST += data32_2.data
 EXTRA_DIST += data32_3.data
+EXTRA_DIST += data33_1.data
+EXTRA_DIST += data33_2.data
 EXTRA_DIST += spec1.spec
 EXTRA_DIST += spec2.spec
 EXTRA_DIST += spec3.spec
@@ -57,3 +59,9 @@ EXTRA_DIST += spec29.spec
 EXTRA_DIST += spec30.spec
 EXTRA_DIST += spec31.spec
 EXTRA_DIST += spec32.spec
+EXTRA_DIST += spec33.spec
+EXTRA_DIST += spec34.spec
+EXTRA_DIST += spec35.spec
+EXTRA_DIST += spec36.spec
+EXTRA_DIST += spec37.spec
+EXTRA_DIST += spec38.spec

+ 7 - 0
src/lib/config/tests/testdata/data33_1.data

@@ -0,0 +1,7 @@
+{
+    "dummy_str": "Dummy String",
+    "dummy_int": 118,
+    "dummy_datetime": "2011-05-27T19:42:57Z",
+    "dummy_date": "2011-05-27",
+    "dummy_time": "19:42:57"
+}

+ 7 - 0
src/lib/config/tests/testdata/data33_2.data

@@ -0,0 +1,7 @@
+{
+    "dummy_str": "Dummy String",
+    "dummy_int": 118,
+    "dummy_datetime": "xxxx",
+    "dummy_date": "xxxx",
+    "dummy_time": "xxxx"
+}

+ 11 - 0
src/lib/config/tests/testdata/spec2.spec

@@ -66,6 +66,17 @@
         "command_description": "Shut down BIND 10",
         "command_args": []
       }
+    ],
+    "statistics": [
+      {
+        "item_name": "dummy_time",
+        "item_type": "string",
+        "item_optional": false,
+        "item_default": "1970-01-01T00:00:00Z",
+        "item_title": "Dummy Time",
+        "item_description": "A dummy date time",
+        "item_format": "date-time"
+      }
     ]
   }
 }

+ 50 - 0
src/lib/config/tests/testdata/spec33.spec

@@ -0,0 +1,50 @@
+{
+  "module_spec": {
+    "module_name": "Spec33",
+    "statistics": [
+      {
+        "item_name": "dummy_str",
+        "item_type": "string",
+        "item_optional": false,
+        "item_default": "Dummy",
+        "item_title": "Dummy String",
+        "item_description": "A dummy string"
+      },
+      {
+        "item_name": "dummy_int",
+        "item_type": "integer",
+        "item_optional": false,
+        "item_default": 0,
+        "item_title": "Dummy Integer",
+        "item_description": "A dummy integer"
+      },
+      {
+        "item_name": "dummy_datetime",
+        "item_type": "string",
+        "item_optional": false,
+        "item_default": "1970-01-01T00:00:00Z",
+        "item_title": "Dummy DateTime",
+        "item_description": "A dummy datetime",
+        "item_format": "date-time"
+      },
+      {
+        "item_name": "dummy_date",
+        "item_type": "string",
+        "item_optional": false,
+        "item_default": "1970-01-01",
+        "item_title": "Dummy Date",
+        "item_description": "A dummy date",
+        "item_format": "date"
+      },
+      {
+        "item_name": "dummy_time",
+        "item_type": "string",
+        "item_optional": false,
+        "item_default": "00:00:00",
+        "item_title": "Dummy Time",
+        "item_description": "A dummy time",
+        "item_format": "time"
+      }
+    ]
+  }
+}

+ 14 - 0
src/lib/config/tests/testdata/spec34.spec

@@ -0,0 +1,14 @@
+{
+  "module_spec": {
+    "module_name": "Spec34",
+    "statistics": [
+      {
+        "item_name": "dummy_str",
+        "item_type": "string",
+        "item_optional": false,
+        "item_default": "Dummy",
+        "item_description": "A dummy string"
+      }
+    ]
+  }
+}

+ 15 - 0
src/lib/config/tests/testdata/spec35.spec

@@ -0,0 +1,15 @@
+{
+  "module_spec": {
+    "module_name": "Spec35",
+    "statistics": [
+      {
+        "item_name": "dummy_str",
+        "item_type": "string",
+        "item_optional": false,
+        "item_default": "Dummy",
+        "item_title": "Dummy String"
+      }
+    ]
+  }
+}
+

+ 17 - 0
src/lib/config/tests/testdata/spec36.spec

@@ -0,0 +1,17 @@
+{
+  "module_spec": {
+    "module_name": "Spec36",
+    "statistics": [
+      {
+        "item_name": "dummy_str",
+        "item_type": "string",
+        "item_optional": false,
+        "item_default": "Dummy",
+        "item_title": "Dummy String",
+        "item_description": "A dummy string",
+        "item_format": "dummy"
+      }
+    ]
+  }
+}
+

+ 7 - 0
src/lib/config/tests/testdata/spec37.spec

@@ -0,0 +1,7 @@
+{
+  "module_spec": {
+    "module_name": "Spec37",
+    "statistics": 8
+  }
+}
+

+ 17 - 0
src/lib/config/tests/testdata/spec38.spec

@@ -0,0 +1,17 @@
+{
+  "module_spec": {
+    "module_name": "Spec38",
+    "statistics": [
+      {
+        "item_name": "dummy_datetime",
+        "item_type": "string",
+        "item_optional": false,
+        "item_default": "11",
+        "item_title": "Dummy DateTime",
+        "item_description": "A dummy datetime",
+        "item_format": "date-time"
+      }
+    ]
+  }
+}
+

+ 1 - 0
src/lib/python/isc/config/ccsession.py

@@ -91,6 +91,7 @@ COMMAND_CONFIG_UPDATE = "config_update"
 COMMAND_MODULE_SPECIFICATION_UPDATE = "module_specification_update"
 
 COMMAND_GET_COMMANDS_SPEC = "get_commands_spec"
+COMMAND_GET_STATISTICS_SPEC = "get_statistics_spec"
 COMMAND_GET_CONFIG = "get_config"
 COMMAND_SET_CONFIG = "set_config"
 COMMAND_GET_MODULE_SPEC = "get_module_spec"

+ 15 - 0
src/lib/python/isc/config/cfgmgr.py

@@ -267,6 +267,19 @@ class ConfigManager:
                 commands[module_name] = self.module_specs[module_name].get_commands_spec()
         return commands
 
+    def get_statistics_spec(self, name = None):
+        """Returns a dict containing 'module_name': statistics_spec for
+           all modules. If name is specified, only that module will
+           be included"""
+        statistics = {}
+        if name:
+            if name in self.module_specs:
+                statistics[name] = self.module_specs[name].get_statistics_spec()
+        else:
+            for module_name in self.module_specs.keys():
+                statistics[module_name] = self.module_specs[module_name].get_statistics_spec()
+        return statistics
+
     def read_config(self):
         """Read the current configuration from the file specificied at init()"""
         try:
@@ -457,6 +470,8 @@ class ConfigManager:
         if cmd:
             if cmd == ccsession.COMMAND_GET_COMMANDS_SPEC:
                 answer = ccsession.create_answer(0, self.get_commands_spec())
+            elif cmd == ccsession.COMMAND_GET_STATISTICS_SPEC:
+                answer = ccsession.create_answer(0, self.get_statistics_spec())
             elif cmd == ccsession.COMMAND_GET_MODULE_SPEC:
                 answer = self._handle_get_module_spec(arg)
             elif cmd == ccsession.COMMAND_GET_CONFIG:

+ 100 - 11
src/lib/python/isc/config/module_spec.py

@@ -23,6 +23,7 @@
 
 import json
 import sys
+import time
 
 import isc.cc.data
 
@@ -91,7 +92,7 @@ class ModuleSpec:
             return _validate_spec_list(data_def, full, data, errors)
         else:
             # no spec, always bad
-            if errors != None:
+            if errors is not None:
                 errors.append("No config_data specification")
             return False
 
@@ -117,6 +118,26 @@ class ModuleSpec:
 
         return False
 
+    def validate_statistics(self, full, stat, errors = None):
+        """Check whether the given piece of data conforms to this
+           data definition. If so, it returns True. If not, it will
+           return false. If errors is given, and is an array, a string
+           describing the error will be appended to it. The current
+           version stops as soon as there is one error so this list
+           will not be exhaustive. If 'full' is true, it also errors on
+           non-optional missing values. Set this to False if you want to
+           validate only a part of a statistics tree (like a list of
+           non-default values). Also it checks 'item_format' in case
+           of time"""
+        stat_spec = self.get_statistics_spec()
+        if stat_spec is not None:
+            return _validate_spec_list(stat_spec, full, stat, errors)
+        else:
+            # no spec, always bad
+            if errors is not None:
+                errors.append("No statistics specification")
+            return False
+
     def get_module_name(self):
         """Returns a string containing the name of the module as
            specified by the specification given at __init__()"""
@@ -152,6 +173,14 @@ class ModuleSpec:
         else:
             return None
     
+    def get_statistics_spec(self):
+        """Returns a dict representation of the statistics part of the
+           specification, or None if there is none."""
+        if 'statistics' in self._module_spec:
+            return self._module_spec['statistics']
+        else:
+            return None
+    
     def __str__(self):
         """Returns a string representation of the full specification"""
         return self._module_spec.__str__()
@@ -160,8 +189,9 @@ def _check(module_spec):
     """Checks the full specification. This is a dict that contains the
        element "module_spec", which is in itself a dict that
        must contain at least a "module_name" (string) and optionally
-       a "config_data" and a "commands" element, both of which are lists
-       of dicts. Raises a ModuleSpecError if there is a problem."""
+       a "config_data", a "commands" and a "statistics" element, all
+       of which are lists of dicts. Raises a ModuleSpecError if there
+       is a problem."""
     if type(module_spec) != dict:
         raise ModuleSpecError("data specification not a dict")
     if "module_name" not in module_spec:
@@ -173,6 +203,8 @@ def _check(module_spec):
         _check_config_spec(module_spec["config_data"])
     if "commands" in module_spec:
         _check_command_spec(module_spec["commands"])
+    if "statistics" in module_spec:
+        _check_statistics_spec(module_spec["statistics"])
 
 def _check_config_spec(config_data):
     # config data is a list of items represented by dicts that contain
@@ -263,34 +295,75 @@ def _check_item_spec(config_item):
             if type(map_item) != dict:
                 raise ModuleSpecError("map_item_spec element is not a dict")
             _check_item_spec(map_item)
+    if 'item_format' in config_item and 'item_default' in config_item:
+        item_format = config_item["item_format"]
+        item_default = config_item["item_default"]
+        if not _check_format(item_default, item_format):
+            raise ModuleSpecError(
+                "Wrong format for " + str(item_default) + " in " + str(item_name))
 
+def _check_statistics_spec(statistics):
+    # statistics is a list of items represented by dicts that contain
+    # things like "item_name", depending on the type they can have
+    # specific subitems
+    """Checks a list that contains the statistics part of the
+       specification. Raises a ModuleSpecError if there is a
+       problem."""
+    if type(statistics) != list:
+        raise ModuleSpecError("statistics is of type " + str(type(statistics))
+                              + ", not a list of items")
+    for stat_item in statistics:
+        _check_item_spec(stat_item)
+        # Additionally checks if there are 'item_title' and
+        # 'item_description'
+        for item in [ 'item_title',  'item_description' ]:
+            if item not in stat_item:
+                raise ModuleSpecError("no " + item + " in statistics item")
+
+def _check_format(value, format_name):
+    """Check if specified value and format are correct. Return True if
+       is is correct."""
+    # TODO: should be added other format types if necessary
+    time_formats = { 'date-time' : "%Y-%m-%dT%H:%M:%SZ",
+                     'date'      : "%Y-%m-%d",
+                     'time'      : "%H:%M:%S" }
+    for fmt in time_formats:
+        if format_name == fmt:
+            try:
+                # reverse check
+                return value == time.strftime(
+                    time_formats[fmt],
+                    time.strptime(value, time_formats[fmt]))
+            except (ValueError, TypeError):
+                break
+    return False
 
 def _validate_type(spec, value, errors):
     """Returns true if the value is of the correct type given the
        specification"""
     data_type = spec['item_type']
     if data_type == "integer" and type(value) != int:
-        if errors != None:
+        if errors is not None:
             errors.append(str(value) + " should be an integer")
         return False
     elif data_type == "real" and type(value) != float:
-        if errors != None:
+        if errors is not None:
             errors.append(str(value) + " should be a real")
         return False
     elif data_type == "boolean" and type(value) != bool:
-        if errors != None:
+        if errors is not None:
             errors.append(str(value) + " should be a boolean")
         return False
     elif data_type == "string" and type(value) != str:
-        if errors != None:
+        if errors is not None:
             errors.append(str(value) + " should be a string")
         return False
     elif data_type == "list" and type(value) != list:
-        if errors != None:
+        if errors is not None:
             errors.append(str(value) + " should be a list")
         return False
     elif data_type == "map" and type(value) != dict:
-        if errors != None:
+        if errors is not None:
             errors.append(str(value) + " should be a map")
         return False
     elif data_type == "named_set" and type(value) != dict:
@@ -300,6 +373,18 @@ def _validate_type(spec, value, errors):
     else:
         return True
 
+def _validate_format(spec, value, errors):
+    """Returns true if the value is of the correct format given the
+       specification. And also return true if no 'item_format'"""
+    if "item_format" in spec:
+        item_format = spec['item_format']
+        if not _check_format(value, item_format):
+            if errors is not None:
+                errors.append("format type of " + str(value)
+                              + " should be " + item_format)
+            return False
+    return True
+
 def _validate_item(spec, full, data, errors):
     if not _validate_type(spec, data, errors):
         return False
@@ -308,6 +393,8 @@ def _validate_item(spec, full, data, errors):
         for data_el in data:
             if not _validate_type(list_spec, data_el, errors):
                 return False
+            if not _validate_format(list_spec, data_el, errors):
+                return False
             if list_spec['item_type'] == "map":
                 if not _validate_item(list_spec, full, data_el, errors):
                     return False
@@ -322,6 +409,8 @@ def _validate_item(spec, full, data, errors):
                     return False
                 if not _validate_item(named_set_spec, full, data_el, errors):
                     return False
+    elif not _validate_format(spec, data, errors):
+        return False
     return True
 
 def _validate_spec(spec, full, data, errors):
@@ -333,7 +422,7 @@ def _validate_spec(spec, full, data, errors):
     elif item_name in data:
         return _validate_item(spec, full, data[item_name], errors)
     elif full and not item_optional:
-        if errors != None:
+        if errors is not None:
             errors.append("non-optional item " + item_name + " missing")
         return False
     else:
@@ -358,7 +447,7 @@ def _validate_spec_list(module_spec, full, data, errors):
                 if spec_item["item_name"] == item_name:
                     found = True
             if not found and item_name != "version":
-                if errors != None:
+                if errors is not None:
                     errors.append("unknown item " + item_name)
                 validated = False
     return validated

+ 22 - 0
src/lib/python/isc/config/tests/cfgmgr_test.py

@@ -219,6 +219,25 @@ class TestConfigManager(unittest.TestCase):
         commands_spec = self.cm.get_commands_spec('Spec2')
         self.assertEqual(commands_spec['Spec2'], module_spec.get_commands_spec())
 
+    def test_get_statistics_spec(self):
+        statistics_spec = self.cm.get_statistics_spec()
+        self.assertEqual(statistics_spec, {})
+        module_spec = isc.config.module_spec.module_spec_from_file(self.data_path + os.sep + "spec1.spec")
+        self.assert_(module_spec.get_module_name() not in self.cm.module_specs)
+        self.cm.set_module_spec(module_spec)
+        self.assert_(module_spec.get_module_name() in self.cm.module_specs)
+        statistics_spec = self.cm.get_statistics_spec()
+        self.assertEqual(statistics_spec, { 'Spec1': None })
+        self.cm.remove_module_spec('Spec1')
+        module_spec = isc.config.module_spec.module_spec_from_file(self.data_path + os.sep + "spec2.spec")
+        self.assert_(module_spec.get_module_name() not in self.cm.module_specs)
+        self.cm.set_module_spec(module_spec)
+        self.assert_(module_spec.get_module_name() in self.cm.module_specs)
+        statistics_spec = self.cm.get_statistics_spec()
+        self.assertEqual(statistics_spec['Spec2'], module_spec.get_statistics_spec())
+        statistics_spec = self.cm.get_statistics_spec('Spec2')
+        self.assertEqual(statistics_spec['Spec2'], module_spec.get_statistics_spec())
+
     def test_read_config(self):
         self.assertEqual(self.cm.config.data, {'version': config_data.BIND10_CONFIG_DATA_VERSION})
         self.cm.read_config()
@@ -241,6 +260,7 @@ class TestConfigManager(unittest.TestCase):
         self._handle_msg_helper("", { 'result': [ 1, 'Unknown message format: ']})
         self._handle_msg_helper({ "command": [ "badcommand" ] }, { 'result': [ 1, "Unknown command: badcommand"]})
         self._handle_msg_helper({ "command": [ "get_commands_spec" ] }, { 'result': [ 0, {} ]})
+        self._handle_msg_helper({ "command": [ "get_statistics_spec" ] }, { 'result': [ 0, {} ]})
         self._handle_msg_helper({ "command": [ "get_module_spec" ] }, { 'result': [ 0, {} ]})
         self._handle_msg_helper({ "command": [ "get_module_spec", { "module_name": "Spec2" } ] }, { 'result': [ 0, {} ]})
         #self._handle_msg_helper({ "command": [ "get_module_spec", { "module_name": "nosuchmodule" } ] },
@@ -329,6 +349,7 @@ class TestConfigManager(unittest.TestCase):
                                                { "module_name" : "Spec2" } ] },
                                 { 'result': [ 0, self.spec.get_full_spec() ] })
         self._handle_msg_helper({ "command": [ "get_commands_spec" ] }, { 'result': [ 0, { self.spec.get_module_name(): self.spec.get_commands_spec() } ]})
+        self._handle_msg_helper({ "command": [ "get_statistics_spec" ] }, { 'result': [ 0, { self.spec.get_module_name(): self.spec.get_statistics_spec() } ]})
         # re-add this once we have new way to propagate spec changes (1 instead of the current 2 messages)
         #self.assertEqual(len(self.fake_session.message_queue), 2)
         # the name here is actually wrong (and hardcoded), but needed in the current version
@@ -450,6 +471,7 @@ class TestConfigManager(unittest.TestCase):
 
     def test_run(self):
         self.fake_session.group_sendmsg({ "command": [ "get_commands_spec" ] }, "ConfigManager")
+        self.fake_session.group_sendmsg({ "command": [ "get_statistics_spec" ] }, "ConfigManager")
         self.fake_session.group_sendmsg({ "command": [ "shutdown" ] }, "ConfigManager")
         self.cm.run()
         pass

+ 109 - 0
src/lib/python/isc/config/tests/module_spec_test.py

@@ -81,6 +81,11 @@ class TestModuleSpec(unittest.TestCase):
         self.assertRaises(ModuleSpecError, self.read_spec_file, "spec20.spec")
         self.assertRaises(ModuleSpecError, self.read_spec_file, "spec21.spec")
         self.assertRaises(ModuleSpecError, self.read_spec_file, "spec26.spec")
+        self.assertRaises(ModuleSpecError, self.read_spec_file, "spec34.spec")
+        self.assertRaises(ModuleSpecError, self.read_spec_file, "spec35.spec")
+        self.assertRaises(ModuleSpecError, self.read_spec_file, "spec36.spec")
+        self.assertRaises(ModuleSpecError, self.read_spec_file, "spec37.spec")
+        self.assertRaises(ModuleSpecError, self.read_spec_file, "spec38.spec")
 
     def validate_data(self, specfile_name, datafile_name):
         dd = self.read_spec_file(specfile_name);
@@ -123,6 +128,17 @@ class TestModuleSpec(unittest.TestCase):
         self.assertEqual(False, self.validate_command_params("spec27.spec", "data22_8.data", 'cmd1'))
         self.assertEqual(False, self.validate_command_params("spec27.spec", "data22_8.data", 'cmd2'))
 
+    def test_statistics_validation(self):
+        def _validate_stat(specfile_name, datafile_name):
+            dd = self.read_spec_file(specfile_name);
+            data_file = open(self.spec_file(datafile_name))
+            data_str = data_file.read()
+            data = isc.cc.data.parse_value_str(data_str)
+            return dd.validate_statistics(True, data, [])
+        self.assertFalse(self.read_spec_file("spec1.spec").validate_statistics(True, None, None));
+        self.assertTrue(_validate_stat("spec33.spec", "data33_1.data"))
+        self.assertFalse(_validate_stat("spec33.spec", "data33_2.data"))
+
     def test_init(self):
         self.assertRaises(ModuleSpecError, ModuleSpec, 1)
         module_spec = isc.config.module_spec_from_file(self.spec_file("spec1.spec"), False)
@@ -269,6 +285,80 @@ class TestModuleSpec(unittest.TestCase):
                           }
                          )
 
+        self.assertRaises(ModuleSpecError, isc.config.module_spec._check_item_spec,
+                          { 'item_name': "a_datetime",
+                            'item_type': "string",
+                            'item_optional': False,
+                            'item_default': 1,
+                            'item_format': "date-time"
+                          }
+                         )
+
+        self.assertRaises(ModuleSpecError, isc.config.module_spec._check_item_spec,
+                          { 'item_name': "a_date",
+                            'item_type': "string",
+                            'item_optional': False,
+                            'item_default': 1,
+                            'item_format': "date"
+                          }
+                         )
+
+        self.assertRaises(ModuleSpecError, isc.config.module_spec._check_item_spec,
+                          { 'item_name': "a_time",
+                            'item_type': "string",
+                            'item_optional': False,
+                            'item_default': 1,
+                            'item_format': "time"
+                          }
+                         )
+
+        self.assertRaises(ModuleSpecError, isc.config.module_spec._check_item_spec,
+                          { 'item_name': "a_datetime",
+                            'item_type': "string",
+                            'item_optional': False,
+                            'item_default': "2011-05-27T19:42:57Z",
+                            'item_format': "dummy-format"
+                          }
+                         )
+
+        self.assertRaises(ModuleSpecError, isc.config.module_spec._check_item_spec,
+                          { 'item_name': "a_date",
+                            'item_type': "string",
+                            'item_optional': False,
+                            'item_default': "2011-05-27",
+                            'item_format': "dummy-format"
+                          }
+                         )
+
+        self.assertRaises(ModuleSpecError, isc.config.module_spec._check_item_spec,
+                          { 'item_name': "a_time",
+                            'item_type': "string",
+                            'item_optional': False,
+                            'item_default': "19:42:57Z",
+                            'item_format': "dummy-format"
+                          }
+                         )
+
+    def test_check_format(self):
+        self.assertTrue(isc.config.module_spec._check_format('2011-05-27T19:42:57Z', 'date-time'))
+        self.assertTrue(isc.config.module_spec._check_format('2011-05-27', 'date'))
+        self.assertTrue(isc.config.module_spec._check_format('19:42:57', 'time'))
+        self.assertFalse(isc.config.module_spec._check_format('2011-05-27T19:42:57Z', 'dummy'))
+        self.assertFalse(isc.config.module_spec._check_format('2011-05-27', 'dummy'))
+        self.assertFalse(isc.config.module_spec._check_format('19:42:57', 'dummy'))
+        self.assertFalse(isc.config.module_spec._check_format('2011-13-99T99:99:99Z', 'date-time'))
+        self.assertFalse(isc.config.module_spec._check_format('2011-13-99', 'date'))
+        self.assertFalse(isc.config.module_spec._check_format('99:99:99', 'time'))
+        self.assertFalse(isc.config.module_spec._check_format('', 'date-time'))
+        self.assertFalse(isc.config.module_spec._check_format(None, 'date-time'))
+        self.assertFalse(isc.config.module_spec._check_format(None, None))
+        # wrong date-time-type format not ending with "Z"
+        self.assertFalse(isc.config.module_spec._check_format('2011-05-27T19:42:57', 'date-time'))
+        # wrong date-type format ending with "T"
+        self.assertFalse(isc.config.module_spec._check_format('2011-05-27T', 'date'))
+        # wrong time-type format ending with "Z"
+        self.assertFalse(isc.config.module_spec._check_format('19:42:57Z', 'time'))
+
     def test_validate_type(self):
         errors = []
         self.assertEqual(True, isc.config.module_spec._validate_type({ 'item_type': 'integer' }, 1, errors))
@@ -306,6 +396,25 @@ class TestModuleSpec(unittest.TestCase):
         self.assertEqual(False, isc.config.module_spec._validate_type({ 'item_type': 'map' }, 1, errors))
         self.assertEqual(['1 should be a map'], errors)
 
+    def test_validate_format(self):
+        errors = []
+        self.assertEqual(True, isc.config.module_spec._validate_format({ 'item_format': 'date-time' }, "2011-05-27T19:42:57Z", errors))
+        self.assertEqual(False, isc.config.module_spec._validate_format({ 'item_format': 'date-time' }, "a", None))
+        self.assertEqual(False, isc.config.module_spec._validate_format({ 'item_format': 'date-time' }, "a", errors))
+        self.assertEqual(['format type of a should be date-time'], errors)
+
+        errors = []
+        self.assertEqual(True, isc.config.module_spec._validate_format({ 'item_format': 'date' }, "2011-05-27", errors))
+        self.assertEqual(False, isc.config.module_spec._validate_format({ 'item_format': 'date' }, "a", None))
+        self.assertEqual(False, isc.config.module_spec._validate_format({ 'item_format': 'date' }, "a", errors))
+        self.assertEqual(['format type of a should be date'], errors)
+
+        errors = []
+        self.assertEqual(True, isc.config.module_spec._validate_format({ 'item_format': 'time' }, "19:42:57", errors))
+        self.assertEqual(False, isc.config.module_spec._validate_format({ 'item_format': 'time' }, "a", None))
+        self.assertEqual(False, isc.config.module_spec._validate_format({ 'item_format': 'time' }, "a", errors))
+        self.assertEqual(['format type of a should be time'], errors)
+
     def test_validate_spec(self):
         spec = { 'item_name': "an_item",
                  'item_type': "string",