-
Notifications
You must be signed in to change notification settings - Fork 7
How To Write a Module
All modules are derived from the AbstractModule-class. It provides a basic frame for each module. In the /util folder you find the script create-new-cpp-module.sh (or the respective java script) which you can run to create a new raw implementation of a module which you can extend afterwards. The script will ask you for the name the module should carry and the unique id it should have, which must be given as hex, e.g. 00f2.
Afterwards a folder with the provided module name will be created in the /modules/cpp(or java) folder. This folder will already contain a buildable project for your previously created module, which you can build using cmake (in case of C++) or maven (in case of java). The folder also contains the .topics file (Communication topics/categories for your module) and a .proto file (Protocol buffer file describing the data exchange format for you module).
To get a general idea of how modules communicate with each other, please consult: here Also, the source code of the Core modules is well documented and serves as example for both simple and more complex module interaction.
The next section explains some key concepts regarding Modules, and is focussed on the C++ implementation. However, it would look very similar if written for Java, as the concepts are identical.
You can specify what modules must be enabled before your module is enabled. If a dependency goes missing, your module will be shut down as well.
vector<ModuleDependency> deps =
{
{zsdn::MODULE_TYPE_ID_SwitchRegistryModule, switchRegistryModuleDependencyVersion_},
{zsdn::MODULE_TYPE_ID_LinkDiscoveryModule, linkDiscoveryModuleDependencyVersion_}
}The dependencies are supplied to the constructor of the Abstract module.
First there is the enable()-method. This method is called when the module is enabled, meaning here is where code for start-up will have to be inserted. Here you can subscribe to another module if you want to react to their (specific) messages. As an example the subscription to an IPV4 message from all OpenFlow Switches (SwitchAdapters):
getZmf()->subscribe(
switchAdapterTopics_.from().switch_adapter().openflow().packet_in().multicast_group_default().ipv4().build(),
[this](const ZmfMessage& message, const ModuleUniqueId& sender) {
// Do something in here (e.g. call a specific method like below)
// handleIPv4SubscriptionMethod(message, sender);
});Do not forget to include the specific topics file. For example for the switchAdapterTopics used above you would have to include "#include <zsdn/topics/SwitchAdapterTopics.hpp>".
Another possibility is to request all existing modules of a certain kind that are enabled within ZMF, e.g. all SwitchAdapter-Instances
list<shared_ptr<ModuleHandle>> moduleList = getZmf()->getPeerRegistry()->getPeersWithType(
zsdn::MODULE_TYPE_ID_SwitchAdapter, true);Will return a list of all currently active SwitchAdapters.
The disable()-method is called when the instance is disabled (or before beeing stopped permanently). Every action taken in enable should be undone here so that the module can cleanly enable again afterwards.
You can subscribe to certain messages of other modules. The method you define will then be called every time you receive the message type you subscribed to. For an example on how to subscribe refer to this. The following example will show how to process an IPv4 message:
void YourModule::handleIPv4SubscriptionMethod(const ZmfMessage& message, const ModuleUniqueId& id) {
// Unpack ZmfMessage which contains OpenFlow packet
of_object_t* ofPacketIn = zsdn::of_object_new_from_data_string_copy(message.getData());
of_octets_t ofPayload;
of_packet_in_data_get(ofPacketIn, &ofPayload);
uint8_t* payloadData = ofPayload.data;
uint16_t payloadLength = ofPayload.bytes;
Tins::EthernetII ethPacket(payloadData, payloadLength);
of_packet_in_delete(ofPacketIn);
//extract info from ethernetII
Tins::PDU* innerPdu = ethPacket.inner_pdu();
if (innerPdu == nullptr) {
getLogger().warning("Received EthernetII Packet with invalid inner PDU.");
return;
}
Tins::PDU::PDUType pduType = ethPacket.inner_pdu()->pdu_type();
if(pduType==Tins::PDU::PDUType::IP) {//to be really safe that its ipv4
Tins::IP* ipv4Packet = (Tins::IP*) ethPacket.inner_pdu();
//now process the ipv4 the way you want
}
}
Note that in ZSDN, all Modules communicate using google protocol buffers, with the exception of the SwitchAdapter, which communicates using OpenFlow messages. Custom modules are free to use any communication protocol they want.
The handleModuleStateChange()-method is called, when an other modules changes LifecycleState (meaning it is disabled, enabled or stopped). In this method one can define how to react to other modules. As an example we print a message every time any instance of SwitchAdapter changes its LifecycleState.
void YourModule::handleModuleStateChange(
std::shared_ptr<zmf::data::ModuleHandle> changedModule,
zmf::data::ModuleState newState,
zmf::data::ModuleState lastState)
{
if (changedModule.get()->UniqueId.TypeId == zsdn::MODULE_TYPE_ID_SwitchAdapter) {
printf("A SwitchAdapter changed State")
}
}The handleRequest()-method is called when an other module sends a request to your module. In the case of ZSDN, all requests and replies are google protocol buffer messages. Request/Reply always involves only two modules, the requesting and the replying module. You have to parse the request and act depending on the request type. As an example here the processing of a dummy request:
zmf::data::ZmfOutReply YourModule::handleRequest(
const zmf::data::ZmfMessage& message,
const zmf::data::ModuleUniqueId& sender)
{
YourModule_Proto::Request request;
bool parseSuccess = request.ParseFromArray(message.getDataRaw(), message.getDataLength());
if (!parseSuccess) {
return zmf::data::ZmfOutReply::createNoReply();
}
switch (request.RequestMsg_case()) {
case YourModule_Proto::Request::kThisIsARequest: {
YourModule_Proto::Reply reply;
//set the reply-data if necessary
zmf::data::MessageType topicsReply_ = yourModuleTopics_reply().your_module().this_is_a_request().build();
return zmf::data::ZmfOutReply::createImmediateReply(
zmf::data::ZmfMessage(topicsReply_, reply.SerializeAsString()));
break;
}
}
return zmf::data::ZmfOutReply::createNoReply();
}Note that - just like with publish subscribe, Request Reply also uses additional message identifiers (MessageType/Topics), in this case, your_module().this_is_a_request().build();. While not necessary, this feature can be used to know what type of request was received without having to know how to parse it (essentially meaning that this is a label for the request).
You can publish messages through the ZMF. Every module that is subscribed to your message type under which the message is published will automatically receive the message. For sending OpenFlow packet to a Switch, you would publish to a SwitchAdapter (to SwitchAdapter). If you module publishes events, you would publish them as beeing from you. Here an example of a module publishing a message:
YourModule_Proto::From messageContainer;
YouryModule_Proto::From_YourModuleMessage* yourModuleMessage = new YourModule_Proto::From_YourModuleMessage();
//set the data in yourModuleMessage if necessary
messageContainer.set_allocated_your_module_message(yourModuleMessage);
MessageType topic_ = yourModuleTopics.from().your_module().your_module_message().build();
getZmf()->publish(
ZmfMessage(topic_, messageContainer.SerializeAsString())
);You can use requests to get information directly from a specific other module. Therefore you have to create a request and check read the data from the reply, which you receive. Here an example of a module doing a request:
MessageType topic_ = otherModuleTopics.request().other_module().a_request_type().build();
OtherModule_Proto::Request request;
OtherModule_Proto::Request_ARequestType* aRequestType = new OtherModule_Proto::Request_ARequestType();
request.set_allocated_a_request_type(aRequestType);
OtherModule_Proto::Reply reply;
zsdn::RequestUtils::RequestResult requestResult =
zsdn::RequestUtils::sendRequest(*getZmf(),
request,
reply,
topic_,
zsdn::MODULE_TYPE_ID_OtherModule,
0);
switch (requestResult) {
case zsdn::RequestUtils::SUCCESS:
// read data from reply and process it however needed
break;
case zsdn::RequestUtils::NO_MODULE_INSTANCE_FOUND:
// no module found to which the request could be sent to
break;
case zsdn::RequestUtils::TIMEOUT:
//request time out (no response received)
break;
case zsdn::RequestUtils::REQUEST_SERIALIZATION_FAILED:
//message construction problem on your side
break;
case zsdn::RequestUtils::RESPONSE_PARSE_FAILED:
// could not parse response
break;
}Your module is ready to be started from the get-go; just run the main.cpp (or Main.java). This will create a module instance and then give the control over to ZMF / JMF.