Traffic Control Development Guide¶
Traffic Control interacts with Traffic Server through the JSONRPC 2.0 endpoint. All interaction is done by following the JSONRPC 2.0 protocol.
Overall structure¶
The whole point is to separate the command handling from the printing part.
Printing should be done by an appropriate Printer implementation, this should support several kinds of printing formats.
For now, everything is printing in the standard output, but a particular printer can be implemented in such way that the output could be sent to a different destination.
JSONRPC requests have a base class that hides some of the basic and common parts, like
id, andversion. When deriving from this class, the only thing that needs to be override is themethod
重要
CtrlCommand will invoke _invoked_func when executed, this should be set by the derived class
The whole design is that the command will execute the
_invoked_funconce invoked. This function ptr should be set by the appropriated derived class based on the passed parameters. The derived class have the option to override the execute() which is avirtualfunction and does something else. CheckRecordCommandas an example.
Command implementation¶
Add the right command to the
ArgParserobject inside thetraffic_ctl.cc.If needed, define a new
Commandderived class inside theCtrlCommandsfile. if it's not an new command level, and it's a subcommand, then you should check the existing command to decide where to place it.Implement the member function that will be dealing with the particular command, ie: (config_status())
If a new JsonRPC Message needs to be done, then implement it by deriving from
shared::rpc::ClientRequestif a method is needed, or fromshared::rpc::ClientRequestNotificationif it's a notification. More info can be found here Requests and Design. This can be done inside theRPCRequest.hfile.
注釈
Make sure you override the
std::string get_method() constmember function with the appropriate api method name.If needed define a new
Printerderived class inside theCtrlPrinterfile.If pretty printing format will be supported, then make sure you read the
_formatmember you get from theBasePrinterclass.
If it's a new command level (like config, metric, etc), make sure you update the
Commandcreation inside thetraffic_ctl.ccfile.
Implementation Example¶
Let's define a new command for a new specific API with name == admin_new_command_1 with the following json structure:
$ traffic_ctl new-command new-subcommand1
Update
traffic_ctl.cc. I will ignore the details as they are trivial.Define a new Request.
So based on the above json, we can model our request as:
// RPCRequests.h struct NewCommandJsonRPCRequest : shared::rpc::ClientRequest { using super = shared::rpc::ClientRequest; struct Params { std::string named_var_1; }; NewCommandJsonRPCRequest(Params p) { super::params = p; // This will invoke the built-in conversion mechanism in the yamlcpp library. } // Important to override this function, as this is the only way that the "method" field will be set. std::string get_method() const { return "admin_new_command_1"; } };
Implement the yamlcpp convert function, Yaml-cpp has a built-in conversion mechanism. You can refer to YAML for more info.
// yaml_codecs.h template <> struct convert<NewCommandJsonRPCRequest::Params> { static Node encode(NewCommandJsonRPCRequest::Params const ¶ms) { Node node; node["named_var_1"] = params.named_var_1; return node; } };
Define a new command. For the sake of simplicity I'll only implement it in the
.hfiles.// CtrlCommands.h & CtrlCommands.cc struct NewCommand : public CtrlCommand { NewCommand(ts::Arguments args): CtrlCommand(args) { // we are interested in the format. auto fmt = parse_format(_arguments); if (args.get("new-subcommand1") { // we need to create the right printer. _printer = std::make_sharec<MyNewSubcommandPrinter>(fmt); // we need to set the _invoked_func that will be called when execute() is called. _invoked_func = [&]() { handle_new_subcommand1(); }; } // if more subcommands are needed, then add them here. } private: void handle_new_subcommand1() { NewCommandJsonRPCRequest req{}; // fill the req if needed. auto response = invoke_rpc(req); _printer->write_output(response); } };
Define a new printer to deal with this command. We will assume that the printing will be different for every subcommand. so we will create our own one.
class MyNewSubcommandPrinter : public BasePrinter { void write_output(YAML::Node const &result) override { // result will contain what's coming back from the server. } };
In case that the format type is important, then we should allow it by accepting the format being passed in the constructor. And let it set the base one as well.
MyNewSubcommandPrinter(BasePrinter::Format fmt) : BasePrinter(fmt) {}
The way you print and the destination of the message is up to the developer's needs, either a terminal or some other place. If the response from the server is a complex object, you can always model the response with your own type and use the built-in yamlcpp mechanism to decode the
YAML::Node.write_output(YAML::Node const &result)will only have the result defined in the protocol, check Result for more detail. So something like this can be easily achieved:void GetHostStatusPrinter::write_output(YAML::Node const &result) { auto response = result.as<NewCommandJsonRPCRResponse>(); // will invoke the yamlcpp decode. // you can now deal with the Record object and not with the yaml node. }
Notes¶
There is code that was written in this way by design, RecordPrinter and RecordRequest are meant to be use by any command
that needs to query and print records without any major hassle.
See also¶
Configuration, traffic_ctl, Handler implementation, JSON RPC errors