/* * Copyright (c) 2013 Big Switch Networks, Inc. * * Licensed under the Eclipse Public License, Version 1.0 (the * "License"); you may not use this file except in compliance with the * License. You may obtain a copy of the License at * * http://www.eclipse.org/legal/epl-v10.html * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or * implied. See the License for the specific language governing * permissions and limitations under the License. */ package org.sdnplatform.ovsdb.internal; import java.net.InetSocketAddress; import java.util.ArrayList; import java.util.HashMap; import java.util.Iterator; import java.util.Map.Entry; import java.util.concurrent.atomic.AtomicInteger; import org.jboss.netty.bootstrap.ClientBootstrap; import org.jboss.netty.channel.Channel; import org.jboss.netty.channel.ChannelFuture; import org.jboss.netty.handler.ssl.SslHandler; import org.openflow.util.HexString; import org.sdnplatform.ovsdb.IOVSDB; import org.sdnplatform.ovsdb.internal.JSONShowReplyMsg.ShowResult; import org.slf4j.Logger; import org.slf4j.LoggerFactory; /** * IOVDBImpl maps the ovsdb state of the OVS switch that is configured for * tunneling. It creates tunnels in this switch via JSON RPC calls to * ovsdb-server. It offers a public API for managing tunnels in this switch * potentially used by TunnelManager and VCenterManager * * @author Saurav Das * */ public class OVSDBImpl implements IOVSDB { private static Logger logger = LoggerFactory.getLogger(OVSDBImpl.class); private long dpid; private String hexDpid; private Channel channel; private boolean useSSL; private AtomicInteger messageid; private String mgmtIPAddr; private String tunnelIPAddr; private String tunnelIPAddrName; private OVSDBClientPipelineFactory ovsdbcfact; private ClientBootstrap bootstr; private Object statusObj; private String description; // OVS-db state protected HashMap<String, OVSPort> port; protected HashMap<String, OVSController> controller; protected HashMap<String, OVSInterface> intf; protected HashMap<String, OVSDatabase> open_vswitch; protected HashMap<String, OVSBridge> bridge; //JSON RPC reply messages private static final int SHOW_REPLY = 0; private static final int ADD_PORT_REPLY = 1; private static final int DEL_PORT_REPLY = 2; private static final int SET_DPID_REPLY = 3; private static final int SET_CIP_REPLY = 4; private static int OVSDB_SERVER_PORT = 6635; private static int OVSDB_SSL_SERVER_PORT = 6636; private int expectedMessage; private int expectedMessageReplyId; /** * Constructor * @param dpid of ovs * @param mgmtIPAddr the IP address used by JSON RPC messaging * @param ovsdbcfact netty client pipeline factory for plaintext * @param bootstr netty client bootstrap * @param statusObj ad-hoc object for RPC synchronization */ public OVSDBImpl(long dpid, String mgmtIPAddr, OVSDBClientPipelineFactory ovsdbcfact, ClientBootstrap bootstr, Object statusObj) { this.dpid = dpid; this.mgmtIPAddr = mgmtIPAddr; this.hexDpid = (this.dpid != -1) ? HexString.toHexString(this.dpid) : null; this.ovsdbcfact = ovsdbcfact; this.bootstr = bootstr; this.statusObj = statusObj; this.useSSL = false; // Disable SSL usage for now this.tunnelIPAddr = null; this.messageid = new AtomicInteger(0); port = new HashMap<String, OVSPort>() ; controller = new HashMap<String, OVSController>(); intf = new HashMap<String, OVSInterface>(); open_vswitch = new HashMap<String, OVSDatabase>(); bridge = new HashMap<String, OVSBridge>(); } private void setChannel(Channel channel) { this.channel = channel; } @Override public String toString() { return hexDpid; } @Override public String getDescription() { return description; } @Override public void setDescription(String description) { this.description = description; } public String getMgmtIPAddr() { return mgmtIPAddr; } //********************* // DPID related //********************* public long getDpid() { return dpid; } public String getHexDpid() { return hexDpid; } @Override public String getBridgeDpid() { sendShowMessage(); synchronized(this) { if (bridge.size() != 0) { for (OVSBridge b : bridge.values()) { if (b.getNew().getName().equals("ovs-br0")) { String dpidstr = b.getNew().getReportedDpidString(); if (dpidstr != null && !dpidstr.equals("")) { //set cached values in this object dpid = Long.parseLong(dpidstr, 16); hexDpid = HexString.toHexString(dpid); } return dpidstr; } } } } return null; } @Override public void setBridgeDpid(String dpidstr) { if (Long.parseLong(dpidstr, 16) == dpid) { return; } // set locally dpid = Long.parseLong(dpidstr, 16); hexDpid = HexString.toHexString(dpid); // set dpid in ovs sendShowMessage(false); expectedMessageReplyId = peekNextMessageId(); expectedMessage = SET_DPID_REPLY; synchronized(this) { JSONSetDpidMsg jsdpid; try { jsdpid = new JSONSetDpidMsg(dpidstr, this, getNextMessageId()); if (channel != null) channel.write(jsdpid); } catch (OVSDBBridgeUnknown e) { logger.error("Couldn't set-bridge-dpid {} for sw @ {}: could" + " not find ovs-br0 bridge", dpidstr, mgmtIPAddr); } } //wait for an update synchronized(statusObj) { try { statusObj.wait(1000); } catch (InterruptedException e) { logger.error("Interrupted while waiting for set bridge", e); } } if (logger.isDebugEnabled()) logger.debug("channel closing - set bridge"); if (channel != null) channel.close(); resetMessageId(); } //********************* // Controller IP related //********************* @Override public ArrayList<String> getControllerIPs() { ArrayList<String> cntrlIPAddrs = new ArrayList<String>(); sendShowMessage(); synchronized(this) { if (controller.size() != 0) { for (OVSController c : controller.values()) { String cip = c.getNew().getTarget(); if (cip != null && !cip.equals("")) cntrlIPAddrs.add(cip); } } } return cntrlIPAddrs; } @Override public void setControllerIPs(ArrayList<String> cntrIP) { sendShowMessage(false); // set dpid expectedMessageReplyId = peekNextMessageId(); expectedMessage = SET_CIP_REPLY; synchronized(this) { JSONSetCIPMsg jsetcip; try { jsetcip = new JSONSetCIPMsg(cntrIP, this, getNextMessageId()); if (channel != null) channel.write(jsetcip); } catch (OVSDBBridgeUnknown e) { logger.error("Couldn't set-controller-ips for {}: could" + " not find ovs-br0 bridge", dpid); } } //wait for an update synchronized(statusObj) { try { statusObj.wait(1000); } catch (InterruptedException e) { // ignore } } if (logger.isDebugEnabled()) logger.debug("channel closing - set controller IP"); if (channel != null) channel.close(); resetMessageId(); } //********************* // Tunnel IP related //********************* public String getTunnelIPAddress() { if (tunnelIPAddr != null) { return tunnelIPAddr; } return discoverIPAddress(); } public String getTunnelIPAddress(boolean refresh) { if (refresh) { return discoverIPAddress(); } else { return getTunnelIPAddress(); } } protected String discoverIPAddress() { // discover IP addr from ovsdb server sendShowMessage(); synchronized(this) { if (bridge.size() != 0) { //FIXME only works iff one of the bridges is tunnel endpoint for(OVSBridge b : bridge.values()) { tunnelIPAddr = b.getNew().getTunnelIPAddress(); if (tunnelIPAddr != null) return tunnelIPAddr; } } return null; } } public String getTunnelIPAddrName() { if (tunnelIPAddrName != null) { return tunnelIPAddrName; } if (tunnelIPAddr == null) { return null; } // convert tunnelIPAddr to tunnelIPAddrName String after = tunnelIPAddr; String before = ""; tunnelIPAddrName = "vta"; for (int i=0; i<4; i++) { int index = after.indexOf('.'); if (index == -1) { before = after; } else { before = after.substring(0, index); after = after.substring(index+1); } if (before.length() == 1) tunnelIPAddrName += "00" + before; else if (before.length() == 2) tunnelIPAddrName += "0"+before; else tunnelIPAddrName += before; } return tunnelIPAddrName; } public ArrayList<String> getTunnelPortNames() { sendShowMessage(); ArrayList<String> tunnports = new ArrayList<String>(); synchronized(this) { if (port.size() != 0) { for (OVSPort op : port.values()) { if (op.getNew().getName().startsWith("vta")) { tunnports.add(op.getNew().getName()); } } } } return tunnports; } //*************************** // JSON RPC Message ID related //*************************** /** * Transaction id for JSON RPC messages. Calling this function * returns the next message Id and internally increments the counter * To query messageId without changing it use peekNextMessageId * */ private int getNextMessageId() { return messageid.getAndIncrement(); } /** * Transaction id for JSON RPC messages. Calling this function returns * the next message id, but does NOT increment the counter. * No message should be sent by the caller with the returned messageId */ private int peekNextMessageId() { return messageid.get(); } private void resetMessageId() { messageid.set(0); } //************************** // JSON Show related //************************** /** * Start a connection to the OVS DB server. * * Initially we try to establish an SSL connection. If it succeeds (and we * handshake successfully) we contiune using SSL. * If *any* SSL connection attempt fails we retry with plaintext and will * use plaintext from then on. */ protected Channel connect(boolean newChannelForShow) { // Make a new connection. For standalone show-commands a separate // channel is created which wouldn't clash with other commands Channel showCh = null; if (!newChannelForShow) { setChannel(null); } synchronized(ovsdbcfact) { ovsdbcfact.setCurSwitch(this); ovsdbcfact.setStatusObject(statusObj); ovsdbcfact.setUseSSL(true); if (useSSL) { // Try to connect via SSL ChannelFuture connectFuture = bootstr.connect( new InetSocketAddress(mgmtIPAddr, OVSDB_SSL_SERVER_PORT)); connectFuture.awaitUninterruptibly(); if (connectFuture.isDone() && connectFuture.isSuccess()) { SslHandler h = connectFuture.getChannel().getPipeline() .get(SslHandler.class); ChannelFuture hsFuture = h.handshake(); hsFuture.awaitUninterruptibly(); if (hsFuture.isDone() && hsFuture.isSuccess()) { if (!newChannelForShow) setChannel(connectFuture.getChannel()); else showCh = connectFuture.getChannel(); } else { useSSL = false; } } else { // connection failed useSSL = false; } if (!useSSL) { // TODO: SSL failed, fall back now logger.info("OVSDB on {} <{}> does not listen for SSL " + "connections. Using plain text.", hexDpid, mgmtIPAddr); } } if (!useSSL) { ovsdbcfact.setUseSSL(false); // Now try plain connection. If the SSL connection failed we // will fall through and now try the plain connection. // FIXME: add option to never fall back to plain ChannelFuture connectFuture = bootstr.connect( new InetSocketAddress(mgmtIPAddr, OVSDB_SERVER_PORT)); connectFuture.awaitUninterruptibly(); if (connectFuture.isDone() && connectFuture.isSuccess()) { if (!newChannelForShow) setChannel(connectFuture.getChannel()); else showCh = connectFuture.getChannel(); } } } if (channel == null && showCh == null) logger.error("Failed to connect to OVSDB on tunnel switch {} <{}>", hexDpid, mgmtIPAddr); return showCh; } /** * sendShowMessage sends a JSON RPC show command to this OVS and updates * the OVSDB state to mirror the OVS state learned from the reply to this * show message. addTunnelPort and delTunnelPort method implementations * first synchronize the OVSDB state with the OVS's state by calling this * method; and then try to add or delete a port. */ public void sendShowMessage() { sendShowMessage(true); } private void sendShowMessage(boolean closeAfterReceive) { Channel showCh = null; showCh = connect(closeAfterReceive); expectedMessageReplyId = peekNextMessageId(); expectedMessage = SHOW_REPLY; synchronized(this) { JSONShowMsg jshow = new JSONShowMsg(getNextMessageId()); if (closeAfterReceive) { if (showCh != null) showCh.write(jshow); else logger.error("Show failed on connect to ovs {}", hexDpid); } else { if (channel != null) channel.write(jshow); else logger.error("Show failed to connect to ovs {}", hexDpid); } } //wait for a reply synchronized(statusObj) { try { statusObj.wait(1000); } catch (InterruptedException e) { // ignore } } if (closeAfterReceive) { if (showCh != null) { if (logger.isDebugEnabled()) logger.debug("closing channel {} after show-only", showCh.toString()); showCh.close(); } else { logger.error("Failed to connect to ovsdb on switch {}", hexDpid); } resetMessageId(); } } //************************** // JSON Add/Del Port related //************************** /** * addPort adds a regular port or a CAPWAP tunnel port on this OVS with * name "name". If "isTunnelPort" is true then the port is a CAPWAP tunnel * port with remote IP address "remoteIPAddr". If such a tunnel already * exists this method will have no effect. * Example: name = "vta192168002003"; remoteIPAddr = "192.168.2.3"; * If "isTunnelPort" is false, then "remoteIPAddr" is ignored and can be * any string or null. */ public void addPort(String name, String localIPAddr, String remoteIPAddr, boolean isTunnelPort) { if ((localIPAddr == null || remoteIPAddr == null) && isTunnelPort) { logger.debug("Error in call: cannot add a tunnel-port on switch " + hexDpid + " local IP " + localIPAddr + " remote IP " + remoteIPAddr); return; } //Make a new connection with the show message sendShowMessage(false); //ensure port does not exist boolean exists = false; for(OVSPort p : port.values()) { if (p.getNew().getName().equals(name)) { exists = true; break; } } if (exists) { logger.debug("port {} already exists on switch {}", name, hexDpid); if (channel != null) channel.close(); resetMessageId(); return; } //otherwise create port expectedMessageReplyId = peekNextMessageId(); expectedMessage = ADD_PORT_REPLY; synchronized(this) { JSONAddPortMsg jadd; try { jadd = new JSONAddPortMsg(name, localIPAddr, remoteIPAddr, this, getNextMessageId(), isTunnelPort); if (channel != null) channel.write(jadd); } catch (OVSDBBridgeUnknown e) { logger.error(String.format( "Couldn't add port %s for sw %s with remote IP %s ::"+ " no bridge found", name, hexDpid, remoteIPAddr)); } } //wait for a reply and an update for (int i=0; i<2; i++) { synchronized(statusObj) { try { statusObj.wait(1000); } catch (InterruptedException e) { // ignore } } } if (logger.isDebugEnabled()) logger.debug("channel {} closing - add port", channel.toString()); if (channel != null) channel.close(); resetMessageId(); } /** * delPort deletes a port on this OVS with the given name, where the port * can be a tunnel-port where name is of the form "vta192168002003"; or * a regular port where name could be of the form "eth2.vlan20" (or not). * If such a port does not exist on the OVS, this method will have no * effect. */ public void delPort(String name) { //Make a new connection with the show message sendShowMessage(false); // ensure port exists Iterator<Entry<String, OVSPort>> iter = port.entrySet().iterator(); String portHash = null; while(iter.hasNext()) { Entry<String, OVSPort> e = iter.next(); ArrayList<String> intfHash = e.getValue().getNew().getInterfaces(); for (int j=0; j<intfHash.size(); j++) { if (intf.containsKey(intfHash.get(j))) { if (intf.get(intfHash.get(j)).getNew().getName() .equals(name)) { portHash = e.getKey(); break; } } } if (portHash != null) break; } if (portHash == null) { logger.debug("port {} does not exist on switch {}", name, HexString.toHexString(dpid)); if (channel != null) channel.close(); resetMessageId(); return; } //otherwise delete port expectedMessageReplyId = peekNextMessageId(); expectedMessage = DEL_PORT_REPLY; synchronized(this) { JSONDelPortMsg jdel; try { jdel = new JSONDelPortMsg(name, portHash, this, getNextMessageId()); if (channel != null) channel.write(jdel); } catch (OVSDBBridgeUnknown e) { logger.error(String.format( "Couldn't del port %s for switch %s ::"+ " no bridge found", name, hexDpid)); } } //wait for a reply and an update for (int i=0; i<2; i++) { synchronized(statusObj) { try { statusObj.wait(1000); } catch (InterruptedException e) { // ingore } } } if (logger.isDebugEnabled()) logger.debug("channel {} closing - del port", channel.toString()); if (channel != null) channel.close(); resetMessageId(); } //************************** // JSON RPC Message 'from' OVS //************************** /** * public method not exposed by interface - called by JSONMsgHandler * to correlate a received JSON RPC transaction id to the id of a sent * JSON RPC request. */ public int getExpectedMessage(int messageReplyId) { if (messageReplyId == expectedMessageReplyId) { return expectedMessage; } else { return -1; } } /** * public method not exposed by interface - called by JSONMsgHandler * to update OVSDB with a showReply received in response to a sent showMsg */ public synchronized void updateTunnelSwitchFromShow( JSONShowReplyMsg showReply) { ShowResult sr = showReply.getResult(); if(sr.getError() != null){ logger.error("Received error from tunnesw:{} of type {}", dpid, sr.getDetails()); return; } if(!sr.getOpen_vSwitch().toString().equals(open_vswitch.toString())) { open_vswitch = sr.getOpen_vSwitch(); } if(!sr.getController().toString().equals(controller.toString())) { controller = sr.getController(); } if(!sr.getInterface().toString().equals(intf.toString())) { intf = sr.getInterface(); } if(!sr.getPort().toString().equals(port.toString())) { port = sr.getPort(); } if(!sr.getBridge().toString().equals(bridge.toString())) { bridge = sr.getBridge(); } } public synchronized void updateTunnelSwitchFromUpdate( ShowResult update) { // FIXME - not needed or used right now } }