/* * Copyright 2016-present Open Networking Laboratory * * Licensed under the Apache License, Version 2.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.apache.org/licenses/LICENSE-2.0 * * 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.onosproject.driver.optical.handshaker; import org.onosproject.net.behaviour.protection.ProtectionConfigBehaviour; import org.projectfloodlight.openflow.protocol.OFCalientPortDescStatsEntry; import org.projectfloodlight.openflow.protocol.OFCalientPortDescStatsReply; import org.projectfloodlight.openflow.protocol.OFCalientPortDescStatsRequest; import org.projectfloodlight.openflow.protocol.OFCalientPortStatsEntry; import org.projectfloodlight.openflow.protocol.OFCalientPortStatsRequest; import org.projectfloodlight.openflow.protocol.OFCalientPortStatsReply; import org.projectfloodlight.openflow.protocol.OFCalientStatsReply; import org.projectfloodlight.openflow.protocol.OFMessage; import org.projectfloodlight.openflow.protocol.OFPortDesc; import org.projectfloodlight.openflow.protocol.OFStatsRequest; import org.projectfloodlight.openflow.protocol.OFStatsReply; import org.projectfloodlight.openflow.protocol.OFStatsType; import org.projectfloodlight.openflow.protocol.OFType; import java.io.IOException; import java.util.ArrayList; import java.util.HashMap; import java.util.List; import java.util.Set; import java.util.concurrent.atomic.AtomicBoolean; import java.util.stream.Collectors; import org.onosproject.net.DefaultAnnotations; import org.onosproject.net.Device; import org.onosproject.net.device.DefaultPortDescription; import org.onosproject.net.device.DeviceService; import org.onosproject.net.device.PortDescription; import org.onosproject.net.Port; import org.onosproject.openflow.controller.OpenFlowOpticalSwitch; import org.onosproject.openflow.controller.PortDescPropertyType; import org.onosproject.openflow.controller.driver.AbstractOpenFlowSwitch; import org.onosproject.openflow.controller.driver.SwitchDriverSubHandshakeAlreadyStarted; import org.onosproject.openflow.controller.driver.SwitchDriverSubHandshakeCompleted; import org.onosproject.openflow.controller.driver.SwitchDriverSubHandshakeNotStarted; import org.projectfloodlight.openflow.protocol.OFObject; import com.google.common.collect.ImmutableList; import com.google.common.collect.ImmutableSet; import static org.onosproject.net.optical.OpticalAnnotations.*; /** * Oplink Open Flow Protection Optical Switch handshaker - for Open Flow 1.3. * In order to reduce the code changes in the short term, we reuse Calient message structure. */ public class OplinkSwitchHandshaker extends AbstractOpenFlowSwitch implements OpenFlowOpticalSwitch { private static final String PROTECTION_FINGERPRINT = "OplinkOPS"; private static final int PROTECTION_VIRTUAL_PORT = 0; private final AtomicBoolean driverHandshakeComplete = new AtomicBoolean(false); private List<OFCalientPortDescStatsEntry> opticalPorts = new ArrayList<>(); private enum SubType { PORT_DESC_STATS, // Port description stats openflow message FLOW_STATS, // Flow stats openflow message PORT_STATS // Port stats openflow message } @Override public Boolean supportNxRole() { return false; } @Override public void startDriverHandshake() { log.info("OPLK Switch: Starting driver handshake for sw {}", getStringId()); if (startDriverHandshakeCalled) { throw new SwitchDriverSubHandshakeAlreadyStarted(); } startDriverHandshakeCalled = true; log.debug("OPLK Switch: sendHandshakeOFExperimenterPortDescRequest for sw {}", getStringId()); try { sendHandshakeOFExperimenterPortDescRequest(); } catch (IOException e) { log.error("OPLK Switch: exception while sending experimenter port desc:", e); } } @Override public void processDriverHandshakeMessage(OFMessage m) { if (!startDriverHandshakeCalled) { throw new SwitchDriverSubHandshakeNotStarted(); } if (driverHandshakeComplete.get()) { throw new SwitchDriverSubHandshakeCompleted(m); } log.info("OPLK Switch: processDriverHandshakeMessage for sw {}", getStringId()); switch (m.getType()) { case STATS_REPLY: // multipart message is reported as STAT processOFMultipartReply((OFStatsReply) m); driverHandshakeComplete.set(true); break; default: log.warn("OPLK Switch: Received message {} during switch-driver " + "subhandshake from switch {} ... " + "Ignoring message", m, getStringId()); } } @Override public final void sendMsg(OFMessage m) { List<OFMessage> messages = new ArrayList<>(); messages.add(m); if (m.getType() == OFType.STATS_REQUEST) { OFStatsRequest sr = (OFStatsRequest) m; log.debug("OPLK Switch: Rebuilding stats request type {}", sr.getStatsType()); switch (sr.getStatsType()) { case PORT: //Send experiment status request for Optical Fiber switch to device //Note: We just re-use calient message for a short term. OFCalientPortStatsRequest portRequest = this.factory().buildCalientPortStatsRequest() .setXid(sr.getXid()) .setFlags(sr.getFlags()) .build(); messages.add(portRequest); break; default: break; } } else { log.debug("OPLK Switch: sends msg:{}, as is", m.getType()); } super.sendMsg(messages); } @Override public boolean isDriverHandshakeComplete() { return driverHandshakeComplete.get(); } private void sendHandshakeOFExperimenterPortDescRequest() throws IOException { /* *Note: * Oplink protection switch and Calient switch are both optical fiber switch, * so Calient port description matches well for Oplink switch. * OFCalientPortDescStatsRequest is generated by loxi. * If change the OF message name, we need to change onos-loxi. * To reduce code change for a short term, we reuse calient message and message name. * These will be re-processed in the future. */ OFCalientPortDescStatsRequest preq = factory() .buildCalientPortDescStatsRequest() .setXid(getNextTransactionId()) .build(); log.info("OPLK Switch: Sending experimented port description message {}", preq); this.sendHandshakeMessage(preq); } @Override public Device.Type deviceType() { return Device.Type.FIBER_SWITCH; } /* * OduClt ports are reported as regular ETH ports. */ @Override public List<OFPortDesc> getPorts() { return ImmutableList.copyOf( ports.stream().flatMap(p -> p.getEntries().stream()) .collect(Collectors.toList())); } @Override public List<? extends OFObject> getPortsOf(PortDescPropertyType type) { return ImmutableList.copyOf(opticalPorts); } @Override public Set<PortDescPropertyType> getPortTypes() { return ImmutableSet.of(PortDescPropertyType.OPTICAL_TRANSPORT); } @Override public List<PortDescription> processExpPortStats(OFMessage msg) { OFCalientStatsReply statsReply = (OFCalientStatsReply) msg; //Sub type from openflowj is start from index 1 SubType type = SubType.values()[(int) statsReply.getSubtype() - 1]; switch (type) { case PORT_STATS: //Note: We just re-use calient message for a short term. OFCalientPortStatsReply portStats = (OFCalientPortStatsReply) msg; return buildPortDescriptions(portStats.getEntries()); default: //Ignore other messages log.warn("OPLK Switch: Received message {} from switch {} ... " + "Ignoring message", msg, getStringId()); return null; } } private List<PortDescription> buildPortDescriptions(List<OFCalientPortStatsEntry> entries) { DeviceService deviceService = this.handler().get(DeviceService.class); List<Port> ports = deviceService.getPorts(this.data().deviceId()); HashMap<Long, OFCalientPortStatsEntry> statsMap = new HashMap<>(entries.size()); entries.forEach(entry -> statsMap.put((long) entry.getPortNo().getPortNumber(), entry)); final List<PortDescription> portDescs = new ArrayList<>(); for (Port port : ports) { DefaultAnnotations.Builder builder = DefaultAnnotations.builder(); builder.putAll(port.annotations()); //set fingerprint for the virtual port (port 0) if (port.number().toLong() == PROTECTION_VIRTUAL_PORT) { builder.set(ProtectionConfigBehaviour.FINGERPRINT, PROTECTION_FINGERPRINT); } OFCalientPortStatsEntry entry = statsMap.get(port.number().toLong()); if (entry == null) { continue; } builder.set(CURRENT_POWER, entry.getInportPower()); builder.set(OUTPUT_POWER, entry.getOutportPower()); //Note: There are some mistakes about bitmask encoding and decoding in openflowj. //We just use this code for a short term, and will modify in the future. if (entry.getInOperStatus().isEmpty()) { builder.set(INPUT_PORT_STATUS, STATUS_IN_SERVICE); } else { builder.set(INPUT_PORT_STATUS, STATUS_OUT_SERVICE); } if (entry.getOutOperStatus().isEmpty()) { builder.set(OUTPUT_PORT_STATUS, STATUS_IN_SERVICE); } else { builder.set(OUTPUT_PORT_STATUS, STATUS_OUT_SERVICE); } portDescs.add(new DefaultPortDescription(port.number(), port.isEnabled(), port.type(), port.portSpeed(), builder.build())); } return portDescs; } private void processOFMultipartReply(OFStatsReply stats) { log.debug("OPLK Switch: Received message {} during switch-driver " + "subhandshake from switch {} ... ", stats, getStringId()); //Process experimenter messages if (stats.getStatsType() == OFStatsType.EXPERIMENTER) { try { //Note: We just re-use calient message for a short term. OFCalientPortDescStatsReply descReply = (OFCalientPortDescStatsReply) stats; opticalPorts.addAll(descReply.getPortDesc()); driverHandshakeComplete.set(true); } catch (ClassCastException e) { log.error("OPLK Switch: Unexspected Experimenter Multipart message type {} ", stats.getClass().getName()); } } } }