blob: 20c28567f069884547d629dc8623b2a4728b5450 [file] [log] [blame]
/*
* Copyright 2015-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.pcep.controller.impl;
import java.util.Arrays;
import java.util.Collection;
import java.util.Collections;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Iterator;
import java.util.LinkedList;
import java.util.List;
import java.util.ListIterator;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.ConcurrentHashMap;
import org.apache.felix.scr.annotations.Activate;
import org.apache.felix.scr.annotations.Component;
import org.apache.felix.scr.annotations.Deactivate;
import org.apache.felix.scr.annotations.Reference;
import org.apache.felix.scr.annotations.ReferenceCardinality;
import org.apache.felix.scr.annotations.Service;
import org.onosproject.incubator.net.tunnel.IpTunnelEndPoint;
import org.onosproject.incubator.net.tunnel.Tunnel;
import org.onosproject.incubator.net.tunnel.TunnelService;
import org.onosproject.incubator.net.tunnel.Tunnel.State;
import org.onosproject.net.device.DeviceService;
import org.onosproject.pcep.controller.LspKey;
import org.onosproject.pcep.controller.PccId;
import org.onosproject.pcep.controller.PcepClient;
import org.onosproject.pcep.controller.PcepClientController;
import org.onosproject.pcep.controller.PcepClientListener;
import org.onosproject.pcep.controller.PcepEventListener;
import org.onosproject.pcep.controller.PcepNodeListener;
import org.onosproject.pcep.controller.PcepPacketListener;
import org.onosproject.pcep.controller.PcepSyncStatus;
import org.onosproject.pcep.controller.driver.PcepAgent;
import org.onosproject.pcepio.exceptions.PcepParseException;
import org.onosproject.pcepio.protocol.PcInitiatedLspRequest;
import org.onosproject.pcepio.protocol.PcepError;
import org.onosproject.pcepio.protocol.PcepErrorInfo;
import org.onosproject.pcepio.protocol.PcepErrorMsg;
import org.onosproject.pcepio.protocol.PcepErrorObject;
import org.onosproject.pcepio.protocol.PcepFactory;
import org.onosproject.pcepio.protocol.PcepInitiateMsg;
import org.onosproject.pcepio.protocol.PcepLspObject;
import org.onosproject.pcepio.protocol.PcepMessage;
import org.onosproject.pcepio.protocol.PcepReportMsg;
import org.onosproject.pcepio.protocol.PcepStateReport;
import org.onosproject.pcepio.types.PcepValueType;
import org.onosproject.pcepio.types.StatefulIPv4LspIdentifiersTlv;
import org.onosproject.pcepio.types.SymbolicPathNameTlv;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import com.google.common.collect.Sets;
import static com.google.common.base.Preconditions.checkNotNull;
import static org.onosproject.pcep.controller.PcepSyncStatus.IN_SYNC;
import static org.onosproject.pcep.controller.PcepLspSyncAction.REMOVE;
import static org.onosproject.pcep.controller.PcepLspSyncAction.SEND_UPDATE;
import static org.onosproject.pcep.controller.PcepLspSyncAction.SEND_DELETE;
import static org.onosproject.pcep.controller.PcepLspSyncAction.UNSTABLE;
import static org.onosproject.pcepio.types.PcepErrorDetailInfo.ERROR_TYPE_19;
import static org.onosproject.pcepio.types.PcepErrorDetailInfo.ERROR_VALUE_5;
/**
* Implementation of PCEP client controller.
*/
@Component(immediate = true)
@Service
public class PcepClientControllerImpl implements PcepClientController {
private static final Logger log = LoggerFactory.getLogger(PcepClientControllerImpl.class);
@Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
protected DeviceService deviceService;
protected ConcurrentHashMap<PccId, PcepClient> connectedClients =
new ConcurrentHashMap<>();
protected PcepClientAgent agent = new PcepClientAgent();
protected Set<PcepClientListener> pcepClientListener = new HashSet<>();
protected Set<PcepEventListener> pcepEventListener = Sets.newHashSet();
protected Set<PcepNodeListener> pcepNodeListener = Sets.newHashSet();
protected Set<PcepPacketListener> pcepPacketListener = Sets.newHashSet();
private final Controller ctrl = new Controller();
public static final String BANDWIDTH = "bandwidth";
public static final String LSP_SIG_TYPE = "lspSigType";
public static final String PCC_TUNNEL_ID = "PccTunnelId";
public static final String PLSP_ID = "PLspId";
public static final String LOCAL_LSP_ID = "localLspId";
public static final String PCE_INIT = "pceInit";
public static final String COST_TYPE = "costType";
public static final String DELEGATE = "delegation";
@Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
protected TunnelService tunnelService;
@Activate
public void activate() {
ctrl.start(agent);
log.info("Started");
}
@Deactivate
public void deactivate() {
// Close all connected clients
closeConnectedClients();
ctrl.stop();
log.info("Stopped");
}
@Override
public Collection<PcepClient> getClients() {
return connectedClients.values();
}
@Override
public PcepClient getClient(PccId pccId) {
return connectedClients.get(pccId);
}
@Override
public void addListener(PcepClientListener listener) {
if (!pcepClientListener.contains(listener)) {
this.pcepClientListener.add(listener);
}
}
@Override
public void removeListener(PcepClientListener listener) {
this.pcepClientListener.remove(listener);
}
@Override
public void addEventListener(PcepEventListener listener) {
pcepEventListener.add(listener);
}
@Override
public void removeEventListener(PcepEventListener listener) {
pcepEventListener.remove(listener);
}
@Override
public void addPacketListener(PcepPacketListener listener) {
pcepPacketListener.add(listener);
}
@Override
public void removePacketListener(PcepPacketListener listener) {
pcepPacketListener.remove(listener);
}
@Override
public void writeMessage(PccId pccId, PcepMessage msg) {
this.getClient(pccId).sendMessage(msg);
}
@Override
public void addNodeListener(PcepNodeListener listener) {
pcepNodeListener.add(listener);
}
@Override
public void removeNodeListener(PcepNodeListener listener) {
pcepNodeListener.remove(listener);
}
@Override
public void processClientMessage(PccId pccId, PcepMessage msg) {
PcepClient pc = getClient(pccId);
switch (msg.getType()) {
case NONE:
break;
case OPEN:
break;
case KEEP_ALIVE:
break;
case PATH_COMPUTATION_REQUEST:
break;
case PATH_COMPUTATION_REPLY:
break;
case NOTIFICATION:
break;
case ERROR:
break;
case INITIATE:
if (!pc.capability().pcInstantiationCapability()) {
pc.sendMessage(Collections.singletonList(getErrMsg(pc.factory(), ERROR_TYPE_19,
ERROR_VALUE_5)));
}
break;
case UPDATE:
if (!pc.capability().statefulPceCapability()) {
pc.sendMessage(Collections.singletonList(getErrMsg(pc.factory(), ERROR_TYPE_19,
ERROR_VALUE_5)));
}
break;
case LABEL_UPDATE:
if (!pc.capability().pceccCapability()) {
pc.sendMessage(Collections.singletonList(getErrMsg(pc.factory(), ERROR_TYPE_19,
ERROR_VALUE_5)));
}
break;
case CLOSE:
log.info("Sending Close Message to {" + pccId.toString() + "}");
pc.sendMessage(Collections.singletonList(pc.factory().buildCloseMsg().build()));
//now disconnect client
pc.disconnectClient();
break;
case REPORT:
//Only update the listener if respective capability is supported else send PCEP-ERR msg
if (pc.capability().statefulPceCapability()) {
ListIterator<PcepStateReport> listIterator = ((PcepReportMsg) msg).getStateReportList().listIterator();
while (listIterator.hasNext()) {
PcepStateReport stateRpt = listIterator.next();
if (stateRpt.getLspObject().getSFlag()) {
if (pc.lspDbSyncStatus() != PcepSyncStatus.IN_SYNC) {
// Initialize LSP DB sync and temporary cache.
pc.setLspDbSyncStatus(PcepSyncStatus.IN_SYNC);
pc.initializeSyncMsgList(pccId);
}
// Store stateRpt in temporary cache.
pc.addSyncMsgToList(pccId, stateRpt);
// Don't send to provider as of now.
continue;
} else {
if (pc.lspDbSyncStatus() == PcepSyncStatus.IN_SYNC) {
// Set end of LSPDB sync.
pc.setLspDbSyncStatus(PcepSyncStatus.SYNCED);
// Call packet provider to initiate label DB sync (only if PCECC capable).
if (pc.capability().pceccCapability()) {
pc.setLabelDbSyncStatus(IN_SYNC);
for (PcepPacketListener l : pcepPacketListener) {
l.sendPacketIn(pccId);
}
} else {
// If label db sync is not to be done, handle end of LSPDB sync actions.
agent.analyzeSyncMsgList(pccId);
}
continue;
}
}
// It's a usual report message while sync is not undergoing. So process it immediately.
LinkedList<PcepStateReport> llPcRptList = new LinkedList<>();
llPcRptList.add(stateRpt);
PcepMessage pcReportMsg = pc.factory().buildReportMsg().setStateReportList((llPcRptList))
.build();
for (PcepEventListener l : pcepEventListener) {
l.handleMessage(pccId, pcReportMsg);
}
}
} else {
// Send PCEP-ERROR message.
pc.sendMessage(Collections.singletonList(getErrMsg(pc.factory(),
ERROR_TYPE_19, ERROR_VALUE_5)));
}
break;
case LABEL_RANGE_RESERV:
break;
case LS_REPORT: //TODO: need to handle LS report to add or remove node
break;
case MAX:
break;
case END:
break;
default:
break;
}
}
@Override
public void closeConnectedClients() {
PcepClient pc;
for (PccId id : connectedClients.keySet()) {
pc = getClient(id);
pc.disconnectClient();
}
}
/**
* Returns pcep error message with specific error type and value.
*
* @param factory represents pcep factory
* @param errorType pcep error type
* @param errorValue pcep error value
* @return pcep error message
*/
public PcepErrorMsg getErrMsg(PcepFactory factory, byte errorType, byte errorValue) {
LinkedList<PcepError> llPcepErr = new LinkedList<>();
LinkedList<PcepErrorObject> llerrObj = new LinkedList<>();
PcepErrorMsg errMsg;
PcepErrorObject errObj = factory.buildPcepErrorObject().setErrorValue(errorValue).setErrorType(errorType)
.build();
llerrObj.add(errObj);
PcepError pcepErr = factory.buildPcepError().setErrorObjList(llerrObj).build();
llPcepErr.add(pcepErr);
PcepErrorInfo errInfo = factory.buildPcepErrorInfo().setPcepErrorList(llPcepErr).build();
errMsg = factory.buildPcepErrorMsg().setPcepErrorInfo(errInfo).build();
return errMsg;
}
/**
* Implementation of an Pcep Agent which is responsible for
* keeping track of connected clients and the state in which
* they are.
*/
public class PcepClientAgent implements PcepAgent {
private final Logger log = LoggerFactory.getLogger(PcepClientAgent.class);
@Override
public boolean addConnectedClient(PccId pccId, PcepClient pc) {
if (connectedClients.get(pccId) != null) {
log.error("Trying to add connectedClient but found a previous "
+ "value for pcc ip: {}", pccId.toString());
return false;
} else {
log.debug("Added Client {}", pccId.toString());
connectedClients.put(pccId, pc);
for (PcepClientListener l : pcepClientListener) {
l.clientConnected(pccId);
}
return true;
}
}
@Override
public boolean validActivation(PccId pccId) {
if (connectedClients.get(pccId) == null) {
log.error("Trying to activate client but is not in "
+ "connected client: pccIp {}. Aborting ..", pccId.toString());
return false;
}
return true;
}
@Override
public void removeConnectedClient(PccId pccId) {
connectedClients.remove(pccId);
for (PcepClientListener l : pcepClientListener) {
log.warn("removal for {}", pccId.toString());
l.clientDisconnected(pccId);
}
}
@Override
public void processPcepMessage(PccId pccId, PcepMessage m) {
processClientMessage(pccId, m);
}
@Override
public void addNode(PcepClient pc) {
for (PcepNodeListener l : pcepNodeListener) {
l.addNode(pc);
}
}
@Override
public void deleteNode(PccId pccId) {
for (PcepNodeListener l : pcepNodeListener) {
l.deleteNode(pccId);
}
}
@SuppressWarnings({ "unchecked", "rawtypes" })
@Override
public boolean analyzeSyncMsgList(PccId pccId) {
PcepClient pc = getClient(pccId);
/*
* PLSP_ID is null while tunnel is created at PCE and PCInit msg carries it as 0. It is allocated by PCC and
* in that case it becomes the first PCRpt msg from PCC for this LSP, and hence symbolic path name must be
* carried in the PCRpt msg. Draft says: The SYMBOLIC-PATH-NAME TLV "MUST" be included in the LSP object in
* the LSP State Report (PCRpt) message when during a given PCEP session an LSP is "first" reported to a
* PCE. So two separate lists with separate keys are maintained.
*/
Map<LspKey, Tunnel> preSyncLspDbByKey = new HashMap<>();
Map<String, Tunnel> preSyncLspDbByName = new HashMap<>();
// Query tunnel service and fetch all the tunnels with this PCC as ingress.
// Organize into two maps, with LSP key if known otherwise with symbolic path name, for quick search.
Collection<Tunnel> queriedTunnels = tunnelService.queryTunnel(Tunnel.Type.MPLS);
for (Tunnel tunnel : queriedTunnels) {
if (((IpTunnelEndPoint) tunnel.src()).ip().equals(pccId.ipAddress())) {
String pLspId = tunnel.annotations().value(PLSP_ID);
if (pLspId != null) {
String localLspId = tunnel.annotations().value(LOCAL_LSP_ID);
checkNotNull(localLspId);
LspKey lspKey = new LspKey(Integer.valueOf(pLspId), Short.valueOf(localLspId));
preSyncLspDbByKey.put(lspKey, tunnel);
} else {
preSyncLspDbByName.put(tunnel.tunnelName().value(), tunnel);
}
}
}
List<PcepStateReport> syncStateRptList = pc.getSyncMsgList(pccId);
Iterator<PcepStateReport> stateRptListIterator = syncStateRptList.iterator();
// For every report, fetch PLSP id, local LSP id and symbolic path name from the message.
while (syncStateRptList.iterator().hasNext()) {
PcepStateReport stateRpt = stateRptListIterator.next();
Tunnel tunnel = null;
PcepLspObject lspObj = stateRpt.getLspObject();
ListIterator<PcepValueType> listTlvIterator = lspObj.getOptionalTlv().listIterator();
StatefulIPv4LspIdentifiersTlv ipv4LspIdenTlv = null;
SymbolicPathNameTlv pathNameTlv = null;
while (listTlvIterator.hasNext()) {
PcepValueType tlv = listTlvIterator.next();
switch (tlv.getType()) {
case StatefulIPv4LspIdentifiersTlv.TYPE:
ipv4LspIdenTlv = (StatefulIPv4LspIdentifiersTlv) tlv;
break;
case SymbolicPathNameTlv.TYPE:
pathNameTlv = (SymbolicPathNameTlv) tlv;
break;
default:
break;
}
}
LspKey lspKeyOfRpt = new LspKey(lspObj.getPlspId(), ipv4LspIdenTlv.getLspId());
tunnel = preSyncLspDbByKey.get(lspKeyOfRpt);
// PCE tunnel is matched with PCRpt LSP. Now delete it from the preSyncLspDb list as the residual
// non-matching list will be processed at the end.
if (tunnel != null) {
preSyncLspDbByKey.remove(lspKeyOfRpt);
} else if (pathNameTlv != null) {
tunnel = preSyncLspDbByName.get(Arrays.toString(pathNameTlv.getValue()));
if (tunnel != null) {
preSyncLspDbByName.remove(tunnel.tunnelName());
}
}
if (tunnel == null) {
// If remove flag is set, and tunnel is not known to PCE, ignore it.
if (lspObj.getCFlag() && !lspObj.getRFlag()) {
// For initiated LSP, need to send PCInit delete msg.
try {
PcInitiatedLspRequest releaseLspRequest = pc.factory().buildPcInitiatedLspRequest()
.setLspObject(lspObj).build();
LinkedList<PcInitiatedLspRequest> llPcInitiatedLspRequestList
= new LinkedList<PcInitiatedLspRequest>();
llPcInitiatedLspRequestList.add(releaseLspRequest);
PcepInitiateMsg pcInitiateMsg = pc.factory().buildPcepInitiateMsg()
.setPcInitiatedLspRequestList(llPcInitiatedLspRequestList).build();
for (PcepEventListener l : pcepEventListener) {
l.handleEndOfSyncAction(pccId, pcInitiateMsg, SEND_DELETE);
}
} catch (PcepParseException e) {
log.error("Exception occured while sending initiate delete message {}", e.getMessage());
}
}
continue;
}
if (!lspObj.getCFlag()) {
// For learned LSP process both add/update PCRpt.
LinkedList<PcepStateReport> llPcRptList = new LinkedList<>();
llPcRptList.add(stateRpt);
PcepMessage pcReportMsg = pc.factory().buildReportMsg().setStateReportList((llPcRptList))
.build();
for (PcepEventListener l : pcepEventListener) {
l.handleMessage(pccId, pcReportMsg);
}
continue;
}
// Implied that tunnel != null and lspObj.getCFlag() is set
// State different for PCC sent LSP and PCE known LSP, send PCUpd msg.
State tunnelState = PcepLspStatus
.getTunnelStatusFromLspStatus(PcepLspStatus.values()[lspObj.getOFlag()]);
if (tunnelState != tunnel.state()) {
for (PcepEventListener l : pcepEventListener) {
l.handleEndOfSyncAction(tunnel, SEND_UPDATE);
}
}
}
// Check which tunnels are extra at PCE that were not reported by PCC.
Map<Object, Tunnel> preSyncLspDb = (Map) preSyncLspDbByKey;
handleResidualTunnels(preSyncLspDb);
preSyncLspDbByKey = null;
preSyncLspDb = (Map) preSyncLspDbByName;
handleResidualTunnels(preSyncLspDb);
preSyncLspDbByName = null;
preSyncLspDb = null;
pc.removeSyncMsgList(pccId);
return true;
}
/*
* Go through the tunnels which are known by PCE but were not reported by PCC during LSP DB sync and take
* appropriate actions.
*/
private void handleResidualTunnels(Map<Object, Tunnel> preSyncLspDb) {
for (Tunnel pceExtraTunnel : preSyncLspDb.values()) {
if (pceExtraTunnel.annotations().value(PCE_INIT) == null
|| "false".equalsIgnoreCase(pceExtraTunnel.annotations().value(PCE_INIT))) {
// PCC initiated tunnels should be removed from tunnel store.
for (PcepEventListener l : pcepEventListener) {
l.handleEndOfSyncAction(pceExtraTunnel, REMOVE);
}
} else {
// PCE initiated tunnels should be initiated again.
for (PcepEventListener l : pcepEventListener) {
l.handleEndOfSyncAction(pceExtraTunnel, UNSTABLE);
}
}
}
}
}
}