Commit a970f6c5 authored by Thomas Markwalder's avatar Thomas Markwalder

[master] Merge branch 'trac3052'

Adds initial implementation of D2QueueMgr class to DHCP_DDNS.
This class is manages the queue of inbound DHCP_DDNS requests.
parents 0efb9524 508b40fd
......@@ -54,6 +54,7 @@ b10_dhcp_ddns_SOURCES += d2_controller.cc d2_controller.h
b10_dhcp_ddns_SOURCES += d_cfg_mgr.cc d_cfg_mgr.h
b10_dhcp_ddns_SOURCES += d2_config.cc d2_config.h
b10_dhcp_ddns_SOURCES += d2_cfg_mgr.cc d2_cfg_mgr.h
b10_dhcp_ddns_SOURCES += d2_queue_mgr.cc d2_queue_mgr.h
b10_dhcp_ddns_SOURCES += d2_update_message.cc d2_update_message.h
b10_dhcp_ddns_SOURCES += d2_zone.cc d2_zone.h
b10_dhcp_ddns_SOURCES += dns_client.cc dns_client.h
......
......@@ -138,6 +138,19 @@ request for the FQDN cannot be processed.
This is a debug message issued when the Dhcp-Ddns application enters
its init method.
% DHCP_DDNS_QUEUE_MGR_QUEUE_FULL application request queue has reached maximum number of entries: %1
This an error message indicating that DHCP-DDNS is receiving DNS update
requests faster than they can be processed. This may mean the maximum queue
needs to be increased, the DHCP-DDNS clients are simply generating too many
requests too quickly, or perhaps upstream DNS servers are experiencing
load issues.
% DHCP_DDNS_QUEUE_MGR_RECV_ERROR application's queue manager was notified of a request receive error by its listener.
This is an error message indicating that the NameChangeRequest listener used by
DHCP-DDNS to receive requests encountered a IO error. There should be
corresponding log messages from the listener layer with more details. This may
indicate a network connectivity or system resource issue.
% DHCP_DDNS_RUN_ENTER application has entered the event loop
This is a debug message issued when the Dhcp-Ddns application enters
its run method.
......
// Copyright (C) 2013 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
// copyright notice and this permission notice appear in all copies.
//
// THE SOFTWARE IS PROVIDED "AS IS" AND ISC DISCLAIMS ALL WARRANTIES WITH
// REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
// AND FITNESS. IN NO EVENT SHALL ISC BE LIABLE FOR ANY SPECIAL, DIRECT,
// INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
// LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE
// OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
// PERFORMANCE OF THIS SOFTWARE.
#include <d2/d2_log.h>
#include <d2/d2_queue_mgr.h>
#include <dhcp_ddns/ncr_udp.h>
namespace isc {
namespace d2 {
// Makes constant visible to Google test macros.
const size_t D2QueueMgr::MAX_QUEUE_DEFAULT;
D2QueueMgr::D2QueueMgr(isc::asiolink::IOService& io_service,
const size_t max_queue_size)
: io_service_(io_service), max_queue_size_(max_queue_size),
mgr_state_(NOT_INITTED) {
// Use setter to do validation.
setMaxQueueSize(max_queue_size);
}
D2QueueMgr::~D2QueueMgr() {
// clean up
try {
stopListening();
} catch (...) {
// This catch is strictly for safety's sake, in case a future
// implementation isn't tidy or careful.
}
}
void
D2QueueMgr::operator()(const dhcp_ddns::NameChangeListener::Result result,
dhcp_ddns::NameChangeRequestPtr& ncr) {
// Note that error conditions must be handled here without throwing
// exceptions. Remember this is the application level "link" in the
// callback chain. Throwing an exception here will "break" the
// io_service "run" we are operating under. With that in mind,
// if we hit a problem, we will stop the listener transition to
// the appropriate stopped state. Upper layer(s) must monitor our
// state as well as our queue size.
// If the receive was successful, attempt to queue the request.
if (result == dhcp_ddns::NameChangeListener::SUCCESS) {
if (getQueueSize() < getMaxQueueSize()) {
// There's room on the queue, add to the end
enqueue(ncr);
return;
}
// Queue is full, stop the listener.
stopListening(STOPPED_QUEUE_FULL);
LOG_ERROR(dctl_logger, DHCP_DDNS_QUEUE_MGR_QUEUE_FULL)
.arg(max_queue_size_);
} else {
// Receive failed, stop the listener.
stopListening(STOPPED_RECV_ERROR);
LOG_ERROR(dctl_logger, DHCP_DDNS_QUEUE_MGR_RECV_ERROR);
}
}
void
D2QueueMgr::initUDPListener(const isc::asiolink::IOAddress& ip_address,
const uint32_t port,
const dhcp_ddns::NameChangeFormat format,
const bool reuse_address) {
if (listener_) {
isc_throw(D2QueueMgrError,
"D2QueueMgr listener is already initialized");
}
// Instantiate a UDP listener and set state to INITTED.
// Note UDP listener constructor does not throw.
listener_.reset(new dhcp_ddns::
NameChangeUDPListener(ip_address, port, format, *this,
reuse_address));
mgr_state_ = INITTED;
}
void
D2QueueMgr::startListening() {
// We can't listen if we haven't initialized the listener yet.
if (!listener_) {
isc_throw(D2QueueMgrError, "D2QueueMgr "
"listener is not initialized, cannot start listening");
}
// If we are already listening, we do not want to "reopen" the listener
// and really we shouldn't be trying.
if (mgr_state_ == RUNNING) {
isc_throw(D2QueueMgrError, "D2QueueMgr "
"cannot call startListening from the RUNNING state");
}
// Instruct the listener to start listening and set state accordingly.
try {
listener_->startListening(io_service_);
mgr_state_ = RUNNING;
} catch (const isc::Exception& ex) {
isc_throw(D2QueueMgrError, "D2QueueMgr listener start failed: "
<< ex.what());
}
}
void
D2QueueMgr::stopListening(const State stop_state) {
// Note, stopListening is guaranteed not to throw.
if (listener_) {
listener_->stopListening();
}
// Enforce only valid "stop" states.
if (stop_state != STOPPED && stop_state != STOPPED_QUEUE_FULL &&
stop_state != STOPPED_RECV_ERROR) {
// This is purely a programmatic error and should never happen.
isc_throw(D2QueueMgrError, "D2QueueMgr invalid value for stop state: "
<< stop_state);
}
mgr_state_ = stop_state;
}
void
D2QueueMgr::removeListener() {
// Force our managing layer(s) to stop us properly first.
if (mgr_state_ == RUNNING) {
isc_throw(D2QueueMgrError,
"D2QueueMgr cannot delete listener while state is RUNNING");
}
listener_.reset();
mgr_state_ = NOT_INITTED;
}
const dhcp_ddns::NameChangeRequestPtr&
D2QueueMgr::peek() const {
if (getQueueSize() == 0) {
isc_throw(D2QueueMgrQueueEmpty,
"D2QueueMgr peek attempted on an empty queue");
}
return (ncr_queue_.front());
}
const dhcp_ddns::NameChangeRequestPtr&
D2QueueMgr::peekAt(const size_t index) const {
if (index >= getQueueSize()) {
isc_throw(D2QueueMgrInvalidIndex,
"D2QueueMgr peek beyond end of queue attempted"
<< " index: " << index << " queue size: " << getQueueSize());
}
return (ncr_queue_.at(index));
}
void
D2QueueMgr::dequeueAt(const size_t index) {
if (index >= getQueueSize()) {
isc_throw(D2QueueMgrInvalidIndex,
"D2QueueMgr dequeue beyond end of queue attempted"
<< " index: " << index << " queue size: " << getQueueSize());
}
RequestQueue::iterator pos = ncr_queue_.begin() + index;
ncr_queue_.erase(pos);
}
void
D2QueueMgr::dequeue() {
if (getQueueSize() == 0) {
isc_throw(D2QueueMgrQueueEmpty,
"D2QueueMgr dequeue attempted on an empty queue");
}
ncr_queue_.pop_front();
}
void
D2QueueMgr::enqueue(dhcp_ddns::NameChangeRequestPtr& ncr) {
ncr_queue_.push_back(ncr);
}
void
D2QueueMgr::clearQueue() {
ncr_queue_.clear();
}
void
D2QueueMgr::setMaxQueueSize(const size_t new_queue_max) {
if (new_queue_max < 1) {
isc_throw(D2QueueMgrError,
"D2QueueMgr maximum queue size must be greater than zero");
}
if (new_queue_max < getQueueSize()) {
isc_throw(D2QueueMgrError, "D2QueueMgr maximum queue size value cannot"
" be less than the current queue size :" << getQueueSize());
}
max_queue_size_ = new_queue_max;
}
} // namespace isc::d2
} // namespace isc
// Copyright (C) 2013 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
// copyright notice and this permission notice appear in all copies.
//
// THE SOFTWARE IS PROVIDED "AS IS" AND ISC DISCLAIMS ALL WARRANTIES WITH
// REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
// AND FITNESS. IN NO EVENT SHALL ISC BE LIABLE FOR ANY SPECIAL, DIRECT,
// INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
// LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE
// OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
// PERFORMANCE OF THIS SOFTWARE.
#ifndef D2_QUEUE_MGR_H
#define D2_QUEUE_MGR_H
/// @file d2_queue_mgr.h This file defines the class D2QueueMgr.
#include <asiolink/io_address.h>
#include <asiolink/io_service.h>
#include <exceptions/exceptions.h>
#include <dhcp_ddns/ncr_msg.h>
#include <dhcp_ddns/ncr_io.h>
#include <boost/noncopyable.hpp>
#include <deque>
namespace isc {
namespace d2 {
/// @brief Defines a queue of requests.
/// @todo This may be replaced with an actual class in the future.
typedef std::deque<dhcp_ddns::NameChangeRequestPtr> RequestQueue;
/// @brief Thrown if the queue manager encounters a general error.
class D2QueueMgrError : public isc::Exception {
public:
D2QueueMgrError(const char* file, size_t line, const char* what) :
isc::Exception(file, line, what) { };
};
/// @brief Thrown if the queue manager's receive handler is passed
/// a failure result.
class D2QueueMgrReceiveError : public isc::Exception {
public:
D2QueueMgrReceiveError(const char* file, size_t line, const char* what) :
isc::Exception(file, line, what) { };
};
/// @brief Thrown if the request queue is full when an enqueue is attempted.
class D2QueueMgrQueueFull : public isc::Exception {
public:
D2QueueMgrQueueFull(const char* file, size_t line, const char* what) :
isc::Exception(file, line, what) { };
};
/// @brief Thrown if the request queue empty and a read is attempted.
class D2QueueMgrQueueEmpty : public isc::Exception {
public:
D2QueueMgrQueueEmpty(const char* file, size_t line, const char* what) :
isc::Exception(file, line, what) { };
};
/// @brief Thrown if a queue index is beyond the end of the queue
class D2QueueMgrInvalidIndex : public isc::Exception {
public:
D2QueueMgrInvalidIndex(const char* file, size_t line, const char* what) :
isc::Exception(file, line, what) { };
};
/// @brief D2QueueMgr creates and manages a queue of DNS update requests.
///
/// D2QueueMgr is class specifically designed as an integral part of DHCP-DDNS.
/// Its primary responsibility is to listen for NameChangeRequests from
/// DHCP-DDNS clients (e.g. DHCP servers) and queue them for processing. In
/// addition it may provide a number services to locate entries in the queue
/// such as by FQDN or DHCID. These services may eventually be used
/// for processing optimization. The initial implementation will support
/// simple FIFO access.
///
/// D2QueueMgr uses a NameChangeListener to asynchronously receive requests.
/// It derives from NameChangeListener::RequestReceiveHandler and supplies an
/// implementation of the operator()(Result, NameChangeRequestPtr). It is
/// through this operator() that D2QueueMgr is passed inbound NCRs. D2QueueMgr
/// will add each newly received request onto the back of the request queue
///
/// D2QueueMgr defines a simple state model constructed around the status of
/// its NameChangeListener, consisting of the following states:
///
/// * NOT_INITTED - D2QueueMgr has been constructed, but its listener has
/// not been initialized.
///
/// * INITTED - The listener has been initialized, but it is not open for
/// listening. To move from NOT_INITTED to INITTED, one of the D2QueueMgr
/// listener initialization methods must be invoked. Currently there is
/// only one type of listener, NameChangeUDPListener, hence there is only
/// one listener initialization method, initUDPListener. As more listener
/// types are created, listener initialization methods will need to be
/// added.
///
/// * RUNNING - The listener is open and listening for requests.
/// Once initialized, in order to begin listening for requests, the
/// startListener() method must be invoked. Upon successful completion of
/// of this call, D2QueueMgr will begin receiving requests as they arrive
/// without any further steps. This method may be called from the INITTED
/// or one of the STOPPED states.
///
/// * STOPPED - The listener has been listening but has been stopped
/// without error. To return to listening, startListener() must be invoked.
///
/// * STOPPED_QUEUE_FULL - Request queue is full, the listener has been
/// stopped. D2QueueMgr will enter this state when the request queue
/// reaches the maximum queue size. Once this limit is reached, the
/// listener will be closed and no further requests will be received.
/// To return to listening, startListener() must be invoked. Note that so
/// long as the queue is full, any attempt to queue a request will fail.
///
/// * STOPPED_RECV_ERROR - The listener has experienced a receive error
/// and has been stopped. D2QueueMgr will enter this state when it is
/// passed a failed status into the request completion handler. To return
/// to listening, startListener() must be invoked.
///
/// D2QueueMgr does not attempt to recover from stopped conditions, this is left
/// to upper layers.
///
/// It is important to note that the queue contents are preserved between
/// state transitions. In other words entries in the queue remain there
/// until they are removed explicitly via the deque() or implicitly by
/// via the clearQueue() method.
///
class D2QueueMgr : public dhcp_ddns::NameChangeListener::RequestReceiveHandler,
boost::noncopyable {
public:
/// @brief Maximum number of entries allowed in the request queue.
/// NOTE that 1024 is an arbitrary choice picked for the initial
/// implementation.
static const size_t MAX_QUEUE_DEFAULT = 1024;
/// @brief Defines the list of possible states for D2QueueMgr.
enum State {
NOT_INITTED,
INITTED,
RUNNING,
STOPPED_QUEUE_FULL,
STOPPED_RECV_ERROR,
STOPPED,
};
/// @brief Constructor
///
/// Creates a D2QueueMgr instance. Note that the listener is not created
/// in the constructor. The initial state will be NOT_INITTED.
///
/// @param io_service IOService instance to be passed into the listener for
/// IO management.
/// @param max_queue_size the maximum number of entries allowed in the
/// queue.
/// This value must be greater than zero. It defaults to MAX_QUEUE_DEFAULT.
///
/// @throw D2QueueMgrError if max_queue_size is zero.
D2QueueMgr(isc::asiolink::IOService& io_service,
const size_t max_queue_size = MAX_QUEUE_DEFAULT);
/// @brief Destructor
virtual ~D2QueueMgr();
/// @brief Initializes the listener as a UDP listener.
///
/// Instantiates the listener_ member as NameChangeUDPListener passing
/// the given parameters. Upon successful completion, the D2QueueMgr state
/// will be INITTED.
///
/// @param ip_address is the network address on which to listen
/// @param port is the IP port on which to listen
/// @param format is the wire format of the inbound requests.
/// @param reuse_address enables IP address sharing when true
/// It defaults to false.
void initUDPListener(const isc::asiolink::IOAddress& ip_address,
const uint32_t port,
const dhcp_ddns::NameChangeFormat format,
const bool reuse_address = false);
/// @brief Starts actively listening for requests.
///
/// Invokes the listener's startListening method passing in our
/// IOService instance.
///
/// @throw D2QueueMgrError if the listener has not been initialized,
/// state is already RUNNING, or the listener fails to actually start.
void startListening();
/// @brief Function operator implementing the NCR receive callback.
///
/// This method is invoked by the listener as part of its receive
/// completion callback and is how the inbound NameChangeRequests are
/// passed up to the D2QueueMgr for queueing.
/// If the given result indicates a successful receive completion and
/// there is room left in the queue, the given request is queued.
///
/// If the queue is at maximum capacity, stopListening() is invoked and
/// the state is set to STOPPED_QUEUE_FULL.
///
/// If the result indicates a failed receive, stopListening() is invoked
/// and the state is set to STOPPED_RECV_ERROR.
///
/// This method specifically avoids throwing on an error as any such throw
/// would surface at the io_service::run (or run variant) method invocation
/// site. The upper layers are expected to monitor D2QueueMgr's state and
/// act accordingly.
///
/// @param result contains that receive outcome status.
/// @param ncr is a pointer to the newly received NameChangeRequest if
/// result is NameChangeListener::SUCCESS. It is indeterminate other
/// wise.
virtual void operator ()(const dhcp_ddns::NameChangeListener::Result result,
dhcp_ddns::NameChangeRequestPtr& ncr);
/// @brief Stops listening for requests.
///
/// Invokes the listener's stopListening method which should cause it to
/// cancel any pending IO and close its IO source. It the sets the state
/// to the given value.
///
/// @param stop_state is one of the three stopped state values.
///
/// @throw D2QueueMgrError if stop_state is a valid stop state.
void stopListening(const State stop_state = STOPPED);
/// @brief Deletes the current listener
///
/// This method will delete the current listener and returns the manager
/// to the NOT_INITTED state. This is provided to support reconfiguring
/// a new listener without losing queued requests.
///
/// @throw D2QueMgrError if called when the manager state is RUNNING.
void removeListener();
/// @brief Returns the number of entries in the queue.
size_t getQueueSize() const {
return (ncr_queue_.size());
};
/// @brief Returns the maximum number of entries allowed in the queue.
size_t getMaxQueueSize() const {
return (max_queue_size_);
}
/// @brief Sets the maximum number of entries allowed in the queue.
///
/// @param max_queue_size is the new maximum size of the queue.
///
/// @throw D2QueueMgrError if the new value is less than one or if
/// the new value is less than the number of entries currently in the
/// queue.
void setMaxQueueSize(const size_t max_queue_size);
/// @brief Returns the current state.
State getMgrState() const {
return (mgr_state_);
}
/// @brief Returns the entry at the front of the queue.
///
/// The entry returned is next in line to be processed, assuming a FIFO
/// approach to task selection. Note, the entry is not removed from the
/// queue.
///
/// @return Pointer reference to the queue entry.
///
/// @throw D2QueueMgrQueEmpty if there are no entries in the queue.
const dhcp_ddns::NameChangeRequestPtr& peek() const;
/// @brief Returns the entry at a given position in the queue.
///
/// Note that the entry is not removed from the queue.
/// @param index the index of the entry in the queue to fetch.
/// Valid values are 0 (front of the queue) to (queue size - 1).
///
/// @return Pointer reference to the queue entry.
///
/// @throw D2QueueMgrInvalidIndex if the given index is beyond the
/// end of the queue.
const dhcp_ddns::NameChangeRequestPtr& peekAt(const size_t index) const;
/// @brief Removes the entry at a given position in the queue.
///
/// @param index the index of the entry in the queue to remove.
/// Valid values are 0 (front of the queue) to (queue size - 1).
///
/// @throw D2QueueMgrInvalidIndex if the given index is beyond the
/// end of the queue.
void dequeueAt(const size_t index);
/// @brief Removes the entry at the front of the queue.
///
/// @throw D2QueueMgrQueEmpty if there are no entries in the queue.
void dequeue();
/// @brief Adds a request to the end of the queue.
///
/// @param ncr pointer to the NameChangeRequest to add to the queue.
void enqueue(dhcp_ddns::NameChangeRequestPtr& ncr);
/// @brief Removes all entries from the queue.
void clearQueue();
private:
/// @brief IOService that our listener should use for IO management.
isc::asiolink::IOService& io_service_;
/// @brief Dictates the maximum number of entries allowed in the queue.
size_t max_queue_size_;
/// @brief Queue of received NameChangeRequests.
RequestQueue ncr_queue_;
/// @brief Listener instance from which requests are received.
boost::shared_ptr<dhcp_ddns::NameChangeListener> listener_;
/// @brief Current state of the manager.
State mgr_state_;
};
/// @brief Defines a pointer for manager instances.
typedef boost::shared_ptr<D2QueueMgr> D2QueueMgrPtr;
} // namespace isc::d2
} // namespace isc
#endif
......@@ -59,6 +59,7 @@ d2_unittests_SOURCES += ../d2_controller.cc ../d2_controller.h
d2_unittests_SOURCES += ../d_cfg_mgr.cc ../d_cfg_mgr.h
d2_unittests_SOURCES += ../d2_config.cc ../d2_config.h
d2_unittests_SOURCES += ../d2_cfg_mgr.cc ../d2_cfg_mgr.h
d2_unittests_SOURCES += ../d2_queue_mgr.cc ../d2_queue_mgr.h
d2_unittests_SOURCES += ../d2_update_message.cc ../d2_update_message.h
d2_unittests_SOURCES += ../d2_zone.cc ../d2_zone.h
d2_unittests_SOURCES += ../dns_client.cc ../dns_client.h
......@@ -69,6 +70,7 @@ d2_unittests_SOURCES += d_controller_unittests.cc
d2_unittests_SOURCES += d2_controller_unittests.cc
d2_unittests_SOURCES += d_cfg_mgr_unittests.cc
d2_unittests_SOURCES += d2_cfg_mgr_unittests.cc
d2_unittests_SOURCES += d2_queue_mgr_unittests.cc
d2_unittests_SOURCES += d2_update_message_unittests.cc
d2_unittests_SOURCES += d2_zone_unittests.cc
d2_unittests_SOURCES += dns_client_unittests.cc
......
// Copyright (C) 2013 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
// copyright notice and this permission notice appear in all copies.
//
// THE SOFTWARE IS PROVIDED "AS IS" AND ISC DISCLAIMS ALL WARRANTIES WITH
// REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
// AND FITNESS. IN NO EVENT SHALL ISC BE LIABLE FOR ANY SPECIAL, DIRECT,
// INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
// LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE
// OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
// PERFORMANCE OF THIS SOFTWARE.
#include <asiolink/interval_timer.h>
#include <d2/d2_queue_mgr.h>
#include <dhcp_ddns/ncr_udp.h>
#include <util/time_utilities.h>
#include <boost/function.hpp>
#include <boost/bind.hpp>
#include <gtest/gtest.h>
#include <gtest/gtest.h>
#include <algorithm>
#include <vector>
using namespace std;
using namespace isc;
using namespace isc::dhcp_ddns;
using namespace isc::d2;
namespace {
/// @brief Defines a list of valid JSON NameChangeRequest test messages.
const char *valid_msgs[] =
{
// Valid Add.
"{"
" \"change_type\" : 0 , "
" \"forward_change\" : true , "
" \"reverse_change\" : false , "
" \"fqdn\" : \"walah.walah.com\" , "
" \"ip_address\" : \"192.168.2.1\" , "
" \"dhcid\" : \"010203040A7F8E3D\" , "
" \"lease_expires_on\" : \"20130121132405\" , "
" \"lease_length\" : 1300 "
"}",
// Valid Remove.
"{"
" \"change_type\" : 1 , "
" \"forward_change\" : true , "
" \"reverse_change\" : false , "
" \"fqdn\" : \"walah.walah.com\" , "
" \"ip_address\" : \"192.168.2.1\" , "
" \"dhcid\" : \"010203040A7F8E3D\" , "
" \"lease_expires_on\" : \"20130121132405\" , "
" \"lease_length\" : 1300 "
"}",
// Valid Add with IPv6 address