Commit 2a89ec50 authored by Tomek Mrugalski's avatar Tomek Mrugalski 🛰
Browse files

Merge branch 'master' of ssh://git.kea.isc.org/git/kea

# Conflicts:
#	ChangeLog
parents 482925df 3e6e2246
1255. [bug] tomek
1256. [bug] tomek
Control Agent now writes proper configuration when using
config-write command.
(Trac #5253, git a1b5da4db6ebfa9635bbe411ec363cdcc4fd1d28)
1255. [bug] marcin
Fixed failing unit tests in libkea-http.
(Trac #5260, git 43394f76efb1634155c04b205dec7361fc21f4f9)
1254. [func] tomek
Various improvements needed for upcoming host commands library:
host data source is now able to delete hosts, hosts can be exported
......
......@@ -25,7 +25,7 @@
<title>Shell Usage</title>
<para><command>kea-shell</command> is run as follows:
<screen>
kea-shell [--host hostname] [--port number] [--timeout seconds] [command]
kea-shell [--host hostname] [--port number] [--timeout seconds] [--service service-name] [command]
</screen>
where:
</para>
......@@ -51,6 +51,15 @@ kea-shell [--host hostname] [--port number] [--timeout seconds] [command]
</simpara>
</listitem>
<listitem>
<simpara>
<command>--service <replaceable>serive-name</replaceable></command> specifies the
target of a command. If not given, CA will be used as target. May be used more
than once to specify multiple targets.
</simpara>
</listitem>
<listitem>
<simpara>
<command>command</command> specifies the command to be sent. If not specified,
......@@ -89,18 +98,19 @@ kea-shell [--host hostname] [--port number] [--timeout seconds] [command]
<para>The following shows a simple example of usage:
<screen>
$ <userinput>kea-shell --host 192.0.2.1 --port 8001 list-commands</userinput>
$ <userinput>kea-shell --host 192.0.2.1 --port 8001 --service dhcp4 list-commands</userinput>
^D
</screen>
After the command line is entered, the program waits for command parameters to be entered.
Since <command>list-commands</command> does not take any
arguments, CTRL-D (represented in the above example by "^D") is pressed to indicate end
of file (and so terminate the parameter input). The Shell will then contact
the CA and print out the list of available commands returned.
the CA and print out the list of available commands returned for the service named <command>dhcp4</command>.
</para>
<para>It is envisaged that Kea Shell will be most frequently used in scripts. The next example
shows a simple scripted execution. It sends the command "config-write" to the CA, along
shows a simple scripted execution. It sends the command "config-write" to the CA
(<command> --service </command> parameter hasn't been used), along
with the parameters specified in param.json. The result will be stored in result.json.
<screen>
$ cat param.json
......
......@@ -63,6 +63,9 @@ def shell_body():
parser.add_argument('--timeout', type=int, default='10',
help='Timeout (in seconds) when attempting to '
'connect to CA (default: 10)')
parser.add_argument('--service', nargs="?", action="append",
help='target spcified service. If not specidied,'
'control agent will receive command.')
parser.add_argument('command', type=str, nargs="?",
default='list-commands',
help='command to be executed. If not specified, '
......@@ -78,6 +81,7 @@ def shell_body():
# used by the connection.
params = CARequest()
params.command = cmd_args.command
params.service = cmd_args.service
params.http_host = cmd_args.host
params.http_port = cmd_args.port
params.timeout = cmd_args.timeout
......
......@@ -40,7 +40,7 @@
<docinfo>
<copyright>
<year>2016</year>
<year>2017</year>
<holder>Internet Systems Consortium, Inc. ("ISC")</holder>
</copyright>
</docinfo>
......@@ -53,6 +53,7 @@
<arg><option>--host</option></arg>
<arg><option>--port</option></arg>
<arg><option>--timeout</option></arg>
<arg><option>--service</option></arg>
<arg><option>command</option></arg>
</cmdsynopsis>
</refsynopsisdiv>
......@@ -116,6 +117,15 @@
</para></listitem>
</varlistentry>
<varlistentry>
<term><option>--service</option></term>
<listitem><para>
Specifies the service that is the target of a command. If not
specified, Control Agent will be targeted. May be used more than
once to specify multiple targets.
</para></listitem>
</varlistentry>
<varlistentry>
<term><option>command</option></term>
<listitem><para>
......
......@@ -16,6 +16,7 @@ class CARequest:
- http_host - hostname of the CA
- http-port - TCP port of the CA
- command - specifies the command to send (e.g. list-commands)
- service - specifies service that is target for the command (e.g. dhcp4)
- timeout - timeout (in ms)
- args - extra arguments my be added here
- headers - extra HTTP headers may be added here
......@@ -25,6 +26,7 @@ class CARequest:
http_host = ''
http_port = 0
command = ''
service = ''
timeout = 0
args = ''
headers = {}
......@@ -39,6 +41,10 @@ class CARequest:
this stores the output in self.content
"""
self.content = '{ "command": "' + self.command + '"'
if self.service is not None:
self.service = [x for x in self.service if x]
if len(self.service) > 0:
self.content += ', "service": ["' + '","'.join(self.service) + '"]'
if len(self.args) > 1:
self.content += ', "arguments": { ' + self.args + ' }'
self.content += ' }'
......
......@@ -26,6 +26,39 @@ class CARequestUnitTest(unittest.TestCase):
"""
pass
def test_body_with_service(self):
"""
This test verifies if the CARequest object generates the request
content properly when there is one target service.
"""
request = CARequest()
request.command = "foo"
request.service= ["service1"]
request.generate_body()
self.assertEqual(request.content, '{ "command": "foo", "service": ["service1"] }')
def test_body_with_multiple_service(self):
"""
This test verifies if the CARequest object generates the request
content properly when there are two target service.
"""
request = CARequest()
request.command = "foo"
request.service= ["service1","service2/2"]
request.generate_body()
self.assertEqual(request.content, '{ "command": "foo", "service": ["service1","service2/2"] }')
def test_body_with_malformed_service(self):
"""
This test verifies if the CARequest object generates the request
content properly when there are two target service, one is empty
"""
request = CARequest()
request.command = "foo"
request.service= ["service1",""]
request.generate_body()
self.assertEqual(request.content, '{ "command": "foo", "service": ["service1"] }')
def test_body_without_args(self):
"""
This test verifies if the CARequest object generates the request
......
......@@ -34,10 +34,6 @@ HttpConnection:: HttpConnection(asiolink::IOService& io_service,
: request_timer_(io_service),
request_timeout_(request_timeout),
socket_(io_service),
socket_callback_(boost::bind(&HttpConnection::socketReadCallback, this,
_1, _2)),
socket_write_callback_(boost::bind(&HttpConnection::socketWriteCallback,
this, _1, _2)),
acceptor_(acceptor),
connection_pool_(connection_pool),
response_creator_(response_creator),
......@@ -54,6 +50,7 @@ HttpConnection::~HttpConnection() {
void
HttpConnection::close() {
request_timer_.cancel();
socket_.close();
}
......@@ -71,8 +68,12 @@ HttpConnection::stopThisConnection() {
void
HttpConnection::asyncAccept() {
// Create instance of the callback. It is safe to pass the local instance
// of the callback, because the underlying boost functions make copies
// as needed.
HttpAcceptorCallback cb = boost::bind(&HttpConnection::acceptorCallback,
this, _1);
shared_from_this(),
boost::asio::placeholders::error);
try {
acceptor_.asyncAccept(socket_, cb);
......@@ -86,8 +87,15 @@ void
HttpConnection::doRead() {
try {
TCPEndpoint endpoint;
// Create instance of the callback. It is safe to pass the local instance
// of the callback, because the underlying boost functions make copies
// as needed.
SocketCallback cb(boost::bind(&HttpConnection::socketReadCallback,
shared_from_this(),
boost::asio::placeholders::error,
boost::asio::placeholders::bytes_transferred));
socket_.asyncReceive(static_cast<void*>(buf_.data()), buf_.size(),
0, &endpoint, socket_callback_);
0, &endpoint, cb);
} catch (const std::exception& ex) {
stopThisConnection();
......@@ -98,9 +106,16 @@ void
HttpConnection::doWrite() {
try {
if (!output_buf_.empty()) {
// Create instance of the callback. It is safe to pass the local instance
// of the callback, because the underlying boost functions make copies
// as needed.
SocketCallback cb(boost::bind(&HttpConnection::socketWriteCallback,
shared_from_this(),
boost::asio::placeholders::error,
boost::asio::placeholders::bytes_transferred));
socket_.asyncSend(output_buf_.data(),
output_buf_.length(),
socket_write_callback_);
cb);
} else {
stopThisConnection();
}
......@@ -133,7 +148,8 @@ HttpConnection::acceptorCallback(const boost::system::error_code& ec) {
HTTP_REQUEST_RECEIVE_START)
.arg(getRemoteEndpointAddressAsText())
.arg(static_cast<unsigned>(request_timeout_/1000));
request_timer_.setup(boost::bind(&HttpConnection::requestTimeoutCallback, this),
request_timer_.setup(boost::bind(&HttpConnection::requestTimeoutCallback,
shared_from_this()),
request_timeout_, IntervalTimer::ONE_SHOT);
doRead();
}
......
......@@ -187,12 +187,6 @@ private:
/// @brief Socket used by this connection.
asiolink::TCPSocket<SocketCallback> socket_;
/// @brief Callback invoked when data received over the socket.
SocketCallback socket_callback_;
/// @brief Callback invoked when data sent over the socket.
SocketCallback socket_write_callback_;
/// @brief Reference to the TCP acceptor used to accept new connections.
HttpAcceptor& acceptor_;
......
......@@ -168,10 +168,21 @@ public:
[this, request](const boost::system::error_code& ec,
std::size_t bytes_transferred) mutable {
if (ec) {
ADD_FAILURE() << "error occurred while connecting: "
<< ec.message();
io_service_.stop();
return;
if (ec.value() == boost::asio::error::operation_aborted) {
return;
} else if ((ec.value() == boost::asio::error::try_again) ||
(ec.value() == boost::asio::error::would_block)) {
// If we should try again make sure there is no garbage in the
// bytes_transferred.
bytes_transferred = 0;
} else {
ADD_FAILURE() << "error occurred while connecting: "
<< ec.message();
io_service_.stop();
return;
}
}
// Remove the part of the request which has been sent.
......@@ -199,14 +210,21 @@ public:
std::size_t bytes_transferred) {
if (ec) {
// IO service stopped so simply return.
if (ec == boost::asio::error::operation_aborted) {
if (ec.value() == boost::asio::error::operation_aborted) {
return;
}
// Error occurred, bail...
ADD_FAILURE() << "error occurred while receiving HTTP"
" response from the server: " << ec.message();
io_service_.stop();
} else if ((ec.value() == boost::asio::error::try_again) ||
(ec.value() == boost::asio::error::would_block)) {
// If we should try again, make sure that there is no garbage
// in the bytes_transferred.
bytes_transferred = 0;
} else {
// Error occurred, bail...
ADD_FAILURE() << "error occurred while receiving HTTP"
" response from the server: " << ec.message();
io_service_.stop();
}
}
if (bytes_transferred > 0) {
......
Markdown is supported
0% or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment