HP VAN SDN Controller Programming Guide Abstract The HP VAN SDN Controller is a Java-based OpenFlow controller enabling SDN solutions such as network controllers for the data center, public cloud, private cloud, and campus edge networks. This includes providing an open platform for developing experimental and special-purpose network control protocols using a built-in OpenFlow controller. This document provides detailed documentation for writing applications to run on the HP VAN SDN Controller platform.
© Copyright 2013, 2014 Hewlett-Packard Development Company, L.P. No part of this documentation may be reproduced or transmitted in any form or by any means without prior written consent of Hewlett-Packard Development Company, L.P. The information contained herein is subject to change without notice. HEWLETT-PACKARD COMPANY MAKES NO WARRANTY OF ANY KIND WITH REGARD TO THIS MATERIAL, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE.
Contents 1 Introduction ··································································································································································· 1 Overview ··········································································································································································· 1 Basic Architecture ························································································································
Distributed Persistence Overview ························································································································· 85 Backup and Restore ····················································································································································· 111 Backup ································································································································································· 111 Restore
Creating Domain Service (Business Logic) ······································································································· 156 Creating a REST API ··········································································································································· 169 Creating RSdoc ··················································································································································· 193 Creating a GUI··················
1 Introduction This document describes the process of developing applications to run on the HP VAN SDN Controller platform. The base SDN Controller serves as a delivery vehicle for SDN solutions. It provides a platform for developing various types of network controllers, e.g. data-center, public cloud, private cloud, campus edge networks, etc. This includes being an open platform for development of experimental and special-purpose network control protocols using a built-in OpenFlow controller.
The Administration tier of the controller will host a web-layer through which software modules installed on the appliance can expose REST APIs [1] [2] (or RESTful web services) to other external entities. Similarly, modules can extend the available web-based GUI to allow network administrators and other personae to directly interact with the features of the software running on the SDN Controller.
Figure 4 HP VAN SDN Controller Software Stack Jersey [2] is a JAX-RS (JSR 311) reference Implementation for building RESTful Web services. In Representational State Transfer (REST) architectural style, data and functionality are considered resources, and these resources are accessed using Uniform Resource Identifiers (URIs), typically links on the web. REST-style architectures conventionally consist of clients and servers and they are designed to use a stateless communication protocol, typically HTTP.
The approach aims to achieve connectivity in a controlled manner and without creating undue dependencies on specifics of component implementations. The separate tiers are expected to interact over well-defined mutual interfaces, with decreasing coarseness from top to bottom.
Figure 5 HP VAN SDN Controller Tiers Internal Applications vs. External Applications Internal applications (“Native” Applications / Modules) are ideal to exert relatively fine-grained, frequent and low-latency control interactions with the environment, for example, handling packet-in events. Some key points to consider when developing internal applications: • Authored in Java or a byte-code compatible language, e.g. Scala, or Scala DSL.
External applications are suitable to exert relatively coarse-grained, infrequent, and high-latency control interactions with the environment, such as path provisioning and flow inspections. External applications can have these characteristics: • This can be written any language capable of establishing a secure HTTP connection. Example: Java, C, C++, Python, Ruby, C#, bash, and so on. • They can be deployed on a platform of choice outside of the SDN Controller platform.
2 Establishing Your Test and Development Environments The suggested development environment contains two separate environments, a Test Environment and a Development Environment. It is recommended to use a different machine for each of these environments. The Test Environment is where the HP VAN SDN Controller and all the dependency systems will be installed; it will be very similar to a real deployment, however virtual machines [13] are useful during development phase.
• OSX Snow Leopard or later. Java The Software Development Language used is Java SE SDK 1.6 or later. To install Java go to [15] and follow the download and installation instructions. Maven Apache Maven is a software project management and comprehension tool. Based on the concept of a project object model (POM), Maven can manage a project's build, reporting and documentation from a central piece of information [16]. To install Maven go to [16] and follow the download and installation instructions.
directory where the SDK was unzipped, as follows (Note: Java SDK and Maven must already be installed and properly configured): $ bin/install-sdk To verify that the SDK has been properly installed look for the HP SDN libraries installed in the local Maven repository at: ~/.m2/repository/com/hp. Javadoc The controller Java APIs are documented in Javadoc format in the hp-sdn-apidoc-*.jar file. Download the file and unzip its contents. To view the Java API documentation, open the index.html file.
3 Developing Applications Internal applications (“Native” Applications / Modules) are ideal to exert relatively fine-grained, frequent and low-latency control interactions with the environment, for example, handling packet-in events. Some key points to consider when developing internal applications: • Authored in Java or a byte-code compatible language, e.g. Scala, or Scala DSL. • Deployed on the SDN Controller platform as collections of OSGi bundles.
Figure 8 HP Application Modules 11
Web Layer Components in this layer are responsible for receiving and consuming appropriate external representations (XML, JSON, binary...) suitable for communicating with various external entities and, if applicable, for utilizing the APIs from the business logic layer to appropriately interact with the business logic services to achieve the desired tasks and/or to obtain or process the desired information.
shutdown the Openflow port to stop processing Openflow events. environment, the controller will remove itself from the team. If in a teaming Persistence Layer Data Access API—Interfaces, which prescribe how to persist and retrieve the domain model information, such as locations, devices, topology, etc. This can also include any prescribed routing and flow control policies.
Figure 9 Token-based Authentication Flow 1) API Client presents credentials (username/password) to the AuthToken REST API. 2) Authentication is performed by the backing Authentication Server. The SDN Appliance includes a local Keystone-based Authentication Server, but the Authentication Server may also be hosted elsewhere by the customer (and maybe integrated with an enterprise directory such as LDAP for example), as long as it implements the AuthToken REST API (described elsewhere).
Public API: 1) Create token. This accepts username/password credentials and return back a unique token with some expiration. Service API: 1) Revoke token. This revokes a given token. 2) Validate token. This validates a given token and returns back the appropriate principal's information. Authentication services have been split into these two APIs to limit sensitive services (Service API) to only authorized clients.
REST API Documentation In addition to the Rsdoc, the HP VAN SDN Controller REST API provides information for interacting with the controller’s REST API. Rsdoc Rsdoc is a semi-automated interactive RESTful API documentation. It offers a useful way to interact with REST APIs. Figure 10 RSdoc It is called RSdoc because is a combination of JAX-RS annotations [2] and Javadoc [21] (Illustrated in Figure 11).
Figure 11 RSdoc, JAX-RS and Javadoc JAX-RS annotations and Javadoc are already written when implementing RESTful Web Services, and they are re-used to generate an interactive API documentation. Rsdoc Extension The HP VAN SDN Controller SDK offers a method to extend the Rsdoc to include applications specific RESTful Web Services (As the example illustrated in Figure 11).
Figure 12 Authenticating via RSdoc Step 1 3. Set the authentication token as the X-AUTH-TOKEN in the RSdoc and then click “Explore,” as illustrated in Figure 13. From this point all requests done via RSdoc will be authenticated as long as the token is valid.
Figure 13 Authenticating via RSdoc Step 2 Audit Logging The Audit Log retains information concerning activities, operations and configuration changes that have been performed by an authorized end user. The purpose of this subsystem is to allow tracking of significant system changes. This subsystem provides an API which various components can use to record the fact that some important operation occurred, when and who triggered the operation and potentially why.
• Origin—a string representation of the application or component that originated this audit log entry. • Controller ID—the unique identification of the controller that originated the audit log entry. Applications may contribute to the Audit Log via the Audit Log service. When creating an audit log entry the user, activity, origin and data must be provided. The time-stamp and controller identification is populated by the audit log framework.
Configuration The SDN controller presents configurable properties and allows the end user to modify configurations via both the UI and REST API layers. The HP VAN SDN Controller uses the OSGi Configuration Admin [22] [23] and MetaType [24] [25] services to present the configuration data. For an application to provide configuration properties that are automatically presented by the SDN controller, they must provide the MetaType information for the configurable properties.
bundle war The component can then use Annotations to define the configuration properties as illustrated in the following listing. Configurable Property Key Definition Example: package com.hp.hm.impl; import org.apache.felix.scr.annotations.*; ...
someIntVariable = ConfigUtils.readInt(config, CONFIG_KEY, null, 100); } ... } As the configuration property value can one of several different kinds of Java object (Integer, Long, String, etc.) a utility class is provided to read the appropriate Java object type from the configuration map. The ConfigUtils.java class provides methods to read integers, longs, strings, Booleans and ports from the configuration map of key -> value pairs.
1) Create controller team: Using the teaming interfaces, a team of controllers need to be defined for leveraging High Availability features. 2) Create Region: the network devices for which the given controller has been identified as a master are grouped into “regions”. This grouping is defined in the HP VAN SDN Controller using the Region interface detailed in subsequent sections.
// Mandatory dependency. private final RoleService roleService; public void doAct() { IpAddress masterIp = roleService.getMaster(dpid).ip(); if(masterIp.equals(sysInfoService. getSystem().getAddress())){ log.
} } Notification on Region and Role changes Applications can express interest in region change notifications using the addListener(...) API in RegionService and providing an implementation of the RegionListener. A sample listener implementation is illustrated in the following listing: Region Listener Example: import com.hp.sdn.adm.region.RegionListener; import com.hp.sdn.region.Region; ... public class RegionListenerImpl implements RegionListener { ... @Override public void added(Region region) { log.
The Message Library is a Java implementation of the OpenFlow specification, providing facilities for encoding and decoding OpenFlow messages from and to Java rich data types. The controller handles the connections from OpenFlow switches and provides the means for upper layers of software to interact with those switches via the ControllerService API.
Design Choices Some specific design choices were made to establish the underlying principles of the implementation, to help meet the goals specified above. • All OpenFlow messages are fully creatable/encodable/decodable, making the library completely symmetrical in this respect. • However, providing a complete solution allows us to emulate OpenFlow switches in Java code. This facilitates the writing of automated tests to verify switch/controller interactions in a deterministic manner.
Figure 15 Message Factory Role Message Composition and Type Hierarchy All OpenFlow message instances are subclasses of the OpenflowMessage abstract class. Every message includes an internal Header instance that encapsulates: • The protocol version • The message type • The message length (in bytes) • The transaction ID (XID) In addition to the header, specific messages may include: • Data values, such as “port number”, “# bytes processed”, “metadata mask”, “h/w address”, etc.
Figure 16 OpenFlow Message Class Diagram Each mutable subclass includes a private Mutable object that determines whether the instance is still “writable”. While writable, the “payload” of the mutable message can be set. Once the message has been made immutable, the mutable instance is marked as “no longer writable”; any attempt to change its state will result in an InvalidMutableException being thrown. Note that messages are passive in nature as they are simply data carriers. Note also that structures (e.
Figure 17 Message Factory Class Diagram The other factories that a developer might use are: • MatchFactory—creates matches, used in FlowMods • FieldFactory—creates match fields, used in Matches • InstructionFactory—creates instructions for FlowMods • ActionFactory—creates actions for instructions, (1.
private static final int FLOW_IDLE_TIMEOUT = 300; private static final int FLOW_HARD_TIMEOUT = 600; private static final int FLOW_PRIORITY = 50; private static final Set FLAGS = EnumSet.of( FlowModFlag.SEND_FLOW_REM, FlowModFlag.CHECK_OVERLAP, FlowModFlag.NO_BYTE_COUNTS ); private static final MacAddress MAC = MacAddress.valueOf("00001e:000000"); private static final MacAddress MAC_MASK = MacAddress.valueOf("ffffff:000000"); private static final PortNumber SMTP_PORT = PortNumber.
.addField(createBasicField(PV, ETH_SRC, MAC, MAC_MASK)) .addField(createBasicField(PV, ETH_TYPE, EthernetType.IPv4)) .addField(createBasicField(PV, IP_PROTO, IpProtocol.TCP)) .addField(createBasicField(PV, TCP_DST, SMTP_PORT)); return (Match) mm.toImmutable(); } private static final long INS_META_MASK = 0xffff0000; private static final long INS_META_DATA = 0x33ab0000; private List createInstructions() { // NOTE static imports of: // com.hp.of.lib.instr.ActionFactory.createAction; // com.hp.
Maintaining information about the state of all OpenFlow ports on connected switches Conforming to protocol rules for sending messages back to switches • To provide a modular framework for controller sub-components, facilitating extensibility of the core controller. • To provide an elegant, yet simple, API for Network Service components and SDN Applications to access the core services.
In the following code examples, it is assumed that a reference to the controller service implementation has been stored in the field cs: private ControllerService cs = ...
Listeners Application code may wish to be notified of events via a callback mechanism. A number of methods allow the consumer to register as a listener for certain types of event: • Message Listeners – notified when OpenFlow messages arrive from a datapath. At registration, the listener specifies the message types of interest. Note that one exception to this is PACKET_IN messages; to hear about these, one must register as a SequencedPacketListener.
} } private void handleEchoReply(OfmEchoReply msg, DataPathId dpid) { ... } private void handlePortStatus(OfmPortStatus msg, DataPathId dpid) { ... } } Statistics The ControllerService API has a number of methods for retrieving various “statistics” about the controller, or about datapaths in the network. • getStats()—returns statistics on byte and packet counts, from the controller’s perspective. • getPortStats(...)—queries the specified datapath for statistics on its ports. • getFlowStats(...
print("Match : {}", fs.getMatch()); // Note: this is one area where we need to be cognizant of the version: if (fs.getVersion() == ProtocolVersion.V_1_0) print("Actions : {}", fs.getActions()); else print("Instructions : {}", fs.
long duration = now - then; log.info("ECHO Latency to {} is {} ms", dpid, duration); } else { log.warn(E_ECHO_FAILED, future.result()); } } catch (Exception e) { log.warn(E_ECHO_FAILED, e.toString()); } } private OpenflowMessage createEchoRequest(byte[] timestamp) { OfmMutableEchoRequest echo = (OfmMutableEchoRequest) MessageFactory.create(PV, MessageType.ECHO_REQUEST); echo.data(timestamp); return echo.
Figure 18 Packet-In Processing The Roles provide three broad bands of participation with the processing of PACKET_IN messages: • An ADVISOR may analyze and provide additional metadata about the packet (attached as “hints” for listeners further downstream), but does not contribute directly to the formation of the PACKET_OUT message.
Indicate to the sequencer that the packet should be sent (i.e. the PACKET_OUT should be transmitted back to the source datapath). • During an OBSERVER’s event callback, the context can be examined to determine the outcome of the packet processing. • Once a DIRECTOR invokes the PacketOut.send() method from their callback, the sequencer will convert the mutable PACKET_OUT message to its immutable form and attempt to send it back to the datapath.
Message Context The MessageContext is the object which maintains the state of processing a PACKET_IN message, and the formulation of the PACKET_OUT message to be returned to the source datapath.
Flow Tracker and Pipeline Manager The Flow Tracker is a sub-component of the core controller that facilitates management of flow rules, meters and groups across all datapaths managed by the controller. Its functionality is accessed through the ControllerService API. The Pipeline Manager is a sub-component that maintains an in-memory model of the flow table capabilities of (1.3) datapaths.
flow classes also enables the controller to arbitrate flow priorities and therefore minimize conflicts amongst co-resident SDN applications. A flow class can be registered with code similar to the following: import static com.hp.of.ctl.prio.FlowClass.ActionClass.FORWARD; import static com.hp.of.lib.match.OxmBasicFieldType.*; private static final String L2_PATH_FWD = "com.foo.app.l2.
The flow class is assigned a unique “base cookie” (top 16 bits of the 64 bit field) which must be “OR”ed with any cookie value that you wish to include in the flow (bottom 48 bits of the 64 bit field). The flow class “priority” is a private, logical key to be stored in the FlowMod “priority” field. It is used by the controller to look up the pre-registered flow class record, so that the match fields and actions of the FlowMod can be validated against the list of intended matches/actions.
Note that the info parameter provides information about the newly-connected datapath, and the isHybrid parameter indicates whether the controller is configured for hybrid mode or not. Such a component must register with the controller service to have its callback invoked at the appropriate times: controller.registerInitialFlowContributor(this); Metrics Framework The fundamental objectives to be addressed by the metering framework are as follows.
diagram, should demand relatively little effort from a developer beyond creating and updating the metrics they wish to utilize. Figure 19 Metrics Architecture Essentially a component or application must contact the MetricService to create a new TimeStampedMetric on their behalf; they will be returned a reference to the resulting (new) TimeStampedMetric object.
• An instantaneous measure. Example application: the amount of disk space consumed by metric data. TimeStampedHistogram • Example application: the frequency with which OpenFlow flow requests are sent to the controller by a specific switch. A ratio between two non-cumulative instantaneous numbers. Example application: the amount of disk space consumed by a specific application's metric data compared to all metric data.
• A primary tag (String, no default). • A secondary tag (String, no default). • A description (String, no default). • The summary interval in minutes (enumerated value, defaulted to 1 minute). • Whether values for the resulting TimeStampedMetric should be visible to the controller's JMX server (boolean, defaulted to false). • Whether values for the resulting TimeStampedMetric should be persisted (boolean, defaulted to true).
Tim eSt a m p edR ollin gCou n t er RollingCounterDescriptor RollingCounterDescriptorBuilder Tim eSt a m p ed Tim er TimerDescriptor TimerDescriptorBuilder Using MetricDescriptorBuilders represents the application of a well-known design pattern that allows most of the fields of each MetricDescriptor subtype instance that is produced to be defaulted to commonly-used values.
TimeStampedMetric is updated, relative to the controller's system clock, to indicate when the update occurred; this time stamp is used by the framework in processing the resultant values. The following methods may be used to update the value of each TimeStampedMetric type. • • TimeStampedCounter dec()—Decrements the current count by one. dec(long)—Decrements the current count by the specified number. inc()—Increments the current count by one.
public interface MetricService { public void removeMetric(TimeStampedMetric toRemove); } This method effectively unregisters the TimeStampedMetric from the metering framework so that the framework no longer holds any references to it and thus no longer exposes it via JMX, summarizes and persists its values, or does any other sort of processing on the TimeStampedMetric.
component or application can then iterate through the Collection, calling the following MetricService method for each TimeStampedMetric. Metric Registration API: public interface MetricService { public void registerMetric(TimeStampedMetric toRegister); } This will re-register the existing TimeStampedMetric reference with the metering framework. Depending upon how long the bounce took there may be a gap in the resulting data on disk for TimeStampedMetrics that are to be persisted.
• When the value of the TimeStampedMetric that the data point was formulated from was last updated • How many milliseconds (prior to the last update time) are encompassed by the reported value • The value measured over the milliseconds spanned by the data point • Sufficient information is thus provided should the data recipient wish to normalize the data to a standard interval length to smooth fluctuations in value that may be introduced by variations in the milliseconds spanned by time series values
• TimeStampedCounter • TimeStampedGauge • Ratio values from each "raw" data point are averaged, producing double values for the numerator and denominator readings during the summarized interval. TimeStampedRollingCounter • Sample counts from the "raw" data points are summed and rates from the "raw" data points are averaged, producing a long value for the total sample count and a double value for the average rate during the summarized interval.
The content exposed for each TimeStampedMetric is contingent on the type of TimeStampedMetric, but generally speaking the "live" values used by the TimeStampedMetric are visible as they are updated by the creator of the TimeStampedMetric.
Figure 21 JConsole In the list of nodes shows on the left, note the one that says HP VAN SDN Controller; this is the node under which all metrics exposed via JMX will be nested. Each application installed on the HP VAN SDN Controller will have a similar node under which all of the metrics exposed by that application are nested. Expanding the node will reveal all of the exposed metrics, which will look something like Figure 22 (note that this is just an example; real metrics will have different names).
Figure 22 JConsole – HP VAN SDN Controller Metrics The name displayed for each TimeStampedMetric is a combination of the primary and secondary tags and metric name specified in its MetricDescriptor during its creation; this combination will be unique among all TimeStampedMetrics monitored for a specific application. If the optional primary and/or secondary tags are not specified then only the fields provided will be used to formulate the displayed name for the TimeStampedMetric.
Figure 23 JConsole – Metric Example The metric UID, value field(s), and time spanned by the reported value (in seconds) are among the attributes that will be displayed. For those TimeStampedMetrics that are persisted as well as exposed via JMX, it is possible to see the seconds get reset when the value is stored; otherwise they grow forever. GUI SKI Framework - Overview The SKI Framework provides a foundation on which developers can create a browser-based web application.
• SKI Assets (Client Side): HTML Templates—providing alternate layouts for the UI Core SKI Framework—providing navigation, search, and basic view functionality Reference Documentation—documenting the core framework and library APIs • Reference Implementation—providing an example of how application code might be written SKI Assets (Server Side): Java Classes—providing assistance in formulating RESTful Responses Figure 24 SDN Controller main UI SKI Framework - Navigation Tree The SKI fr
Figure 25 SKI UI view diagram SKI Framework - Hash Navigation The SKI Framework encodes context and navigation information in the URL hash. For example, consider the URL: http://appserver.rose.hp.
Figure 26 SKI UI view hash diagram Next, the ctx (context), shown in Figure 27, can be used to help determine what data to retrieve from the Server RESTlet. Figure 27 SKI UI view and context hash diagram When the Asynchronous HTTP request returns, the data (likely in JSON form), as shown in Figure 28, can be used to populate the view’s DOM (grids, widgets, etc.).
Figure 28 SKI UI view data retrieval diagram Finally, the sub (sub-context) can be used to specify addition context information to the view. In this, case the second item is selected, as shown in Figure 29.
SKI Framework - View Life-Cycle All views are event driven and can react to the following life-cycle events: • Create—called a single time when the view needs to be created (that is, navigation item is clicked for the first time). At this time, a view will return its created DOM structure (that is, an empty table). • Preload—called only once, after the view is in the DOM. At this time, a view can perform any initialization that can only be done after the DOM structure has been realized.
Figure 30 SKI UI reference application From these pages, you have access to the most up to date documentation and reference code. The reference application includes examples on how to: • Add categories, navigation items and views. • Create a jQuery UI layout in your view. • Create various widgets (buttons, radios, and so on) in your view. UI Extension The SDN UI Extension framework allows third-party application to inject UI content seamlessly into the main SDN UI.
Introduction In a network managed by a controller, the controller itself stands out to be a single point of failure. Controller failures can disrupt the entire network functionality. HP VAN SDN Controller Distributed Coordination infrastructure provides various mechanisms that controller applications can make use of in achieving active-active, active-standby Distributed Coordination paradigms and internode communication.
Controller Teaming Teaming Configuration Service The Teaming Configuration Service provides REST interfaces (/team) that can be used to set up a team of controllers. Without team configuration, controller nodes will bootstrap in standalone mode. As the teaming is configured, identified nodes form a cluster and the controller Applications can communicate across the cluster using Coordination Service interfaces. The following curl command is used to get the current team configuration. 192.168.66.
import com.hp.api.Distributable class ValidDistributableType implements Distributable { } class ValidDistributableType implements Distributable, Serializable { } class ValidDistributableType extends SerializableType implements Distributable { } class InvalidDistributableType implements Serializable, Distributable { } Example of serializer registration: @Component public class Consumer { @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY, policy = ReferencePolicy.
Publish Subscribe service is provided by the Distributed Coordination Service which is in turn provided by the Teaming service. Please refer to the Javadoc for a detailed explanation of methods provided by publish-subscribe service. Publish Subscribe service also provides mechanisms to enable global ordering for specific message types. Global ordering is disabled by default. With global ordering enabled, all receivers will receive all messages from all sources with the same order.
import com.hp.util.dcord.CoordinationService; import com.hp.sdn.demo.example.SampleMessage; @Component public class PubSubExample { private CoordinationService coordinationService; private PublishSubscribeService pubSubService; @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY, policy = ReferencePolicy.DYNAMIC) protected volatile TeamingService teamingSvc; @Activate protected void activate() { coordinationService = teamingSvc.getCoordinationService(); pubSubService = coordinationService.
} Distributed Map A Distributed Map is a class of a decentralized distributed system that provides a lookup service similar to a hash table; (key, value) pairs are stored in a Distributed Map, and any participating node can efficiently retrieve the value associated with a given key. Responsibility for maintaining the mapping from keys to values is distributed among the nodes, in such a way that a change in the set of participants causes a minimal amount of disruption.
protected volatile TeamingService teamingSvc; @Activate protected void activate() { coordinationService = teamingSvc.getCoordinationService(); } public void createDistributedMap(String namespace) { Namespace mapNamespace = Namespace.valueOf(namespace); DistributedMap coordinationService.getMap(mapNamespace); distMap = if(distMap == null) { throw new RuntimeException("Can't get a Distributed Map instance.
} public void writeDistributedMap(String namespace, String key, String value) { ObjectMapper mapper = new ObjectMapper(); Namespace mapNamespace = Namespace.valueOf(namespace); DistributedMap coordinationService.getMap(mapNamespace); distMap = if(distMap == null){ throw instance."); new RuntimeException("Can't get a Distributed Map } distMap.put(key, value); } public void subscribeListener(String namespace) { Namespace mapNamespace = Namespace.
if (listener != null) { distMap.unregister(listener); } } } SimpleEntryListener.java package com.hp.dcord_test.impl; import com.hp.util.dcord.EntryEvent; import com.hp.util.dcord.EntryListener; public class SimpleEntryListener implements EntryListener { @Override public void added(EntryEvent entry) { // Any action to be taken on receipt of a message notification. // In this example, there is a simple print String string = "Added notification recieved"; System.out.
- The distributed map is already in memory and serves this purpose. If your application needs this data to be available if and when the coordination service is down then a local cache could be appropriate as well as reading from persistence any previously saved records to startup the cache in those scenarios. 3. Minimize tying map entry listeners to persistence.
GPB is a strongly-typed Interface Definition Language (IDL) with many primitive data types. It also allows for composite types and namespaces through packages. Users define the type of data they wish to send/store by defining a protocol file (.proto) that defines the field names, types, default values, requirements, and other metadata that specifies the content of a given record. [50, 51] Versioning is controlled in the .
repeated PhoneNumber phone = 4; } message AddressBook { repeated Person person = 1; } The protocol file above would be run through GPB’s Java compiler (See “.proto Compilation Process” Below) to generate the data access classes to represent these messages. Message builders would allow new instances of the message to be created for distribution by the Coordination Services. Normal set/get accessor methods will also be provided for each field.
A message version is a function of the field numbering and tags provided by GPB and how those are changed between different iterations of the data structure. The following are general rules about how .proto fields should be updated to insure compatible GPB versioned data: · Do not change the numeric tags for any existing (previous version) fields. · New fields should be tagged OPTIONAL/REPEATED (never REQUIRED). New fields should also be assigned a new, unique field ID.
We recommend under the project you wish to define and use GBP you place .proto files under the /src/main/proto directory. You can then make use of the GPB “option java_package” syntax to control the subdirectory/package structure that will be created for the generated Java code from the .proto file. The projects pom.xml file requires the following GPB related fields: com.google.protobuf protobuf-java 2.5.
· Package up the class files into a jar in the target directory · Install the compiled jar into your local Maven cache (~/.m2/repository) Have the .proto file and generated .java file displayed properly in your IDE from your project’s root directory, i.e. where the project’s pom.xml file is, execute the following: · mvn eclipse:clean · mvn eclipse:eclipse · Refresh the project in your IDE (Optional: clean the project as well).
implement com.hp.api.Distributable (a marker interface) to make it compatible with the Coordination Service. In terms of sharing these objects via the Coordination Service, the application developer should consider which field(s) are required to constitute a version of the Model Object versus which fields are optional. Commonly those fields that are defined in the objects constructor arguments can be considered required fields for a version of the object.
see not all fields in the Person Model Object must be defined and distributed with the .proto message. option java_outer_classname = "PersonProto; // Wrapper class name. message Person { required string name = 1; required int32 id = 2; optional string email = 3; } The application developer will generate the matching wrapper and builder classes for the .proto message to have a Java class that defines the message using protoc in the .proto Compilation Process section above.
PersonProto.Person message = null; try { message = PersonProto.Person.parseFrom(serialization); } catch (InvalidProtocolBufferException e) { // Handle the error } Person newPerson = new Person(); if (message != null) { newPerson.setName(message.getName()); newPerson.setId(message.getId()); return newPerson; } return null; } } In the serialize() method the builder pattern of the generated GPB message class is used to create a GPB version of the Person Model Object.
Table 3 System Status System Status Coordination Services Active Suspended Unreachable Depends Reason Available The controller is healthy and part of a cluster with a quorum. Unavailable The controller is unhealthy or part of a cluster with no quorum. whether active suspended or The controller is unreachable because of failures or network partition. Considerations: • A system never sees itself as unreachable.
Figure 32 Application Directory Structure Persistence Distributed Persistence Overview The SDN Controller provides a distributed persistence for applications in form of a Cassandra [10] database node running on each controller instance. A team of controllers serves as a Cassandra cluster. Cassandra provides the following benefit as a distributed database: • A distributed, peer-to-peer datastore with no single point of failure. • Automatic replication of data for improved reliability and availability.
• Consumer applications have high scalability requirements i.e. there are generally multiple instances of the app running on different controller nodes that need access to a common distributed database store. • The distributed database should be available independent of whether individual nodes are present or not e.g. if there are controller node crashes. • The applications have high throughput requirements: large number of I/O operations.
Figure 34 DAO pattern Distributed Data Model Overview Cassandra is a “column oriented” distributed database system and provides a structured key-value store. It is a NOSQL database and this means it is completely non-relational in nature. A reference table which can be useful for migration of a MySQL (RDBMS) to a NOSQL DB (Cassandra) is as illustrated in Figure 35.
Figure 36 Cassandra Row This is a simple row with columns. There are other variants like Composite Columns and Super Columns which allow more levels of nesting. These can be visited if there is a need for these in the design. One important characteristic of Cassandra is that it is schema-optional. This means the columns need not be defined upfront. They can be added dynamically as and when required and further all rows need not have the same number and type of columns.
• DAO–Data access object to interact with the persistence layer. • A sample of each of these will be presented in this section. For demonstration purposes a Demo application that persists Alerts in the Distributed Database (Cassandra) has been created. Business Logic Reference When the Cassandra demo application is installed, the OSGi service for business logic gets activated. This service provides a north bound interface.
package com.hp.demo.cassandra.model.alert; import com.hp.api.Id; import com.hp.demo.cassandra.model.AbstractTransportable; ... public class CassandraAlert extends AbstractTransportable { ...
The function of a DTO is to list out all the columns and provide setters/getters for each of the attributes. The application fills out all the values as necessary and passes the object down to the persistence layer using various queries.
ReadQuery getFindAlertByUidAndSysIdQuery( String uid, String sysId); WriteQuery getUpdateAlertStateQuery( CassandraAlert alert); WriteQuery getTrimAlertQuery(CassandraAlertFilter alertFilter); WriteQuery getAddAlertListQuery(List alerts); WriteQuery getUpdateAlertListQuery( List uids, String sysId, boolean state); WriteQuery getDeleteAlertListQuery( List uids, String sysId); ReadQuery getCou
return alert; } The method from the previous listing posts a new Alert into the database. It is a write query that creates a new row for every alert posted. The post method is called from other components whenever they want to log an alert message in the database. In this method, the call flow is as follows: 1. Create a transport object (DTO) for the incoming alert 2. Call the Distributed Query Service API (getAddAlertQuery) to get an object of type AddQuery.
} } The two methods shown read from the database in different ways. The first one issues a find query using a filter object. The filter specifies the pivot around which the query results are read. The second method reads a page of alerts and is used when there is a need to paginate results. This is mostly used by a GUI where pages of Alerts are displayed instead of a single long list of Alerts. The following is an example of filter object as defined in the demo application. CassandraAlertFilter.
CassandraAlertSortAttribute> { public CassandraAlertDao() throws PersistenceConnException { cfList.add(new AlertsBySeverity()); cfList.add(new AlertsByState()); cfList.add(new AlertsByTopic()); cfList.add(new AlertsByOrigin()); cfList.add(new AlertsByTimeStamp()); cfList.add(new AlertsCount()); cfList.add(new AlertsByUidAndSysId()); } private static class AlertColumnFamily { private static final ColumnName SYS_ID_NAME = ColumnName.valueOf("sysId", BasicType.
private static ColumnFamilyDefinition CF_DEF = new ColumnFamilyDefinition( COL_FAMILY, BasicType.UTF8, BasicType.BYTES, BasicType.UTF8, null, cfMeta); private static final EnumColumnDecoder SEV_DECODER = new EnumColumnDecoder(Severity.
case TOPIC: StringColumn topic1 = (StringColumn) row1.getColumn(TOPIC_COL_NAME); StringColumn topic2 = (StringColumn) row2.getColumn(TOPIC_COL_NAME); retVal = topic1.compareTo(topic2); break; } return retVal; } } ... In this code, there is defined a constructor and the main column family.
@Component (ordinal = 1) private String id; private SeverityComposite(Severity severity, String id) { this.severity = (severity == null) ? null :severity.name(); this.id = id; } public Severity getSeverity() { return Enum.valueOf(Severity.class, this.severity); } public String getId() { return this.id; } @Override public int hashCode() { ... } @Override public boolean equals(Object obj) { ... } @Override public int compareTo(SeverityComposite other) { int comparison = 0; if (other.
private static final ColumnFamily COL_FAMILY = ColumnFamily.newColumnFamily("AlertsBySeverity", StringSerializer .get(), serializer, ByteSerializer .get()); private static final ColumnFamilyDefinition CF_DEF = new ColumnFamilyDefinition( COL_FAMILY, BasicType.UTF8, BasicType.BYTES, new CompositeType(BasicType.UTF8, BasicType.
@Override public void prepareDelete(CassandraAlert transportable, DataStoreContext context) throws Exception { SeverityComposite deleteColumn = new SeverityComposite(transportable.getSeverity(), transportable.getId().getValue()); context.getContext().delete(COL_FAMILY, ROW_KEY, ColumnName . valueOf(deleteColumn)); } @Override public void prepareUpdate(CassandraAlert oldT, CassandraAlert newT, DataStoreContext context) throws Exception { ...
CassandraAlertDao.java: @Override public CassandraAlert convert(CassandraStorable source) { if (source == null) { throw new NullPointerException(...); } final CassandraAlert alert = new CassandraAlert(source.getId()); ColumnVisitor visitor = new ColumnVisitorAdapter() { @Override public void visit(BooleanColumn column) { alert.setState(column.getValue()); } @Override public void visit(DateColumn column) { alert.setTimestamp(column.
} return alert; } getColumnDecoder() getColumnDecoder–This method takes a column as argument and returns the data type of that column to the caller. This is required for reading the columns in correct format. CassandraAlertDao.java: @Override protected ColumnDecoder getColumnDecoder( ColumnName columnName) { if (columnName == null) { throw new NullPointerException(...); } if (AlertColumnFamily.SEVERITY_COL_NAME.equals(columnName)) { return AlertColumnFamily.
AlertColumnFamily.DESC_COL_NAME,transportable.getDescription())); storable.setColumn(new EnumColumn( AlertColumnFamily.SEVERITY_COL_NAME, transportable.getSeverity())); storable.setColumn(new DateColumn( AlertColumnFamily.TIMESTAMP_COL_NAME, transportable.getTimestamp())); storable.setColumn(new BooleanColumn( AlertColumnFamily.STATE_COL_NAME, transportable.getState())); storable.setColumn(new StringColumn( AlertColumnFamily.ORIGIN_COL_NAME, transportable.
colFamilies.add(AlertColumnFamily.CF_DEF); colFamilies.add(AlertsBySeverity.CF_DEF); colFamilies.add(AlertsByState.CF_DEF); colFamilies.add(AlertsByTopic.CF_DEF); colFamilies.add(AlertsByOrigin.CF_DEF); colFamilies.add(AlertsCount.CF_DEF); colFamilies.add(AlertsByUidAndSysId.CF_DEF); colFamilies.add(AlertsByTimeStamp.CF_DEF); return colFamilies; } getMainColumnFamily() This method returns a handle to the main column family. CassandraAlertDao.
context.getTransactionContext() .prepareTransaction(AlertsCount.COL_FAMILY.getName(), AlertsCount.ROW_KEY); CassandraStorable row = context.getTransactionContext(). executeCriticalSection(procedure); for (Column col : row.getColumns()) { id.add(col.getName().getValue()); } return id; } if (filter.getOriginCondition() != null) { final ByteBufferRange range; switch (filter.getOriginCondition().getMode()) { case EQUAL: range = AlertsByOrigin.serializer.buildRange() .
context.getTransactionContext() .prepareTransaction(AlertsByOrigin.COL_FAMILY.getName(), AlertsByOrigin.ROW_KEY); CassandraStorable rows = context .getTransactionContext(). executeCriticalSection(procedure); Collection id = new ArrayList(); for (Column orig : rows.getColumns()) { id.add(orig.getName().getValue().getId()); } // Add row Id's to the final Id set rowsSet.retainAll(id); } // Severity Condition. Only IN is supported for now. if (filter.
executeCriticalSection(procedure); Collection id = new ArrayList(); for (Column sevRow : rows.getColumns()) { id.add(sevRow.getName().getValue().getId()); } if (rowsSet.isEmpty()) { rowsSet.addAll(id); } else { rowsSet.retainAll(id); } } } } //Topic filter if (filter.getTopicCondition() != null) { final ByteBufferRange range; switch (filter.getTopicCondition().getMode()) { case EQUAL: range = AlertsByOrigin.serializer.buildRange() .withPrefix(filter.
} }; // Start the transaction context.getTransactionContext() .prepareTransaction(AlertsByTopic.COL_FAMILY.getName(), AlertsByTopic.ROW_KEY); CassandraStorable rows = context .getTransactionContext() .executeCriticalSection(procedure); // Add the rows to the row set Collection id = new ArrayList(); for (Column topic : rows.getColumns()) { id.add(topic.getName().getValue().getId()); } // Add row Id's to the final Id set if(rowsSet.isEmpty()) { rowsSet.
@Override public CassandraStorable execute() throws Exception { return context.getContext() .get(AlertsByState.COL_FAMILY, AlertsByState.ROW_KEY, range, AlertsByState.STATE_DECODER); } }; // Start the transaction context.getTransactionContext() .prepareTransaction(AlertsByState.COL_FAMILY.getName(), AlertsByState.ROW_KEY); CassandraStorable rows = context .getTransactionContext().
} // Convert the pageRequest CassandraStorable convertMark = (CassandraStorable) pageRequest.getMark(); final MarkPageRequest convertedPageRequest = pageRequest.convert((convertMark != null) ? convertMark.getId() : null); Procedure>> procedure = new Procedure>>() { @Override public MarkPage> execute() throws Exception { return context.getContext().get(AlertsCount.COL_FAMILY, AlertsCount.
.getValue()); return new MarkPage(pageRequest1, id); } return null; } compareRows() Compares two rows. This method is used for sorting the result set. CassandraAlertDao.java: @Override protected int compareRows(CassandraStorable row1, CassandraStorable row2, SortSpecification .SortComponent s) { if (s.getSortOrder() == SortOrder.ASCENDING) { return AlertColumnFamily.compareColumns(row1, row2, s.
Controller databases License compliance history and metrics log data In a teaming environment, the teaming configuration User repository folder (for user-installed applications) • • • • • Controller configuration folder All application data that goes into the controller databases will be automatically backed up. The applications should consider using backup and restore if they have external data that is not already a part of the list mentioned above.
// Please make sure you catch exceptions and throw runtime exceptions // so that backup service can operate correctly. // If the error is not thrown, backup service will assume // a successful staging operation for the given application. } catch (Exception e) { // Throw RuntimeException if you need to stop backup in the // event of a failure. throw new RuntimeException (e); } } @Override public void onBackupDone(BackupRestoreStatus status) { // Take application specific action on backup done.
Device Driver Framework Device Driver Framework Overview The SDN Controller provides a Device Driver Framework with the following capabilities: • Maintains identity information about the types of physical devices recognized by the framework. • Determines the type of each physical device using information discovered through the OpenFlow handshake, as well as direct interaction with the device.
user of the device driver framework does not require knowledge of the type of device with which it is interacting. Device Type Information Information describing a type of device is stored in XML files. This information describes the attributes (capabilities) of a type of device, not an actual device that exists on the network. As physical devices are discovered, the Device Driver Framework will determine the best device type for the discovered device.
each product in the 5400 product line. The device type J9642A is the 6 slot 5406zl product, and the J9643A is the 12 slot 5412zl product. For each specific device type the XML file contains the product description, the SNMP sysObjectId assigned to the device, and Facets that can be used with this type of device. The XML file may also contain “flags” that define additional information about the type of device. For example, the 5400.xml file contains a flag indicating 5400 products are chassis products.
ProCurve 3800 3800 .1.3.6.1.4.1.11.2.3.7.11.119 J9573A 3800-24G-PoE+-2SFP+ Switch . . . . . . . Component Responsibilities The following figure illustrates the components that make up the Device Driver Framework.
Handler Facet may also be used to obtain information directly from the device in order to better determine its type. Device Manager: The Device Manager receives information about devices from the discovery components. The Device Manager will maintain the device information, store the information in a database, and share the information with other team members if configured to operate as a member of a team.
4) Assume that OF Device Discovery was unable to determine the exact type of device in step 3. Also assume in step 3 that it is determined that the device is manufactured by HP. OF Device Discovery will interact with the Device Driver Manager to obtain the HP Device Identity Handler Facet that can be used to interact with the device using SNMP to obtain additional information. 5) The HP Device Identity Handler Facet must obtain the correct security key to interact with the device.
Chassis Devices A “Chassis Device” is a modular chassis product that accommodates switching and management modules. Modules with different capabilities can be inserted into the chassis. The HP Device Identity Handler Facet will examine the flags in the Device Type data (i.e., XML data) to determine if the device is a chassis product. For HP chassis switches, module identification is important.
Device: This object is maintained by Device Manager and contains a DeviceInfo object and other information and status about the device. This object can be obtained by Applications using the Device Service API. DeviceHandler: This object contains a DeviceInfo object and the IP address of the device. This object is used by Application to get Handler Facets. Note: Ports/Interfaces are maintained as a separate entity associated to a device and are not shown in the figure above.
* @param ofm the FlowMod to be validated and adjusted if needed. * @return set of FlowMods. */ private Set adjustFlowMod(DeviceService ds, DataPathId dpid, OfmFlowMod ofm) { Set adjustedFlows = new HashSet<>(OfmFlowMod); try { // get the device object associted with the data path id (dpid) Device dev = ds.getDevice(dpid); if (!dev.isOnline()) { // get the DeviceInfo object which is needed to get a Facet DeviceInfo di = dev.
At line 15 the reference to the Device Service (ds) is used to get a Device object for the Data Path ID. The Device object contains information about the device including the Facets that can be used with that device. At line 17 the Device object (dev) is used to check if the device is online. If the device is offline, then no validation and adjustment is performed. At line 19 the Device object is used to get a DeviceInfo object (di).
if (dh.isSupported(VlanHandlerFacet.class)) { // use the Devcie Handler to get the Handler Facet VlanHandler hf = dh.getFacet(VlanHandlerFacet.class); // use the Handler Facet to get vlan information vlans = hf.getVlans(); } } catch (Exception e) { e.printStackTrace(); } return vlans; } /** * Returns the IP address associated with the DeviceInfo object. * * @param di DeviceInfo object * @return IP address for the DeviceInfo object */ private IpAddress getIp(DeviceInfo di) { DeviceIdentity facet = di.
method getIp() is used to get the IP address. The getIp() method is shown in lines 35 through 44, and will use the Device Identity Facet to get the IP address. At line 22 the Device Handler (dh) is used to determine if the VlanHandlerFacet is available for this device. At line 24 the Device Handler is used to get the Handler Facet (VlanHandlerFacet). AT line 27 the Handler Facet (hf) is used to interact with the device and retrieve the vlan information.
4 Application Security Introduction This chapter provides recommendations and requirements for designing secure applications.
Assumptions Software development practices It is assumed that secure and high-assurance development practices are used, including: • • • • Good design practice including design reviews and threat analysis Security awareness through requirements and training Static code analysis with corrective action taken before product release Product testing includes “negative” testing, i.e., responses to input errors, network protocol fuzzing, etc.
• an untested or corrupted module. (This is currently expected to be a future REQUIREMENT.) External applications performing signature validation (e.g., on updates) SHOULD run with low privilege but require high user privilege (e.g., root) to initiate installation or modification. Keys and credentials There SHALL NOT be any default credentials. There SHALL NOT be any permanent credentials. Keys used for management and authentication must not be transferable.
System Integrity External applications must run in separate memory spaces. Software validation • • • • All downloadable files must be signed. All file signatures must be validated 1) at time of file saving and 2) loading. Signatures and validation shall apply to script files, e.g., Tcl, Python, as well as to binary executables and Java .jar files. It is highly desirable to validate system integrity on a running system—boot time is good, but might not be sufficient.
5 Including Debian Packages with Applications This chapter documents the requirements for installing a debian package with an application. Steps for removing a debian package are also provided.
private static final String APP_DEBIAN_FILE = "debinst-sample_1.0_amd64.deb"; . . . private void installDebian() throws IOException { File unzipDir = appService.getAppUnzipDir(APP_ID, APP_VERSION); File deb = new File(unzipDir, APP_DEBIAN_FILE); . . . Programming Your Application to Install a Debian Package on the Controller Determining when to install the Debian Package The application will need to determine its state at start up to know when it should install its debian packages.
. . . State state = appService.state(APP_ID); switch (state) { case INSTALLING: case ENABLING: installDebian(); installMonitorTask = taskExecutorService.scheduleWithFixedDelay(new InstallMonitor(), Measure.valueOf(61, SI.SECOND), Measure.valueOf(60, SI.
if an error occurs writing the file (which will currently manifest itself as an internal server error 500 back to the application code). After the debian file has been uploaded to the admin space it must be installed. The installation is accomplished via a script that is executed by the business logic in the admin space. The business logic will only look in the admin upload directory for the debian package to install.
Installing a debian package The path to install the debian file is “/” and requires an action string with the action word “install” and the name of the debian package. The REST API will return a status of 200, and the string associated with the response is JSON structure with the current status of the contorller and Admin services (sdnc and sdna). . . . uri = adminRest.uri(IpAddress.LOOPBACK_IPv4, "/"); // then we need to install it ResponseData response = adminRest.post(adminRest.
App Event Listener If an application registers an AppEventListener with the application service then it can receive notification of pending uninstall or disable actions. These notifications are made prior to shutting down the application in the OSGi runtime environment. The AppEventListener registered by the application will hear of any application event for all installed applications.
private byte[] uninstallRequestBytes() throws UnsupportedEncodingException { String uninstall "{ \”action\”: " + “\”uninstall\”, \”name\”: “ + “\”” + APP_DEBIAN_FILE + “\”}"; return uninstall.
6 Sample Application The following information describes how to create a complete sample application to show how all the parts fit together, using various parts of the SDN Controller framework. The SDK provides a tool to generate a skeletal application project structure as a starting template for custom projects. This tool automates the steps described in the following information.
Table 4 Sample Application Information Property Value Application Name Health Monitor Application Short Name hm Company Hewlett-Packard Company Short Name hp The following information describes the how to manually create the application workspace. Creating Application Directory Structure Source projects and configuration files will be organized in a directory structure. Any structure works but one similar to the one suggested in Figure 37 is recommended.
applications. hm-bl Module / Source Code Project Business logic. Implementation of the application’s API. This project is private to the application. hm-rs Module / Source Code Project Application’s Representational State Transfer (REST) API or RESTful Web Services. This project is private to the application; however RESTful web services are accessible via the HTTP protocol. hm-ui Module / Source Code Project Application’s WEB interface. This project is private to the application.
Under the application root folder (hm-root) create the application parent POM file using the template from the HP VAN SDN Controller SDK. The following list shows the root pom.xml after updating the template with Table 4. Sample Application Root POM File: 4.0.0 com.hp.
Sample Application Module POM File: com.hp.hm hm-root 1.0.0-SNAPSHOT ../hm-root/pom.xml 4.0.
the name property. The order property must contain a comma separated list of bundle symbolic names indicating the order each bundle should be started in. The first bundle in this list is started by OSGi first, whereas the last bundle in the list is started by OSGi last. Application Packaging POM File The installable application is a simple .zip file containing the output .jar files generated at the target directory - at compile time - of each application module (“…/health-monitor/hmapi/target/hm-api-1.0.0.
com.hp.hm hm-bl ${project.version} maven-antrun-plugin package-app package PAGE 149Creating Module Directory Structure At this point there should be a pom.xml file under each folder listed in Table 5. Each application module folder is also a source code project, so a few more subdirectories must be created in order to properly generate the Java Eclipse projects. For each application module create the directory structure as displayed in Figure 39. Figure 39 Application Module Directory Structure The application development workspace is now completed.
--company-name Optional full company name, for example, 'Hewlett-Packard' --description Optional brief description of the application --rest-path Optional REST API path , e.g. 'health' --template Template used to generate the application code, for example, ‘skeleton' To protect any existing code customizations, the 'directory' parameter needs to denote a new directory, that is, one that does not exist yet.
Updating Project Dependencies The command described in Creating Eclipse Projects on page 145 creates the Eclipse projects resolving all dependencies defined in the POM files. Once the projects have been created and imported into Eclipse, the same command may be used to maintain dependencies. Execute the command when a dependency is added to the POM file or removed, and then just refresh the projects within Eclipse.
Model com.[COMPANY_SHORT_NAME].[APPLICATION_SHORT_NAME].model com.hp.hm.model API com.[COMPANY_SHORT_NAME].[APPLICATION_SHORT_NAME].api com.hp.hm.api Business Logic com.[COMPANY_SHORT_NAME].[APPLICATION_SHORT_NAME].impl com.hp.hm.impl REST API com.[COMPANY_SHORT_NAME].[APPLICATION_SHORT_NAME].rs com.hp.hm.rs UI com.[COMPANY_SHORT_NAME].[APPLICATION_SHORT_NAME].ui com.hp.hm.ui DAO API com.[COMPANY_SHORT_NAME].[APPLICATION_SHORT_NAME].dao.api com.hp.hm.dao.api DAO Model com.
Figure 41 SDN Controller Applications 3. Upload and install the application as illustrated in Figure 42. 4. Click Browse button to select the application zip file (hm-*.zip in our example) and select Upload; when the upload operation is completed the dialog should load the application’s META-data defined in the application descriptor file created in Application Descriptor on page 141. 5. Click Deploy to install the application.
Figure 43 Application Management Application Code The following information walks through the code and shows how to implement the application. This is useful as it illustrates different services in action. Space doesn’t permit implementing the entire application, however this shows the major parts, and finishing the implementation is a matter of creating a variation of what is shown. Javadocs will be omitted to save space, however they are important and must be provided in production code.
Defining Model Objects The application requires some standard data structures that act as transfer objects [31]. This example uses a Switch data structure to hold all the information about the Open Flow Switch, shown in the following listing (note that a better name would be OpenFlowSwitch, but a shorter name was selected due space limitations illustrating code samples). Switch.java: package com.hp.hm.model; import java.util.UUID; import com.hp.api.Id; import com.hp.api.Transportable; import com.hp.sdn.
required modules are used at all application levels (Presentation logic, controller logic, business logic, cross-cutting logic, and so on), thus all the application modules will depend on them. Later it’ll be shown how specific dependencies to certain modules are added into the specific module POM file.
NOTE At this point AbstractModel (which Model extends) and Transportable are used just to denote that Switch follows the data transfer object pattern [31]. AbstractModel is a convenient partial implementation because it properly overrides equals() and hashCode() methods. However, if the HP VAN SDN Controller’s persistence framework is used to persist data, then data transfer objects take an explicit role and they must follow certain hierarchical constraints.
Open the hm-root/pom.xml file and add the XML extract from the following list to the node. After updating the POM file update the Eclipse project dependencies (see Updating Project Dependencies on page 146). HP SDN Controller Framework Common Dependencies: com.hp.util hp-util-misc ${hp-util.version} com.hp.util hp-util-api ${hp-util.
import com.hp.util.filter.EqualityCondition; import com.hp.util.filter.StringCondition; ... public class SwitchFilter { private StringCondition nameCondition; ... // Implement setters and getters for all conditions. // Good practice to override toString() } The following listing depicts a usage example: Create a filter to retrieve all open flow switches with a name that contains the text ‘My Switch.’ Switch Filter Usage Example: SwitchFilter filter = new SwitchFilter(); filter.
application source code included with the HP VAN SDN Controller SDK. SwitchTest should be located under hm-model/src/test/java/com/hp/hm/model directory. SwitchTest.java: package com.hp.hm.model; import com.hp.test.BeanTest; import com.hp.test.EqualityTester; import com.hp.test.SerializabilityTester; public class SwitchTest { ... @Test public void testGettersAndSetters() throws Exception { Switch device = //... create an instance BeanTest.
Creating Domain Service (Business Logic) The following information defines a service to provide Open Flow Switches functionality (The sample application’s business logic). This service basically provides operations to create, read, update and delete open flow switches (CRUD operations). Service API Service API abstracts the business logic implementation by defining an API that clients or consumers use in order to interact with Open Flow switches. This API will act as the Open Flow Switch service contract.
Service Implementation Implementation of our API or services will be located at the hm-bl module. As with hm-api, the business logic module will also depend on the hm-model, as well as on the hm-api module. So open the hm-bl/pom.xml file and add the XML extract from the following listing to the node; after updating the POM file update the Eclipse project dependencies (see Updating Project Dependencies on page 146). Application API Dependency: com.hp.
SwitchManager.java (Sample Application Service Implementation): package com.hp.hm.impl; ... public class SwitchManager implements SwitchService { @Override public Switch create(String name) { Switch s = new Switch(name); if (isEmpty(s.name()) { s.setname("Switch-" + s.getId().getValue().toString()); } store.put(s.getId(), s); return s; } @Override public Collection getAll() { synchronized (store) { return Collections.unmodifiableCollection(store.
Providing Services with OSGi Declarative Services The OSGi standard component framework, called Declarative Services [33], is used to create component-oriented applications. It is called declarative because there is no need to write explicit code to publish or consume services. A component describes functional building blocks that are typically more coarse-grained than what we normally associate with objects. These building blocks are typically business logic; they provide functionality via interfaces.
NOTE The usage of SwitchComponent may be omitted and directly annotate SwitchManager if preferred. The generated example application does not provide a SwitchComponent.java file. SwitchComponent.java (Sample Application OSGi Service Component): package com.hp.hm.impl; import org.apache.felix.scr.annotations.Component; import org.apache.felix.scr.annotations.Service; ...
components. It is also annotated with @Service to denote this component should be published so it is consumed by other components. As mentioned above, don’t write explicit code to publish or consume services. Thus, SwitchService is ready to be published when the application is installed into the HP VAN SDN Controller.
Figure 47 Virgo 3.5.0 Admin Console Artifacts Figure 48 Virgo 3.5.
Figure 49 Virgo 3.5.0 Admin Console Application’s Business Logic Bundle Virgo 3.6.1 Since Virgo dropped the “Published Services” section illustrated in Figure 49, it is not possible to see published services unless they are already consumed. At this point in this example, the service is not being consumed so it is not possible to see it as published service. However, this section illustrates the way of verifying consumed services when the SwitchService is already being consumed by the hm-rs module.
Figure 50 Virgo 3.6.1 Admin Console Figure 51 Virgo 3.6.
Figure 52 Virgo 3.6.1 Admin Console Application Plan Figure 53 Virgo 3.6.
Consuming Services with OSGi Declarative Services OSGi Declarative Services may also be used to consume other services: injecting references of other components (Dependency components) into our components (via OSGi’s dependencyinjection framework). Assume the business service implementation (SwitchManager) depends on the SystemInformationService - a service provided by the HP VAN SDN Controller to request system information such as the system IP Address, and so on.
public void setAlertService(AlertService alertService) { // Mutators are used for optional dependencies. this.alertService = alertService; } ... } SystemInformationService Module Dependency: com.hp.sdn sdn-adm-api ${sdn.
private SwitchManager delegate; @Activate public void activate() { // activate() is called after all mandatory dependencies // are satisfied delegate = new SwitchManager(systemInformationService); delegate.setAlertService(alertService); } @Deactivate public void deactivate() { delegate = null; } protected void bindAlertService(AlertService service) { alertService = service; // TODO: Decorate the business logic with the optional service. if (delegate != null) { delegate.
allows us to do any pre/post processing when the binding/unbinding takes place - useful when using optional services. The name for the methods to bind/unbind follows a standard defined by OSGi [5]. The name is composed by the prefix bind/unbind plus the name of the variable in camel case format. Since the variable is called alertService, the method to bind must be called bindAlertService (“bind” plus the name of the variable with the first letter upper case).
Implementation of the REST API is located in the hm-rs module. Now, create the Switch REST API which is named SwitchResource – The suffix Resource is used to denote REST web services. The following listing shows an extract of the resource. For the moment use fake data, in later information replace the fake implementations by more realistic ones. In order to implement REST web services the module needs to declare some dependencies. Open the hm-rs/pom.
public Response delete(@PathParam("id") long id) { return Response.ok().build(); } } REST Module Dependencies: com.hp.hm hm-model ${project.version} com.hp.hm hm-api ${project.version} com.hp.util hp-util-rs ${hp-util.
com.sun.jersey.jersey-test-framework jersey-test-framework-grizzly 1.17 test com.sun.jersey jersey-servlet 1.17 The hm-rs module needs to be modified so it produces a web application archive (.war file) [35] as output so it is deployed as a web application that serves the RESTful web services for this sample application.
exclude-paths ^(NONE)[/]*(.*)$ com.sun.jersey.config.property.resourceConfigClass com.sun.jersey.api.core.ClassNamesResourceConfig com.sun.jersey.config.property.classnames com.hp.hm.rs.SwitchResource com.hp.
/* Next, update the hm-rs module POM file hm-rs/pom.xml with the extract shown in the following listing to generate the .war file during the build process. hm-rs/pom.xml to generate .war: ... 4.0.0 hm-rs war ... com.hp.hm.rs sdn/hm/v1.0 sdn/hm/v1.0
com.sun.jersey.server.impl.container.servlet, com.hp.util.rs, com.hp.util.rs.auth, com.hp.sdn.rs.misc,* !${banned.rs.paths} ${webapp.context} ${web.context.path} org.apache.maven.plugins maven-war-plugin 2.2 WEB-INF/lib/*.
http://www.eclipse.org/virgo/schema/plan http://www.eclipse.org/virgo/schema/plan/eclipse-virgo-plan.xsd"> Finally update the application packaging POM file hm-app/pom.
... Trying the REST API with Curl The following information illustrates a method to try the REST API created previously in Creating Domain Service Resource (REST Interface of Business Logic Service) on page 169 using Curl [17]. See Figure 6 for installation instructions. Build and install the application as described in Building the Application on page 146 and Installing the Application on page 147.
Figure 54 REST API CURL Execution Example RESTful Web Services Unit Test Even though at this point the implementation uses fake data, a unit test is shown to illustrate the utility classes provided by the HP VAN SDN Controller SDK; creating good test cases is application dependent and it is out of the scope of this document. The following listing shows the unit test for SwitchResource using the infrastructure class ClientResourceTest provided by the HP VAN SDN Controller SDK.
ResourceTest.setDefaultMediaType(MediaType.APPLICATION_JSON); } // When using the inherited methods get(...), post(...), put(...) and // delete(..) if exceptions are thrown by the Resource (REST) or if the // returned code is different than 200 (OK) the test fail.
commons-configuration commons-configuration 1.6 com.fasterxml.jackson.core jackson-databind 2.1.4 Domain Service - REST API Integration Figure 3 illustrates a common pattern used when working with Servlets: The Model-View-Controller (MVC) pattern. In this pattern the Servlet acts as the Controller.
import org.apache.felix.scr.annotations.Reference; import org.apache.felix.scr.annotations.ReferenceCardinality; import org.apache.felix.scr.annotations.ReferencePolicy; import org.apache.felix.scr.annotations.References; import com.hp.hm.api.SwitchService; import com.hp.sdn.rs.misc.ServiceLocator; ... @Component(immediate=true, specVersion="1.1") @References( value={ // Add a @Reference (Separated by comma) for each // domain service exposed to the REST layer.
import com.hp.hm.api.SwitchService; ... @Path("switches") public class SwitchResource extends ControllerResource { @GET @Produces(MediaType.APPLICATION_JSON) public Response list() { SwitchService service = get(SwitchService.class); List switches = service. getAll(); String result = "{switches:{}}"; // TODO: Encode switches return ok(result).build(); } ... } The following SwitchResourceTest.
@Override @After public void tearDown() throws Exception { super.tearDown(); sl.unregister(SwitchService.class, switchServiceMock); } @Test public void testList() { // Create mocks and define test case data List switches = Collections.emptyList(); // Create test case // Recording phase (Define expectations) EasyMock.expect( switchServiceMock.getAll().andReturn(switches); // Execution phase EasyMock.
package com.hp.hm.rs.json; import com.fasterxml.jackson.databind.JsonNode; import com.fasterxml.jackson.databind.node.ObjectNode; import com.hp.util.json.AbstractJsonCodec; import com.hp.util.json.JsonCodec; ...
return device; } @Override public ObjectNode encode(Switch device) { ObjectNode node = mapper.createObjectNode(); node.put(MAC_ADDRESS, device.getMacAddress().toString()); if (device.getId() != null) { node.put(ID, device.getId().getValue().longValue()); } if (device.getIpAddress() != null) { node.put(IP_ADDRESS, device.getIpAddress().toString()); } if (device.getFriendlyName() != null) { node.put(FRIENDLY_NAME, device.getFriendlyName()); } if (device.getActiveState() != null) { node.
JSON Module Dependencies: com.hp.util hp-util-codec ${hp-util.version} com.hp.sdn sdn-adm-api ${sdn.version} The HP VAN SDN Controller SDK uses the Jackson API [38] as the underlying API to handle JSON conversion.
Now that the JSON factory is in place, update the SwitchResource to use the JsonService to encode and decode Switch objects. The SwitchResource.java Using JSON Codecs listing shows a modification of SwitchResource that uses a JSON codec to encode Switch objects. SwitchResource.java Using JSON Codecs: package com.hp.hm.rs; import com.hp.sdn.json.JsonService; ... @Path("switches") public class SwitchResource extends ControllerResource { @GET @Produces(MediaType.
super("com.hp.hm.rs"); } @Override @Before public void setUp() throws Exception { super.setUp(); ResourceTest.setDefaultMediaType(MediaType.APPLICATION_JSON); switchServiceMock = EasyMock.createMock(SwitchService.class); sl.register(SwitchService.class, switchServiceMock, Collections. emptyMap()); jsonServiceMock = EasyMock.createMock(JsonService.class); sl.register(JsonService.class, jsonServiceMock, Collections.
// Execution phase EasyMock.replay(switchServiceMock, jsonServiceMock); String response = get(BASE_PATH); // Verification phase assertResponseContains(response, switchesJson); EasyMock.verify(switchServiceMock, jsonServiceMock); } ... } JSON Message Versioning While there are no built-in constructs for JSON message versioning there are conventions application developers can follow to ensure that multiple versions of a messages can be exchanged via REST without causing unexpected errors.
Controller-Controller Communication via REST (Sideways APIs) RESTful Web Services (or REST APIs) [2] [1] also represent a convenient way to enable communication between controllers, and the HP VAN SDN Controller framework provides some facilities to do so. This section illustrates a way to enable such communication. This section is optional and the code illustrated here won’t be part of our sample application, it is just a section dedicated to illustrate this useful communication mechanism.
import com.hp.sdn.json.JsonService; import com.hp.sdn.misc.ResponseData; import com.hp.sdn.misc.ServiceRest; import com.hp.util.StringUtils; ... @Component @Service public class SwitchTransferManager implements SwitchTransferService { // Some specific dependencies (like javax.ws.rs.core.Response) are needed // to implement transfer services that use RESTful web services as the // underlying mechanism to achieve communication. It is recommended to // locate transfer services in a separated module.
throw new RuntimeException(message.toString()); } List remoteDevices = jsonService.fromJsonList( responseData, Switch.class); return new HashSet(remoteDevices); } } ServiceRest is a service provided by the HP VAN SDN Controller framework that enables HTTP communication by offering the common operations GET, POST, PUT and DELETE. It also takes care of service authentication. In order to use from ServiceRest we need to add the module it is located at as a dependency. Open the hm-bl/pom.
In real applications creating `new modules to locate communication services would result in a better organization: For example, using hm-ext-api module for the communication service interfaces (instead of hm-api as in this example) and hm-ext for the implementations (instead than hm-bl as in this example). Creating RSdoc Trying the REST API with Curl on page 177 describes a way to try the REST API by executing commands.
public DocProvider() { super("hm", "rsdoc", DocProvider.class.getClassLoader()); } } NOTE The name used to call the super class constructor (“hm” in DocProvider.java listing) must not contain spaces; it may be any name but with no spaces because it is used to generate internal paths. RSdoc Provider Dependency: com.hp.sdn sdn-adm-api ${sdn.version} Modify hm-rs/pom.
run ...
consumption or because it is meant to be used internally by a sideway API (see ControllerController Communication via REST (Sideways APIs) on page 190) it may be annotated with @RsDocIgnore as illustrated in the following listing. RsDocIgnore Annotation: package com.hp.hm.rs; import com.hp.api.rsdoc.RsDocIgnore; ... @Path("mypath") public class MyResource extends ControllerResource { @GET @Path("internal") @Produces(MediaType.APPLICATION_JSON) @RsDocIgnore public Response internalMethod() { ...
Figure 57 Trying Application’s REST API with RSdoc Example NOTE The tool offered by the HP VAN SDN Controller SDK that generates the RSdoc takes the Javadoc [21] to generate the REST API documentation as illustrated in Figure 11. Therefore, it is mandatory to write Javadoc for the REST APIs (In general, production code classes should be properly documented). If a REST API method does not contain Javadoc, the entire REST API won’t be included in the RSDoc.
Creating Views The SKI framework uses JavaScript [40] as the underlying technology, thus the views are DynamicHTML based. Start by creating the application’s cascading style sheets [41]. Create the file hmui/src/main/webapp/css/hm.css with the content from the following hm.css listing. This example uses a very simple cascading style sheet, however any style desired can be created and as many style sheets as needed. hm.css: .hm { background-color: red; } Now create a view to display the Open Flow Switches.
def.addView('hmTab') }; }(SKI)); Now create a script that adds a menu entry to the navigation panel so the hm view is accessible from the SDN Controller’s GUI. Create the file hm-ui/src/main/webapp/js/hm-nav.js with the content from the following hm-nav.js listing. Use hm-nav.js to add as many entries in the navigation panel as the application needs; there is just one JavaScript file dealing with the navigation panel. hm-nav.js: // JSLint directive...
icon = icon-grid button = Refresh Switch On the other hand, the properties file associated to hm-nav.js from the hm-nav.js listing needs a special treatment: nav-lion.properties is a reserved name for properties files that contain text associated to the navigation panel, and since hm-nav.js is adding content to it, add the text in the file hm-ui/src/main/resources/com/hp/hm/io/lion/nav-lion.properties. The following navlion.properties listing shows the content of the nav-lion.properties file. nav-lion.
tests test com.hp.util hp-util-skis ${hp-util.version} The second parameter (“com/hp/hm/ui”) of the super(…) call in the constructor of the UIExtension.java listing specifies the location of the two files the framework uses to integrate the views: css.hml and js.html.
Now update the hm-ui module POM file hm-ui/pom.xml with the extract shown in the following hm-ui/pom.xml to generate .war listing, to generate the .war file during the build process. hm-ui/pom.xml to generate .war: ... 4.0.0 hm-ui war ... 1.17 com.hp.hm.ui sdn/ui/health sdn/ui/health
com.hp.util.rs.auth, com.hp.sdn.rs.misc, com.hp.sdn.ui.misc,* !${banned.rs.paths} ${webapp.context} ${web.context.path} org.apache.maven.plugins maven-war-plugin 2.2 WEB-INF/lib/*.
Figure 58 Sample Application GUI Entry Figure 59 Sample Application View GUI-Specific REST API As seen previously the SKI framework uses JavaScript [40] as the underlying technology to create Dynamic-HTML based views. Such dynamism comes from logic executed at the SDN Controller or WEB server from the JavaScript point of view. The SKI framework integrates the jQuery [42] tool which allows for the execution of asynchronous HTTP requests.
functionality refers to the application’s domain model or business logic. Besides the domain model functionality, a view normally has requirements that are specific to presentation logic (For example a view could call the server to retrieve a catalog of pictures related to an item). It is not desired to pollute the RESTful web services from hm-rs module with presentation logic specific methods. Therefore, it’s considered a good practice creating GUI-specific REST APIs in the hm-ui module.
GUI REST Services com.sun.jersey.spi.container.servlet.ServletContainer com.sun.jersey.spi.container.ContainerRequestFilters com.hp.util.rs.auth.AuthJerseyFilter exclude-paths ^$ com.sun.jersey.config.property.
Token Authentication Filter com.hp.sdn.rs.misc.TokenAuthFilter Token Authentication Filter /app/rs/* Now update the switches view from Creating Views on page 198 under the “hm.js” listing to make the remote call as illustrated in the following listing: hm.js Requesting Data to the Controller: ...
Using SDN Controller Services In the following information some of the services provided by the HP VAN SDN Controller will be consumed to illustrate the philosophy followed by the controller: OSGi declarative services as depicted in section Consuming Services with OSGi Declarative Services on page 166. Services published by the controller are meant to be consumed by the application’s business logic.
devices = new HashMap, Switch>(); idCount = new AtomicLong(1); } public void setAlertService(AlertService alertService) { this.alertService = alertService; } @Override public Switch create(String name) { Switch device = new Switch(name); if (isEmpty(device.name())) { device.setName("Switch-" + device.getId().getValue().toString()); } devices.put(device.getId(), device); return device; } @Override public Collection getAll() { synchronized (devices) { return Collections.
throw new NullPointerException("id cannot be null"); } synchronized (devices) { Switch s = devices.remove(id); if (s == null) { throw new NotFoundException("Switch with id "id + "not found"): } } } SwitchResource.java Delegating to Business Logic: package com.hp.hm.rs; ... @Path("health") public class SwitchResource extends ControllerResource { @GET @Produces(MediaType.APPLICATION_JSON) public Response getAll() { SwitchService service = get(SwitchService.
@POST @Produces(MediaType.APPLICATION_JSON) public Response create(String request) {ObjectMapper mapper = new ObjectMapper(); JsonNode root = parse(mapper, request, "Switch data"); JsonNode node = root.path("item"); String name = exists(node, "name") ? node.path("name").asText() : null; SwitchService service = get(SwitchService.class); Switchdevice = service.create(name); return response(device, mapper).build(); } @DELETE @Path("{uid}") @Produces(MediaType.
return node; } } Posting Alerts In order to illustrate how alerts may be posted using the AlertService published by the controller, SwitchManager of the sample application will post an alert if a device is read with an. See Alert Logging on page 20 to get more information. At this point SwitchManager already depends on the AlertService, so it is ready to use this service. The following listing illustrates an extract of a modified SwitchManager that posts an alert when a device is retrieved. SwitchManager.
When the optional AlertService is set, an alert topic is registered using the AlertService. This registration process will return the alert topic to use when the alert is posted. Alert topics are persistent, thus if the topic was already registered, registering it again will have no effect. Since AlertService is optional in SwitchManager, the alert will be posted just if the service is available, thus a check for null is needed before posting the alert.
4. Retrieve (GET) the device (as illustrated in Figure 61). Figure 61 Updating OpenFlow Switch 5.
Figure 62 Alerts View Auditing with Logs In order to illustrate how audit logs may be posted using the AuditLogService published by the controller, SwitchManager of the sample application will post an audit log when a device is added. See Audit Logging on page 19 to get more information. The AuditLogService dependency must be added as any other service to consume; see Consuming Services with OSGi Declarative Services on page 166.
public Switch create(String name) { ... if (auditLogService != null) { // TODO: com.hp.sdn.rs.misc.ControllerResource (super class of // RESTful Web Services) offers a method to retrieve the // authenticated user: getAuthRecord(). SwitchService may be // modified to receive com.hp.api.auth.Authentication as // parameter and extract the authenticated user from there.
} } protected void unbindAuditLogService(AuditLogService service) { if (auditLogService == service) { auditLogService = null; if (delegate != null) { delegate.setAuditLogService(null); } } } ... } To try the new audit log feature follow the same steps from Posting Alerts on page 212 to add an OpenFlow switch so an audit log is generated. Figure 63 Audit Logs View Debugging with Logs The HP VAN SDN Controller uses the Simple Logging Facade for Java (SLF4J) [44] logging framework to generate support logs.
... private Logger logger; public SwitchManager() { ... // The LoggerFactory may be wrapped by a class in charge of providing // loggers to guarantee loggers are created in a consistent manner logger = LoggerFactory.getLogger(getClass()); } ... @Override public Switch add(Switch device) { ... logger.info("Device {} added", device); ... } ... } Log entries are stored in the file logs/log.log; see the HP VAN SDN Controller Admin Guide [29] to get instructions about exporting support logs.
As a preparation to exercise the Role Orchestration Service (ROS) in the HP VAN SDN Controller, there are two pre-requisite operations that needs to be carried out beforehand: 3) Create controller team: Using the teaming interfaces, a team of controllers need to be defined for leveraging High Availability features. 4) Create Region: the network devices for which the given controller has been identified as a master are grouped into “regions”.
private final SystemInformationService sysInfoService; // Mandatory dependency. private final RoleService roleService; public void doAct() { IpAddress masterIp = roleService.getMaster(dpid).ip(); if(masterIp.equals(sysInfoService. getSystem().getAddress())){ log.
// to any region. break; } } Notification on Region and Role changes Applications can express interest in region change notifications using the addListener(...) API in RegionService and providing an implementation of the RegionListener. A sample listener implementation is illustrated in the following listing: Region Listener Example: import com.hp.sdn.adm.region.RegionListener; import com.hp.sdn.region.Region; ... public class RegionListenerImpl implements RegionListener { ...
• MessageListener—To receive events about OpenFlow messages received by the controller from connected datapaths. • SequencedPacketListener—To participate in the processing of Packet-In messages • FlowListener—To receive events about flow. DataPathListener will be used to monitor connections and thus translate connected devices to reachable devices. Even though just DataPathListener will be shown here, using the other listeners is a matter of creating a variation of what is shown.
EqualityCondition.Mode mode = filter.getMacAddressCondition() .getMode(); for (Switch device : switches) { if (device.getMacAddress().equals(filterMacAddress)) { if (mode == EqualityCondition.Mode.UNEQUAL) { toDelete.add(device); } } else { if (mode == EqualityCondition.Mode.EQUAL) { toDelete.add(device); } } } switches.removeAll(toDelete); } // ----return switches; } ... private Switch getByMacAddress(MacAddress macAddress) { SwitchFilter filter = new SwitchFilter(); filter.
} else { device.setActiveState(ActiveState.OFF); } update(device); } } void stopHandlingControllerEvents(ControllerService controllerService) { controllerService.removeDataPathListener(dataPathListener); } private class DataPathListenerImpl implements DataPathListener { @Override public void queueEvent(QueueEvent event) { } @Override public void event(DataPathEvent event) { if (event.type() == OpenflowEventType.DATAPATH_CONNECTED || event.type() == OpenflowEventType.
private volatile ControllerService controllerService; ... @Activate public void activate() { delegate = new SwitchManager(systemInformationService); delegate.setAlertService(alertService); delegate.setAuditLogService(auditLogService); delegate.startHandlingControllerEvents(controllerService); } @Deactivate public void deactivate() { delegate.stopHandlingControllerEvents(controllerService); delegate = null; } ...
capable devices virtualized by Mininet [45] with MAC Addresses 00:00:00:00:00:01 and 00:00:00:00:00:02 were connected. Figure 64 OpenFlow Topology View 3. Verify the active state of the device added in Step 1 has been updated using the Rsdoc as illustrated in Figure 65.
After disconnecting the devices from the HP VAN SDN Controller (Stopping Mininet [45] in case of a virtualized network) the device’s active state should be updated to OFF. Application Manager Events/State In addition to the OSGI application service events, developers have access to SDN application events and SDN application state. For example, applications can query their own state during deactivation to perform pre-uninstall and/or pre-upgrade work.
The following is an example of how to query an application’s state: ApplicationService as; protected void bindAppService(ApplicationService as) { this.as = as; } protected void unbindAppService(ApplicationService as) { this.as = null; } @Deactivate protected void deactivate() { if (as != null) { Application.State myState = as.state(“my-app_id”) if (myState == State.UNINSTALLING) { // handle uninstalling } else if (myState == State.
7 Testing Applications The following information describes how to test SDN applications by executing Unit Test and enabling remote debugging in the controller. Unit Testing Unit tests are automatically run when building the application; see Building the Application on page 146. There is a version of this command to avoid running unit tests: Building Application Ignoring Unit Test: $ mvn clean install -Dmaven.test.
Figure 67 Running Unit Test within Eclipse (Step 2) There are several tools that calculate unit test coverage which are very useful. EclEmma [46] is a free Java code coverage tool for Eclipse, available under the Eclipse Public License. It brings code coverage analysis directly into the Eclipse workbench. When EclEmma is installed as an Eclipse plug-in, the unit test needs to be rerun using EclEmma as illustrated in Figure 68 and Figure 69.
Figure 68 Unit Test Coverage Figure 69 Unit Test Coverage Result 231
Remote Debugging with Eclipse It is possible to enable remote debugging with the controller; to do so setup a debugging session with the controller: Go to Run → Debug Configurations… to open the debug configurations dialog and select Remote Java Applications, click New as illustrated in Figure 70. Figure 70 Remote Java Application’s Debug Configuration Set the SDN Controller configuration with the data shown in Figure 71.
Figure 71 HP VAN SDN Controller’s Remote Debug Configuration Figure 72 HP VAN SDN Controller Saved Remote Debug Configuration 233
Now add a break point and verify that the controller stops at that point. You may skip the rest of this information if familiar with Eclipse’s debug perspective. Use the application developed on page 126 at the point of section GUI-Specific REST API on page 204. The reason to do so is because at that point, the application generates a very simple user interface with a single button that displays a message retrieved from the server via RESTful web services; and this adds a breakpoint in that REST API.
Figure 74 Sample Application’s View Remotely Debugged Figure 75 Perspective Switch The code stopped at the break point can be seen, as in Figure 76, however, as we can see in Figure 75 the source file was not found. If the source code cannot be seen add the Eclipse projects as Source Lookup Path: See Attaching Source Files when Debugging on page 248.
Figure 76 Debugging HP VAN SDN Controller Figure 77 shows the code stopped at the break point and the state of the SDN Controller’s view that depends on the code being debugged. It is only until we resume execution by clicking the Resume tool bar action that the controller’s view completes as illustrated in Figure 78.
Figure 77 Controller’s View Waiting for Code Being Debugged Figure 78 SDN Controller’s View Completed after Execution Resumed 237
8 Built-In Applications The HP VAN SDN Controller ships with a default set of core network service components, which provide an out-of-box experience in terms of enabling connectivity across network applications in the Openflow network. The details of each are captured below. Node Manager Node network service component is responsible for creating and maintaining the node table. Each end-host (called a node) is uniquely identified by the combination of IP address and network segment.
ControllerManager configuration has hybrid.mode=false, all packets are implicitly stolen to the controller and processed by OpenFlow Node Discovery. Based upon the information supplied by these copied ARP, DHCP, and IP packets the OpenFlow Node Discovery application will register as a node supplier and supply updates to the node table. The timeout value for nodes discovered by each protocol is configurable, as shown in Table 11.
OpenFlow Link Discovery OpenFlow Link Discovery pushes flow-mods to controlled devices and listens for PACKET_IN messages in order to discover links on the controlled network. When hybrid.mode=true in the ControllerManager configuration, OpenFlow Link Discovery will push a flow to controlled devices which steals all controller-generated link discovery packets to the controller. The controllergenerated link discovery packets use a non-standard protocol (BDDP), which utilizes a payload format similar to LLDP.
• For a given switch {s1}, can provide the list of ports that this service has discovered • For two switches {s1, s2}, the service can indicate if they are "strongly connected" i.e. form part of same cluster.
PathDaemon configuration allows configuration of each of these parameters as idle.timeout (default 60 seconds) and hard.timeout (default 0, which implies infinite timeout).
Appendix A Using the Eclipse Application Environment This appendix describes some of the Eclipse [47] features that an SDN Controller Application developer will often use. Importing Java Projects To import an entire Eclipse project from an archive file, follow these steps: 1. Go to File → Import. The following dialog appears. Figure 79 Eclipse Source Selection Dialog (Import Java Project) 2. Select Existing Projects into Workspace. Then Click the button next.
Figure 80 Eclipse Directory Selection Dialog (Import Java Project) 3. Click B r ow s e button and find the root folder (SDN Controller Application Workspace folder) on your hard disk. Several projects can be imported together depending on the selected root directory. Then click OK to select it. Figure 81 Eclipse File Chooser Dialog (Import Java Project) 4. Click Finish to perform the import.
Figure 82 Import Dialog (Import java Project) Figure 83 Eclipse Imported Projects 245
Setting M2_REPO Classpath Variable Go to Window → Preferences. Then add the location of Maven repository as illustrated in Figure 84. Figure 84 Setting M2_REPO Classpath Variable Installing Eclipse Plug-ins Most plug-ins will have an update site, making it easy to add and update plug-ins within Eclipse. 1. Find the URL of the update site for the plug-in. 2. Go to Help → Install New Software… and create a connection to an update site within Eclipse by adding a repository, as in Figure 85.
Figure 85 Adding Plug-in Repository 3. Select the checkbox of the plug-in and follow the installation wizard.
Eclipse Perspectives A perspective defines the initial set and layout of views in the Workbench window [47]. Within the window, each perspective shares the same set of editors. Each perspective provides a set of functionality aimed at accomplishing a specific type of task or works with specific types of resources. For example, the Java perspective combines views that you would commonly use while editing Java source files, while the Debug perspective contains the views used while debugging Java programs.
Figure 88 Source Not Found 2. Click Add button from the Edit Source Lookup Path dialog. Figure 89 Edit Source Lookup Path Dialog 3. Select Java Project as the source.
Figure 90 Lookup Path Resource Type 4. Select projects. Figure 91 Lookup Path Resource Selections 5. Confirm configuration.
Appendix B Troubleshooting Maven Cannot Download Required Libraries Problem This problem occurs when Internet access requires a proxy. Maven is unable to download required libraries due connection time outs. Figure 93 Maven Problem: No Proxy Configured The output shown in Figure 94 is also related to the proxy problem and it happens when Maven proxy configuration is incorrect. Figure 94 Maven Problem: Invalid Proxy Configuration Solution Make sure the proper proxy is configured in Maven.
Maven Proxy Configuration: optional true http proxyuser proxypass web-proxy.rose.hp.com 8088 local.net|some.host.com Path Errors in Eclipse Projects after Importing Problem This problem occurs when the M2_REPO variable is not set in Eclipse.
Solution SDN Controller Applications relies on Maven to resolve project dependencies, thus the Maven repository location must be configured in Eclipse. For more information see Setting M2_REPO Classpath Variable on page 246.
Bibliography [1] Hewlett-Packard, "REST Guidelines," [Online]. Available: http://h17007.www1.hp.com/us/en/networking/solutions/technology/sdn/devc enter/index.aspx. [2] Jersey, "Jersey," [Online]. Available: https://jersey.java.net/. [3] Oracle, "Servlets," [Online]. http://www.oracle.com/technetwork/java/index-jsp-135475.html. [4] B. Basham, K. Sierra and B. Bates, Head First Servlets & JSP, O'REILLY, 2008. [5] OSGi Alliance, "OSGi," http://www.osgi.org/Main/HomePage. [6] T. E.
[19] Wikipedia, "Plain Old Java Object (POJO)," http://en.wikipedia.org/wiki/Plain_Old_Java_Object. [20] IBM, "RESTful Web Services," [Online]. Available: http://www.ibm.com/developerworks/webservices/library/ws-restful/. [21] Oracle, "Javadoc," [Online]. Available: http://www.oracle.com/technetwork/java/javase/documentation/index-jsp135444.html. [22] OSGi Alliance, "OSGi Services - Configuration Admin," [Online]. Available: http://www.osgi.org/javadoc/r4v42/org/osgi/service/cm/ConfigurationAdmin.
[35] Oracle, "The Java EE 5 Tutorial," http://docs.oracle.com/javaee/5/tutorial/doc/. [36] JSON, "JSON," [Online]. Available: http://www.json.org/. [37] SourceForge, "EasyMock," [Online]. Available: http://www.easymock.org/. [38] Codehaus, "Jackson Java http://jackson.codehaus.org/. [39] The Apache Software Foundation, http://zookeeper.apache.org/. [40] D. Flanagan, JavaScript The definiteve Guide, O'REILLY, 2011. [41] Wikipedia, "Cascading Style Sheets," [Online]. https://en.wikipedia.