Java tutorial
/* * ESnet Network Operating System (ENOS) Copyright (c) 2016, The Regents * of the University of California, through Lawrence Berkeley National * Laboratory (subject to receipt of any required approvals from the * U.S. Dept. of Energy). All rights reserved. * * If you have questions about your rights to use or distribute this * software, please contact Berkeley Lab's Innovation & Partnerships * Office at IPO@lbl.gov. * * NOTICE. This Software was developed under funding from the * U.S. Department of Energy and the U.S. Government consequently retains * certain rights. As such, the U.S. Government has been granted for * itself and others acting on its behalf a paid-up, nonexclusive, * irrevocable, worldwide license in the Software to reproduce, * distribute copies to the public, prepare derivative works, and perform * publicly and display publicly, and to permit other to do so. * */ package net.es.netshell.controller.impl; import com.rabbitmq.client.Channel; import com.rabbitmq.client.Connection; import com.rabbitmq.client.AMQP.BasicProperties; import com.rabbitmq.client.ConnectionFactory; import com.rabbitmq.client.QueueingConsumer; import net.es.netshell.controller.core.Controller; import net.es.netshell.controller.intf.*; import net.es.netshell.odlcorsa.OdlCorsaIntf; import net.es.netshell.odlmdsal.impl.OdlMdsalImpl; import org.codehaus.jackson.map.ObjectMapper; import org.opendaylight.yang.gen.v1.urn.ietf.params.xml.ns.yang.ietf.yang.types.rev100924.MacAddress; import org.opendaylight.yang.gen.v1.urn.opendaylight.flow.inventory.rev130819.FlowCapableNode; import org.opendaylight.yang.gen.v1.urn.opendaylight.flow.inventory.rev130819.FlowId; import org.opendaylight.yang.gen.v1.urn.opendaylight.flow.inventory.rev130819.meters.Meter; import org.opendaylight.yang.gen.v1.urn.opendaylight.flow.inventory.rev130819.meters.MeterKey; import org.opendaylight.yang.gen.v1.urn.opendaylight.flow.inventory.rev130819.tables.Table; import org.opendaylight.yang.gen.v1.urn.opendaylight.flow.inventory.rev130819.tables.TableKey; import org.opendaylight.yang.gen.v1.urn.opendaylight.flow.inventory.rev130819.tables.table.Flow; import org.opendaylight.yang.gen.v1.urn.opendaylight.flow.inventory.rev130819.tables.table.FlowKey; import org.opendaylight.yang.gen.v1.urn.opendaylight.flow.types.rev131026.FlowRef; import org.opendaylight.yang.gen.v1.urn.opendaylight.inventory.rev130819.Nodes; import org.opendaylight.yang.gen.v1.urn.opendaylight.meter.types.rev130918.MeterId; import org.opendaylight.yang.gen.v1.urn.opendaylight.inventory.rev130819.NodeConnectorRef; import org.opendaylight.yang.gen.v1.urn.opendaylight.inventory.rev130819.node.NodeConnector; import org.opendaylight.yang.gen.v1.urn.opendaylight.inventory.rev130819.nodes.Node; import org.opendaylight.yang.gen.v1.urn.opendaylight.inventory.rev130819.nodes.NodeKey; import org.opendaylight.yang.gen.v1.urn.opendaylight.meter.types.rev130918.MeterRef; import org.opendaylight.yang.gen.v1.urn.opendaylight.packet.service.rev130709.PacketReceived; import org.opendaylight.yangtools.yang.binding.InstanceIdentifier; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import java.nio.ByteBuffer; import java.util.concurrent.ExecutionException; /** * Created by bmah on 1/8/16. */ public class SdnController implements Runnable, AutoCloseable, OdlMdsalImpl.Callback { // Logging static final private Logger logger = LoggerFactory.getLogger(SdnController.class); // RabbitMQ stuff private Connection connection; private Channel channel; private QueueingConsumer consumer; private ObjectMapper mapper; // OpenFlow stuff Controller controller; public SdnController() { try { // Get a connection to the AMPQ broker (e.g. RabbitMQ server) ConnectionFactory factory = new ConnectionFactory(); factory.setHost("localhost"); connection = factory.newConnection(); channel = connection.createChannel(); // Create the SDN controller queue, non-durable, non-exclusive, non-auto-delete, no other args channel.queueDeclare(Common.controllerRequestQueueName, false, false, false, null); channel.basicQos(1); // what does this do? // Set up a consumer who will read messages from the queue consumer = new QueueingConsumer(channel); channel.basicConsume(Common.controllerRequestQueueName, false, consumer); // Create exchange for notifications channel.exchangeDeclare(Common.notificationExchangeName, "fanout"); // XXX we need to do some error-checking here to handle the case that the AMPQ server is dead // or unreachable. // JSON parser setup mapper = new ObjectMapper(); // OpenFlow controller setup controller = Controller.getInstance(); logger.info(SdnController.class.getName() + " ready"); } catch (Exception e) { e.printStackTrace(); } } public void close() throws Exception { if (connection != null) { connection.close(); connection = null; } } /** * Program PACKET_IN callback to point to us * @return boolean true indicates success */ public boolean setCallback() { if (controller.getOdlMdsalImpl() != null) { controller.getOdlMdsalImpl().setPacketInCallback(this); return true; } else { return false; } } /** * Clear the PACKET_IN callback * @return boolean true indicates success */ public boolean clearCallback() { controller.getOdlMdsalImpl().setPacketInCallback(null); return true; } Node getNetworkDeviceByDpidArray(byte[] dpid) { return controller.getOdlMdsalImpl().getNetworkDeviceByDpidArray(dpid); } private SdnDeleteMeterReply doSdnDeleteMeter(SdnDeleteMeterRequest req) { SdnDeleteMeterReply rep = new SdnDeleteMeterReply(); rep.setError(false); // This feature only works on Corsas (for now) if (Controller.isCorsa(req.dpid)) { // Figure out what node Node node = getNetworkDeviceByDpidArray(req.dpid); if (node == null) { rep.setErrorMessage("Cannot find switch with DPID"); rep.setError(true); } NodeKey nodeKey = new NodeKey(node.getId()); // Get the Corsa driver OdlCorsaIntf odlc = controller.getOdlCorsaImpl(); if (odlc == null) { rep.setErrorMessage("Cannot find Corsa driver"); rep.setError(true); } // Get a meter ref from the string we got passed in. // See OdlMdsalImpl.addFlow to see how to do this // build instanceidentifier to a meter // MeterRef comes from the instance identifier // XXX maybe the right thing to do is to have the delete meter request // include a dpid and a meter number, not just a string. The guy who created the meter had // to have had these in the first place. From that, we can construct a // a MeterRef, similar to how we do that in OdlMdsalImpl.addFlow. // //>>> mr.value.getPath() //[org.opendaylight.yang.gen.v1.urn.opendaylight.inventory.rev130819.Nodes, org.opendaylight.yang.gen.v1.urn.opendaylight.inventory.rev130819.nodes.Node[key=NodeKey [_id=Uri [_value=openflow:144397094251034113]]], org.opendaylight.yang.gen.v1.urn.opendaylight.flow.inventory.rev130819.FlowCapableNode, org.opendaylight.yang.gen.v1.urn.opendaylight.flow.inventory.rev130819.meters.Meter[key=MeterKey [_meterId=MeterId [_value=20]]]] //>>> print mr.toString() //MeterRef [_value=KeyedInstanceIdentifier{targetType=interface org.opendaylight.yang.gen.v1.urn.opendaylight.flow.inventory.rev130819.meters.Meter, path=[org.opendaylight.yang.gen.v1.urn.opendaylight.inventory.rev130819.Nodes, org.opendaylight.yang.gen.v1.urn.opendaylight.inventory.rev130819.nodes.Node[key=NodeKey [_id=Uri [_value=openflow:144397094251034113]]], org.opendaylight.yang.gen.v1.urn.opendaylight.flow.inventory.rev130819.FlowCapableNode, org.opendaylight.yang.gen.v1.urn.opendaylight.flow.inventory.rev130819.meters.Meter[key=MeterKey [_meterId=MeterId [_value=20]]]]}] //>>> print mr.value //KeyedInstanceIdentifier{targetType=interface org.opendaylight.yang.gen.v1.urn.opendaylight.flow.inventory.rev130819.meters.Meter, path=[org.opendaylight.yang.gen.v1.urn.opendaylight.inventory.rev130819.Nodes, org.opendaylight.yang.gen.v1.urn.opendaylight.inventory.rev130819.nodes.Node[key=NodeKey [_id=Uri [_value=openflow:144397094251034113]]], org.opendaylight.yang.gen.v1.urn.opendaylight.flow.inventory.rev130819.FlowCapableNode, org.opendaylight.yang.gen.v1.urn.opendaylight.flow.inventory.rev130819.meters.Meter[key=MeterKey [_meterId=MeterId [_value=20]]]]} /* InstanceIdentifier<Flow> flowInstanceIdentifier = InstanceIdentifier.builder(Nodes.class). child(Node.class, nodeKey). augmentation(FlowCapableNode.class). child(Table.class, new TableKey(flow.getTableId())). child(Flow.class, flow.getKey()). build(); */ // Another approach (suggested by macauley) is to maintain a mapping of active (dpid, meterid) => // MeterRef. MeterId mid = new MeterId(req.getMeter()); InstanceIdentifier<Meter> meterInstanceIdentifier = InstanceIdentifier.builder(Nodes.class) .child(Node.class, nodeKey).augmentation(FlowCapableNode.class) .child(Meter.class, new MeterKey(mid)).build(); MeterRef meterRef = new MeterRef(meterInstanceIdentifier); try { odlc.deleteMeter(meterRef); } catch (InterruptedException | ExecutionException e) { rep.setErrorMessage(e.toString()); rep.setError(true); } } return rep; } private SdnDeleteForwardReply doSdnDeleteForward(SdnDeleteForwardRequest req) { SdnDeleteForwardReply rep = new SdnDeleteForwardReply(); rep.setError(false); // Figure out what node Node node = getNetworkDeviceByDpidArray(req.getDpid()); if (node == null) { rep.setErrorMessage("Cannot find switch with DPID"); rep.setError(true); } FlowId fid = new FlowId(req.getFlowId()); // Construct a FlowRef for the flow to go away. This derived by dpid and flowId. InstanceIdentifier<Flow> flowInstanceIdentifier = InstanceIdentifier.builder(Nodes.class) .child(Node.class, node.getKey()).augmentation(FlowCapableNode.class) .child(Table.class, new TableKey(req.getTableId())).child(Flow.class, new FlowKey(fid)).build(); FlowRef flowRef = new FlowRef(flowInstanceIdentifier); boolean rc = controller.deleteFlow(req.getDpid(), flowRef); if (!rc) { rep.setErrorMessage("Could not delete flow"); rep.setError(true); } return rep; } private SdnForwardReply doSdnForward(SdnForwardRequest req) { SdnForwardReply rep = new SdnForwardReply(); rep.setError(false); try { // Translate SdnForwardRequest to L2Translation Controller.L2Translation l2t = new Controller.L2Translation(); l2t.dpid = req.getDpid(); l2t.priority = req.getPriority(); l2t.c = req.getC(); l2t.inPort = req.getInPort(); l2t.vlan1 = (short) req.getVlan1(); if (req.getSrcMac1() != null) { l2t.srcMac1 = new MacAddress(req.getSrcMac1()); } l2t.dstMac1 = new MacAddress(req.getDstMac1()); int numOutputs = req.outputs.length; l2t.outputs = new Controller.L2Translation.L2TranslationOutput[numOutputs]; for (int i = 0; i < numOutputs; i++) { l2t.outputs[i] = new Controller.L2Translation.L2TranslationOutput(); l2t.outputs[i].outPort = req.outputs[i].outPort; l2t.outputs[i].vlan = (short) req.outputs[i].vlan; l2t.outputs[i].dstMac = new MacAddress(req.outputs[i].dstMac); } l2t.pcp = (short) req.getPcp(); l2t.queue = (short) req.getQueue(); l2t.meter = req.getMeter(); FlowRef fr = controller.installL2ForwardingRule(l2t); if (fr == null) { rep.setErrorMessage("Unable to install forwarding flow"); rep.setError(true); } else { rep.setDpid(req.dpid); TableKey tk = fr.getValue().firstIdentifierOf(Table.class).firstKeyOf(Table.class, TableKey.class); rep.setTableId(tk.getId().shortValue()); FlowKey fk = fr.getValue().firstIdentifierOf(Flow.class).firstKeyOf(Flow.class, FlowKey.class); rep.setFlowId(fk.getId().getValue()); } } catch (Exception e) { e.printStackTrace(); rep.setErrorMessage("Exception: " + e.toString()); rep.setError(true); } return rep; } private SdnForwardReply doSdnForwardToController(SdnForwardToControllerRequest req) { SdnForwardReply rep = new SdnForwardReply(); rep.setError(false); try { // Translate SdnForwardToControllerRequest to L2Translation // This code based on doSdnForward() Controller.L2Translation l2t = new Controller.L2Translation(); l2t.dpid = req.getDpid(); l2t.priority = req.getPriority(); l2t.c = req.getC(); l2t.inPort = req.getInPort(); l2t.vlan1 = (short) req.getVlan1(); l2t.srcMac1 = new MacAddress(req.getSrcMac1()); l2t.dstMac1 = new MacAddress(req.getDstMac1()); FlowRef fr = controller.installL2ControllerRule(l2t); if (fr == null) { rep.setErrorMessage("Unable to install forwarding flow"); rep.setError(true); } else { rep.setDpid(req.dpid); TableKey tk = fr.getValue().firstIdentifierOf(Table.class).firstKeyOf(Table.class, TableKey.class); rep.setTableId(tk.getId().shortValue()); FlowKey fk = fr.getValue().firstIdentifierOf(Flow.class).firstKeyOf(Flow.class, FlowKey.class); rep.setFlowId(fk.getId().getValue()); } } catch (Exception e) { e.printStackTrace(); rep.setErrorMessage("Exception: " + e.toString()); rep.setError(true); } return rep; } private SdnInstallMeterReply doSdnInstallMeter(SdnInstallMeterRequest req) { SdnInstallMeterReply rep = new SdnInstallMeterReply(); rep.setError(false); // This feature only works on Corsas (for now) if (Controller.isCorsa(req.dpid)) { // Figure out what node Node node = getNetworkDeviceByDpidArray(req.dpid); if (node == null) { rep.setErrorMessage("Cannot find switch with DPID"); rep.setError(true); } // Get the Corsa driver OdlCorsaIntf odlc = controller.getOdlCorsaImpl(); if (odlc == null) { rep.setErrorMessage("Cannot find Corsa driver"); rep.setError(true); } // Figure out what kind of meter we need to set and do it. // // If there's no committed or excess rate, then it's a green meter if (req.getCr() == 0 && req.getEr() == 0) { try { MeterRef mr = odlc.createGreenMeter(node, req.getMeter()); } catch (Exception e) { rep.setErrorMessage(e.toString()); rep.setError(true); } } // If there's a committed rate but no excess rate, then a green-yellow meter else if (req.getCr() != 0 && req.getEr() == 0) { try { MeterRef mr = odlc.createGreenYellowMeter(node, req.getMeter(), req.getCr(), req.getCbs()); } catch (Exception e) { rep.setErrorMessage(e.toString()); rep.setError(true); } } // If there's an excess rate by no committed rate, then a green-red meter else if (req.getCr() == 0 && req.getEr() != 0) { try { MeterRef mr = odlc.createGreenRedMeter(node, req.getMeter(), req.getEr(), req.getEbs()); } catch (Exception e) { rep.setErrorMessage(e.toString()); rep.setError(true); } } // If there is both a committed and excess rate, then it's a green-yellow-red meter else if (req.getCr() != 0 && req.getEr() != 0) { try { MeterRef mr = odlc.createGreenYellowRedMeter(node, req.getMeter(), req.getCr(), req.getCbs(), req.getEr(), req.getEbs()); } catch (Exception e) { rep.setErrorMessage(e.toString()); rep.setError(true); } } // The preceding chain of conditionals shouldn't let us get here... else { rep.setErrorMessage("Cannot determine meter type."); rep.setError(true); } } else { rep.setError(true); rep.setErrorMessage("Functionality not available on this switch."); } return rep; } private SdnTransmitPacketReply doSdnTransmitPacket(SdnTransmitPacketRequest req) { SdnTransmitPacketReply rep = new SdnTransmitPacketReply(); rep.setError(false); boolean rc = controller.transmitDataPacket(req.dpid, req.outPort, req.payload); if (!rc) { rep.setErrorMessage("Could not send PACKET_OUT"); rep.setError(true); } return rep; } public void run() { // Loop forever while (true) { try { // Get the next message QueueingConsumer.Delivery delivery = consumer.nextDelivery(); // Get the properties for the request message, set up the properties // for the reply message. BasicProperties props = delivery.getProperties(); BasicProperties replyProps = new BasicProperties.Builder().correlationId(props.getCorrelationId()) .build(); // Placeholder for a reply, if we have one to send String message2 = null; try { // Parse the body. Get the string containing the JSON data. String message = new String(delivery.getBody(), "UTF-8"); logger.info("Received: " + message); // Figure out the message type as a string so we know how to parse it. SdnRequest req = mapper.readValue(message, SdnRequest.class); SdnReply rep = null; // Dispatch to command handlers depending on the type of message // // Handle ping requests here since they're pretty trivial if (req.getRequestType().equals(SdnPingRequest.TYPE)) { SdnPingRequest pingReq = mapper.readValue(message, SdnPingRequest.class); SdnPingReply pingRep = new SdnPingReply(); pingRep.setError(false); pingRep.setPayload(pingReq.getPayload()); rep = pingRep; } // Other request types dispatch to handler functions in this module. // Place in alphabetical order. else if (req.getRequestType().equals(SdnDeleteMeterRequest.TYPE)) { SdnDeleteMeterRequest meterReq = mapper.readValue(message, SdnDeleteMeterRequest.class); rep = doSdnDeleteMeter(meterReq); } else if (req.getRequestType().equals(SdnDeleteForwardRequest.TYPE)) { SdnDeleteForwardRequest forwardReq = mapper.readValue(message, SdnDeleteForwardRequest.class); rep = doSdnDeleteForward(forwardReq); } else if (req.getRequestType().equals(SdnForwardRequest.TYPE)) { SdnForwardRequest forwardReq = mapper.readValue(message, SdnForwardRequest.class); rep = doSdnForward(forwardReq); } else if (req.getRequestType().equals(SdnForwardToControllerRequest.TYPE)) { SdnForwardToControllerRequest flowReq = mapper.readValue(message, SdnForwardToControllerRequest.class); rep = doSdnForwardToController(flowReq); } else if (req.getRequestType().equals(SdnInstallMeterRequest.TYPE)) { SdnInstallMeterRequest meterReq = mapper.readValue(message, SdnInstallMeterRequest.class); rep = doSdnInstallMeter(meterReq); } else if (req.getRequestType().equals(SdnTransmitPacketRequest.TYPE)) { SdnTransmitPacketRequest packetReq = mapper.readValue(message, SdnTransmitPacketRequest.class); rep = doSdnTransmitPacket(packetReq); } else { // Unknown message. rep = new SdnReply(); rep.setError(true); rep.setErrorMessage("Unknown message type"); } // If there's a response, then get it in JSON representation if (rep != null) { message2 = mapper.writeValueAsString(rep); } } catch (Exception e) { e.printStackTrace(); } finally { // If we have a reply to send, then great, send it and ACK the old message if (message2 != null) { logger.info("Reply: " + message2); channel.basicPublish("", props.getReplyTo(), replyProps, message2.getBytes("UTF-8")); } channel.basicAck(delivery.getEnvelope().getDeliveryTag(), false); } } catch (Exception e) { e.printStackTrace(); // We can get here if there was a problem reading a message from the AMPQ service. // Sleep for a second to avoid us busy-waiting in this loop. try { Thread.sleep(1000); } catch (Exception e2) { } } } } /** * ODL callback for PacketReceived notification * @param notification PACKET_IN message */ public void callback(PacketReceived notification) { SdnReceivePacketReply rep = new SdnReceivePacketReply(); try { // Compose this data structure. Much of this processing was originally derived from Python // callback code. NodeConnectorRef nodeConnectorRef = notification.getIngress(); String nodeId = nodeConnectorRef.getValue().firstIdentifierOf(Node.class) .firstKeyOf(Node.class, NodeKey.class).getId().getValue(); // The NodeId in ODL is the literal string "openflow:" followed by a decimal representation // of the DPID. We want just the DPID portion as an array of bytes. String[] nodeIdComponents = nodeId.split(":", 2); Long dpidNumeric = 0L; assert (nodeIdComponents[0].equals("openflow")); String dpidString = nodeIdComponents[1]; dpidNumeric = Long.parseLong(dpidString); rep.dpid = ByteBuffer.allocate(Long.SIZE / Byte.SIZE).putLong(dpidNumeric).array(); String nodeConnectorId = null; NodeConnector nc = controller.getOdlMdsalImpl() .getNodeConnectorByInstanceId(nodeConnectorRef.getValue()); if (nc != null) { nodeConnectorId = controller.getOdlMdsalImpl().getNodeConnectorName(nc); } rep.inPort = nodeConnectorId; rep.payload = notification.getPayload(); String dpidHexString = String.format("[%02x, %02x, %02x, %02x, %02x, %02x, %02x, %02x]", rep.dpid[0], rep.dpid[1], rep.dpid[2], rep.dpid[3], rep.dpid[4], rep.dpid[5], rep.dpid[6], rep.dpid[7]); logger.debug("node " + nodeId + ", dpid " + dpidHexString + ", nodeConnector " + nodeConnectorId + " payload bytes ?"); // JSON encode String callbackMessage = mapper.writeValueAsString(rep); // Set message properties for the notification. // We make notifications expire after 30 seconds to avoid building up huge queues // of messages if for example there is no ENOS running. BasicProperties props = new BasicProperties.Builder().expiration("30000").build(); // Send it. channel.basicPublish(Common.notificationExchangeName, "", props, callbackMessage.getBytes("UTF-8")); } catch (Exception e) { // If we run into a problem dealing with the PacketReceived (e.g. parsing), note that // we won't be emitting a notification to the client side. e.printStackTrace(); return; } } }