blob: 361d53873a3ad7b686fc54e12d44073e4c1b3cf3 [file] [log] [blame]
fahadnaeemkhan71827242017-09-21 15:10:07 -07001/*
2 * Copyright 2016-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 */
16package org.onosproject.drivers.ciena;
17
fahadnaeemkhan71d50eb2017-12-06 19:52:38 -080018import com.fasterxml.jackson.databind.JsonNode;
19import com.fasterxml.jackson.databind.ObjectMapper;
20import com.fasterxml.jackson.databind.ObjectReader;
21import com.google.common.collect.ImmutableList;
22import com.google.common.collect.Lists;
23import org.apache.commons.lang3.tuple.Pair;
fahadnaeemkhan71827242017-09-21 15:10:07 -070024import org.onlab.util.Frequency;
fahadnaeemkhana62bd2f2017-10-11 12:39:55 -070025import org.onosproject.driver.optical.flowrule.CrossConnectCache;
fahadnaeemkhan4b6731a2017-10-18 11:13:02 -070026import org.onosproject.incubator.net.faultmanagement.alarm.Alarm;
27import org.onosproject.incubator.net.faultmanagement.alarm.AlarmEntityId;
28import org.onosproject.incubator.net.faultmanagement.alarm.AlarmId;
29import org.onosproject.incubator.net.faultmanagement.alarm.DefaultAlarm;
fahadnaeemkhana62bd2f2017-10-11 12:39:55 -070030import org.onosproject.net.ChannelSpacing;
fahadnaeemkhan71827242017-09-21 15:10:07 -070031import org.onosproject.net.DeviceId;
fahadnaeemkhana62bd2f2017-10-11 12:39:55 -070032import org.onosproject.net.OchSignal;
33import org.onosproject.net.OchSignalType;
fahadnaeemkhan71d50eb2017-12-06 19:52:38 -080034import org.onosproject.net.Port;
35import org.onosproject.net.PortNumber;
fahadnaeemkhana62bd2f2017-10-11 12:39:55 -070036import org.onosproject.net.device.DeviceService;
fahadnaeemkhan71827242017-09-21 15:10:07 -070037import org.onosproject.net.driver.DriverHandler;
fahadnaeemkhana62bd2f2017-10-11 12:39:55 -070038import org.onosproject.net.flow.DefaultFlowEntry;
39import org.onosproject.net.flow.DefaultFlowRule;
40import org.onosproject.net.flow.DefaultTrafficSelector;
41import org.onosproject.net.flow.DefaultTrafficTreatment;
42import org.onosproject.net.flow.FlowEntry;
43import org.onosproject.net.flow.FlowId;
44import org.onosproject.net.flow.FlowRule;
45import org.onosproject.net.flow.TrafficSelector;
46import org.onosproject.net.flow.TrafficTreatment;
47import org.onosproject.net.flow.criteria.Criteria;
fahadnaeemkhan71827242017-09-21 15:10:07 -070048import org.onosproject.protocol.rest.RestSBController;
49import org.slf4j.Logger;
50
fahadnaeemkhan71d50eb2017-12-06 19:52:38 -080051import javax.ws.rs.core.MediaType;
52import javax.ws.rs.core.Response;
53import java.io.ByteArrayInputStream;
54import java.io.IOException;
55import java.io.InputStream;
56import java.nio.charset.StandardCharsets;
fahadnaeemkhan4b6731a2017-10-18 11:13:02 -070057import java.time.LocalDateTime;
58import java.time.ZoneId;
59import java.time.format.DateTimeFormatter;
60import java.time.format.DateTimeParseException;
fahadnaeemkhana62bd2f2017-10-11 12:39:55 -070061import java.util.Collection;
fahadnaeemkhan71d50eb2017-12-06 19:52:38 -080062import java.util.List;
63import java.util.Objects;
fahadnaeemkhana62bd2f2017-10-11 12:39:55 -070064import java.util.stream.Collectors;
fahadnaeemkhana62bd2f2017-10-11 12:39:55 -070065
fahadnaeemkhan71827242017-09-21 15:10:07 -070066import static com.google.common.base.Preconditions.checkNotNull;
67import static org.slf4j.LoggerFactory.getLogger;
68
69public class CienaRestDevice {
fahadnaeemkhana37be5c2018-02-15 18:39:55 -080070 private static final Frequency CENTER_FREQUENCY = Frequency.ofGHz(195_950);
fahadnaeemkhan71827242017-09-21 15:10:07 -070071 private static final String ENABLED = "enabled";
72 private static final String DISABLED = "disabled";
fahadnaeemkhana62bd2f2017-10-11 12:39:55 -070073 private static final String VALUE = "value";
74 private static final String STATE = "state";
75 private static final String ADMIN_STATE = "admin-state";
76 private static final String WAVELENGTH = "wavelength";
77 private static final String DATA = "data";
fahadnaeemkhan4b6731a2017-10-18 11:13:02 -070078 private static final String ACTIVE = "active";
79 private static final String ACKNOWLEDGE = "acknowledged";
80 private static final String SEVERITY = "severity";
81 private static final String DESCRIPTION = "description";
82 private static final String INSTANCE = "instance";
83 private static final String PORT = "port";
84 private static final String PTP = "ptp";
85 private static final String UTC = "UTC";
86 private static final String OTHER = "other";
87 private static final String DATE_TIME_FORMAT = "EEE MMM [ ]d HH:mm:ss yyyy";
88 //keys
89 private static final String ALARM_KEY = "ws-alarms";
90 private static final String ALARM_INSTANCE_ID = "alarm-instance-id";
91 private static final String ALARM_TABLE_ID = "alarm-table-id";
92 private static final String ALARM_LOCAL_DATE_TIME = "local-date-time";
fahadnaeemkhana62bd2f2017-10-11 12:39:55 -070093 private static final String LINE_SYSTEM_CHANNEL_NUMBER = "ciena-ws-ptp-modem:line-system-channel-number";
94 private static final String FREQUENCY_KEY = "ciena-ws-ptp-modem:frequency";
fahadnaeemkhan71827242017-09-21 15:10:07 -070095 //URIs
96 private static final String PORT_URI = "ws-ptps/ptps/%s";
97 private static final String TRANSMITTER_URI = PORT_URI + "/properties/transmitter";
fahadnaeemkhana62bd2f2017-10-11 12:39:55 -070098 private static final String PORT_STATE_URI = PORT_URI + "/" + STATE;
99 private static final String WAVELENGTH_URI = TRANSMITTER_URI + "/" + WAVELENGTH;
100 private static final String FREQUENCY_URI = TRANSMITTER_URI + "/" + FREQUENCY_KEY;
101 private static final String CHANNEL_URI = TRANSMITTER_URI + "/" + LINE_SYSTEM_CHANNEL_NUMBER;
fahadnaeemkhan4b6731a2017-10-18 11:13:02 -0700102 private static final String ACTIVE_ALARMS_URL = ALARM_KEY + "/" + ACTIVE;
fahadnaeemkhana62bd2f2017-10-11 12:39:55 -0700103 private static final List<String> LINESIDE_PORT_ID = ImmutableList.of("4", "48");
fahadnaeemkhana37be5c2018-02-15 18:39:55 -0800104 private static final ChannelSpacing CHANNEL_SPACING = ChannelSpacing.CHL_50GHZ;
fahadnaeemkhan71827242017-09-21 15:10:07 -0700105
fahadnaeemkhan4b6731a2017-10-18 11:13:02 -0700106 private final Logger log = getLogger(getClass());
107
108 private DeviceId deviceId;
109 private RestSBController controller;
110 private CrossConnectCache crossConnectCache;
111 private DeviceService deviceService;
112
113
fahadnaeemkhan71827242017-09-21 15:10:07 -0700114 public CienaRestDevice(DriverHandler handler) throws NullPointerException {
115 deviceId = handler.data().deviceId();
116 controller = checkNotNull(handler.get(RestSBController.class));
fahadnaeemkhana62bd2f2017-10-11 12:39:55 -0700117 crossConnectCache = checkNotNull(handler.get(CrossConnectCache.class));
118 deviceService = checkNotNull(handler.get(DeviceService.class));
119 }
120
121 /**
122 * return the Line side ports.
123 *
124 * @return List of Line side ports.
125 */
126 public static List<String> getLinesidePortId() {
127 return LINESIDE_PORT_ID;
128 }
129
130 /**
131 * add the given flow rules to cross connect-cache.
132 *
133 * @param flowRules flow rules that needs to be cached.
134 */
135 public void setCrossConnectCache(Collection<FlowRule> flowRules) {
136 flowRules.forEach(xc -> crossConnectCache.set(
137 Objects.hash(deviceId, xc.selector(), xc.treatment()),
138 xc.id(),
139 xc.priority()));
140 }
141
142 /**
143 * remove the given flow rules form the cross-connect cache.
144 *
145 * @param flowRules flow rules that needs to be removed from cache.
146 */
147 public void removeCrossConnectCache(Collection<FlowRule> flowRules) {
148 flowRules.forEach(xc -> crossConnectCache.remove(Objects.hash(deviceId, xc.selector(), xc.treatment())));
fahadnaeemkhan71827242017-09-21 15:10:07 -0700149 }
150
151 private final String genPortStateRequest(String state) {
152 String request = "{\n" +
fahadnaeemkhana62bd2f2017-10-11 12:39:55 -0700153 "\"" + STATE + "\": {\n" +
154 "\"" + ADMIN_STATE + "\": \"" + state + "\"\n}\n}";
fahadnaeemkhan71827242017-09-21 15:10:07 -0700155 log.debug("generated request: \n{}", request);
156 return request;
157 }
158
159 private String genWavelengthChangeRequest(String wavelength) {
160 String request = "{\n" +
fahadnaeemkhana62bd2f2017-10-11 12:39:55 -0700161 "\"" + WAVELENGTH + "\": {\n" +
162 "\"" + VALUE + "\": " + wavelength + "\n" +
fahadnaeemkhan71827242017-09-21 15:10:07 -0700163 "}\n" +
164 "}";
165 log.debug("request:\n{}", request);
166 return request;
167
168 }
169
fahadnaeemkhana37be5c2018-02-15 18:39:55 -0800170 private String genFrequencyChangeRequest(double frequency) {
fahadnaeemkhan71827242017-09-21 15:10:07 -0700171 String request = "{\n" +
fahadnaeemkhana62bd2f2017-10-11 12:39:55 -0700172 "\"" + FREQUENCY_KEY + "\": {\n" +
fahadnaeemkhana37be5c2018-02-15 18:39:55 -0800173 "\"" + VALUE + "\": " + Double.toString(frequency) + "\n" +
fahadnaeemkhan71827242017-09-21 15:10:07 -0700174 "}\n" +
175 "}";
176 log.debug("request:\n{}", request);
177 return request;
178
179 }
180
181 private String genChannelChangeRequest(int channel) {
182 String request = "{\n" +
fahadnaeemkhana62bd2f2017-10-11 12:39:55 -0700183 "\"" + LINE_SYSTEM_CHANNEL_NUMBER + "\": " +
fahadnaeemkhan71827242017-09-21 15:10:07 -0700184 Integer.toString(channel) + "\n}";
185 log.debug("request:\n{}", request);
186 return request;
187
188 }
189
190
191 private final String genUri(String uriFormat, PortNumber port) {
192 return String.format(uriFormat, port.name());
193 }
194
fahadnaeemkhan71d50eb2017-12-06 19:52:38 -0800195 private boolean isPortState(PortNumber number, String state) {
196 log.debug("checking port {} state is {} or not on device {}", number, state, deviceId);
197 String uri = genUri(PORT_STATE_URI, number);
198 JsonNode jsonNode;
199 try {
200 jsonNode = get(uri);
201 } catch (IOException e) {
202 log.error("unable to get port state on device {}", deviceId);
203 return false;
204 }
205 return jsonNode.get(STATE).get(ADMIN_STATE).asText().equals(state);
206
207 }
208
209 private boolean confirmPortState(long timePeriodInMillis, int iterations, PortNumber number, String state) {
210 for (int i = 0; i < iterations; i++) {
211 log.debug("looping for port state with time period {}ms on device {}. try number {}/{}",
212 timePeriodInMillis, deviceId, i + 1, iterations);
213 if (isPortState(number, state)) {
214 return true;
215 }
216 try {
217 Thread.sleep(timePeriodInMillis);
218 } catch (InterruptedException e) {
219 log.error("unable to sleep thread for device {}\n", deviceId, e);
220 Thread.currentThread().interrupt();
221 return false;
222 }
223 }
224 return false;
225 }
226
fahadnaeemkhan71827242017-09-21 15:10:07 -0700227 private boolean changePortState(PortNumber number, String state) {
fahadnaeemkhana62bd2f2017-10-11 12:39:55 -0700228 log.debug("changing the port {} on device {} state to {}", number, deviceId, state);
fahadnaeemkhan71827242017-09-21 15:10:07 -0700229 String uri = genUri(PORT_STATE_URI, number);
230 String request = genPortStateRequest(state);
fahadnaeemkhan71d50eb2017-12-06 19:52:38 -0800231
fahadnaeemkhana62bd2f2017-10-11 12:39:55 -0700232 boolean response = putNoReply(uri, request);
233 if (!response) {
234 log.error("unable to change port {} on device {} state to {}", number, deviceId, state);
235 }
fahadnaeemkhan71d50eb2017-12-06 19:52:38 -0800236
237 // 5 tries with 2 sec delay
238 long timePeriod = 2000;
239 int iterations = 5;
240 return confirmPortState(timePeriod, iterations, number, state);
fahadnaeemkhan71827242017-09-21 15:10:07 -0700241 }
242
243 public boolean disablePort(PortNumber number) {
244 return changePortState(number, DISABLED);
245 }
246
247 public boolean enablePort(PortNumber number) {
248 return changePortState(number, ENABLED);
249 }
250
251 public final boolean changeFrequency(OchSignal signal, PortNumber outPort) {
252 String uri = genUri(FREQUENCY_URI, outPort);
fahadnaeemkhana37be5c2018-02-15 18:39:55 -0800253 double frequency = signal.centralFrequency().asGHz();
fahadnaeemkhan71827242017-09-21 15:10:07 -0700254 String request = genFrequencyChangeRequest(frequency);
fahadnaeemkhana62bd2f2017-10-11 12:39:55 -0700255 boolean response = putNoReply(uri, request);
256 if (!response) {
257 log.error("unable to change frequency of port {} on device {}", outPort, deviceId);
258 }
259 return response;
fahadnaeemkhan71827242017-09-21 15:10:07 -0700260 }
261
262 public final boolean changeChannel(OchSignal signal, PortNumber outPort) {
263 String uri = genUri(CHANNEL_URI, outPort);
fahadnaeemkhanffc917f2017-10-03 14:04:46 -0700264 int channel = signal.spacingMultiplier();
fahadnaeemkhan71827242017-09-21 15:10:07 -0700265 log.debug("channel is {} for port {} on device {}", channel, outPort.name(), deviceId);
266 String request = genChannelChangeRequest(channel);
fahadnaeemkhana62bd2f2017-10-11 12:39:55 -0700267 boolean response = putNoReply(uri, request);
268 if (!response) {
269 log.error("unable to change channel to {} for port {} on device {}",
270 channel, outPort.name(), deviceId);
271 }
272 return response;
fahadnaeemkhan71827242017-09-21 15:10:07 -0700273 }
274
fahadnaeemkhana37be5c2018-02-15 18:39:55 -0800275 private int getChannel(PortNumber port) {
fahadnaeemkhana62bd2f2017-10-11 12:39:55 -0700276 try {
277 String uri = genUri(CHANNEL_URI, port);
278 JsonNode response = get(uri);
279 return response.get(LINE_SYSTEM_CHANNEL_NUMBER).asInt();
280 } catch (IOException e) {
281 // this is expected for client side ports as they don't contain channel data
282 log.error("unable to get channel for port {} on device {}:\n{}", port, deviceId, e);
283 return -1;
284 }
285
286 }
287
fahadnaeemkhana37be5c2018-02-15 18:39:55 -0800288 private int getChannelFromFrequency(Frequency frequency) {
289 return (int) CENTER_FREQUENCY.subtract(frequency)
290 .floorDivision(CHANNEL_SPACING.frequency().asHz()).asHz();
291
292 }
293
294 private Frequency getFrequency(PortNumber port) {
295 try {
296 String uri = genUri(FREQUENCY_URI, port);
297 JsonNode response = get(uri);
298 return Frequency.ofGHz(response.get(FREQUENCY_KEY).get(VALUE).asDouble());
299 } catch (IOException e) {
300 // this is expected for client side ports as they don't contain channel data
301 log.error("unable to get frequency for port {} on device {}:\n{}", port, deviceId, e);
302 return null;
303 }
304
305 }
306
fahadnaeemkhan4b6731a2017-10-18 11:13:02 -0700307 private AlarmEntityId getAlarmSource(String instance) {
308 AlarmEntityId source;
309 if (instance.contains(PORT)) {
310 source = AlarmEntityId.alarmEntityId(instance.replace("-", ":"));
311 } else if (instance.contains(PTP)) {
312 source = AlarmEntityId.alarmEntityId(instance.replace(PTP + "-", PORT + ":"));
313 } else {
314 source = AlarmEntityId.alarmEntityId(OTHER + ":" + instance);
315 }
316 return source;
317 }
318
319 private long parseAlarmTime(String time) {
320 /*
321 * expecting WaveServer time to be set to UTC.
322 */
323 try {
324 DateTimeFormatter formatter = DateTimeFormatter.ofPattern(DATE_TIME_FORMAT);
325 LocalDateTime localDateTime = LocalDateTime.parse(time, formatter);
326 return localDateTime.atZone(ZoneId.of(UTC)).toInstant().toEpochMilli();
327 } catch (DateTimeParseException e2) {
328 log.error("unable to parse time {}, using system time", time);
329 return System.currentTimeMillis();
330 }
331 }
332
333 private Alarm newAlarmFromJsonNode(JsonNode jsonNode) {
334 try {
335 AlarmId alarmId = AlarmId.alarmId(checkNotNull(jsonNode.get(ALARM_INSTANCE_ID)).asText());
336 String time = checkNotNull(jsonNode.get(ALARM_LOCAL_DATE_TIME)).asText();
337 String instance = checkNotNull(jsonNode.get(INSTANCE).asText()).toLowerCase();
338 String description = checkNotNull(jsonNode.get(DESCRIPTION)).asText() + " - " + instance + " - " + time;
339 AlarmEntityId source = getAlarmSource(instance);
340 Alarm.SeverityLevel severity = Alarm.SeverityLevel.valueOf(checkNotNull(
341 jsonNode.get(SEVERITY)).asText().toUpperCase());
342
343 long timeRaised = parseAlarmTime(time);
344 boolean isAcknowledged = checkNotNull(jsonNode.get(ACKNOWLEDGE)).asBoolean();
345
346 return new DefaultAlarm.Builder(alarmId, deviceId, description, severity, timeRaised)
347 .withAcknowledged(isAcknowledged)
348 .forSource(source)
349 .build();
350
351 } catch (NullPointerException e) {
352 log.error("got exception while parsing alarm json node {} for device {}:\n", jsonNode, deviceId, e);
353 return null;
354 }
355
356 }
357
358 private List<Alarm> getActiveAlarms() {
359 log.debug("getting active alarms for device {}", deviceId);
360 try {
361 List<JsonNode> alarms = Lists.newArrayList(get(ACTIVE_ALARMS_URL).get(ACTIVE).elements());
362 return alarms.stream()
fahadnaeemkhana37be5c2018-02-15 18:39:55 -0800363 .map(this::newAlarmFromJsonNode)
364 .filter(Objects::nonNull)
fahadnaeemkhan4b6731a2017-10-18 11:13:02 -0700365 .collect(Collectors.toList());
366 } catch (IOException e) {
367 log.error("unable to get active alarms for device {}:\n", deviceId, e);
368 return null;
369 }
370 }
371
fahadnaeemkhana62bd2f2017-10-11 12:39:55 -0700372 public Collection<FlowEntry> getFlowEntries() {
373 List<Port> ports = deviceService.getPorts(deviceId);
374 //driver only handles lineSide ports
375 //TODO: handle client ports as well
376 return ports.stream()
377 .filter(p -> LINESIDE_PORT_ID.contains(p.number().name()))
378 .map(p -> fetchRule(p.number()))
fahadnaeemkhana37be5c2018-02-15 18:39:55 -0800379 .filter(Objects::nonNull)
fahadnaeemkhana62bd2f2017-10-11 12:39:55 -0700380 .map(fr -> new DefaultFlowEntry(fr, FlowEntry.FlowEntryState.ADDED, 0, 0, 0))
381 .collect(Collectors.toList());
382 }
383
384 private FlowRule fetchRule(PortNumber port) {
fahadnaeemkhana37be5c2018-02-15 18:39:55 -0800385 Frequency frequency = getFrequency(port);
386 if (frequency == null) {
fahadnaeemkhana62bd2f2017-10-11 12:39:55 -0700387 return null;
388 }
fahadnaeemkhana37be5c2018-02-15 18:39:55 -0800389 int channel = getChannelFromFrequency(frequency);
fahadnaeemkhana62bd2f2017-10-11 12:39:55 -0700390 /*
fahadnaeemkhan4b6731a2017-10-18 11:13:02 -0700391 * both inPort and outPort will be same as WaveServer only deal with same port ptp-indexes
fahadnaeemkhana62bd2f2017-10-11 12:39:55 -0700392 * channel and spaceMultiplier are same.
393 * TODO: find a way to get both inPort and outPort for future when inPort may not be equal to outPort
394 */
395
396 TrafficSelector selector = DefaultTrafficSelector.builder()
397 .matchInPort(port)
398 .add(Criteria.matchOchSignalType(OchSignalType.FIXED_GRID))
fahadnaeemkhana37be5c2018-02-15 18:39:55 -0800399 .add(Criteria.matchLambda(OchSignal.newDwdmSlot(CHANNEL_SPACING, channel)))
fahadnaeemkhana62bd2f2017-10-11 12:39:55 -0700400 .build();
401 TrafficTreatment treatment = DefaultTrafficTreatment.builder()
402 .setOutput(port)
403 .build();
404
405 int hash = Objects.hash(deviceId, selector, treatment);
406 Pair<FlowId, Integer> lookup = crossConnectCache.get(hash);
407 if (lookup == null) {
408 return null;
409 }
410
fahadnaeemkhana37be5c2018-02-15 18:39:55 -0800411 return DefaultFlowRule.builder()
fahadnaeemkhana62bd2f2017-10-11 12:39:55 -0700412 .forDevice(deviceId)
413 .makePermanent()
414 .withSelector(selector)
415 .withTreatment(treatment)
416 .withPriority(lookup.getRight())
417 .withCookie(lookup.getLeft().value())
418 .build();
fahadnaeemkhana62bd2f2017-10-11 12:39:55 -0700419 }
420
fahadnaeemkhan4b6731a2017-10-18 11:13:02 -0700421 public List<Alarm> getAlarms() {
422 log.debug("getting alarms for device {}", deviceId);
423 return getActiveAlarms();
424 }
425
fahadnaeemkhana62bd2f2017-10-11 12:39:55 -0700426 private JsonNode get(String uri) throws IOException {
427 InputStream response = controller.get(deviceId, uri, MediaType.valueOf(MediaType.APPLICATION_JSON));
428 ObjectMapper om = new ObjectMapper();
429 final ObjectReader reader = om.reader();
430 // all waveserver responses contain data node, which contains the requested data
431 return reader.readTree(response).get(DATA);
432 }
433
fahadnaeemkhan71827242017-09-21 15:10:07 -0700434 private int put(String uri, String request) {
435 InputStream payload = new ByteArrayInputStream(request.getBytes(StandardCharsets.UTF_8));
436 int response = controller.put(deviceId, uri, payload, MediaType.valueOf(MediaType.APPLICATION_JSON));
437 log.debug("response: {}", response);
438 return response;
439 }
440
441 private boolean putNoReply(String uri, String request) {
442 return put(uri, request) == Response.Status.NO_CONTENT.getStatusCode();
443 }
444
445}