Skip to content

Module Architecture

AndreKutzleb edited this page Nov 10, 2015 · 7 revisions

#ZMF/JMF Basics

ZeroSDN StartUpSelector initial screen

As the figure shows, the framework consists of several units: the ZMFCore, which encapsulates the main functionality of the framework such as starting and stopping of modules. Also it provides an interface that modules can use for accessing the framework and for connecting to other modules through it. These modules are always derived from the AbstractModule-class.

Furthermore, the ZMFCore controls the execution of two more essential services that are started and stopped with an instance of the ZMF. The first is called the PeerDiscoveryService which subscribes to a multicast group in the network to which all other ZMF instances also subscribe. The service frequently sends out so called heartbeats in form of udp-multicast messages. This way every instance of the ZMF knows about all other module instances that are available at this time and what state the currently have.

The ZMQMessagingService provides functionality to modules that can be used for sending direct ZMQ request messages to other modules and for receiving their replies in the same way. It can also be used to publish ZMQ messages to a certain topic or to receive messages from other modules through a preceding subscription to this topic.

Module applications call the run method of the ZMF framework (eg. from the main method) to start the ZMF instance.

For further information about ZMF see ZMF Wiki.

Communication

Communication

MessageTypes

General Information

The message type identifier is a 32 byte long field to identify a message. It is used for matching for pub/sub messages.

.topics files declare what Message Types a module supports.

From this file, factories for creating topics are created in C++ and Java.

Using these factories yields a MessageType. the MessageType denotes the type of a message sent in ZSDN.

Format of the File

Example file:

TO = 0x01
	EXAMPLE_MODULE_A = 0x0009
		SOME_EVENT = 0x00
			TO_INSTANCE_ID = 0x????????????????
		ANOTHER_EVENT = 0x01
		SOME_REQUEST = 0x02


# A Comment
FROM = 0x02
	EXAMPLE_MODULE_A = 0x0009
		RANDOM_NUMBER = 0x00
			ZERO_TO_TEN = 0x00
			GREATER_TEN = 0x01

REQUEST = 0x03
    EXAMPLE_MODULE_A = 0x0009
        SOME_REQUEST = 0x00

REPLY = 0x04
    EXAMPLE_MODULE_A = 0x0009
        SOME_REPLY = 0x00
  • MessageType definition is strictly hierarchical. The hierachy is repesented as a tree.
  • Indentation is done by using a tab or 4 spaces for each level.
  • All Topics with the same parent require a unique value or alternatively a variable value (using question marks).
  • All Topics with the same parent should use the same length - otherwise multiple factory paths may result in the same MessageType.

Details

If you are a Module of Type A and want to receive messages adressed at you, you have to create a Message Type that looks something like this:

"TO" "MODULE_A" ...

How is this represented internally? TO is represented by a byte with a value of 1, or 0x01 (hex) MODULE_A is represented by its module type id, which is 2 bytes long - lets assume that our module has the module type of 9 or 0x0009 in hex.

The actual messageId will therefore look like this internally:

 {1,0,9} or {0x01,0x00,0x09}
  • the first byte beeing "TO" and the next 2 bytes "MODULE_A".

Since creating topics like this is not exactly intuitive, the factories generated from a .topics will help you build topics easily and without having to worry about the underlying specifics.

In C++, creating a MessageType looks like this:

ModuleATopics<MessageType> moduleATopics;
 MessageType t = moduleATopics.to().module_a().build();

which is equivalent of manually building a MessageType like this, if you want to do it manually:

ModuleATopics<MessageType> moduleATopics;
 MessageType t;
 moduleATopics.append<uint8_t>(0x01);
 moduleATopics.append<uint16_t>(0x0009);

Topic definition is strictly hierarchical. On layer 0 (no indendation) top level topics are declared. These are:

 - TO      : Top level topic for all Messages to modules.                       (0x01 -> 1 byte)
 - FROM    : Top level topic for all Messages from modules.                     (0x02 -> 1 byte)
 - REQUEST : All requests a module supports                                     (0x03 -> 1 byte)
 - REPLY   : All reply topics the Module can send back for an incoming request.	(0x04 -> 1 byte)

On the second layer of these types, the name of the module together with its module-type-ID is written in hexadecimal notation.

Everything beyond that is module defined - no other module can have conflicting messages from here on, since each module type has a unique ID.

Data types

For the definitions present in the .topics file of each module, it is VERY IMPORTANT to write the numbers in hexadecimal notation - When the code is generated, the number of digits will determine the datatype for this topic part.

Constants

Notation interpreted as
0x00 constant uint8_t (byte)
0x0000 constant uint16_t (short)
0x00000000 constant uint32_t (int)
0x0000000000000000 constant uint64_t (long)

Wildcards

If, instead of a constant, a variable should be set at a certain point, simply replace a concrete value by question marks:

Notation interpreted as
0x?? variable uint8_t (byte)
0x???? variable uint16_t (short)
0x???????? variable uint32_t (int)
0x???????????????? variable uint64_t (long)

Default values

For a wildcard entry, a default value can be set. this can be done like this:

TO = 0x01
	EXAMPLE_MODULE_A = 0x0009
		SOME_EVENT = 0x?? default=0x42

This will add the option to build the topic using both wildcard, and the given default value:

ModuleATopics<MessageType> moduleATopics;
 MessageType default = moduleATopics.to().example_module_a().some_event_default().build();
 MessageType custom  = moduleATopics.to().example_module_a().some_event(123).build();

Example

Adressing of a specific switch:

SwitchAdapterTopics<MessageType> swTopics;
MessageType toSwitch = swTopics.to().switchadapter().switchId(42).openflow().packetOut().build();

You can also stop at any point in the factory and create a MessageType. instead of doing this:

 MessageType tcpPacketIns = swTopics.from().switchadapter().openflow().packet_in().multicast_group_default().ipv4().TCP().build();

You can also simply do this:

 MessageType tcpPacketIns = swTopics.from().switchadapter().openflow().packet_in().multicast_group_default().ipv4().build();

which means that your module will receive all packet-in messages, not only those of type IPv4.TCP.

Comparing MessageTypes

If you want to know whether a Message is of a certain type, you can compare them to other MessageTypes.

If, for example, you get a Message and don't know anything about it (usually you should at least know the general type of messages you are handling), you can do the following:

 MessageType fromSwitch = swTopics.from().switchadapter().build(); // assume we dont know the exact topic

Now we want to find out what exactly received is. Using this notation, you can find out any relationship between two MesageTypes:

 MessageType fromSwitchOpenflow = swTopics.from().switchadapter().openflow().build();
 
 fromSwitch.containsTopic(fromSwitchOpenflow) ->  false // fromSwitchOpenflow is more specific 
 fromSwitchOpenFlow.containsTopic(fromSwitch) ->  true  // fromSwitch is less specific than fromSwitchOpenFlow

Google Protocol Buffer Communication

Container

Protobuf messages relating to these top-level types always use a top-level protobuf message. for example, all Messages FROM a specific module are bundled up like this:

message From {
    oneof FromMsg {
        <FromMsg_1_Type> <fromMsg_1_Name> = 1;
        <FromMsg_2_Type> <fromMsg_2_Name> = 2;
        ...
        <FromMsg_n_Type> <fromMsg_n_Name> = n;
    }
}

The same is true for TO, REQUEST and REPLY. This pattern allows easy extensions which can be handled at runtime - modules can depend on parsing the respective top-level message first, then check the actual Message they are interested in.

Third party Modules are free to send their data in any format they want - however, all standard components of ZSDN use google protocol buffers for communication.

Each ZSDN module has its own .proto file containing all messages belonging to the module. The top level messages are equal to the top level topics - To, From, Request, Reply. Using a top level message as container for exactly one subtype of message allows easy runtime checks on the type of a message - for example, a Module a can parse all its messages it receives as a request as a "Request" msg - and then look up the specific subtype.

IMPORTANT: Not every topic (see .topics / MessageType) has to have an associated protobuf message - there is no point in having hundreds of submessages for something that can be expressed as a single integer in a top level message, for example. Modules have to specify the kind of protobuf message expected/sent out for each topic in the topics file.

package ExampleModuleA_Proto;

// Main message type for messages TO ExampleModuleA

message To {

    oneof ToMsg {
        SomeEvent someEvent = 1;
        AnotherEvent anotherEvent = 2;
        SomeRequest someRequest = 3;
    }

    // SubMessage definition of ExampleModuleA

    message SomeEvent {
        required string foo = 1;
        optional uint32 bar = 2;
    }

    message AnotherEvent {
        required string foo = 1;
        optional uint32 bar = 2;
    }

    message SomeRequest {
        repeated uint64 bar = 1;
        repeated SomePayload payload = 2;
    }
}

// Main message type for messages FROM ExampleModuleA

message From {

    oneof FromMsg {
        RandomNumber randomNumbers = 1;
    }

    message RandomNumber {
        required uint64 randomNumber = 1; 
        optional SomePayload payload = 2;
    }
}

message Request {
    oneof Req {
        SomeRequest request = 1;
    }
    message SomeRequest {}
}

message Reply {
    oneof Rep {
         SomeReply reply = 1;
    }
    message SomeReply{}
}




// Message types used in both FROM and TO messages

message SomePayload {
    required string something = 1;
    required bytes payload = 2;
}


Clone this wiki locally