blob: 0fce6548922a64984f7cbd9ddf25cd855af461a7 [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 {
fahadnaeemkhan4b6731a2017-10-18 11:13:02 -070070 private static final Frequency BASE_FREQUENCY = Frequency.ofGHz(193_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");
fahadnaeemkhan71827242017-09-21 15:10:07 -0700104
fahadnaeemkhan4b6731a2017-10-18 11:13:02 -0700105 private final Logger log = getLogger(getClass());
106
107 private DeviceId deviceId;
108 private RestSBController controller;
109 private CrossConnectCache crossConnectCache;
110 private DeviceService deviceService;
111
112
fahadnaeemkhan71827242017-09-21 15:10:07 -0700113 public CienaRestDevice(DriverHandler handler) throws NullPointerException {
114 deviceId = handler.data().deviceId();
115 controller = checkNotNull(handler.get(RestSBController.class));
fahadnaeemkhana62bd2f2017-10-11 12:39:55 -0700116 crossConnectCache = checkNotNull(handler.get(CrossConnectCache.class));
117 deviceService = checkNotNull(handler.get(DeviceService.class));
118 }
119
120 /**
121 * return the Line side ports.
122 *
123 * @return List of Line side ports.
124 */
125 public static List<String> getLinesidePortId() {
126 return LINESIDE_PORT_ID;
127 }
128
129 /**
130 * add the given flow rules to cross connect-cache.
131 *
132 * @param flowRules flow rules that needs to be cached.
133 */
134 public void setCrossConnectCache(Collection<FlowRule> flowRules) {
135 flowRules.forEach(xc -> crossConnectCache.set(
136 Objects.hash(deviceId, xc.selector(), xc.treatment()),
137 xc.id(),
138 xc.priority()));
139 }
140
141 /**
142 * remove the given flow rules form the cross-connect cache.
143 *
144 * @param flowRules flow rules that needs to be removed from cache.
145 */
146 public void removeCrossConnectCache(Collection<FlowRule> flowRules) {
147 flowRules.forEach(xc -> crossConnectCache.remove(Objects.hash(deviceId, xc.selector(), xc.treatment())));
fahadnaeemkhan71827242017-09-21 15:10:07 -0700148 }
149
150 private final String genPortStateRequest(String state) {
151 String request = "{\n" +
fahadnaeemkhana62bd2f2017-10-11 12:39:55 -0700152 "\"" + STATE + "\": {\n" +
153 "\"" + ADMIN_STATE + "\": \"" + state + "\"\n}\n}";
fahadnaeemkhan71827242017-09-21 15:10:07 -0700154 log.debug("generated request: \n{}", request);
155 return request;
156 }
157
158 private String genWavelengthChangeRequest(String wavelength) {
159 String request = "{\n" +
fahadnaeemkhana62bd2f2017-10-11 12:39:55 -0700160 "\"" + WAVELENGTH + "\": {\n" +
161 "\"" + VALUE + "\": " + wavelength + "\n" +
fahadnaeemkhan71827242017-09-21 15:10:07 -0700162 "}\n" +
163 "}";
164 log.debug("request:\n{}", request);
165 return request;
166
167 }
168
fahadnaeemkhanffc917f2017-10-03 14:04:46 -0700169 private String genFrequencyChangeRequest(long frequency) {
fahadnaeemkhan71827242017-09-21 15:10:07 -0700170 String request = "{\n" +
fahadnaeemkhana62bd2f2017-10-11 12:39:55 -0700171 "\"" + FREQUENCY_KEY + "\": {\n" +
172 "\"" + VALUE + "\": " + Long.toString(frequency) + "\n" +
fahadnaeemkhan71827242017-09-21 15:10:07 -0700173 "}\n" +
174 "}";
175 log.debug("request:\n{}", request);
176 return request;
177
178 }
179
180 private String genChannelChangeRequest(int channel) {
181 String request = "{\n" +
fahadnaeemkhana62bd2f2017-10-11 12:39:55 -0700182 "\"" + LINE_SYSTEM_CHANNEL_NUMBER + "\": " +
fahadnaeemkhan71827242017-09-21 15:10:07 -0700183 Integer.toString(channel) + "\n}";
184 log.debug("request:\n{}", request);
185 return request;
186
187 }
188
189
190 private final String genUri(String uriFormat, PortNumber port) {
191 return String.format(uriFormat, port.name());
192 }
193
fahadnaeemkhan71d50eb2017-12-06 19:52:38 -0800194 private boolean isPortState(PortNumber number, String state) {
195 log.debug("checking port {} state is {} or not on device {}", number, state, deviceId);
196 String uri = genUri(PORT_STATE_URI, number);
197 JsonNode jsonNode;
198 try {
199 jsonNode = get(uri);
200 } catch (IOException e) {
201 log.error("unable to get port state on device {}", deviceId);
202 return false;
203 }
204 return jsonNode.get(STATE).get(ADMIN_STATE).asText().equals(state);
205
206 }
207
208 private boolean confirmPortState(long timePeriodInMillis, int iterations, PortNumber number, String state) {
209 for (int i = 0; i < iterations; i++) {
210 log.debug("looping for port state with time period {}ms on device {}. try number {}/{}",
211 timePeriodInMillis, deviceId, i + 1, iterations);
212 if (isPortState(number, state)) {
213 return true;
214 }
215 try {
216 Thread.sleep(timePeriodInMillis);
217 } catch (InterruptedException e) {
218 log.error("unable to sleep thread for device {}\n", deviceId, e);
219 Thread.currentThread().interrupt();
220 return false;
221 }
222 }
223 return false;
224 }
225
fahadnaeemkhan71827242017-09-21 15:10:07 -0700226 private boolean changePortState(PortNumber number, String state) {
fahadnaeemkhana62bd2f2017-10-11 12:39:55 -0700227 log.debug("changing the port {} on device {} state to {}", number, deviceId, state);
fahadnaeemkhan71827242017-09-21 15:10:07 -0700228 String uri = genUri(PORT_STATE_URI, number);
229 String request = genPortStateRequest(state);
fahadnaeemkhan71d50eb2017-12-06 19:52:38 -0800230
fahadnaeemkhana62bd2f2017-10-11 12:39:55 -0700231 boolean response = putNoReply(uri, request);
232 if (!response) {
233 log.error("unable to change port {} on device {} state to {}", number, deviceId, state);
234 }
fahadnaeemkhan71d50eb2017-12-06 19:52:38 -0800235
236 // 5 tries with 2 sec delay
237 long timePeriod = 2000;
238 int iterations = 5;
239 return confirmPortState(timePeriod, iterations, number, state);
fahadnaeemkhan71827242017-09-21 15:10:07 -0700240 }
241
242 public boolean disablePort(PortNumber number) {
243 return changePortState(number, DISABLED);
244 }
245
246 public boolean enablePort(PortNumber number) {
247 return changePortState(number, ENABLED);
248 }
249
250 public final boolean changeFrequency(OchSignal signal, PortNumber outPort) {
251 String uri = genUri(FREQUENCY_URI, outPort);
252 long frequency = toFrequency(signal);
253 String request = genFrequencyChangeRequest(frequency);
fahadnaeemkhana62bd2f2017-10-11 12:39:55 -0700254 boolean response = putNoReply(uri, request);
255 if (!response) {
256 log.error("unable to change frequency of port {} on device {}", outPort, deviceId);
257 }
258 return response;
fahadnaeemkhan71827242017-09-21 15:10:07 -0700259 }
260
261 public final boolean changeChannel(OchSignal signal, PortNumber outPort) {
262 String uri = genUri(CHANNEL_URI, outPort);
fahadnaeemkhanffc917f2017-10-03 14:04:46 -0700263 int channel = signal.spacingMultiplier();
fahadnaeemkhan71827242017-09-21 15:10:07 -0700264 log.debug("channel is {} for port {} on device {}", channel, outPort.name(), deviceId);
265 String request = genChannelChangeRequest(channel);
fahadnaeemkhana62bd2f2017-10-11 12:39:55 -0700266 boolean response = putNoReply(uri, request);
267 if (!response) {
268 log.error("unable to change channel to {} for port {} on device {}",
269 channel, outPort.name(), deviceId);
270 }
271 return response;
fahadnaeemkhan71827242017-09-21 15:10:07 -0700272 }
273
274 private final long toFrequency(OchSignal signal) {
275 double frequency = BASE_FREQUENCY.asGHz() +
276 (signal.channelSpacing().frequency().asGHz() * (double) signal.slotGranularity());
277 return Double.valueOf(frequency).longValue();
278 }
279
fahadnaeemkhana62bd2f2017-10-11 12:39:55 -0700280 private final int getChannel(PortNumber port) {
281 try {
282 String uri = genUri(CHANNEL_URI, port);
283 JsonNode response = get(uri);
284 return response.get(LINE_SYSTEM_CHANNEL_NUMBER).asInt();
285 } catch (IOException e) {
286 // this is expected for client side ports as they don't contain channel data
287 log.error("unable to get channel for port {} on device {}:\n{}", port, deviceId, e);
288 return -1;
289 }
290
291 }
292
fahadnaeemkhan4b6731a2017-10-18 11:13:02 -0700293 private AlarmEntityId getAlarmSource(String instance) {
294 AlarmEntityId source;
295 if (instance.contains(PORT)) {
296 source = AlarmEntityId.alarmEntityId(instance.replace("-", ":"));
297 } else if (instance.contains(PTP)) {
298 source = AlarmEntityId.alarmEntityId(instance.replace(PTP + "-", PORT + ":"));
299 } else {
300 source = AlarmEntityId.alarmEntityId(OTHER + ":" + instance);
301 }
302 return source;
303 }
304
305 private long parseAlarmTime(String time) {
306 /*
307 * expecting WaveServer time to be set to UTC.
308 */
309 try {
310 DateTimeFormatter formatter = DateTimeFormatter.ofPattern(DATE_TIME_FORMAT);
311 LocalDateTime localDateTime = LocalDateTime.parse(time, formatter);
312 return localDateTime.atZone(ZoneId.of(UTC)).toInstant().toEpochMilli();
313 } catch (DateTimeParseException e2) {
314 log.error("unable to parse time {}, using system time", time);
315 return System.currentTimeMillis();
316 }
317 }
318
319 private Alarm newAlarmFromJsonNode(JsonNode jsonNode) {
320 try {
321 AlarmId alarmId = AlarmId.alarmId(checkNotNull(jsonNode.get(ALARM_INSTANCE_ID)).asText());
322 String time = checkNotNull(jsonNode.get(ALARM_LOCAL_DATE_TIME)).asText();
323 String instance = checkNotNull(jsonNode.get(INSTANCE).asText()).toLowerCase();
324 String description = checkNotNull(jsonNode.get(DESCRIPTION)).asText() + " - " + instance + " - " + time;
325 AlarmEntityId source = getAlarmSource(instance);
326 Alarm.SeverityLevel severity = Alarm.SeverityLevel.valueOf(checkNotNull(
327 jsonNode.get(SEVERITY)).asText().toUpperCase());
328
329 long timeRaised = parseAlarmTime(time);
330 boolean isAcknowledged = checkNotNull(jsonNode.get(ACKNOWLEDGE)).asBoolean();
331
332 return new DefaultAlarm.Builder(alarmId, deviceId, description, severity, timeRaised)
333 .withAcknowledged(isAcknowledged)
334 .forSource(source)
335 .build();
336
337 } catch (NullPointerException e) {
338 log.error("got exception while parsing alarm json node {} for device {}:\n", jsonNode, deviceId, e);
339 return null;
340 }
341
342 }
343
344 private List<Alarm> getActiveAlarms() {
345 log.debug("getting active alarms for device {}", deviceId);
346 try {
347 List<JsonNode> alarms = Lists.newArrayList(get(ACTIVE_ALARMS_URL).get(ACTIVE).elements());
348 return alarms.stream()
349 .map(a -> newAlarmFromJsonNode(a))
350 .filter(a -> a != null)
351 .collect(Collectors.toList());
352 } catch (IOException e) {
353 log.error("unable to get active alarms for device {}:\n", deviceId, e);
354 return null;
355 }
356 }
357
fahadnaeemkhana62bd2f2017-10-11 12:39:55 -0700358 public Collection<FlowEntry> getFlowEntries() {
359 List<Port> ports = deviceService.getPorts(deviceId);
360 //driver only handles lineSide ports
361 //TODO: handle client ports as well
362 return ports.stream()
363 .filter(p -> LINESIDE_PORT_ID.contains(p.number().name()))
364 .map(p -> fetchRule(p.number()))
365 .filter(p -> p != null)
366 .map(fr -> new DefaultFlowEntry(fr, FlowEntry.FlowEntryState.ADDED, 0, 0, 0))
367 .collect(Collectors.toList());
368 }
369
370 private FlowRule fetchRule(PortNumber port) {
371 int channel = getChannel(port);
372 if (channel == -1) {
373 return null;
374 }
375
376 /*
fahadnaeemkhan4b6731a2017-10-18 11:13:02 -0700377 * both inPort and outPort will be same as WaveServer only deal with same port ptp-indexes
fahadnaeemkhana62bd2f2017-10-11 12:39:55 -0700378 * channel and spaceMultiplier are same.
379 * TODO: find a way to get both inPort and outPort for future when inPort may not be equal to outPort
380 */
381
382 TrafficSelector selector = DefaultTrafficSelector.builder()
383 .matchInPort(port)
384 .add(Criteria.matchOchSignalType(OchSignalType.FIXED_GRID))
385 .add(Criteria.matchLambda(OchSignal.newDwdmSlot(ChannelSpacing.CHL_50GHZ, channel)))
386 .build();
387 TrafficTreatment treatment = DefaultTrafficTreatment.builder()
388 .setOutput(port)
389 .build();
390
391 int hash = Objects.hash(deviceId, selector, treatment);
392 Pair<FlowId, Integer> lookup = crossConnectCache.get(hash);
393 if (lookup == null) {
394 return null;
395 }
396
397 FlowRule fr = DefaultFlowRule.builder()
398 .forDevice(deviceId)
399 .makePermanent()
400 .withSelector(selector)
401 .withTreatment(treatment)
402 .withPriority(lookup.getRight())
403 .withCookie(lookup.getLeft().value())
404 .build();
405
406 return fr;
407 }
408
fahadnaeemkhan4b6731a2017-10-18 11:13:02 -0700409 public List<Alarm> getAlarms() {
410 log.debug("getting alarms for device {}", deviceId);
411 return getActiveAlarms();
412 }
413
fahadnaeemkhana62bd2f2017-10-11 12:39:55 -0700414 private JsonNode get(String uri) throws IOException {
415 InputStream response = controller.get(deviceId, uri, MediaType.valueOf(MediaType.APPLICATION_JSON));
416 ObjectMapper om = new ObjectMapper();
417 final ObjectReader reader = om.reader();
418 // all waveserver responses contain data node, which contains the requested data
419 return reader.readTree(response).get(DATA);
420 }
421
fahadnaeemkhan71827242017-09-21 15:10:07 -0700422 private int put(String uri, String request) {
423 InputStream payload = new ByteArrayInputStream(request.getBytes(StandardCharsets.UTF_8));
424 int response = controller.put(deviceId, uri, payload, MediaType.valueOf(MediaType.APPLICATION_JSON));
425 log.debug("response: {}", response);
426 return response;
427 }
428
429 private boolean putNoReply(String uri, String request) {
430 return put(uri, request) == Response.Status.NO_CONTENT.getStatusCode();
431 }
432
433}