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