blob: 17fce90a61e1937a2ea51d2e961c26c0c352e0ea [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
18import org.onlab.util.Frequency;
fahadnaeemkhana62bd2f2017-10-11 12:39:55 -070019import org.onosproject.driver.optical.flowrule.CrossConnectCache;
fahadnaeemkhan4b6731a2017-10-18 11:13:02 -070020import org.onosproject.incubator.net.faultmanagement.alarm.Alarm;
21import org.onosproject.incubator.net.faultmanagement.alarm.AlarmEntityId;
22import org.onosproject.incubator.net.faultmanagement.alarm.AlarmId;
23import org.onosproject.incubator.net.faultmanagement.alarm.DefaultAlarm;
fahadnaeemkhana62bd2f2017-10-11 12:39:55 -070024import org.onosproject.net.ChannelSpacing;
fahadnaeemkhan71827242017-09-21 15:10:07 -070025import org.onosproject.net.DeviceId;
fahadnaeemkhana62bd2f2017-10-11 12:39:55 -070026import org.onosproject.net.Port;
fahadnaeemkhan71827242017-09-21 15:10:07 -070027import org.onosproject.net.PortNumber;
fahadnaeemkhana62bd2f2017-10-11 12:39:55 -070028import org.onosproject.net.OchSignal;
29import org.onosproject.net.OchSignalType;
30import org.onosproject.net.device.DeviceService;
fahadnaeemkhan71827242017-09-21 15:10:07 -070031import org.onosproject.net.driver.DriverHandler;
fahadnaeemkhana62bd2f2017-10-11 12:39:55 -070032import org.onosproject.net.flow.DefaultFlowEntry;
33import org.onosproject.net.flow.DefaultFlowRule;
34import org.onosproject.net.flow.DefaultTrafficSelector;
35import org.onosproject.net.flow.DefaultTrafficTreatment;
36import org.onosproject.net.flow.FlowEntry;
37import org.onosproject.net.flow.FlowId;
38import org.onosproject.net.flow.FlowRule;
39import org.onosproject.net.flow.TrafficSelector;
40import org.onosproject.net.flow.TrafficTreatment;
41import org.onosproject.net.flow.criteria.Criteria;
fahadnaeemkhan71827242017-09-21 15:10:07 -070042import org.onosproject.protocol.rest.RestSBController;
43import org.slf4j.Logger;
44
fahadnaeemkhan4b6731a2017-10-18 11:13:02 -070045import java.time.LocalDateTime;
46import java.time.ZoneId;
47import java.time.format.DateTimeFormatter;
48import java.time.format.DateTimeParseException;
fahadnaeemkhana62bd2f2017-10-11 12:39:55 -070049import java.util.Objects;
50import java.util.List;
51import java.util.Collection;
52import java.util.stream.Collectors;
53import java.io.IOException;
fahadnaeemkhan71827242017-09-21 15:10:07 -070054import java.io.ByteArrayInputStream;
55import java.io.InputStream;
56import java.nio.charset.StandardCharsets;
57
fahadnaeemkhana62bd2f2017-10-11 12:39:55 -070058import javax.ws.rs.core.MediaType;
59import javax.ws.rs.core.Response;
60
61import com.google.common.collect.ImmutableList;
fahadnaeemkhan4b6731a2017-10-18 11:13:02 -070062import com.google.common.collect.Lists;
63
fahadnaeemkhana62bd2f2017-10-11 12:39:55 -070064import org.apache.commons.lang3.tuple.Pair;
65
66import com.fasterxml.jackson.databind.ObjectMapper;
67import com.fasterxml.jackson.databind.JsonNode;
68import com.fasterxml.jackson.databind.ObjectReader;
69
fahadnaeemkhan71827242017-09-21 15:10:07 -070070import static com.google.common.base.Preconditions.checkNotNull;
71import static org.slf4j.LoggerFactory.getLogger;
72
73public class CienaRestDevice {
fahadnaeemkhan4b6731a2017-10-18 11:13:02 -070074 private static final Frequency BASE_FREQUENCY = Frequency.ofGHz(193_950);
fahadnaeemkhan71827242017-09-21 15:10:07 -070075 private static final String ENABLED = "enabled";
76 private static final String DISABLED = "disabled";
fahadnaeemkhana62bd2f2017-10-11 12:39:55 -070077 private static final String VALUE = "value";
78 private static final String STATE = "state";
79 private static final String ADMIN_STATE = "admin-state";
80 private static final String WAVELENGTH = "wavelength";
81 private static final String DATA = "data";
fahadnaeemkhan4b6731a2017-10-18 11:13:02 -070082 private static final String ACTIVE = "active";
83 private static final String ACKNOWLEDGE = "acknowledged";
84 private static final String SEVERITY = "severity";
85 private static final String DESCRIPTION = "description";
86 private static final String INSTANCE = "instance";
87 private static final String PORT = "port";
88 private static final String PTP = "ptp";
89 private static final String UTC = "UTC";
90 private static final String OTHER = "other";
91 private static final String DATE_TIME_FORMAT = "EEE MMM [ ]d HH:mm:ss yyyy";
92 //keys
93 private static final String ALARM_KEY = "ws-alarms";
94 private static final String ALARM_INSTANCE_ID = "alarm-instance-id";
95 private static final String ALARM_TABLE_ID = "alarm-table-id";
96 private static final String ALARM_LOCAL_DATE_TIME = "local-date-time";
fahadnaeemkhana62bd2f2017-10-11 12:39:55 -070097 private static final String LINE_SYSTEM_CHANNEL_NUMBER = "ciena-ws-ptp-modem:line-system-channel-number";
98 private static final String FREQUENCY_KEY = "ciena-ws-ptp-modem:frequency";
fahadnaeemkhan71827242017-09-21 15:10:07 -070099 //URIs
100 private static final String PORT_URI = "ws-ptps/ptps/%s";
101 private static final String TRANSMITTER_URI = PORT_URI + "/properties/transmitter";
fahadnaeemkhana62bd2f2017-10-11 12:39:55 -0700102 private static final String PORT_STATE_URI = PORT_URI + "/" + STATE;
103 private static final String WAVELENGTH_URI = TRANSMITTER_URI + "/" + WAVELENGTH;
104 private static final String FREQUENCY_URI = TRANSMITTER_URI + "/" + FREQUENCY_KEY;
105 private static final String CHANNEL_URI = TRANSMITTER_URI + "/" + LINE_SYSTEM_CHANNEL_NUMBER;
fahadnaeemkhan4b6731a2017-10-18 11:13:02 -0700106 private static final String ACTIVE_ALARMS_URL = ALARM_KEY + "/" + ACTIVE;
fahadnaeemkhana62bd2f2017-10-11 12:39:55 -0700107 private static final List<String> LINESIDE_PORT_ID = ImmutableList.of("4", "48");
fahadnaeemkhan71827242017-09-21 15:10:07 -0700108
fahadnaeemkhan4b6731a2017-10-18 11:13:02 -0700109 private final Logger log = getLogger(getClass());
110
111 private DeviceId deviceId;
112 private RestSBController controller;
113 private CrossConnectCache crossConnectCache;
114 private DeviceService deviceService;
115
116
fahadnaeemkhan71827242017-09-21 15:10:07 -0700117 public CienaRestDevice(DriverHandler handler) throws NullPointerException {
118 deviceId = handler.data().deviceId();
119 controller = checkNotNull(handler.get(RestSBController.class));
fahadnaeemkhana62bd2f2017-10-11 12:39:55 -0700120 crossConnectCache = checkNotNull(handler.get(CrossConnectCache.class));
121 deviceService = checkNotNull(handler.get(DeviceService.class));
122 }
123
124 /**
125 * return the Line side ports.
126 *
127 * @return List of Line side ports.
128 */
129 public static List<String> getLinesidePortId() {
130 return LINESIDE_PORT_ID;
131 }
132
133 /**
134 * add the given flow rules to cross connect-cache.
135 *
136 * @param flowRules flow rules that needs to be cached.
137 */
138 public void setCrossConnectCache(Collection<FlowRule> flowRules) {
139 flowRules.forEach(xc -> crossConnectCache.set(
140 Objects.hash(deviceId, xc.selector(), xc.treatment()),
141 xc.id(),
142 xc.priority()));
143 }
144
145 /**
146 * remove the given flow rules form the cross-connect cache.
147 *
148 * @param flowRules flow rules that needs to be removed from cache.
149 */
150 public void removeCrossConnectCache(Collection<FlowRule> flowRules) {
151 flowRules.forEach(xc -> crossConnectCache.remove(Objects.hash(deviceId, xc.selector(), xc.treatment())));
fahadnaeemkhan71827242017-09-21 15:10:07 -0700152 }
153
154 private final String genPortStateRequest(String state) {
155 String request = "{\n" +
fahadnaeemkhana62bd2f2017-10-11 12:39:55 -0700156 "\"" + STATE + "\": {\n" +
157 "\"" + ADMIN_STATE + "\": \"" + state + "\"\n}\n}";
fahadnaeemkhan71827242017-09-21 15:10:07 -0700158 log.debug("generated request: \n{}", request);
159 return request;
160 }
161
162 private String genWavelengthChangeRequest(String wavelength) {
163 String request = "{\n" +
fahadnaeemkhana62bd2f2017-10-11 12:39:55 -0700164 "\"" + WAVELENGTH + "\": {\n" +
165 "\"" + VALUE + "\": " + wavelength + "\n" +
fahadnaeemkhan71827242017-09-21 15:10:07 -0700166 "}\n" +
167 "}";
168 log.debug("request:\n{}", request);
169 return request;
170
171 }
172
fahadnaeemkhanffc917f2017-10-03 14:04:46 -0700173 private String genFrequencyChangeRequest(long frequency) {
fahadnaeemkhan71827242017-09-21 15:10:07 -0700174 String request = "{\n" +
fahadnaeemkhana62bd2f2017-10-11 12:39:55 -0700175 "\"" + FREQUENCY_KEY + "\": {\n" +
176 "\"" + VALUE + "\": " + Long.toString(frequency) + "\n" +
fahadnaeemkhan71827242017-09-21 15:10:07 -0700177 "}\n" +
178 "}";
179 log.debug("request:\n{}", request);
180 return request;
181
182 }
183
184 private String genChannelChangeRequest(int channel) {
185 String request = "{\n" +
fahadnaeemkhana62bd2f2017-10-11 12:39:55 -0700186 "\"" + LINE_SYSTEM_CHANNEL_NUMBER + "\": " +
fahadnaeemkhan71827242017-09-21 15:10:07 -0700187 Integer.toString(channel) + "\n}";
188 log.debug("request:\n{}", request);
189 return request;
190
191 }
192
193
194 private final String genUri(String uriFormat, PortNumber port) {
195 return String.format(uriFormat, port.name());
196 }
197
198 private boolean changePortState(PortNumber number, String state) {
fahadnaeemkhana62bd2f2017-10-11 12:39:55 -0700199 log.debug("changing the port {} on device {} state to {}", number, deviceId, state);
fahadnaeemkhan71827242017-09-21 15:10:07 -0700200 String uri = genUri(PORT_STATE_URI, number);
201 String request = genPortStateRequest(state);
fahadnaeemkhana62bd2f2017-10-11 12:39:55 -0700202 boolean response = putNoReply(uri, request);
203 if (!response) {
204 log.error("unable to change port {} on device {} state to {}", number, deviceId, state);
205 }
206 return response;
fahadnaeemkhan71827242017-09-21 15:10:07 -0700207 }
208
209 public boolean disablePort(PortNumber number) {
210 return changePortState(number, DISABLED);
211 }
212
213 public boolean enablePort(PortNumber number) {
214 return changePortState(number, ENABLED);
215 }
216
217 public final boolean changeFrequency(OchSignal signal, PortNumber outPort) {
218 String uri = genUri(FREQUENCY_URI, outPort);
219 long frequency = toFrequency(signal);
220 String request = genFrequencyChangeRequest(frequency);
fahadnaeemkhana62bd2f2017-10-11 12:39:55 -0700221 boolean response = putNoReply(uri, request);
222 if (!response) {
223 log.error("unable to change frequency of port {} on device {}", outPort, deviceId);
224 }
225 return response;
fahadnaeemkhan71827242017-09-21 15:10:07 -0700226 }
227
228 public final boolean changeChannel(OchSignal signal, PortNumber outPort) {
229 String uri = genUri(CHANNEL_URI, outPort);
fahadnaeemkhanffc917f2017-10-03 14:04:46 -0700230 int channel = signal.spacingMultiplier();
fahadnaeemkhan71827242017-09-21 15:10:07 -0700231 log.debug("channel is {} for port {} on device {}", channel, outPort.name(), deviceId);
232 String request = genChannelChangeRequest(channel);
fahadnaeemkhana62bd2f2017-10-11 12:39:55 -0700233 boolean response = putNoReply(uri, request);
234 if (!response) {
235 log.error("unable to change channel to {} for port {} on device {}",
236 channel, outPort.name(), deviceId);
237 }
238 return response;
fahadnaeemkhan71827242017-09-21 15:10:07 -0700239 }
240
241 private final long toFrequency(OchSignal signal) {
242 double frequency = BASE_FREQUENCY.asGHz() +
243 (signal.channelSpacing().frequency().asGHz() * (double) signal.slotGranularity());
244 return Double.valueOf(frequency).longValue();
245 }
246
fahadnaeemkhana62bd2f2017-10-11 12:39:55 -0700247 private final int getChannel(PortNumber port) {
248 try {
249 String uri = genUri(CHANNEL_URI, port);
250 JsonNode response = get(uri);
251 return response.get(LINE_SYSTEM_CHANNEL_NUMBER).asInt();
252 } catch (IOException e) {
253 // this is expected for client side ports as they don't contain channel data
254 log.error("unable to get channel for port {} on device {}:\n{}", port, deviceId, e);
255 return -1;
256 }
257
258 }
259
fahadnaeemkhan4b6731a2017-10-18 11:13:02 -0700260 private AlarmEntityId getAlarmSource(String instance) {
261 AlarmEntityId source;
262 if (instance.contains(PORT)) {
263 source = AlarmEntityId.alarmEntityId(instance.replace("-", ":"));
264 } else if (instance.contains(PTP)) {
265 source = AlarmEntityId.alarmEntityId(instance.replace(PTP + "-", PORT + ":"));
266 } else {
267 source = AlarmEntityId.alarmEntityId(OTHER + ":" + instance);
268 }
269 return source;
270 }
271
272 private long parseAlarmTime(String time) {
273 /*
274 * expecting WaveServer time to be set to UTC.
275 */
276 try {
277 DateTimeFormatter formatter = DateTimeFormatter.ofPattern(DATE_TIME_FORMAT);
278 LocalDateTime localDateTime = LocalDateTime.parse(time, formatter);
279 return localDateTime.atZone(ZoneId.of(UTC)).toInstant().toEpochMilli();
280 } catch (DateTimeParseException e2) {
281 log.error("unable to parse time {}, using system time", time);
282 return System.currentTimeMillis();
283 }
284 }
285
286 private Alarm newAlarmFromJsonNode(JsonNode jsonNode) {
287 try {
288 AlarmId alarmId = AlarmId.alarmId(checkNotNull(jsonNode.get(ALARM_INSTANCE_ID)).asText());
289 String time = checkNotNull(jsonNode.get(ALARM_LOCAL_DATE_TIME)).asText();
290 String instance = checkNotNull(jsonNode.get(INSTANCE).asText()).toLowerCase();
291 String description = checkNotNull(jsonNode.get(DESCRIPTION)).asText() + " - " + instance + " - " + time;
292 AlarmEntityId source = getAlarmSource(instance);
293 Alarm.SeverityLevel severity = Alarm.SeverityLevel.valueOf(checkNotNull(
294 jsonNode.get(SEVERITY)).asText().toUpperCase());
295
296 long timeRaised = parseAlarmTime(time);
297 boolean isAcknowledged = checkNotNull(jsonNode.get(ACKNOWLEDGE)).asBoolean();
298
299 return new DefaultAlarm.Builder(alarmId, deviceId, description, severity, timeRaised)
300 .withAcknowledged(isAcknowledged)
301 .forSource(source)
302 .build();
303
304 } catch (NullPointerException e) {
305 log.error("got exception while parsing alarm json node {} for device {}:\n", jsonNode, deviceId, e);
306 return null;
307 }
308
309 }
310
311 private List<Alarm> getActiveAlarms() {
312 log.debug("getting active alarms for device {}", deviceId);
313 try {
314 List<JsonNode> alarms = Lists.newArrayList(get(ACTIVE_ALARMS_URL).get(ACTIVE).elements());
315 return alarms.stream()
316 .map(a -> newAlarmFromJsonNode(a))
317 .filter(a -> a != null)
318 .collect(Collectors.toList());
319 } catch (IOException e) {
320 log.error("unable to get active alarms for device {}:\n", deviceId, e);
321 return null;
322 }
323 }
324
fahadnaeemkhana62bd2f2017-10-11 12:39:55 -0700325 public Collection<FlowEntry> getFlowEntries() {
326 List<Port> ports = deviceService.getPorts(deviceId);
327 //driver only handles lineSide ports
328 //TODO: handle client ports as well
329 return ports.stream()
330 .filter(p -> LINESIDE_PORT_ID.contains(p.number().name()))
331 .map(p -> fetchRule(p.number()))
332 .filter(p -> p != null)
333 .map(fr -> new DefaultFlowEntry(fr, FlowEntry.FlowEntryState.ADDED, 0, 0, 0))
334 .collect(Collectors.toList());
335 }
336
337 private FlowRule fetchRule(PortNumber port) {
338 int channel = getChannel(port);
339 if (channel == -1) {
340 return null;
341 }
342
343 /*
fahadnaeemkhan4b6731a2017-10-18 11:13:02 -0700344 * both inPort and outPort will be same as WaveServer only deal with same port ptp-indexes
fahadnaeemkhana62bd2f2017-10-11 12:39:55 -0700345 * channel and spaceMultiplier are same.
346 * TODO: find a way to get both inPort and outPort for future when inPort may not be equal to outPort
347 */
348
349 TrafficSelector selector = DefaultTrafficSelector.builder()
350 .matchInPort(port)
351 .add(Criteria.matchOchSignalType(OchSignalType.FIXED_GRID))
352 .add(Criteria.matchLambda(OchSignal.newDwdmSlot(ChannelSpacing.CHL_50GHZ, channel)))
353 .build();
354 TrafficTreatment treatment = DefaultTrafficTreatment.builder()
355 .setOutput(port)
356 .build();
357
358 int hash = Objects.hash(deviceId, selector, treatment);
359 Pair<FlowId, Integer> lookup = crossConnectCache.get(hash);
360 if (lookup == null) {
361 return null;
362 }
363
364 FlowRule fr = DefaultFlowRule.builder()
365 .forDevice(deviceId)
366 .makePermanent()
367 .withSelector(selector)
368 .withTreatment(treatment)
369 .withPriority(lookup.getRight())
370 .withCookie(lookup.getLeft().value())
371 .build();
372
373 return fr;
374 }
375
fahadnaeemkhan4b6731a2017-10-18 11:13:02 -0700376 public List<Alarm> getAlarms() {
377 log.debug("getting alarms for device {}", deviceId);
378 return getActiveAlarms();
379 }
380
fahadnaeemkhana62bd2f2017-10-11 12:39:55 -0700381 private JsonNode get(String uri) throws IOException {
382 InputStream response = controller.get(deviceId, uri, MediaType.valueOf(MediaType.APPLICATION_JSON));
383 ObjectMapper om = new ObjectMapper();
384 final ObjectReader reader = om.reader();
385 // all waveserver responses contain data node, which contains the requested data
386 return reader.readTree(response).get(DATA);
387 }
388
fahadnaeemkhan71827242017-09-21 15:10:07 -0700389 private int put(String uri, String request) {
390 InputStream payload = new ByteArrayInputStream(request.getBytes(StandardCharsets.UTF_8));
391 int response = controller.put(deviceId, uri, payload, MediaType.valueOf(MediaType.APPLICATION_JSON));
392 log.debug("response: {}", response);
393 return response;
394 }
395
396 private boolean putNoReply(String uri, String request) {
397 return put(uri, request) == Response.Status.NO_CONTENT.getStatusCode();
398 }
399
400}