/**
* Copyright (c) 2013 Cisco Systems, Inc. and others. All rights reserved.
*
* This program and the accompanying materials are made available under the
* terms of the Eclipse Public License v1.0 which accompanies this distribution,
* and is available at http://www.eclipse.org/legal/epl-v10.html
*/
package org.opendaylight.openflowplugin.openflow.md.core.plan;
import com.google.common.base.Joiner;
import com.google.common.collect.Lists;
import com.google.common.util.concurrent.Futures;
import com.google.common.util.concurrent.ListenableFuture;
import com.google.common.util.concurrent.SettableFuture;
import java.net.InetSocketAddress;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.Stack;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.Future;
import org.opendaylight.controller.sal.common.util.RpcErrors;
import org.opendaylight.controller.sal.common.util.Rpcs;
import org.opendaylight.openflowjava.protocol.api.connection.ConnectionAdapter;
import org.opendaylight.openflowjava.protocol.api.connection.ConnectionReadyListener;
import org.opendaylight.openflowjava.protocol.api.connection.OutboundQueueHandler;
import org.opendaylight.openflowjava.protocol.api.connection.OutboundQueueHandlerRegistration;
import org.opendaylight.yang.gen.v1.urn.opendaylight.openflow.protocol.rev130731.BarrierInput;
import org.opendaylight.yang.gen.v1.urn.opendaylight.openflow.protocol.rev130731.BarrierOutput;
import org.opendaylight.yang.gen.v1.urn.opendaylight.openflow.protocol.rev130731.EchoInput;
import org.opendaylight.yang.gen.v1.urn.opendaylight.openflow.protocol.rev130731.EchoOutput;
import org.opendaylight.yang.gen.v1.urn.opendaylight.openflow.protocol.rev130731.EchoReplyInput;
import org.opendaylight.yang.gen.v1.urn.opendaylight.openflow.protocol.rev130731.EchoRequestMessage;
import org.opendaylight.yang.gen.v1.urn.opendaylight.openflow.protocol.rev130731.ErrorMessage;
import org.opendaylight.yang.gen.v1.urn.opendaylight.openflow.protocol.rev130731.ExperimenterInput;
import org.opendaylight.yang.gen.v1.urn.opendaylight.openflow.protocol.rev130731.ExperimenterMessage;
import org.opendaylight.yang.gen.v1.urn.opendaylight.openflow.protocol.rev130731.FlowModInput;
import org.opendaylight.yang.gen.v1.urn.opendaylight.openflow.protocol.rev130731.FlowRemovedMessage;
import org.opendaylight.yang.gen.v1.urn.opendaylight.openflow.protocol.rev130731.GetAsyncInput;
import org.opendaylight.yang.gen.v1.urn.opendaylight.openflow.protocol.rev130731.GetAsyncOutput;
import org.opendaylight.yang.gen.v1.urn.opendaylight.openflow.protocol.rev130731.GetConfigInput;
import org.opendaylight.yang.gen.v1.urn.opendaylight.openflow.protocol.rev130731.GetConfigOutput;
import org.opendaylight.yang.gen.v1.urn.opendaylight.openflow.protocol.rev130731.GetFeaturesInput;
import org.opendaylight.yang.gen.v1.urn.opendaylight.openflow.protocol.rev130731.GetFeaturesOutput;
import org.opendaylight.yang.gen.v1.urn.opendaylight.openflow.protocol.rev130731.GetQueueConfigInput;
import org.opendaylight.yang.gen.v1.urn.opendaylight.openflow.protocol.rev130731.GetQueueConfigOutput;
import org.opendaylight.yang.gen.v1.urn.opendaylight.openflow.protocol.rev130731.GroupModInput;
import org.opendaylight.yang.gen.v1.urn.opendaylight.openflow.protocol.rev130731.HelloInput;
import org.opendaylight.yang.gen.v1.urn.opendaylight.openflow.protocol.rev130731.HelloMessage;
import org.opendaylight.yang.gen.v1.urn.opendaylight.openflow.protocol.rev130731.MeterModInput;
import org.opendaylight.yang.gen.v1.urn.opendaylight.openflow.protocol.rev130731.MultipartReplyMessage;
import org.opendaylight.yang.gen.v1.urn.opendaylight.openflow.protocol.rev130731.MultipartRequestInput;
import org.opendaylight.yang.gen.v1.urn.opendaylight.openflow.protocol.rev130731.OfHeader;
import org.opendaylight.yang.gen.v1.urn.opendaylight.openflow.protocol.rev130731.OpenflowProtocolListener;
import org.opendaylight.yang.gen.v1.urn.opendaylight.openflow.protocol.rev130731.PacketInMessage;
import org.opendaylight.yang.gen.v1.urn.opendaylight.openflow.protocol.rev130731.PacketOutInput;
import org.opendaylight.yang.gen.v1.urn.opendaylight.openflow.protocol.rev130731.PortModInput;
import org.opendaylight.yang.gen.v1.urn.opendaylight.openflow.protocol.rev130731.PortStatusMessage;
import org.opendaylight.yang.gen.v1.urn.opendaylight.openflow.protocol.rev130731.RoleRequestInput;
import org.opendaylight.yang.gen.v1.urn.opendaylight.openflow.protocol.rev130731.RoleRequestOutput;
import org.opendaylight.yang.gen.v1.urn.opendaylight.openflow.protocol.rev130731.SetAsyncInput;
import org.opendaylight.yang.gen.v1.urn.opendaylight.openflow.protocol.rev130731.SetConfigInput;
import org.opendaylight.yang.gen.v1.urn.opendaylight.openflow.protocol.rev130731.TableModInput;
import org.opendaylight.yang.gen.v1.urn.opendaylight.openflow.system.rev130927.DisconnectEvent;
import org.opendaylight.yang.gen.v1.urn.opendaylight.openflow.system.rev130927.DisconnectEventBuilder;
import org.opendaylight.yang.gen.v1.urn.opendaylight.openflow.system.rev130927.SystemNotificationsListener;
import org.opendaylight.yangtools.yang.binding.Notification;
import org.opendaylight.yangtools.yang.common.RpcError;
import org.opendaylight.yangtools.yang.common.RpcError.ErrorSeverity;
import org.opendaylight.yangtools.yang.common.RpcError.ErrorType;
import org.opendaylight.yangtools.yang.common.RpcResult;
import org.opendaylight.yangtools.yang.common.RpcResultBuilder;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/**
* @author mirehak
*/
public class ConnectionAdapterStackImpl implements ConnectionAdapter, Runnable {
/** notify and rpc-response thread start default delay in [ms] */
public static final int JOB_DELAY = 50;
protected static final Logger LOG = LoggerFactory
.getLogger(ConnectionAdapterStackImpl.class);
protected Stack<? extends SwitchTestEvent> eventPlan;
protected OpenflowProtocolListener ofListener;
protected SystemNotificationsListener systemListener;
protected Map<Long, SettableFuture<?>> rpcResults = new HashMap<>();
protected boolean planTouched = false;
private long proceedTimeout;
protected List<Exception> occuredExceptions = new ArrayList<>();
private ConnectionReadyListener connectionReadyListener;
private int planItemCounter;
private boolean autoRead = true;
private final ExecutorService pool;
private boolean packetInFiltering;
/**
* default ctor
*/
public ConnectionAdapterStackImpl() {
pool = Executors.newSingleThreadExecutor();
}
@Override
public synchronized Future<RpcResult<BarrierOutput>> barrier(
final BarrierInput arg0) {
checkRpcAndNext(arg0, "barrier");
SettableFuture<RpcResult<BarrierOutput>> result = createAndRegisterRpcResult(arg0);
return result;
}
@Override
public synchronized Future<RpcResult<EchoOutput>> echo(final EchoInput arg0) {
checkRpcAndNext(arg0, "echo");
Future<RpcResult<EchoOutput>> result = createAndRegisterRpcResult(arg0);
return result;
}
@Override
public Future<RpcResult<Void>> echoReply(final EchoReplyInput arg0) {
checkRpcAndNext(arg0, "echoReply");
ListenableFuture<RpcResult<Void>> result = createOneWayRpcResult();
return result;
}
@Override
public Future<RpcResult<Void>> experimenter(final ExperimenterInput arg0) {
checkRpcAndNext(arg0, "experimenter");
ListenableFuture<RpcResult<Void>> result = createOneWayRpcResult();
return result;
}
@Override
public Future<RpcResult<Void>> flowMod(final FlowModInput arg0) {
checkRpcAndNext(arg0, "flowMod");
ListenableFuture<RpcResult<Void>> result = createOneWayRpcResult();
return result;
}
@Override
public synchronized Future<RpcResult<GetAsyncOutput>> getAsync(
final GetAsyncInput arg0) {
checkRpcAndNext(arg0, "echo");
Future<RpcResult<GetAsyncOutput>> result = createAndRegisterRpcResult(arg0);
return result;
}
@Override
public synchronized Future<RpcResult<GetConfigOutput>> getConfig(
final GetConfigInput arg0) {
checkRpcAndNext(arg0, "echo");
Future<RpcResult<GetConfigOutput>> result = createAndRegisterRpcResult(arg0);
return result;
}
@Override
public synchronized Future<RpcResult<GetFeaturesOutput>> getFeatures(
final GetFeaturesInput arg0) {
checkRpcAndNext(arg0, "getFeatures");
Future<RpcResult<GetFeaturesOutput>> result = createAndRegisterRpcResult(arg0);
return result;
}
@Override
public synchronized Future<RpcResult<GetQueueConfigOutput>> getQueueConfig(
final GetQueueConfigInput arg0) {
checkRpcAndNext(arg0, "echo");
Future<RpcResult<GetQueueConfigOutput>> result = createAndRegisterRpcResult(arg0);
return result;
}
@Override
public Future<RpcResult<Void>> groupMod(final GroupModInput arg0) {
checkRpcAndNext(arg0, "groupMod");
ListenableFuture<RpcResult<Void>> result = createOneWayRpcResult();
return result;
}
@Override
public Future<RpcResult<Void>> hello(final HelloInput arg0) {
checkRpcAndNext(arg0, "helloReply");
ListenableFuture<RpcResult<Void>> result = createOneWayRpcResult();
return result;
}
@Override
public Future<RpcResult<Void>> meterMod(final MeterModInput arg0) {
checkRpcAndNext(arg0, "meterMod");
ListenableFuture<RpcResult<Void>> result = createOneWayRpcResult();
return result;
}
@Override
public Future<RpcResult<Void>> packetOut(final PacketOutInput arg0) {
checkRpcAndNext(arg0, "packetOut");
ListenableFuture<RpcResult<Void>> result = createOneWayRpcResult();
return result;
}
@Override
public Future<RpcResult<Void>> portMod(final PortModInput arg0) {
checkRpcAndNext(arg0, "portMod");
ListenableFuture<RpcResult<Void>> result = createOneWayRpcResult();
return result;
}
@Override
public synchronized Future<RpcResult<RoleRequestOutput>> roleRequest(
final RoleRequestInput arg0) {
checkRpcAndNext(arg0, "echo");
Future<RpcResult<RoleRequestOutput>> result = createAndRegisterRpcResult(arg0);
return result;
}
@Override
public Future<RpcResult<Void>> setAsync(final SetAsyncInput arg0) {
checkRpcAndNext(arg0, "setAsync");
ListenableFuture<RpcResult<Void>> result = createOneWayRpcResult();
return result;
}
@Override
public Future<RpcResult<Void>> setConfig(final SetConfigInput arg0) {
checkRpcAndNext(arg0, "setConfig");
ListenableFuture<RpcResult<Void>> result = createOneWayRpcResult();
return result;
}
@Override
public Future<RpcResult<Void>> tableMod(final TableModInput arg0) {
checkRpcAndNext(arg0, "tableMod");
ListenableFuture<RpcResult<Void>> result = createOneWayRpcResult();
return result;
}
@Override
public Future<Boolean> disconnect() {
LOG.debug("adapter is told to disconnect");
DisconnectEventBuilder disconnectEventBuilder = new DisconnectEventBuilder();
disconnectEventBuilder.setInfo("disconnected by plugin");
systemListener.onDisconnectEvent(disconnectEventBuilder.build());
return null;
}
@Override
public boolean isAlive() {
// TODO make dynamic
return true;
}
@Override
public void setMessageListener(final OpenflowProtocolListener ofListener) {
this.ofListener = ofListener;
}
@Override
public void checkListeners() {
if (ofListener == null || systemListener == null
|| connectionReadyListener == null) {
occuredExceptions
.add(new IllegalStateException("missing listeners"));
}
}
@Override
public void setSystemListener(final SystemNotificationsListener systemListener) {
this.systemListener = systemListener;
}
/**
* @param rpcInput
* rpc call parameter
* @param rpcName
* rpc yang name
*/
private boolean checkRpc(final OfHeader rpcInput, final String rpcName) {
String msg = null;
boolean finished = true;
if (eventPlan.isEmpty()) {
throw new IllegalStateException("eventPlan already depleted");
}
LOG.debug("checking rpc: name={}, ver={}, xid={}", rpcName,
rpcInput.getVersion(), rpcInput.getXid());
if (!(eventPlan.peek() instanceof SwitchTestWaitForRpcEvent)
&& !(eventPlan.peek() instanceof SwitchTestWaitForAllEvent)) {
if (eventPlan.peek() instanceof SwitchTestNotificationEvent) {
SwitchTestNotificationEvent notifEvent = (SwitchTestNotificationEvent) (eventPlan
.peek());
msg = "expected [notification: "
+ notifEvent.getPlannedNotification() + "], got ["
+ rpcInput.getClass().getSimpleName() + "]";
} else if (eventPlan.peek() instanceof SwitchTestRcpResponseEvent) {
SwitchTestRcpResponseEvent rpcEvent = (SwitchTestRcpResponseEvent) (eventPlan
.peek());
msg = "expected [rpc: " + rpcEvent.getPlannedRpcResponse()
+ "], got [" + rpcInput.getClass().getSimpleName()
+ "]";
}
} else {
if (eventPlan.peek() instanceof SwitchTestWaitForAllEvent) {
SwitchTestWaitForAllEvent switchTestWaitForAll = (SwitchTestWaitForAllEvent) eventPlan
.peek();
Set<SwitchTestWaitForRpcEvent> eventBag = switchTestWaitForAll
.getWaitEventBag();
List<String> msgLot = new ArrayList<>();
if (eventBag == null || eventBag.isEmpty()) {
msg = "no wait events in bag";
} else {
finished = false;
for (SwitchTestWaitForRpcEvent switchTestWaitForRpc : eventBag) {
String msgPart = checkSingleRpcContent(rpcInput,
rpcName, switchTestWaitForRpc);
if (msgPart != null) {
msgLot.add(msgPart);
} else {
LOG.debug("wait event matched: {}", rpcName);
eventBag.remove(switchTestWaitForRpc);
if (eventBag.isEmpty()) {
finished = true;
}
msgLot.clear();
break;
}
}
}
if (!msgLot.isEmpty()) {
msg = Joiner.on(" | ").join(msgLot);
}
} else if (eventPlan.peek() instanceof SwitchTestWaitForRpcEvent) {
SwitchTestWaitForRpcEvent switchTestRpcEvent = (SwitchTestWaitForRpcEvent) eventPlan
.peek();
msg = checkSingleRpcContent(rpcInput, rpcName,
switchTestRpcEvent);
}
}
if (msg != null) {
LOG.debug("rpc check .. FAILED: " + msg);
occuredExceptions.add(new IllegalArgumentException("step:"
+ planItemCounter + " | " + msg));
} else {
LOG.debug("rpc check .. OK");
}
return finished;
}
/**
* @param rpcInput
* @param rpcName
* @param switchTestWaitForRpc
* @return
*/
private static String checkSingleRpcContent(final OfHeader rpcInput,
final String rpcName, final SwitchTestWaitForRpcEvent switchTestWaitForRpc) {
String failureMsg = null;
if (!rpcName.equals(switchTestWaitForRpc.getRpcName())) {
failureMsg = "expected rpc name ["
+ switchTestWaitForRpc.getRpcName() + "], got [" + rpcName
+ "]";
} else if (!rpcInput.getXid().equals(switchTestWaitForRpc.getXid())) {
failureMsg = "expected " + rpcName + ".xid ["
+ switchTestWaitForRpc.getXid() + "], got ["
+ rpcInput.getXid() + "]";
}
return failureMsg;
}
/**
* @param rpcInput
* rpc call parameter
* @param rpcName
* rpc yang name
*/
private synchronized void checkRpcAndNext(final OfHeader rpcInput, final String rpcName) {
boolean finished = checkRpc(rpcInput, rpcName);
if (finished) {
next();
}
}
/**
* discard current event, execute next, if possible
*/
private void next() {
LOG.debug("<---> STEPPING TO NEXT event in plan (leaving [{}] {})",
planItemCounter, eventPlan.peek());
eventPlan.pop();
planItemCounter++;
planTouched = true;
try {
Thread.sleep(JOB_DELAY);
} catch (InterruptedException e) {
LOG.error(e.getMessage(), e);
}
notify();
}
/**
* start or continue processing plan
*/
private synchronized void proceed() {
boolean processed = false;
LOG.debug("proceeding plan item[{}]: {}", planItemCounter,
eventPlan.peek());
if (eventPlan.peek() instanceof SwitchTestNotificationEvent) {
SwitchTestNotificationEvent notification = (SwitchTestNotificationEvent) eventPlan
.peek();
processNotification(notification);
processed = true;
} else if (eventPlan.peek() instanceof SwitchTestRcpResponseEvent) {
SwitchTestRcpResponseEvent rpcResponse = (SwitchTestRcpResponseEvent) eventPlan
.peek();
processRpcResponse(rpcResponse);
processed = true;
} else if (eventPlan.peek() instanceof SwitchTestCallbackEvent) {
SwitchTestCallbackEvent callbackEvent = (SwitchTestCallbackEvent) eventPlan
.peek();
try {
callbackEvent.getCallback().call();
} catch (Exception e) {
LOG.error(e.getMessage(), e);
occuredExceptions.add(e);
}
processed = true;
}
if (processed) {
next();
} else {
try {
LOG.debug("now WAITING for OF_LISTENER to act ..");
wait(proceedTimeout);
} catch (InterruptedException e) {
LOG.error(e.getMessage(), e);
}
}
}
@Override
public void run() {
LOG.debug("|---> evenPlan STARTING ..");
planItemCounter = 0;
while (!eventPlan.isEmpty()) {
planTouched = false;
proceed();
if (!planTouched) {
occuredExceptions
.add(new IllegalStateException(
"eventPlan STALLED, planItemCounter="
+ planItemCounter));
break;
}
}
try {
Thread.sleep(JOB_DELAY);
} catch (InterruptedException e) {
LOG.error(e.getMessage(), e);
}
LOG.debug("<---| eventPlan DONE");
}
/**
* @param notificationEvent
*/
private synchronized void processNotification(
final SwitchTestNotificationEvent notificationEvent) {
Notification notification = notificationEvent.getPlannedNotification();
LOG.debug("notificating OF_LISTENER: "
+ notification.getClass().getSimpleName());
// system events
if (notification instanceof DisconnectEvent) {
systemListener.onDisconnectEvent((DisconnectEvent) notification);
}
// of notifications
else if (notification instanceof EchoRequestMessage) {
ofListener.onEchoRequestMessage((EchoRequestMessage) notification);
} else if (notification instanceof ErrorMessage) {
ofListener.onErrorMessage((ErrorMessage) notification);
} else if (notification instanceof ExperimenterMessage) {
ofListener
.onExperimenterMessage((ExperimenterMessage) notification);
} else if (notification instanceof FlowRemovedMessage) {
ofListener.onFlowRemovedMessage((FlowRemovedMessage) notification);
} else if (notification instanceof HelloMessage) {
ofListener.onHelloMessage((HelloMessage) notification);
} else if (notification instanceof MultipartReplyMessage) {
ofListener
.onMultipartReplyMessage((MultipartReplyMessage) notification);
} else if (notification instanceof PacketInMessage) {
ofListener.onPacketInMessage((PacketInMessage) notification);
} else if (notification instanceof PortStatusMessage) {
ofListener.onPortStatusMessage((PortStatusMessage) notification);
}
// default
else {
occuredExceptions.add(new IllegalStateException("step:"
+ planItemCounter + " | "
+ "message listening not supported for type: "
+ notification.getClass()));
}
LOG.debug("notification [" + notification.getClass().getSimpleName()
+ "] .. done");
}
/**
* @param rpcResponse
*/
private synchronized void processRpcResponse(
final SwitchTestRcpResponseEvent rpcResponse) {
OfHeader plannedRpcResponseValue = rpcResponse.getPlannedRpcResponse();
LOG.debug("rpc-responding to OF_LISTENER: {}", rpcResponse.getXid());
@SuppressWarnings("unchecked")
final SettableFuture<RpcResult<?>> response = (SettableFuture<RpcResult<?>>) rpcResults
.get(rpcResponse.getXid());
if (response != null) {
boolean successful = plannedRpcResponseValue != null;
Collection<RpcError> errors;
if (successful) {
errors = Collections.emptyList();
} else {
errors = Lists.newArrayList(RpcErrors.getRpcError("unit",
"unit", "not requested", ErrorSeverity.ERROR,
"planned response to RPC.id = " + rpcResponse.getXid(),
ErrorType.RPC, new Exception(
"rpc response failed (planned behavior)")));
}
final RpcResult<?> result = Rpcs.getRpcResult(successful,
plannedRpcResponseValue, errors);
setFutureViaPool(response, result);
} else {
String msg = "RpcResponse not expected: xid="
+ rpcResponse.getXid() + ", "
+ plannedRpcResponseValue.getClass().getSimpleName();
LOG.error(msg);
occuredExceptions.add(new IllegalStateException("step:"
+ planItemCounter + " | " + msg));
}
LOG.debug("rpc [" + rpcResponse.getXid() + "] .. done");
}
private void setFutureViaPool(final SettableFuture<RpcResult<?>> response, final RpcResult<?> result) {
pool.execute(new Runnable() {
@Override
public void run() {
response.set(result);
}
});
}
/**
* @param arg0
* rpc call content
* @return rpc future result
*/
private <IN extends OfHeader, OUT extends OfHeader> SettableFuture<RpcResult<OUT>> createAndRegisterRpcResult(
final IN arg0) {
SettableFuture<RpcResult<OUT>> result = SettableFuture.create();
rpcResults.put(arg0.getXid(), result);
return result;
}
/**
* @return rpc future result
*/
private static ListenableFuture<RpcResult<Void>> createOneWayRpcResult() {
return Futures.immediateFuture(RpcResultBuilder.<Void>success().build());
}
/**
* @param eventPlan
* the eventPlan to set
*/
public void setEventPlan(final Stack<? extends SwitchTestEvent> eventPlan) {
this.eventPlan = eventPlan;
}
/**
* @param proceedTimeout
* max timeout for processing one planned event (in [ms])
*/
public void setProceedTimeout(final long proceedTimeout) {
this.proceedTimeout = proceedTimeout;
}
/**
* @return the occuredExceptions
*/
public List<Exception> getOccuredExceptions() {
return occuredExceptions;
}
@Override
public void fireConnectionReadyNotification() {
connectionReadyListener.onConnectionReady();
}
@Override
public void setConnectionReadyListener(
final ConnectionReadyListener connectionReadyListener) {
this.connectionReadyListener = connectionReadyListener;
}
@Override
public Future<RpcResult<Void>> multipartRequest(final MultipartRequestInput arg0) {
checkRpcAndNext(arg0, "multipartRequestInput");
ListenableFuture<RpcResult<Void>> result = createOneWayRpcResult();
return result;
}
@Override
public InetSocketAddress getRemoteAddress() {
return InetSocketAddress.createUnresolved("unittest-odl.example.org", 4242);
}
@Override
public boolean isAutoRead() {
return autoRead;
}
@Override
public <T extends OutboundQueueHandler> OutboundQueueHandlerRegistration<T> registerOutboundQueueHandler(final T t, final int i, final long l) {
return null;
}
@Override
public void setAutoRead(final boolean autoRead) {
this.autoRead = autoRead;
}
@Override
public void setPacketInFiltering(final boolean packetInFiltering) {
this.packetInFiltering = packetInFiltering;
}
}