HP VAN SDN Controller Programming Guide Abstract The HP VAN SDN Controller Appliance serves as a delivery vehicle for SDN solutions providing a platform for developing various flavors of network controllers, e.g. data-center, public cloud, private cloud, campus edge networks, etc. This document provides detailed documentation for writing applications to run on the HP VAN SDN Controller platform. Part number: 5998-4920 Software version: 2.0.
© Copyright 2013 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 ························································································································
Sample Application ················································································································································ 106 Application Description ··············································································································································· 106 Creating Application Development Workspace······································································································· 106 Creating Application
1 Introduction This document describes the process of developing applications to run on the HP VAN SDN Controller platform. The base SDN Controller Appliance will serve as a delivery vehicle for SDN solutions. It aims to provide a platform for developing various flavours of network controllers, e.g. data-centre, public cloud, private cloud, campus edge networks, etc.
Figure 1 Controller Tiers The Administration tier of the controller appliance will host a web-layer through which software modules installed on the appliance can expose REST API [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 Appliance.
Figure 3 Web Application Model View Controller Pattern Basic Architecture The principal software stack of the appliance uses OSGi framework (Equinox) [5] [6] and a container (Virgo) [7] as a basis for modular software deployment and to enforce service provider/consumer separation. The software running in the principal OSGi container may interact with other components running as other processes on the appliance.
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 it is designed to use a stateless communication protocol, typically HTTP.
Figure 5 HP VAN SDN Controller Tiers Internal Applications vs. External Applications Internal applications (“Native” Applications / Modules) are deal to exert relatively fine-grained, frequent and low-latency control interactions with the environment, for example, handling packet-in events. Some key point 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, for instance, path provisioning and flow inspections. Some key point to consider when developing external applications: Authored in any language capable of stablishing a secure HTTP connection. Example: Java, C, C++, Python, Ruby, C#, bash, and so on. Deployed on a platform of choice outside of the SDN Controller platform.
2 Setting Environment The suggested development environment is split into two: Test Environment and Development Environment. And it is recommended to use different machines. 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 [14] are useful during development phase. The Development Environment will be formed by the tools needed to create, build and package the application.
Java The Software Development Language used is Java SE SDK 1.6 or later. To install Java go to [16] 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 [17]. To install Maven go to [17] and follow the download and installation instructions.
Javadoc Download HP VAN SDN Controller SDK API Documentation from [19]. The documentation is contained in the hp-sdn-apidoc-*.jar file (For example: hp-sdn-apidoc-2.0.0.jar). Unzip its contents in any location and open the index.html file. Figure 7 illustrates an example of the HP VAN SDN Controller documentation.
3 Developing Applications Introduction Figure 8 illustrates the various classes of software modules categorized by the nature of their responsibilities and capabilities and the categories of the software layers to which they belong. Also shown are the permitted dependencies among the classes of such modules. Note the explicit separation of the implementations from interfaces (APIs). This separation principle is strictly enforced in order to maintain modularity and elasticity of the application.
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.
Authentication HP VAN SDN Controller’s REST APIs are secured via a token-based authentication scheme. Openstack Keystone [9] is used to provide the token-based authentication. This security mechanism: Provide user authentication functionality with RBAC support. Completely isolate security mechanism from the underlying RESTful API. Work well with Openstack Keystone (even though Keystone is not a requirement).
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 else where 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. Auhtntication services have been split into these two APIs to limit sensitive services (Service API) to only authorized clients.
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 [22] (Illustraed in Figure 11).
JAX-RS annotations and Javadoc are already written when implementing RESTful Web Services, and they are re-used to 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 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 changes system. The subsystem comprises of 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.
An example of an application consuming the Alert service is described at Posting Alerts on page 181. 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 [23] [24] and MetaType [25] [26] services to present the configuration data.
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.*; ...
@Modified protected void modified(Map config) { 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.
This following figure illustrates this: Figure 14 OpenFlow Controller Message Library 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. Design Goals The following are the overall design goals of the library: To span all protocol versions However, actively supporting just 1.0.0 and 1.3.
All OpenFlow messages will be 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 will facilitate the writing of automated tests to verify switch/controller interactions in a deterministic manner. Message instances, for the most part, will be immutable.
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 immutable 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.
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 BufferId BUFFER_ID = BufferId.NO_BUFFER; 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.
.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 = ...
this is PACKET_IN messages; SequencedPacketListener. to hear about these, one must register as a Sequenced Packet Listeners – notified when PACKET_IN messages arrive from a datapath. This mechanism is described in more detail in a following section. Flow Listeners – notified when FLOW_MOD messages are pushed out to datapaths, or when flow rules are removed from datapaths (either explicitly, or by timeout). Group Listeners – notified when GROUP_MOD messages are pushed out to datapaths.
} 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(...)—queries the specified datapath for statistics on installed flows.
print("Instructions : {}", fs.getInstructions()); } Sending Messages Applications may construct and send messages to datapaths via the “send” methods: send(OpenflowMessage, DataPathId) : MessageFuture send(List, DataPathId) : List The returned MessageFuture(s) allow the caller to choose whether to wait synchronously (block until the outcome of the request is known), or whether to do some other work and then check on the result of the request later.
private OpenflowMessage createEchoRequest(byte[] timestamp) { OfmMutableEchoRequest echo = (OfmMutableEchoRequest) MessageFactory.create(PV, MessageType.ECHO_REQUEST); echo.data(timestamp); return echo.toImmutable(); } private long retrieveTimestamp(OpenflowMessage reply) { OfmEchoReply echo = (OfmEchoReply) reply; return ByteUtils.getLong(echo.getData(), 0); } Packet Sequencer PACKET_IN messages are handled by the controller with the Packet Sequencer module.
A DIRECTOR may contribute to the formation of the associated PACKET_OUT message by adding actions to it; DIRECTORs may also determine that the PACKET_OUT message is ready to be sent back to the datapath, and can instruct the Sequencer to send it on its way. An OBSERVER passively monitors the PACKET_IN/PACKET_OUT interactions. Within each role, SPLs are processed in order of decreasing “altitude”. The altitude is specified when the SPL registers with the controller.
reportOnDnsPacket(dns, context.srcEvent().dpid()); return false; } private void reportOnDnsPacket(Dns dns, DataPathId dpid) { // Since packet processing (this thread) is fast-path, // queue the report task onto a separate thread, then return. // ... } @Override public void errorEvent(ErrorEvent event) { // Never gets called for Observers } } Note that event processing should happen as fast as possible, since this is key to the performance of the controller.
isHandled() – returns true if a DIRECTOR has already instructed the Sequencer to send the PACKET_OUT message. failedToSend() – returns true if the attempt to send the PACKET_OUT message failed. toDebugString() – returns a detailed, multi-line string representation of the message context. 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.
Metrics Framework The fundamental objectives to be addressed by the metering framework are as follows. Support components that are part of the HP VAN SDN Controller Framework and applications that aren’t. Make metrics simple to use. Support the creation and updating of metrics within the controller and from outside, to accommodate apps those have external components but want to keep all of their metric data in one repository. Support several metric types: Counter. Gauge.
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. The developer can then manipulate the returned TimeStampedMetric object as appropriate for their own needs, updating its value at their own cadence, on a regular or irregular basis, to reflect changes in whatever is being measured.
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. TimeStampedRollingCounter Aggregates event durations to measure event throughput.
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). The summary interval uses an enumerated data type to restrict the possible values to 1, 5, or 15 minutes.
TimeStampedTimer 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.
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. inc(long)—Increments the current count by the specified number. TimeStampedGauge setValue(long)—Stores the latest snapshot of the gauge value.
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. Whether theTimeStampedMetric is subsequently destroyed by the component or application that requested its creation, it has disappeared from the framework's viewpoint.
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. It is also possible, depending on the type of TimeStampedMetric, that the value produced by the first interval summary following the bounce is affected by the bounce.
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.
TimeStampedHistogram 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.
screen somewhat like Figure 20 (the exact appearance will depend upon what JVMs are running on the system): Figure 20 JConsole – New Connection Choose a local connection to the JMX server instance that looks like the one highlighted in the preceding screenshot and click the Connect button. Upon successfully connecting to that JMX server instance, one should see a screen that looks something like Figure 21.
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.
File: pom.xml Purpose: specifies the Jersey REST URL prefix UI Extension webapp context path: 1.8 acme/ui/myapp Directory:/myapp/src/main/java Javapackage:com.acme.myapp.ui File:MyAppUIExtension.java Purpose: creates the UI Extension registration class, providing SDN with the paths to find the js.html and css.html files.
Directory: /myapp/src/main/resources Java package: com.acme.myapp.ui File: js.html Purpose: Javascript content to inject into the index HTML page generated by SDN (at the JAVASCRIPT-INCLUDES marker). UI Extension Javascript code injection: Directory: /myapp/src/main/resources Java package: com.acme.myapp.ui File: css.
var f = api.fn, // general functions API nav = api.nav; // navigation model API // Add a new item to an existing category nav.insertItemsAfter('n-audits', [ nav.item('n-my-view', 'my-view') ]); }(SKI)); Directory: /myapp/src/main/webapp File: my-app.js Purpose: defines the view content, handling the SKI framework life-cycle events. UI Extension JavaScript View implementation: (function (api) { 'use strict'; //framework APIs var f = api.fn, def = api.
} // load my content function myAppLoad(view) { // update my DOM content from my ajax success call } def.addView('my-view', { create: myAppCreate, load: myAppLoad, }); }(SKI)); Directory: /myapp/src/main/webapp/WEB-INF File: web.xml Purpose: defines the Java server-side resources for your application, note that the full URL path to the resource is acme/ui/myapp/app/rs/. UI Extension web.xml: PAGE 67com.acme.myapp.ui.rs.MyAppResource com.hp.sdn.rs.misc.AuthenticationHandler com.hp.sdn.rs.misc.NotFoundErrorHandler GUI REST Services /app/rs/* Token Authentication Filter com.hp.sdn.rs.misc.
Figure 31 Application view of HA Services Controller Instance 1 App1 - 1 Controller Instance 2 App2 - 1 App1 – 2 HA Service Bus App2 - 2 HA Service KeyStore Locks Teaming Bus Distributed services KeyStore Locks Teaming Distributed services Zookeeper Server Zookeeper Server Network Controller Instance 3 App1 - 3 App2 - 3 HA Service Bus KeyStore Locks Teaming Distributed services Zookeeper Server Controller Teaming At broader level controller teaming provides 2 functionalities t
Teaming Service Teaming helps in grouping a set of mandatory applications and control the role of the controller node in the cluster based on the mandatory application availability status. Based on the teaming configuration a controller node joins the team when all the mandatory applications joins the team. Following are the different types of teaming events that joined application might receive: BECOME_SUSPENDED—Controller node is not part of the team yet.
BECOME_MEMBER to BECOME_SUSPEND–Node is not part of the team now BECOME_MEMBER to BECOME_LEADER–Re-election occurred within the team and the local node is elected the leader BECOME_LEADER to BECOME_MEMBER–Re-election occurred within the team and the local node became a member. New leader detail are notified through NEW_LEADER event BECOME_LEADER to BECOME_SUSPEND–Node is not part of the team now Init sequence First event application receives on controller team join is BECOME_SUSPEND.
try { teamingService.joinTeam(APP_ID, this); } catch (Exception e) { ... } } @Deactivate public void deactivate() { try { teamingService.leaveTeam(APP_ID, this); } catch (Exception e) { ... } ... } @Override public void processTeamEvent(TeamEvent event, Id instanceId) { // Process the message in a separate thread context. ... } private void teamingOperationsExample() { // Query for the healthy members of the team Collection members = teamingService.
Distributed Bus In distributed environment applications tend to communicate with each other. Applications might be co-located on the same controller node or they may exist on different nodes of the same controller cluster. Bus infrastructure provides a way to accomplish this kind of distributed communication mechanism. Note that communication can occur between the nodes of a controller cluster and not across the controller cluster nodes.
BusMessage—represents a bus message. Applications need to create a BusMessage and past it to the interested applications via Bus Interface. Listener application can access various parameters of a message via BusMessage object methods. Please refer to the JavaDocs for BusFactory, Bus, BusListener interfaces for the detailed explanation of the methods provided and corresponding functionality. Bus Service Example: import com.hp.dist.bus.Bus; import com.hp.dist.bus.BusListenerId; import com.hp.dist.bus.
} } private void sendAckForMessage(BusMessage msg) { try { bus.ackMessage(msg.getMessageId(), this); } catch (Exception e) { ... } } @Override public BusListenerId getInstanceId() { return id; } @Override public int getListenerPriority() { return 10; } @Override public void processBusMsg(final BusMessage msg) { Runnable msgProcessor = new Runnable() { switch (BusUserBusTypes.fromOrdinal(msg.getMessageType())) { case BUS_USER_MSGTYPE_1: ... break; case BUS_USER_MSGTYPE_2: ... break; } }; threadExecutor.
Distributed KeyStore KeyStore is a distributed store for the HP SDN HA aware applications. Key is in string format and can be choosen by the application. written to KeyStore by an application can be read by application instances on same or other node of the controller cluster, provided the accessing controller application instance knows the data access parameters [Application name and key].
KeyValueNode node = null; try { node = haService.createNode(appName, "Key", new byte[]{'d', 'a', 't', 'a'}); } catch (Exception e) { ... } try { node.setValue( new byte[] {'n', 'e', 'w', 'd', 'a', 't', 'a'}, true); } catch (Exception e) { ... } try { node.delete(true); } catch (Exception e) { ... } } private void keyQueryOperations() { KeyValueNode node = null; try { node == haService.getNode(appName, "Key"); } catch (KeyNotFoundException e) { ...
Application needs to define a key that can be used to protect parallel access to a shared resource. Applications on different controller nodes should agree upon the key and acquire necessary lock on it before accessing the shared resource. Following are the types of locks supported by distributed locking service: Read Lock is a shared lock. More than one read lock can be allowed on a key simultaneously. Write Lock is an exclusive lock.
try { readLock = haService.createReadLock(id, name); } catch (Exception e) { ... } try { boolean lockAcquired = readLock.tryLock(); if (true == lockAcquired) readLock.unlock(); ... readLock.lock(); readLock.unlock(); ... haService.deleteLock(readLock); } catch (Exception e) { ... } } private void writeLockOperations() { Lock writeLock = null; LockName name = new LockName(appName, "Key1"); try { writeLock = haService.createWriteLock(id, name); } catch (Exception e) { ...
List lockNames = new ArrayList(); lockNames.add(new LockNameType(LockType.READ_LOCK, appName, "KEY1")); lockNames.add(new LockNameType(LockType.WRITE_LOCK, appName, "KEY3")); // MultiLock name should be unique. While constructing the name, // its suggested to prefix application instance id multiLock = haService.createMultiLock( id, new LockName(appName, "MultiLock"), lockNames); try { boolean lockAcquired = multiLock.tryLock( 10, TimeUnit.
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: 1) Create controller team: Using the teaming interfaces, a team of controllers need to be defined for leverging High Availability features. 2) 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.
default: // indicates the controller and device are not associated // 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; ...
Persistence Distributed Persistence Overview The SDN Controller provides a distributed persistence for applications in form of a Cassandra [11] 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.
Figure 33 Data Access Object Pattern Encapsulates Business Object Data Access Object Data Source Uses Obtains / Modifies Transfer Object 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 35 Mental Model Comparison between Relational Models and Cassandra Although this table provides a mapping of the terms, a more accurate analogy is a nested sorted map. Cassandra stores data in the format as follows: Map> So, there is a sorted map of RowKeys to an internal Sorted map of Columns sorted by the ColumnKey. The following figure illustrates a Casssandra row. Figure 36 Cassandra Row This is a simple row with columns.
Unlike with relational systems, where entities and relationships are modeled and then indexes are added to support whatever queries become necessary, with Cassandra queries that need to be supported efficiently are thought of ahead of time. Cassandra does not support joins at the query time because of its high scale distributed nature. This mandates duplication and de-normalization of data. Every column family in a Cassandra keyspace is self-contained with all data necessary to satisfy a given query.
} The above snippet shows the usage of @Reference. OSGi framework caches the dataStoreService and queryService objects in the CassandraAlertManager. Whenever, the client or application issues a query to the database, these objects will be used to get access to the persistence layer. DTO (Transport Object) Data that needs to be persisted can be divided into logical groups and these logical groups are tables of the database.
public CassandraAlert(String uid) { super(uid, null); } @Override public Id getId() { return Id.valueOf(this.uid()); } // Implement getters for immutable fields. // Implement setters and getters for mutable fields. // Good practice to override the following methods on transport objects: // equals(Object), hashCode() and toString() ... } The function of a DTO is to list out all the columns and provide setters/getters for each of the attributes.
import com.hp.util.persistence.ReadQuery; import com.hp.util.persistence.WriteQuery; ...
public CassandraAlert post(Severity severity, CassandraAlertTopic topic, String origin, String data) throws PersistenceException { if (topic == null) { throw new NullPointerException(...); } CassandraAlert alert = new CassandraAlert(sysId, true, topic.id(), origin, new Date(), severity, data); WriteQuery postAlertQuery = queryService.getAddAlertQuery(alert); try { alert = dataStoreService.execute(postAlertQuery); } catch (Exception e) { ...
@Override public MarkPage find(CassandraAlertFilter alertFilter, SortSpecification sortSpec, MarkPageRequest pageRequest) { ReadQuery, DataStoreContext> query = queryService.getPageAlertsQuery( alertFilter, sortSpec, pageRequest); try { return dataStoreService.execute(query); } catch (Exception e) { ... } } The two methods shown read from the database in different ways.
They cater to various conditional queries that can be issued as a read query to the database. The caller who wants to read from the database needs to create a filter object and fill it with appropriate values before issuing a find query. Data Access Object - DAO In the previous information, the business logic called the DataStoreService API to perform any persistence operation. The API performs the operation using a DAO.
ColumnFamily.newColumnFamily("Alerts", StringSerializer .get(), StringSerializer .get(), ByteSerializer .get()); private static Collection> cfMeta; static { Collection>tmpCfMeta = new ArrayList>(); tmpCfMeta.add(SYS_ID_NAME); tmpCfMeta.add(DESC_COL_NAME); tmpCfMeta.add(ORIGIN_COL_NAME); tmpCfMeta.add(SEVERITY_COL_NAME); tmpCfMeta.add(STATE_COL_NAME); tmpCfMeta.add(TIMESTAMP_COL_NAME); tmpCfMeta.add(TOPIC_COL_NAME); cfMeta = Collections.
(DateColumn) row2.getColumn(TIMESTAMP_COL_NAME); retVal = time1.compareTo(time2); break; case SEVERITY: EnumColumn sev1 = (EnumColumn) row1.getColumn(SEVERITY_COL_NAME); EnumColumn sev2 = (EnumColumn) row2.getColumn(SEVERITY_COL_NAME); retVal = sev1.compareTo(sev2); break; case STATE: BooleanColumn state1 = (BooleanColumn) row1.getColumn(STATE_COL_NAME); BooleanColumn state2 = (BooleanColumn) row2.getColumn(STATE_COL_NAME); retVal = state1.
To enable this, a secondary index for each of these columns needs to be created and maintained. This secondary index is another column family and it is called the secondary column family. An example is AlertsBySeverity column family as shown below. The secondary column families use composite columns and a row in AlertsBySeverity would look like this.
int comparison = 0; if (other.id != null) { comparison = id.compareTo(other.id); } if (comparison == 0) { comparison = this.severity.compareTo(other.severity); } return comparison; } } private static class AlertsBySeverity implements CfQueryOperations { private static final AnnotatedCompositeSerializer serializer = new AnnotatedCompositeSerializer (SeverityComposite.
CassandraStorable storable = new CassandraStorable(ROW_KEY); storable.setColumn(new ValuelessColumn( ColumnName. valueOf( new SeverityComposite(transportable.getSeverity(), transportable.getId().getValue())))); context.getContext().prepareMutation(COL_FAMILY, storable); } @Override public void prepareTransaction(CassandraAlert transportable, DataStoreContext context) throws Exception { context.
In addition, there is need to create/update the secondary column families to keep the queries updated. The above mentioned interface operations provide an abstraction to perform a write on all secondary column families along with the main column family. The secondary column family needs to define the necessary serializers for composite columns and a RowKey. In the demo code, every secondary column family has exactly one very wide row. This is done to achieve faster lookup during a read operation.
alert.setTopicId(column.getValue()); } else if (AlertColumnFamily.SYS_ID_NAME .equals(column.getName())) { alert.setSysId(column.getValue()); } } @Override public void visit(EnumColumn> column) { if (AlertColumnFamily.SEVERITY_COL_NAME .equals(column.getName())) { alert.setSeverity((Severity) column.getValue()); } } }; for(Column col : source.getColumns()) { col.
} return null; } createStorableInstance() This method converts the DTO into a storable format. Storable format is the one which underlying database client code understands. More on this in the next section. CassandraAlertDao.java: @Override protected CassandraStorable createStorableInstance(CassandraAlert transportable) { CassandraStorable storable = new CassandraStorable ( transportable.uid(), transportable.getSysId()); storable.
return alert2; } if (alert.getState() != alert2.getState()) { alert.setState(alert2.getState()); } return alert; } getColumnFamilyDefinitions() The abstraction layer calls this method to perform operations on secondary column families. CassandraAlertDao.java: @Override protected Collection> getColumnFamilyDefinitions() { Collection> colFamilies = new ArrayList>(); colFamilies.add(AlertColumnFamily.CF_DEF); colFamilies.
protected Collection findRows(CassandraAlertFilter filter, final DataStoreContext context) throws PersistenceException, Exception { Collection rowsSet = new ArrayList(); if (filter == null) { Collection id = new ArrayList(); Procedure> procedure = new Procedure>() { @Override public CassandraStorable execute() throws Exception { return (context.getContext().get( AlertsCount.
default: range = null; break; } // Find Rows for this filter Procedure> procedure = new Procedure>() { @Override public CassandraStorable execute() throws Exception { return context.getContext() .get(AlertsByOrigin.COL_FAMILY, AlertsByOrigin.ROW_KEY, range, AlertsByOrigin.ORIGIN_DECODER); } }; context.getTransactionContext() .prepareTransaction(AlertsByOrigin.COL_FAMILY.getName(), AlertsByOrigin.
@Override public CassandraStorable execute() throws Exception { return context.getContext() .get(AlertsBySeverity.COL_FAMILY, AlertsBySeverity.ROW_KEY, range, AlertsBySeverity.SEVERITY_DECODER); } }; context.getTransactionContext() .prepareTransaction( AlertsBySeverity.COL_FAMILY.getName(), AlertsBySeverity.ROW_KEY); CassandraStorable rows = context.getTransactionContext().
.getTopicCondition() .getValue()) .lessThan("~"); break; default: range = null; break; } // Find Rows for this filter Procedure> procedure = new Procedure>() { @Override public CassandraStorable execute() throws Exception { return context.getContext() .get(AlertsByTopic.COL_FAMILY, AlertsByTopic.ROW_KEY, range, AlertsByTopic.TOPIC_DECODER); } }; // Start the transaction context.getTransactionContext() .
case EQUAL: range = AlertsByState.serializer.buildRange() .withPrefix(filter.getStateCondition().getValue()) .greaterThan(" ").lessThan("~"); break; case UNEQUAL: range = AlertsByState.serializer.buildRange() .withPrefix(!filter.getStateCondition() .getValue()).greaterThan(" ").
} } return rowsSet; } findPagedRows() Same as the previous one but takes paging into account. CassandraAlertDao.
result = context .getTransactionContext() .executeCriticalSection(procedure); } catch (Exception e) { throw new PersistenceException(e); } // Get the list of Ids from the page List id = new ArrayList(); for (Column c : result.getData()) { id.add(c.getName().getValue()); } MarkPageRequest pageRequest1 = result.getRequest().convert(result .getRequest() .getMark() .getName() .
The convert routine has been described in the previous section. The CassandraStorable stores data in the form of a map very similar to the underlying database. The application only uses the storable and need not write one for itself.
4 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 automatizes the steps described in the following information.
Table 2 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.
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 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 2. Sample Application Root POM File: 4.0.0 com.hp.
pom.xml after updating the template with Table 2. A pom.xml for each application module must be created under the module’s folder. Sample Application Module POM File: com.hp.hm hm-root 1.0.
PAGE 116A POM file can be created to automatically produce the application package or zip file. Under the application app folder (hm-app) create the application packaging pom.xml file using the template from the HP VAN SDN Controller SDK. The following list shows an example for the sample application. Sample Application Packaging POM File: PAGE 117 node for api, bl, dao-api, dao-model and dao --> PAGE 118Application Generator (Automatic Workspace Creation) The HP VAN SDN Controller SDK also contains a utility to generate a skeletal application project structure as a starting template for your custom projects - automatizing all previous steps to create the application workspace. The generated application builds and installs into the HP VAN SDN Controller without any modifications. The tool allows you to tailor your template application using the parameters listed in Table 4.
The template sample application is ready to build and install. It serves as a good starting point to new applications development. To build the application simply change the working directory to the root module and use maven to build the application as described above. When Maven is finished, the application zip file can be found under the ~/sdn-hm/hmapp/target directory.
If the application workspace was created manually, the application modules are probably empty; thus a class that acts as a seed has to be created on each application module. The class can be as simple as the one shown in the following listing. However, even though the seed classes are temporal and is later replaced by real code, it is convenient using the correct java packages; Table 5 lists suggestions. Application Module Seed Java Class: package com.hp.hm.
Figure 40 SDN Controller Login Page 2. Click the New tool bar action as illustrated in Figure 41. 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 111. 5.
Figure 42 Upload/Install Application At this point the application should be part of the applications table and should be installed. To uninstall the application execute the uninstall tool bar action in the same view, as shown in Figure 43.
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.
public Switch(MacAddress macAddress) { this(null, macAddress); } public Switch(Id id, MacAddress macAddress) { super(id); if (macAddress == null) { throw new NullPointerException("macAddress cannot be null"); } this.macAddress = macAddress; } // Implement getters for immutable fields: MAC Address. // Implement setters and getters for mutable fields: IP address, // friendly name and active status.
Open the hm-root/pom.xml file and add the XML extract from following list to the node. After updating the POM file update the Eclipse project dependencies (see Updating Project Dependencies on page 115). HP SDN Controller Framework Common Dependencies: com.hp.util hp-util-misc ${hp-util.version} com.hp.util hp-util-api ${hp-util.
Based on these conditions we will create a filter for the Open Flow switch class as illustrated in the following listing. SwitchFilter.java: package com.hp.hm.model; import com.hp.util.filter.EqualityCondition; import com.hp.util.filter.SetCondition; import com.hp.util.filter.StringCondition; ...
MAC_ADDRESS, FRIENDLY_NAME, ACTIVE_STATE } Switch Sort Specification Usage Example: SortSpecification sort = new SortSpecification(); sort.addSortComponent(SwitchSortKey.MAC_ADDRESS, SortOrder.ASCENDING); sort.addSortComponent(SwitchSortKey.ACTIVE_STATE, SortOrder.DESCENDING); sort.addSortComponent(SwitchSortKey.FRIENDLY_NAME, SortOrder.ASCENDING); Model Objects Unit Test The HP VAN SDN Controller Framework offers some utilities to facilitate writing unit tests.
equals2, unequal); } @Test public void testSerialization() { Switch device = //... create with attributes set to non-null values SerializabilityTester.testSerialization(device); } } BeanTest utility class—A rudimentary facility for generic testing of basic bean getter and setter functionality. It uses reflection to locate matching getter/setter pairs in the supplied bean instance. EqualityTester class—Verifies the equivalence relation on non-null object references as documented in the Java Object.
public Switch get(Id id); public List find(SwitchFilter filter, SortSpecification sortSpecification); public void delete(Id id); } Services expose methods that use transfer objects, primitive types, object value types and common data structures in their signatures; thus, these entities become part of the API and they remain the same no matter the implementation we choose for our services.
... public class SwitchManager implements SwitchService { @Override public Switch add(Switch device) { return device; } @Override public void update(Switch device) { } @Override public Switch get(Id id) { return new Switch(id, MacAddress.valueOf("00:00:00:00:00:01")); } @Override public List find(SwitchFilter filter, SortSpecification sortSpecification){ return Collections.
be declarative, such as using some sort of composition language to describe the components and bindings among them. By using components applications can be created easily and quickly by snapping them together from readily available, reusable components. Components promote separation of concerns and encapsulation with its interface based approach. This enhances the reusability of your code because it limits dependencies on implementation details.
@Override public Switch add(Switch device) { return delegate.add(device); } @Override public void update(Switch device) { delegate.update(device); } @Override public Switch get(Id id) { return delegate.get(id); } @Override public List find(SwitchFilter filter, SortSpecification sortSpecification) { return delegate.find(filter, sortSpecification); } @Override public void delete(Id id) { delegate.
NOTE The following information describes the process using two different versions of Virgo [8] container. The latest HP VAN SDN Controller was upgraded to use the newer version; however this information describes the way of verifying published services using an older version because unfortunately Virgo dropped the “Published Services” information in the new version and now it is not possible to see published services unless they are already consumed. Thus the old version is illustrated as a good reference.
Figure 45 Virgo 3.5.0 Admin Console Artifacts Figure 46 Virgo 3.5.
Figure 47 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 47, 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 48 Virgo 3.6.1 Admin Console Figure 49 Virgo 3.6.
Figure 50 Virgo 3.6.1 Admin Console Application Plan Figure 51 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).
data in JSON format. DELETE /sdn/hm/v1.0/switches/{id} Deletes the switch with the given identity. 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.
} @PUT @Path("{id}") @Produces(MediaType.APPLICATION_JSON) public Response update(@PathParam("id") long id, String request) { return ok("{\”switch\”:{}}").build(); } @DELETE @Path("{id}") @Produces(MediaType.APPLICATION_JSON) public Response delete(@PathParam("id") long id) { return Response.ok().build(); } } REST Module Dependencies: com.hp.hm hm-model ${project.version} com.hp.
sdn-adm-rs-misc ${sdn.version} tests test com.sun.jersey jersey-server 1.17 compile com.sun.jersey.jersey-test-framework jersey-test-framework-grizzly 1.17 test com.sun.
com.hp.sdn.rs.AllowsDomains * com.sun.jersey.spi.container.ContainerRequestFilters com.hp.util.rs.auth.AuthJerseyFilter exclude-paths ^(NONE)[/]*(.*)$ com.sun.jersey.config.property.resourceConfigClass com.sun.
Token Authentication Filter com.hp.sdn.rs.misc.TokenAuthFilter Token Authentication Filter /* 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.
bundle war com.sun.jersey.api.core, com.sun.jersey.spi.container.servlet, 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.
PAGE 149 run ...
--header "X-Auth-Token:[AUTHENTICATION_TOKEN]" \ --fail -ksS -L –f \ --request GET \ --url "https://[SDN_CONTROLLER_ADDRESS]:8443/sdn/hm/v1.0/switches" Figure 52 REST API CURL Execution Example RESTful Web Services Unit Test Even though at this point implementations use fake data, 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.
public void setUp() throws Exception { super.setUp(); // If a specific test case expects a different format, such // format will have to be set calling this method. 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.
public void testDelete() { long idMock = 1; String path = BASE_PATH + "/" + idMock; String response = delete(path); Assert.assertTrue(response.isEmpty()); } } Resource Test Dependencies: com.hp.sdn sdn-common-misc ${sdn.version} commons-configuration commons-configuration 1.6 com.fasterxml.jackson.
137). The web container manages the life-cycle of the Jersey Servlet (as illustrated in Figure 3); the Jersey Servlet is defined at hm-rs/src/main/webapp/WEB-INF/web.xml. Therefore, it is not possible to have OSGi injecting Domain Services into RESTful Web Services because their life-cycle is managed by different technologies: OSGi and Servlets respectively.
} ServiceAssistant shows an alternative way of declaring dependencies. ServiceAssistant is annotated with @References instead of declaring a variable of type SwitchService and then annotate it with @Reference as in Consuming Services with OSGi Declarative Services on page 134 under the Dependent SwitchComponent.java listing. In this case we wouldn’t use the variable since we pass the bound service to the ServiceLocator.
@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()); } @Override @After public void tearDown() throws Exception { super.tearDown(); sl.unregister(SwitchService.
JSON Encoding As described in previously the tasks a REST API normally accomplishes is decoding the request and encode the result into the response. This sample application uses JSON [37] format but could have used any other like XML. There are several different tools to assist on JSON conversion and any tool and any way of organizing the codecs (or converters) could have been selected.
if (!node.path(IP_ADDRESS).isMissingNode()) { device.setIpAddress(IpAddress .valueOf(node.get(IP_ADDRESS).asText())); } if (!node.path(FRIENDLY_NAME).isMissingNode()) { device.setFriendlyName(node.get(FRIENDLY_NAME).asText()); } if (!node.path(ACTIVE_STATE).isMissingNode()) { device.setActiveState(ActiveState.valueOf( node.get(ACTIVE_STATE).asText())); } return device; } @Override public ObjectNode encode(Switch device) { ObjectNode node = mapper.createObjectNode(); node.put(MAC_ADDRESS, device.
for (String field : fields) { if (node.path(field).isMissingNode()) { throw new IllegalArgumentException("JSON node '" + node + "' is missing field '" + field + "'"); } } } } } There are some dependencies to declare in order to implement the codecs. Open the hmrs/pom.xml file and add the XML extract from the JSON Module Dependencies listing to the node; after updating the POM file update the Eclipse project dependencies (see Updating Project Dependencies on page 115).
@Deactivate protected void deactivate() { clearCodecs(); } } HmJsonFactory holds all the JSON codecs; it is an OSGi service so it is registered to the central JSON repository when it is activated and unregistered from the JSON repository when it is deactivated. The registration happens automatically because the HP VAN SDN Controller Framework observes all activated JSON Factories (JsonFactory) services annotated with the following property: “name=flare”.
import com.hp.sdn.json.JsonService; ... public class SwitchResourceTest extends ControllerResourceTest { private static final String BASE_PATH = "switches"; private SwitchService switchServiceMock; private JsonService jsonServiceMock; public SwitchResourceTest() { 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.
String switchesJson = "{\"switches\":[]}"; // Recording phase (Define expectations) EasyMock.expect(switchServiceMock.find( EasyMock.isNull(SwitchFilter.class), EasyMock.isNull(SortSpecification.class))). andReturn(switches); EasyMock.expect(jsonServiceMock.toJsonList( EasyMock.same(switches), EasyMock.eq(Switch.class), EasyMock.eq(true))).andReturn(switchesJson); // Execution phase EasyMock.
Figure 53 Controller-Controller Communication via REST (Sideway API) For example assume there is a need to retrieve all the open flow switches controlled by a remote system. A sideway (or transfer) API could be defined to take care of such communication as shown in the following listing. SwitchTransferService.java: package com.hp.hm.api; ...
@Reference(policy = ReferencePolicy.DYNAMIC, cardinality = ReferenceCardinality.MANDATORY_UNARY) private volatile ServiceRest restClient; @Reference(policy = ReferencePolicy.DYNAMIC, cardinality = ReferenceCardinality.MANDATORY_UNARY) private volatile jsonService jsonService; @Override public Set getControlledDevices(IpAddress ipAddress) { URI uri = restClient.uri(ipAddress, BASE_DESTINATION_PATH); ResponseData response = restClient.get(restClient.
ServiceRest Dependency: com.hp.sdn sdn-common-api ${sdn.version} com.hp.sdn sdn-common-misc ${sdn.version} com.sun.jersey jersey-server 1.17 compile org.apache.
Create a JSON [37] schema to express our data model. Create hmrs/src/main/resources/model.json file with the content from the following RSdoc JSON Schema listing. (To add more schemas separate them by comma). RSdoc JSON Schema: { "com.hp.hm.model.
${sdn.version} Modify hm-rs/pom.xml file to include the plug-in in charge of executing the command used to generate the RSdoc as shown in the following RSdoc Generation Maven Configuration llisting. This plugin executes a tool offered by the HP VAN SDN Controller SDK that generates the RSdoc based on the parameters used in RSdoc Generation Maven Configuration listing. RSdoc Generation Maven Configuration: ... com.hp.hm.rs
run ... Build and install the application as described Building Application on page 115 and Installing Application on page 116. RSdoc is now accessible as illustrated in Figure 54.
@RsDocIgnore public Response internalMethod() { ... } } Trying the REST API with RSdoc At this point try the REST API using the RSdoc, which is the preferred method. Follow the steps from Rsdoc Live Reference on page 16 to open Rsdoc and authenticate, and then try the sample application’s REST API as illustrated in Figure 55. Modify SwitchManager to return some fake data in find(SwitchFilte, SortSpecification) method and try GET switches from the RSdoc.
NOTE The tool offered by the HP VAN SDN Controller SDK that generates the RSdoc takes the Javadoc [22] 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.
view.lion('key-button'), '', function () { v.setContent($(''). addClass('hm-message-style').append('Hello World')); })); } // Adds the view to the framework with ‘hm-switches’ as its name. // The associated property file must have the same name than as view. def.addView('hm-switches', { load: load }); }(SKI)); Now create a script that accessible from the SDN with the content from the navigation panel as the navigation panel.
When adding the “Switches” view to the framework in hm-switches.js, name it “hm-switches” (Highlighted in the hm-switches.js listing), thus the associated properties file must have the same name. Create the file hm-ui/src/main/resources/com/hp/hm/ui/lion/hm-switches.properties with the content from the following hm-switches.properties listing.
com.hp.sdn sdn-adm-rs-misc ${sdn.version} com.hp.sdn sdn-adm-rs-misc ${sdn.version} tests test com.hp.util hp-util-skis ${hp-util.
Health Monitor UI 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.
war com.sun.jersey.api.core, com.sun.jersey.spi.container.servlet, com.sun.jersey.server.impl.container.servlet, com.hp.util.rs, com.hp.util.rs.auth, com.hp.sdn.rs.misc, com.hp.sdn.ui.misc,* !${banned.rs.paths} ${webapp.context} ${web.context.
PAGE 176 node for api, bl, dao-api, dao-model and dao --> run ...
Figure 57 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.
SwitchViewResource.java: package com.hp.hm.ui.rs; ... @Path("switches") public class SwitchViewResource extends ControllerResource { @GET @Produces(MediaType.TEXT_PLAIN) public Response hello() { // SwitchService is used just to illustrate that // services are available. SwitchService service = get(SwitchService.class); List switches = service.find(null, null); return ok("Hello from the HP SDN Controller: " + switches.toString()).build(); } } Now update the web.
com.sun.jersey.config.property.resourceConfigClass com.sun.jersey.api.core.ClassNamesResourceConfig com.sun.jersey.config.property.classnames com.hp.hm.ui.rs.SwitchViewResource com.hp.sdn.rs.misc.DuplicateIdErrorHandler com.hp.sdn.rs.misc.NotFoundErrorHandler com.hp.sdn.rs.misc.
function load(view) { v.setToolbar(def.tbButton(view.mkId('btn'), view.lion('key-button'), '', function () { $.get('/sdn/ui/hm/app/rs/switches', function(data) { v.setContent($('').addClass("hm-messagestyle").append(data)); }); })); } ... As seen in the code the view connects to the relative path “/sdn/ui/hm/app/rs/switches” so the connection is opened to the same controller that generated the web page. The prefix “/sdn/ui/hm” must match the web.context.
NOTE Synchronization on the in-memory data structure and the fact that Switch is a mutable class have been intentionally ignored. Even though it is important to consider the multi-thread environment nature of RESTful web services and data protection (since the same references from the in-memory data store are returned by the business logic) they are irrelevant for the purpose of the illustration: Consuming services published by the controller.
if (device.getId() == null) { Id id = Id.valueOf(idCount.getAndIncrement()); deviceToAdd = new Switch(id, device.getMacAddress()); deviceToAdd.setActiveState(device.getActiveState()); deviceToAdd.setFriendlyName(device.getFriendlyName()); deviceToAdd.setIpAddress(device.getIpAddress()); } devices.put(deviceToAdd.getId(), deviceToAdd); return deviceToAdd; } @Override public void update(Switch device) { if (device == null) { throw new NullPointerException("device cannot be null"); } if (device.
if (id == null) { throw new NullPointerException("id cannot be null"); } devices.remove(id); } } SwitchResource.java Delegating to Business Logic: package com.hp.hm.rs; ... @Path("switches") public class SwitchResource extends ControllerResource { @GET @Produces(MediaType.APPLICATION_JSON) public Response list() { SwitchService service = get(SwitchService.class); List switches = service.find(null, null); jsonService jsonService = get(JsonService.class); String json = jsonService.
String json = jsonService.toJson(device, true); return ok(json).build(); } @PUT @Path("{id}") @Produces(MediaType.APPLICATION_JSON) public Response update(@PathParam("id") long id, String request) { JsonService jsonService = get(JsonService.class); Switch source = jsonService.fromJson(request, Switch.class); SwitchService service = get(SwitchService.class); Id deviceId = Id.valueOf(id); Switch target = service.
} } 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 updated with an unreachable status. See Alert Logging on page 19 to get more information. At this point SwitchManager already depends on the AlertService, so it is ready to use such service. The following listing illustrates an extract of a modified SwitchManager that posts an alert when a device is unreachable.
alertService.post(Severity.WARNING, alertTopic, source, data); } } } ... } When the optional AlertService is set an alert topic is registered using the AlertService. Such 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.
Figure 58 Adding OpenFlow Switch 4. Update (PUT) the device using the following JSON document: {"switch":{"mac_address":"00:00:00:00:00:01","ip_address":"192.168.1.
Figure 59 Updating OpenFlow Switch 5.
Figure 60 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 18 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 134.
// 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. String user = "hm"; String source = "Health Monitor"; String activity = "Device Added"; String description = "OpenFlow Switch: " + deviceToAdd.getId().getValue(); auditLogService.
auditLogService = null; if (delegate != null) { delegate.setAuditLogService(null); } } } ... } To try the new audit log feature follow the same steps from Posting Alerts on page 181 to add and modify an OpenFlow switch so an audit log is generated. Figure 61 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. No extra configuration is needed to enable an application to create loggers.
// 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 [30] to get instructions about exporting support logs.
package com.hp.hm.impl; import com.hp.of.ctl.ControllerService; import com.hp.of.ctl.DataPathEvent; import com.hp.of.ctl.DataPathListener; import com.hp.of.ctl.OpenflowEventType; import com.hp.of.ctl.QueueEvent; import com.hp.of.lib.dt.DataPathInfo; ... public class SwitchManager implements SwitchService { ... private DataPathListener dataPathListener; public SwitchManager(SystemInformationService systemInformationService) { ... dataPathListener = new DataPathListenerImpl(); } ...
} switches.removeAll(toDelete); } // ----return switches; } ... private Switch getByMacAddress(MacAddress macAddress) { SwitchFilter filter = new SwitchFilter(); filter.setMacAddressCondition( new EqualityCondition(macAddress, EqualityCondition.Mode.EQUAL)); List switches = find(filter, null); if (!switches.isEmpty()) { return switches.get(0); } return null; } void startHandlingControllerEvents(ControllerService controllerService) { controllerService.
@Override public void queueEvent(QueueEvent event) { } @Override public void event(DataPathEvent event) { if (event.type() == OpenflowEventType.DATAPATH_CONNECTED || event.type() == OpenflowEventType.DATAPATH_DISCONNECTED) { Switch device = etByMacAddress(event.dpid().getMacAddress()); if (device != null) { if (event.type()== OpenflowEventType.DATAPATH_CONNECTED){ device.setActiveState(ActiveState.ON); } else { device.setActiveState(ActiveState.OFF); } update(device); } } } } } SwitchComponent.
delegate.stopHandlingControllerEvents(controllerService); delegate = null; } ... } From the previous listings it can bee seen that the MAC Address is used in to relate connected devices to the devices managed by the sample application. Some dependencies need to resolved to use the OpenFlow controller services. Open the hmbl/pom.xml file and add the XML extract from the following listing to the node.
Figure 62 OpenFlow Topoligy View 3. Verify the active state of the device added in Step 1 has been updated using the Rsdoc as illustrated in Figure 63. Figure 63 Sample Application OpenFlow Devices After disconnecting the devices from the HP VAN SDN Controller (Stoping Mininet [45] in case of a virtualized network) the device’s active state should be updated to OFF.
5 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 test is automatically run when building the application; see Building Application on page 115. There is a version of this command to avoid running unit tests: Building Application Ignoring Unit Test: $ mvn clean install -Dmaven.test.
Figure 65 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 66 and Figure 67.
Figure 66 Unit Test Coverage Figure 67 Unit Test Coverage Result 196
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 68. Figure 68 Remote Java Application’s Debug Configuration Set the SDN Controller configuration with the data shown in Figure 69.
Figure 69 HP VAN SDN Controller’s Remote Debug Configuration Figure 70 HP VAN SDN Controller Saved Remote Debug Configuration 198
Now add a break point and verify that the controller stops at such point. Skip the rest of this information if familiar with the Eclipse’s debug perspective. Use the application developed at 4 Sample Application on page 106 at the point of section GUI-Specific REST API on page 173.
Figure 72 Sample Application’s View Remotely Debugged Figure 73 Perspective Switch The code stopped at the break point can be seen, as in Figure 74, however, as we can see in Figure 73 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 213.
Figure 74 Debugging HP VAN SDN Controller Figure 75 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 76.
Figure 75 Controller’s View Waiting for Code Being Debugged Figure 76 SDN Controller’s View Completed after Execution Resumed 202
6 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 Device Node Manager Node network service component is responsible for creating and maintaining the ARP Table. It also maintains the list of end points or end hosts. An ARP table is maintained for each VNET.
Network Node state tracking functionality The API’s are described in the following listing. See Javadoc on page 9 for details.
Table 8 Link Table Source Device Source Port Destination Device Destination Port 03:e7:00:26:f1:29:af:00 1 03:e7:00:23:47:ba:05:40 23 03:e8:00:23:47:ba:05:40 11 03:e8:00:26:f1:29:af:00 8 Device Interaction This application listens to LLDP messages, Link up/down messages. The application sends out LLDP PDUs to all devices. Services provided by Link Manager Learns and maintains all inter switch links in the control domain. Used by the Topology Module to construct end-to-end paths.
Add switch port to LLDP suppressed list void addToSuppressLLDPs(DataPathId dpid, BigPortNumber port); Remove a switch port from the LLDP suppressed list void removeFromSuppressLLDPs(DataPathId dpid, BigPortNumber port); Register for getting the link discovery events void addListener(LinkListener listener); Unregister from getting the link discovery events void removeListener(LinkListener listener); Topology Manager Topology Manager provides topology information of the control domain.
Example: if one needs to flood out packets through a port, it can do a check using this API to see if broadcast would be possible through this port. If this API indicates negative, then it would mean the port is in blocked state. Provide hooks for interested components to get notified of topology changes The API’s are described in the following listing. See Javadoc on page 9 for details.
Appendix A Eclipse 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 77 Eclipse Source Selection Dialog (Import Java Project) 2. Select Existing Projects into Workspace. Then Click the button next.
Figure 78 Eclipse Directory Selection Dialog (Import Java Project) 3. Click Browse 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 79 Eclipse File Chooser Dialog (Import Java Project) 4. Click Finish to perform the import.
Figure 80 Import Dialog (Import java Project) Figure 81 Eclipse Imported Projects 210
Setting M2_REPO Classpath Variable Go to Window Preferences. Then add the location of Maven repository as illustrated in Figure 82. Figure 82 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 83.
Figure 83 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 86 Source Not Found 2. Click Add button from the Edit Source Lookup Path dialog. Figure 87 Edit Source Lookup Path Dialog 3. Select Java Project as the source.
Figure 88 Lookup Path Resource Type 4. Select projects. Figure 89 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 91 Maven Problem: No Proxy Configured The output shown in Figure 92 is also related to the proxy problem and it happens when Maven proxy configuration is incorrect. Figure 92 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 211.
Bibliography [1] Hewlett-Packard, "REST Guidelines," [Online]. Available: http://h17007.www1.hp.com/us/en/networking/solutions/technology/sdn/dev center/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] Hewlett-Packard, "HP SDN Developer Kit," [Online]. Available: http://h17007.www1.hp.com/us/en/networking/solutions/technology/sdn/dev center/index.aspx. [20] Wikipedia, "Plain Old Java Object (POJO)," http://en.wikipedia.org/wiki/Plain_Old_Java_Object. [21] IBM, "RESTful Web Services," [Online]. Available: http://www.ibm.com/developerworks/webservices/library/ws-restful/. [22] Oracle, "Javadoc," [Online]. Available: http://www.oracle.com/technetwork/java/javase/documentation/index-jsp135444.
Modular Applications in Java, Manning Publications Co., 2011. [35] E. Gamma, R. Helm, R. Johnson and J. Vlissides, Design Patterns Elements of Reusable Object-Oriented Software, Addison Wesley, 2007. [36] Oracle, "The Java EE 5 Tutorial," http://docs.oracle.com/javaee/5/tutorial/doc/. [37] JSON, "JSON," [Online]. Available: http://www.json.org/. [38] SourceForge, "EasyMock," [Online]. Available: http://www.easymock.org/. [39] Codehaus, "Jackson Java http://jackson.codehaus.org/. [40] D.