Andrea Campanella | 2bdf204 | 2019-01-28 13:47:11 +0100 | [diff] [blame] | 1 | /* |
| 2 | * Copyright 2018-present Open Networking Foundation |
| 3 | * |
| 4 | * Licensed under the Apache License, Version 2.0 (the "License"); |
| 5 | * you may not use this file except in compliance with the License. |
| 6 | * You may obtain a copy of the License at |
| 7 | * |
| 8 | * http://www.apache.org/licenses/LICENSE-2.0 |
| 9 | * |
| 10 | * Unless required by applicable law or agreed to in writing, software |
| 11 | * distributed under the License is distributed on an "AS IS" BASIS, |
| 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| 13 | * See the License for the specific language governing permissions and |
| 14 | * limitations under the License. |
| 15 | * |
| 16 | * This work was partially supported by EC H2020 project METRO-HAUL (761727). |
| 17 | */ |
| 18 | package org.onosproject.drivers.odtn.tapi; |
| 19 | |
| 20 | import com.fasterxml.jackson.core.JsonEncoding; |
| 21 | import com.fasterxml.jackson.core.JsonFactory; |
| 22 | import com.fasterxml.jackson.core.JsonGenerator; |
| 23 | import com.fasterxml.jackson.databind.JsonNode; |
Andrea Campanella | 2bdf204 | 2019-01-28 13:47:11 +0100 | [diff] [blame] | 24 | import com.google.common.collect.ImmutableList; |
| 25 | import org.apache.http.HttpStatus; |
| 26 | import org.onosproject.drivers.odtn.impl.DeviceConnection; |
| 27 | import org.onosproject.drivers.odtn.impl.DeviceConnectionCache; |
| 28 | import org.onosproject.net.DeviceId; |
| 29 | import org.onosproject.net.device.DeviceService; |
| 30 | import org.onosproject.net.driver.AbstractHandlerBehaviour; |
| 31 | import org.onosproject.net.flow.DefaultFlowEntry; |
| 32 | import org.onosproject.net.flow.FlowEntry; |
| 33 | import org.onosproject.net.flow.FlowRule; |
| 34 | import org.onosproject.net.flow.FlowRuleProgrammable; |
| 35 | import org.onosproject.net.flow.criteria.Criterion; |
| 36 | import org.onosproject.net.flow.criteria.PortCriterion; |
| 37 | import org.onosproject.net.flow.instructions.Instruction; |
| 38 | import org.onosproject.net.flow.instructions.Instructions; |
| 39 | import org.onosproject.protocol.rest.RestSBController; |
| 40 | import org.slf4j.Logger; |
| 41 | |
| 42 | import javax.ws.rs.core.MediaType; |
| 43 | import java.io.ByteArrayInputStream; |
| 44 | import java.io.ByteArrayOutputStream; |
| 45 | import java.io.IOException; |
Andrea Campanella | 2bdf204 | 2019-01-28 13:47:11 +0100 | [diff] [blame] | 46 | import java.util.ArrayList; |
| 47 | import java.util.Collection; |
| 48 | import java.util.HashSet; |
| 49 | import java.util.List; |
| 50 | import java.util.Set; |
| 51 | import java.util.UUID; |
Andrea Campanella | b9e491b | 2019-02-18 17:45:01 +0100 | [diff] [blame] | 52 | import java.util.concurrent.CompletableFuture; |
Andrea Campanella | 2bdf204 | 2019-01-28 13:47:11 +0100 | [diff] [blame] | 53 | |
| 54 | import static com.google.common.base.Preconditions.checkNotNull; |
| 55 | import static org.onosproject.drivers.odtn.tapi.TapiDeviceHelper.END_POINT; |
| 56 | import static org.onosproject.drivers.odtn.tapi.TapiDeviceHelper.LAYER_PROTOCOL_NAME; |
| 57 | import static org.onosproject.drivers.odtn.tapi.TapiDeviceHelper.LAYER_PROTOCOL_QUALIFIER; |
| 58 | import static org.onosproject.drivers.odtn.tapi.TapiDeviceHelper.LOCAL_ID; |
| 59 | import static org.onosproject.drivers.odtn.tapi.TapiDeviceHelper.PHOTONIC_MEDIA; |
| 60 | import static org.onosproject.drivers.odtn.tapi.TapiDeviceHelper.POINT_TO_POINT_CONNECTIVITY; |
| 61 | import static org.onosproject.drivers.odtn.tapi.TapiDeviceHelper.SERVICE_INTERFACE_POINT; |
| 62 | import static org.onosproject.drivers.odtn.tapi.TapiDeviceHelper.SERVICE_INTERFACE_POINT_UUID; |
| 63 | import static org.onosproject.drivers.odtn.tapi.TapiDeviceHelper.SERVICE_LAYER; |
| 64 | import static org.onosproject.drivers.odtn.tapi.TapiDeviceHelper.SERVICE_TYPE; |
| 65 | import static org.onosproject.drivers.odtn.tapi.TapiDeviceHelper.TAPI_CONNECTIVITY_CONNECTIVITY_SERVICE; |
| 66 | import static org.onosproject.drivers.odtn.tapi.TapiDeviceHelper.TAPI_PHOTONIC_MEDIA_PHOTONIC_LAYER_QUALIFIER_NMC; |
| 67 | import static org.slf4j.LoggerFactory.getLogger; |
| 68 | |
| 69 | /** |
| 70 | * Driver Implementation of the DeviceDescrption discovery for ONF Transport-API (TAPI) v2.1 based |
| 71 | * open line systems (OLS). |
| 72 | */ |
| 73 | |
| 74 | public class TapiFlowRuleProgrammable extends AbstractHandlerBehaviour |
| 75 | implements FlowRuleProgrammable { |
| 76 | |
| 77 | private static final Logger log = getLogger(TapiFlowRuleProgrammable.class); |
| 78 | private static final String CONN_REQ_POST_API = "/restconf/data/tapi-common:context/" + |
| 79 | "tapi-connectivity:connectivity-context/"; |
| 80 | private static final String CONN_REQ_REMOVE_DATA_API = "/restconf/data/tapi-common:context/" + |
| 81 | "tapi-connectivity:connectivity-context/connectivity-service="; |
| 82 | private static final String CONN_REQ_GET_API = "/restconf/data/tapi-common:context/" + |
Andrea Campanella | c4953eb | 2019-03-18 15:38:46 -0700 | [diff] [blame] | 83 | "tapi-connectivity:connectivity-context/"; |
Andrea Campanella | 2bdf204 | 2019-01-28 13:47:11 +0100 | [diff] [blame] | 84 | |
| 85 | |
| 86 | @Override |
| 87 | public Collection<FlowEntry> getFlowEntries() { |
| 88 | DeviceId deviceId = did(); |
Andrea Campanella | b9e491b | 2019-02-18 17:45:01 +0100 | [diff] [blame] | 89 | //TODO this is a blocking call on ADVA OLS, right now using cache. |
| 90 | // RestSBController controller = checkNotNull(handler().get(RestSBController.class)); |
| 91 | // ObjectMapper om = new ObjectMapper(); |
| 92 | // final ObjectReader reader = om.reader(); |
| 93 | // InputStream response = controller.get(deviceId, CONN_REQ_GET_API, MediaType.APPLICATION_JSON_TYPE); |
| 94 | // JsonNode jsonNode = null; |
| 95 | // try { |
| 96 | // jsonNode = reader.readTree(response); |
| 97 | // if (jsonNode == null) { |
Andrea Campanella | c4953eb | 2019-03-18 15:38:46 -0700 | [diff] [blame] | 98 | // log.error("JsonNode is null for response {}", response); |
Andrea Campanella | b9e491b | 2019-02-18 17:45:01 +0100 | [diff] [blame] | 99 | // return ImmutableList.of(); |
| 100 | // } |
| 101 | // Set<String> uuids = parseTapiGetConnectivityRequest(jsonNode); |
| 102 | // DeviceConnectionCache cache = getConnectionCache(); |
| 103 | // if (cache.get(deviceId) == null) { |
| 104 | // return ImmutableList.of(); |
| 105 | // } |
| 106 | // List<FlowEntry> entries = new ArrayList<>(); |
| 107 | // uuids.forEach(uuid -> { |
| 108 | // FlowRule rule = cache.get(deviceId, uuid); |
| 109 | // if (rule != null) { |
| 110 | // entries.add(new DefaultFlowEntry(rule, FlowEntry.FlowEntryState.ADDED, 0, 0, 0)); |
| 111 | // } else { |
| 112 | // log.info("Non existing rule for uuid {}", uuid); |
| 113 | // } |
| 114 | // }); |
| 115 | // return entries; |
| 116 | // } catch (IOException e) { |
| 117 | // return ImmutableList.of(); |
| 118 | // } |
| 119 | List<FlowEntry> entries = new ArrayList<>(); |
| 120 | Set<FlowRule> rules = getConnectionCache().get(deviceId); |
| 121 | if (rules != null) { |
| 122 | rules.forEach(rule -> { |
| 123 | entries.add(new DefaultFlowEntry(rule, FlowEntry.FlowEntryState.ADDED, 0, 0, 0)); |
Andrea Campanella | 2bdf204 | 2019-01-28 13:47:11 +0100 | [diff] [blame] | 124 | }); |
Andrea Campanella | 2bdf204 | 2019-01-28 13:47:11 +0100 | [diff] [blame] | 125 | } |
Andrea Campanella | b9e491b | 2019-02-18 17:45:01 +0100 | [diff] [blame] | 126 | return entries; |
Andrea Campanella | 2bdf204 | 2019-01-28 13:47:11 +0100 | [diff] [blame] | 127 | } |
| 128 | |
| 129 | @Override |
| 130 | public Collection<FlowRule> applyFlowRules(Collection<FlowRule> rules) { |
| 131 | DeviceId deviceId = handler().data().deviceId(); |
| 132 | RestSBController controller = checkNotNull(handler().get(RestSBController.class)); |
| 133 | ImmutableList.Builder<FlowRule> added = ImmutableList.builder(); |
| 134 | rules.forEach(flowRule -> { |
| 135 | String uuid = createUuid(); |
| 136 | ByteArrayOutputStream applyConnectivityRequest = createConnectivityRequest(uuid, flowRule); |
| 137 | if (applyConnectivityRequest.size() != 0) { |
Andrea Campanella | b9e491b | 2019-02-18 17:45:01 +0100 | [diff] [blame] | 138 | CompletableFuture<Integer> flowInstallation = |
| 139 | CompletableFuture.supplyAsync(() -> controller.post(deviceId, CONN_REQ_POST_API, |
Andrea Campanella | 2bdf204 | 2019-01-28 13:47:11 +0100 | [diff] [blame] | 140 | new ByteArrayInputStream(applyConnectivityRequest.toByteArray()), |
Andrea Campanella | b9e491b | 2019-02-18 17:45:01 +0100 | [diff] [blame] | 141 | MediaType.APPLICATION_JSON_TYPE)); |
| 142 | flowInstallation.thenApply(result -> { |
| 143 | if (result == HttpStatus.SC_CREATED) { |
| 144 | getConnectionCache().add(deviceId, uuid, flowRule); |
| 145 | added.add(flowRule); |
| 146 | } else { |
| 147 | log.error("Can't add flow {}, result {}", flowRule, result); |
| 148 | } |
| 149 | return result; |
| 150 | }); |
Andrea Campanella | 2bdf204 | 2019-01-28 13:47:11 +0100 | [diff] [blame] | 151 | // TODO retrieve the UUID from the location and store with that identifier |
| 152 | // at the moment is implied that the sent one is the same used by the TAPI server. |
Andrea Campanella | 2bdf204 | 2019-01-28 13:47:11 +0100 | [diff] [blame] | 153 | } |
| 154 | }); |
Andrea Campanella | b9e491b | 2019-02-18 17:45:01 +0100 | [diff] [blame] | 155 | //TODO workaround for blocking call on ADVA OLS should return added |
| 156 | return rules; |
Andrea Campanella | 2bdf204 | 2019-01-28 13:47:11 +0100 | [diff] [blame] | 157 | } |
| 158 | |
| 159 | @Override |
| 160 | public Collection<FlowRule> removeFlowRules(Collection<FlowRule> rules) { |
| 161 | DeviceId deviceId = handler().data().deviceId(); |
| 162 | RestSBController controller = checkNotNull(handler().get(RestSBController.class)); |
| 163 | ImmutableList.Builder<FlowRule> removed = ImmutableList.builder(); |
| 164 | rules.forEach(flowRule -> { |
| 165 | DeviceConnection conn = getConnectionCache().get(deviceId, flowRule.id()); |
| 166 | if (conn == null || conn.getId() == null) { |
| 167 | log.warn("Can't find associate device connection for flow {} and device {}", |
| 168 | flowRule.id(), deviceId); |
| 169 | return; |
| 170 | } |
Andrea Campanella | b9e491b | 2019-02-18 17:45:01 +0100 | [diff] [blame] | 171 | CompletableFuture<Integer> flowInstallation = |
| 172 | CompletableFuture.supplyAsync(() -> controller.delete(deviceId, |
| 173 | CONN_REQ_REMOVE_DATA_API + conn.getId(), |
| 174 | null, MediaType.APPLICATION_JSON_TYPE)); |
| 175 | flowInstallation.thenApply(result -> { |
| 176 | if (result == HttpStatus.SC_NO_CONTENT) { |
| 177 | getConnectionCache().remove(deviceId, flowRule); |
| 178 | removed.add(flowRule); |
| 179 | } else { |
| 180 | log.error("Can't remove flow {}, result {}", flowRule, result); |
| 181 | } |
| 182 | return result; |
| 183 | }); |
Andrea Campanella | 2bdf204 | 2019-01-28 13:47:11 +0100 | [diff] [blame] | 184 | }); |
Andrea Campanella | b9e491b | 2019-02-18 17:45:01 +0100 | [diff] [blame] | 185 | //TODO workaround for blocking call on ADVA OLS shoudl return removed |
| 186 | return rules; |
Andrea Campanella | 2bdf204 | 2019-01-28 13:47:11 +0100 | [diff] [blame] | 187 | } |
| 188 | |
| 189 | /** |
| 190 | * Get the deviceId for which the methods apply. |
| 191 | * |
| 192 | * @return The deviceId as contained in the handler data |
| 193 | */ |
| 194 | private DeviceId did() { |
| 195 | return handler().data().deviceId(); |
| 196 | } |
| 197 | |
| 198 | private DeviceConnectionCache getConnectionCache() { |
| 199 | return DeviceConnectionCache.init(); |
| 200 | } |
| 201 | |
| 202 | protected Set<String> parseTapiGetConnectivityRequest(JsonNode tapiConnectivityReply) { |
| 203 | /* |
| 204 | { |
| 205 | "tapi-connectivity:connectivity-service":[ |
| 206 | { |
| 207 | "uuid":"ffb006d4-349e-4d2f-817e-0906c88458d0", |
| 208 | <other fields> |
| 209 | } |
| 210 | ] |
| 211 | } |
| 212 | */ |
| 213 | Set<String> uuids = new HashSet<>(); |
| 214 | if (tapiConnectivityReply.has(TAPI_CONNECTIVITY_CONNECTIVITY_SERVICE)) { |
| 215 | tapiConnectivityReply.get(TAPI_CONNECTIVITY_CONNECTIVITY_SERVICE).elements() |
| 216 | .forEachRemaining(node -> uuids.add(node.get(TapiDeviceHelper.UUID).asText())); |
Andrea Campanella | c4953eb | 2019-03-18 15:38:46 -0700 | [diff] [blame] | 217 | } else { |
Andrea Campanella | b9e491b | 2019-02-18 17:45:01 +0100 | [diff] [blame] | 218 | log.warn("Can't retrieve connectivity UUID from {}", tapiConnectivityReply); |
Andrea Campanella | 2bdf204 | 2019-01-28 13:47:11 +0100 | [diff] [blame] | 219 | } |
| 220 | //This is only one uuid or empty in case of failures |
| 221 | return uuids; |
| 222 | } |
| 223 | |
| 224 | ByteArrayOutputStream createConnectivityRequest(String uuid, FlowRule rule) { |
| 225 | /* |
| 226 | { |
| 227 | "tapi-connectivity:connectivity-service":[ |
| 228 | { |
| 229 | "uuid":"ffb006d4-349e-4d2f-817e-0906c88458d0", |
| 230 | "service-layer":"PHOTONIC_MEDIA", |
| 231 | "service-type":"POINT_TO_POINT_CONNECTIVITY", |
| 232 | "end-point":[ |
| 233 | { |
| 234 | "local-id":"1", |
| 235 | "layer-protocol-name":"PHOTONIC_MEDIA", |
| 236 | "layer-protocol-qualifier":"tapi-photonic-media:PHOTONIC_LAYER_QUALIFIER_NMC", |
| 237 | "service-interface-point":{ |
| 238 | "service-interface-point-uuid":"0923962e-b83f-4702-9b16-a1a0db0dc1f9" |
| 239 | } |
| 240 | }, |
| 241 | { |
| 242 | "local-id":"2", |
| 243 | "layer-protocol-name":"PHOTONIC_MEDIA", |
| 244 | "layer-protocol-qualifier":"tapi-photonic-media:PHOTONIC_LAYER_QUALIFIER_NMC", |
| 245 | "service-interface-point":{ |
| 246 | "service-interface-point-uuid":"76be95de-5769-4e5d-b65e-62cb6c39cf6b " |
| 247 | } |
| 248 | } |
| 249 | ] |
| 250 | } |
| 251 | ] |
| 252 | } |
| 253 | */ |
| 254 | DeviceService deviceService = handler().get(DeviceService.class); |
| 255 | PortCriterion inputPortCriterion = (PortCriterion) checkNotNull(rule.selector() |
| 256 | .getCriterion(Criterion.Type.IN_PORT)); |
| 257 | String inputPortUuid = deviceService.getPort(rule.deviceId(), |
| 258 | inputPortCriterion.port()).annotations().value(TapiDeviceHelper.UUID); |
| 259 | |
| 260 | Instructions.OutputInstruction outInstruction = (Instructions.OutputInstruction) checkNotNull(rule.treatment() |
| 261 | .allInstructions().stream().filter(instr -> instr.type().equals(Instruction.Type.OUTPUT)) |
| 262 | .findFirst().orElse(null)); |
| 263 | String outputPortUuid = deviceService.getPort(rule.deviceId(), |
| 264 | outInstruction.port()).annotations().value(TapiDeviceHelper.UUID); |
| 265 | ByteArrayOutputStream stream = new ByteArrayOutputStream(); |
| 266 | try { |
| 267 | JsonGenerator generator = getJsonGenerator(stream); |
| 268 | generator.writeStartObject(); |
| 269 | generator.writeArrayFieldStart(TAPI_CONNECTIVITY_CONNECTIVITY_SERVICE); |
| 270 | generator.writeStartObject(); |
| 271 | generator.writeStringField(TapiDeviceHelper.UUID, uuid); |
| 272 | generator.writeStringField(SERVICE_LAYER, PHOTONIC_MEDIA); |
| 273 | generator.writeStringField(SERVICE_TYPE, POINT_TO_POINT_CONNECTIVITY); |
| 274 | generator.writeArrayFieldStart(END_POINT); |
| 275 | addEndPoint(generator, inputPortUuid); |
| 276 | addEndPoint(generator, outputPortUuid); |
| 277 | generator.writeEndArray(); |
| 278 | generator.writeEndObject(); |
| 279 | generator.writeEndArray(); |
| 280 | generator.writeEndObject(); |
| 281 | generator.close(); |
| 282 | return stream; |
| 283 | } catch (IOException e) { |
| 284 | log.error("Cant' create json", e); |
| 285 | } |
| 286 | return stream; |
| 287 | } |
| 288 | |
| 289 | private JsonGenerator getJsonGenerator(ByteArrayOutputStream stream) throws IOException { |
| 290 | JsonFactory factory = new JsonFactory(); |
| 291 | return factory.createGenerator(stream, JsonEncoding.UTF8); |
| 292 | } |
| 293 | /* |
| 294 | { |
| 295 | "local-id":"1", |
| 296 | "layer-protocol-name":"PHOTONIC_MEDIA", |
| 297 | "layer-protocol-qualifier":"tapi-photonic-media:PHOTONIC_LAYER_QUALIFIER_NMC", |
| 298 | "service-interface-point":{ |
| 299 | "service-interface-point-uuid":"0923962e-b83f-4702-9b16-a1a0db0dc1f9" |
| 300 | } |
| 301 | } |
| 302 | */ |
| 303 | private void addEndPoint(JsonGenerator generator, String sipUuid) throws IOException { |
| 304 | generator.writeStartObject(); |
| 305 | generator.writeStringField(LOCAL_ID, sipUuid); |
| 306 | generator.writeStringField(LAYER_PROTOCOL_NAME, PHOTONIC_MEDIA); |
| 307 | generator.writeStringField(LAYER_PROTOCOL_QUALIFIER, |
| 308 | TAPI_PHOTONIC_MEDIA_PHOTONIC_LAYER_QUALIFIER_NMC); |
| 309 | generator.writeObjectFieldStart(SERVICE_INTERFACE_POINT); |
| 310 | generator.writeStringField(SERVICE_INTERFACE_POINT_UUID, sipUuid); |
| 311 | generator.writeEndObject(); |
| 312 | generator.writeEndObject(); |
| 313 | } |
| 314 | |
| 315 | private String createUuid() { |
| 316 | return UUID.randomUUID().toString(); |
| 317 | } |
| 318 | } |