| /* |
| * Copyright 2016-present Open Networking Foundation |
| * |
| * 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.provider.te.tunnel; |
| |
| import com.fasterxml.jackson.databind.JsonNode; |
| import com.fasterxml.jackson.databind.node.ObjectNode; |
| 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.onosproject.incubator.net.tunnel.Tunnel; |
| import org.onosproject.incubator.net.tunnel.TunnelDescription; |
| import org.onosproject.incubator.net.tunnel.TunnelId; |
| import org.onosproject.incubator.net.tunnel.TunnelProvider; |
| import org.onosproject.incubator.net.tunnel.TunnelProviderRegistry; |
| import org.onosproject.net.DeviceId; |
| import org.onosproject.net.ElementId; |
| import org.onosproject.net.Path; |
| import org.onosproject.net.provider.AbstractProvider; |
| import org.onosproject.net.provider.ProviderId; |
| import org.onosproject.protocol.restconf.RestConfSBController; |
| import org.onosproject.protocol.restconf.RestconfNotificationEventListener; |
| import org.onosproject.provider.te.utils.DefaultJsonCodec; |
| import org.onosproject.provider.te.utils.YangCompositeEncodingImpl; |
| import org.onosproject.tetopology.management.api.TeTopology; |
| import org.onosproject.tetopology.management.api.TeTopologyKey; |
| import org.onosproject.tetopology.management.api.TeTopologyService; |
| import org.onosproject.tetunnel.api.TeTunnelProviderService; |
| import org.onosproject.tetunnel.api.TeTunnelService; |
| import org.onosproject.tetunnel.api.tunnel.DefaultTeTunnel; |
| import org.onosproject.tetunnel.api.tunnel.TeTunnel; |
| import org.onosproject.yang.gen.v1.urn.ietf.params.xml.ns.yang.ietf.te.rev20160705.IetfTe; |
| import org.onosproject.yang.gen.v1.urn.ietf.params.xml.ns.yang.ietf.te.rev20160705.ietfte.tunnelsgrouping.Tunnels; |
| import org.onosproject.yang.gen.v1.urn.ietf.params.xml.ns.yang.ietf.te.types.rev20160705.IetfTeTypes; |
| import org.onosproject.yms.ych.YangCodecHandler; |
| import org.onosproject.yms.ych.YangCompositeEncoding; |
| import org.onosproject.yms.ych.YangProtocolEncodingFormat; |
| import org.onosproject.yms.ymsm.YmsService; |
| import org.slf4j.Logger; |
| import org.slf4j.LoggerFactory; |
| |
| import java.io.ByteArrayInputStream; |
| import java.io.InputStream; |
| import java.util.List; |
| import java.util.Optional; |
| |
| import static com.google.common.base.Preconditions.checkNotNull; |
| import static com.google.common.base.Preconditions.checkState; |
| import static org.onosproject.provider.te.utils.CodecTools.jsonToString; |
| import static org.onosproject.provider.te.utils.CodecTools.toJson; |
| import static org.onosproject.tetopology.management.api.TeTopology.BIT_MERGED; |
| import static org.onosproject.teyang.utils.tunnel.TunnelConverter.buildIetfTe; |
| import static org.onosproject.teyang.utils.tunnel.TunnelConverter.yang2TeTunnel; |
| import static org.onosproject.yms.ych.YangProtocolEncodingFormat.JSON; |
| import static org.onosproject.yms.ych.YangResourceIdentifierType.URI; |
| import static org.onosproject.yms.ydt.YmsOperationType.EDIT_CONFIG_REQUEST; |
| import static org.onosproject.yms.ydt.YmsOperationType.QUERY_REPLY; |
| |
| |
| /** |
| * Provider which uses RESTCONF to do cross-domain tunnel creation/deletion/ |
| * update/deletion and so on operations on the domain networks. |
| */ |
| |
| @Component(immediate = true) |
| public class TeTunnelRestconfProvider extends AbstractProvider |
| implements TunnelProvider { |
| |
| private final Logger log = LoggerFactory.getLogger(getClass()); |
| |
| private static final String SCHEMA = "ietf"; |
| private static final String IETF = "ietf"; |
| private static final String TE = "te"; |
| private static final int DEFAULT_INDEX = 1; |
| private static final String TUNNELS = "tunnels"; |
| private static final String TUNNELS_URL = IETF + ":" + TE + "/" + TUNNELS; |
| private static final String IETF_NOTIFICATION_URI = "netconf"; |
| private static final String MEDIA_TYPE_JSON = "json"; |
| |
| private static final String SHOULD_IN_ONE = "Tunnel should be setup in one topo"; |
| private static final String PROVIDER_ID = "org.onosproject.provider.ietf"; |
| private static final String RESTCONF_ROOT = "/onos/restconf"; |
| private static final String TE_TUNNEL_KEY = "TeTunnelKey"; |
| |
| //private final RestconfNotificationEventListener listener = |
| // new InternalTunnelNotificationListener(); |
| |
| @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY) |
| protected RestConfSBController controller; |
| |
| @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY) |
| protected YmsService ymsService; |
| |
| @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY) |
| protected TeTunnelService tunnelService; |
| |
| @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY) |
| protected TeTunnelProviderService providerService; |
| |
| @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY) |
| protected TeTopologyService topologyService; |
| |
| @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY) |
| protected TunnelProviderRegistry tunnelProviderRegistry; |
| |
| private YangCodecHandler codecHandler; |
| |
| @Activate |
| public void activate() { |
| tunnelProviderRegistry.register(this); |
| codecHandler = ymsService.getYangCodecHandler(); |
| codecHandler.addDeviceSchema(IetfTe.class); |
| codecHandler.addDeviceSchema(IetfTeTypes.class); |
| codecHandler.registerOverriddenCodec(new DefaultJsonCodec(ymsService), |
| YangProtocolEncodingFormat.JSON); |
| collectInitialTunnels(); |
| subscribe(); |
| log.info("Started"); |
| } |
| |
| @Deactivate |
| public void deactivate() { |
| tunnelProviderRegistry.unregister(this); |
| unsubscribe(); |
| log.info("Stopped"); |
| |
| } |
| |
| public TeTunnelRestconfProvider() { |
| super(new ProviderId(SCHEMA, PROVIDER_ID)); |
| } |
| |
| private void collectInitialTunnels() { |
| for (DeviceId deviceId : controller.getDevices().keySet()) { |
| ObjectNode jsonNodes = executeGetRequest(deviceId); |
| if (jsonNodes == null) { |
| continue; |
| } |
| ObjectNode tunnelsNode = (ObjectNode) jsonNodes.get(TUNNELS); |
| if (tunnelsNode == null) { |
| continue; |
| } |
| Tunnels teTunnels = getYangTunnelsObject(tunnelsNode); |
| if (teTunnels == null) { |
| continue; |
| } |
| updateTeTunnels(teTunnels); |
| } |
| } |
| |
| private void subscribe() { |
| for (DeviceId deviceId : controller.getDevices().keySet()) { |
| try { |
| if (!controller.isNotificationEnabled(deviceId)) { |
| controller.enableNotifications(deviceId, IETF_NOTIFICATION_URI, |
| "application/json", |
| new InternalTunnelNotificationListener()); |
| } else { |
| controller.addNotificationListener(deviceId, |
| new InternalTunnelNotificationListener()); |
| } |
| } catch (Exception e) { |
| log.error("Failed to subscribe for {} : {}", deviceId, |
| e.getMessage()); |
| } |
| } |
| } |
| |
| private void unsubscribe() { |
| controller.getDevices() |
| .keySet() |
| .forEach(deviceId -> controller |
| .removeNotificationListener(deviceId, |
| new InternalTunnelNotificationListener())); |
| } |
| |
| @Override |
| public void setupTunnel(Tunnel tunnel, Path path) { |
| TeTunnel teTunnel = tunnelService.getTeTunnel(tunnel.tunnelId()); |
| long tid = teTunnel.srcNode().topologyId(); |
| checkState(tid == teTunnel.dstNode().topologyId(), SHOULD_IN_ONE); |
| setupTunnel(getOwnDevice(tid), tunnel, path); |
| } |
| |
| @Override |
| public void setupTunnel(ElementId srcElement, Tunnel tunnel, Path path) { |
| if (!tunnel.annotations().keys().contains(TE_TUNNEL_KEY)) { |
| log.warn("No tunnel key info in tunnel {}", tunnel); |
| return; |
| } |
| |
| String teTunnelKey = tunnel.annotations().value(TE_TUNNEL_KEY); |
| |
| Optional<TeTunnel> optTunnel = tunnelService.getTeTunnels() |
| .stream() |
| .filter(t -> t.teTunnelKey().toString().equals(teTunnelKey)) |
| .findFirst(); |
| |
| if (!optTunnel.isPresent()) { |
| log.warn("No te tunnel map to tunnel {}", tunnel); |
| return; |
| } |
| |
| IetfTe ietfTe = buildIetfTe(optTunnel.get(), true); |
| |
| YangCompositeEncoding encoding = codecHandler. |
| encodeCompositeOperation(RESTCONF_ROOT, null, ietfTe, |
| JSON, EDIT_CONFIG_REQUEST); |
| String identifier = encoding.getResourceIdentifier(); |
| String resourceInformation = encoding.getResourceInformation(); |
| |
| if (srcElement == null) { |
| log.error("Can't find remote device for tunnel : {}", tunnel); |
| return; |
| } |
| controller.post((DeviceId) srcElement, identifier, |
| new ByteArrayInputStream(resourceInformation.getBytes()), |
| MEDIA_TYPE_JSON, ObjectNode.class); |
| } |
| |
| @Override |
| public void releaseTunnel(Tunnel tunnel) { |
| //TODO implement release tunnel method |
| } |
| |
| @Override |
| public void releaseTunnel(ElementId srcElement, Tunnel tunnel) { |
| //TODO implement release tunnel with src method |
| } |
| |
| @Override |
| public void updateTunnel(Tunnel tunnel, Path path) { |
| //TODO implement update tunnel method |
| |
| } |
| |
| @Override |
| public void updateTunnel(ElementId srcElement, Tunnel tunnel, Path path) { |
| //TODO implement update tunnel with src method |
| } |
| |
| @Override |
| public TunnelId tunnelAdded(TunnelDescription tunnel) { |
| //TODO implement tunnel add method when te tunnel app merged to core |
| return null; |
| } |
| |
| @Override |
| public void tunnelRemoved(TunnelDescription tunnel) { |
| //TODO implement tunnel remove method when te tunnel app merged to core |
| |
| } |
| |
| @Override |
| public void tunnelUpdated(TunnelDescription tunnel) { |
| //TODO implement tunnel update method when te tunnel app merged to core |
| } |
| |
| @Override |
| public Tunnel tunnelQueryById(TunnelId tunnelId) { |
| return null; |
| } |
| |
| private ObjectNode executeGetRequest(DeviceId deviceId) { |
| //the request url is ietf-te:te/tunnels |
| //the response node will begin with tunnels |
| //be careful here to when get the tunnels data |
| InputStream resultStream = |
| controller.get(deviceId, TUNNELS_URL, MEDIA_TYPE_JSON); |
| return toJson(resultStream); |
| } |
| |
| private Tunnels getYangTunnelsObject(ObjectNode tunnelsNode) { |
| checkNotNull(tunnelsNode, "Input object node should not be null"); |
| |
| YangCompositeEncoding yce = |
| new YangCompositeEncodingImpl(URI, |
| TUNNELS_URL, |
| jsonToString(tunnelsNode)); |
| |
| Object yo = codecHandler.decode(yce, JSON, QUERY_REPLY); |
| |
| if (yo == null) { |
| log.error("YMS decoder returns null"); |
| return null; |
| } |
| IetfTe ietfTe = null; |
| Tunnels tunnels = null; |
| if (yo instanceof List) { |
| List<Object> list = (List<Object>) yo; |
| ietfTe = (IetfTe) list.get(DEFAULT_INDEX); |
| } |
| if (ietfTe != null && ietfTe.te() != null) { |
| tunnels = ietfTe.te().tunnels(); |
| } |
| return tunnels; |
| } |
| |
| private void updateTeTunnels(Tunnels tunnels) { |
| TeTopologyKey key = getTopologyKey(); |
| |
| tunnels.tunnel().forEach(tunnel -> { |
| DefaultTeTunnel teTunnel = yang2TeTunnel(tunnel, key); |
| providerService.updateTeTunnel(teTunnel); |
| }); |
| } |
| |
| private TeTopologyKey getTopologyKey() { |
| TeTopologyKey key = null; |
| Optional<TeTopology> teTopology = topologyService.teTopologies() |
| .teTopologies() |
| .values() |
| .stream() |
| .filter(topology -> topology.flags().get(BIT_MERGED)) |
| .findFirst(); |
| if (teTopology.isPresent()) { |
| TeTopology topology = teTopology.get(); |
| key = topology.teTopologyId(); |
| } |
| return key; |
| } |
| |
| private DeviceId getOwnDevice(long topologyId) { |
| DeviceId deviceId = null; |
| Optional<TeTopology> topoOpt = topologyService.teTopologies() |
| .teTopologies() |
| .values() |
| .stream() |
| .filter(tp -> tp.teTopologyId().topologyId() == topologyId) |
| .findFirst(); |
| |
| if (topoOpt.isPresent()) { |
| deviceId = topoOpt.get().ownerId(); |
| } |
| return deviceId; |
| } |
| |
| |
| private class InternalTunnelNotificationListener implements |
| RestconfNotificationEventListener { |
| |
| @Override |
| public void handleNotificationEvent(DeviceId deviceId, Object eventJsonString) { |
| ObjectNode response = toJson((String) eventJsonString); |
| if (response == null) { |
| return; |
| } |
| JsonNode teNode = response.get(TE); |
| if (teNode == null) { |
| log.error("Illegal te json object from {}", deviceId); |
| return; |
| } |
| JsonNode tunnelsNode = teNode.get(TUNNELS); |
| if (tunnelsNode == null) { |
| log.error("Illegal tunnel json object from {}", deviceId); |
| return; |
| } |
| |
| Tunnels tunnels = getYangTunnelsObject((ObjectNode) tunnelsNode); |
| if (tunnels == null) { |
| return; |
| } |
| updateTeTunnels(tunnels); |
| } |
| } |
| } |