Managed objects
Aside from the links in the center of Figure 1, there is our old friend LSP123. This entity is created by the network management system using a technology called MPLS (mutiprotocol label switching) and represents a logical network connection. LSP, which stands for label switched path, is used for the transfer of transit data between R2 and R5, along what is called a forwarding path. (See [2] in the "Additional Reading" section for more on this technology and how to manage it.) For our purposes, just think of LSP123 as a logical pipe (that is, not a physical entity) that joins R2 and R5 along the path R2-R3-R4-R5.
Listing 2 illustrates some notional code for creating the LSP123 entity.
Listing 2 LSP creation and event handler configuration
Connection* connection = new Connection(CONGESTION); Lsp* lsp = new Lsp("LSP123", connection, LINK_1_BROKEN);
Notice that in Listing 2, I pass two parameter constants: CONGESTION and LINK_1_BROKEN. These constants represent the failure conditions handled by the associated objects; that is, the connection object handles CONGESTION, and the lsp object handles LINK_1_BROKEN. Clearly, having each object handle just one event is a bit of a simplification! A more likely scenario is for each object to handle a range of events, but I take the simpler route in this case.
Let’s now have a look at the handlers that are associated with these objects. Listing 3 illustrates part of my EventHandler class.
Listing 3 The event handler method: HandleEvent()
void EventHandler::HandleEvent (Event incomingEvent) { if (incomingEvent != _event) { if (_successor != 0) { cout << "HandleEvent () calling into successor\n"; _successor->HandleEvent(incomingEvent); } } else { cout << "Base class help now follows"; } }
Notice in Listing 3 the way the HandleEvent() method is invoked with a specific incoming event. This method checks to see whether it matches the event for which the handler has been configured. If no match occurs, the event is passed along the chain of responsibility to see whether another entity can provide the required event-specific help.
As you can see from Listing 3, the HandleEvent() method is part of a base class called EventHandler. All of the other classes derive from this base class and it is the mechanism by which each class can provide its own event handler. This is the essence of the strategy pattern—each class provides its own specific handling for a given event. In some cases, the handling is very helpful to a network operator, while in others the handling is a little more generic. Let’s take an example; Listing 4 illustrates the Lsp class, which is itself a subclass of Connection.
Listing 4 The Lsp subclass
class Lsp : public Connection { public: Lsp::Lsp(char* lspID, Connection* w, Event t) : Connection(0) { cout << "Constructing Lsp: " << lspID << "\n"; lspName = lspID; SetHandler(w, t); } void HandleEvent(Event incomingEvent) { if (HasEventSupport(incomingEvent)) { // Offer event support cout << "\nAt last, here’s some LSP Event support:\n"; cout << "We need a new path for " << lspName << "\n\n"; } else EventHandler::HandleEvent(incomingEvent); } private: char* lspName; };
Remember that I said that different subclasses can provide more specific help? If you look at the Lsp.HandleEvent() method in Listing 4, you can see that if the incoming event matches its configured event, the help indicates that a new path is required for the associated LSP. Objects of the Lsp class therefore provide fairly directed assistance to the network operator. The managed objects (instances of Lsp, Connection, and so on) help to provide a more responsive solution as network events occur.
Let’s try to make this a bit more concrete by looking at the program output shown in Figure 2.
Figure 2 The full program output
The first two lines in Figure 2 illustrate the construction phase in which I create the Lsp and Path objects. Next up in Figure 2, I simulate an event (CONGESTION in this case) using the following code:
path->HandleEvent(CONGESTION);
This code serves to call into the chain of responsibility, starting with the handler for the parent object (in this case, the path object. As discussed in [1], the chain of responsibility presents a series of handler methods, and in this context only the most appropriate handler is called. Remember, the strategy pattern is implemented by the event handler method.
Line 4 indicates that there is no support in the path object for the CONGESTION event type. This is as expected because the path object has been configured to handle the LINK_2_BROKEN event. Now what?
So, a call is made into the base class event handler. As discussed in [1], the chain of responsibility eventually supplies a handler for our event, and you can see in Figure 2 that the Connection object eventually provides help (see the line following the one that states Here’s some Connection Event support). The supplied help indicates that more bandwidth is required for LSP123. Remember that the network is experiencing congestion, so the help supplied is correct. The managed object provides a useful hint to the network operator: We’ve got congestion, so please add more bandwidth.
Congestion is in fact one of the greatest enemies of modern networks! Congestion can result in a sudden and catastrophic loss of network service and is such a serious problem that network equipment vendors implement all sorts of complex schemes to combat it.
The next part of Figure 2 illustrates the simulation of a link failure, specifically LINK_1_BROKEN. A quick look back at Figure 1 indicates that LSP123 now no longer has a path to follow. So if the LSP123 entity is not backed up with another path (or configured to reroute in the event of a link failure), the traffic arriving at R2 can no longer traverse the LSP123 entity. Since this is potentially what is called a service-affecting problem, it’s important that the network administrator gets to hear about it sooner rather than later. So, how does our code handle this failure?
From Figure 2, you can see that the path object offers no help. As usual, we invoke the path handler first in the chain using the following code:
path->HandleEvent(LINK_1_BROKEN);
Remember that this then calls into the chain of responsibility, which eventually results in the invocation of one of the appropriate strategy handler methods. A quick look at Listing 2 indicates that the only entity configured to handle this particular event is the lsp object as per the following code:
Lsp* lsp = new Lsp("LSP123", connection, LINK_1_BROKEN);
How does this object actually configure its handler? Have a look at the constructor code in Listing 4. The following line configures the event handler for the parent object:
SetHandler(w, t);
This method is defined in the base class EventHandler. This is a good example of separation of concerns—each class is written to do a set of tasks, leaving other tasks to subclasses.
Once the handler is configured, it will eventually be invoked when the associated error condition occurs. In the case of LINK_1_BROKEN, the help that appears is the following message taken from Figure 2: We need a new path for LSP123.
The last simulated error in Figure 2 is that of LINK_2_BROKEN, and looking at Listing 1 you can see that the path object is configured to handle this error. Notice this message in Figure 2: We need a new path. This message is less informative than was the case for the LINK_1_BROKEN error because the path object doesn’t have any special network-specific knowledge concerning the error it handles. The lsp object, on the other hand, knows that its incoming event directly affects the associated LSP123 entity.