blob: 840fc99370449e4867f1c715a184e9ad02dc3100 [file] [log] [blame]
Marc De Leenheerc662d322016-02-18 16:05:10 -08001/*
Brian O'Connor5ab426f2016-04-09 01:19:45 -07002 * Copyright 2016-present Open Networking Laboratory
Marc De Leenheerc662d322016-02-18 16:05:10 -08003 *
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.lumentum;
17
18import com.google.common.collect.Lists;
19import org.apache.commons.lang3.tuple.Pair;
Marc De Leenheer40a544b2017-04-25 14:16:02 -070020import org.onosproject.driver.optical.flowrule.CrossConnectCache;
21import org.onosproject.driver.optical.flowrule.CrossConnectFlowRule;
Marc De Leenheerc662d322016-02-18 16:05:10 -080022import org.onosproject.net.ChannelSpacing;
23import org.onosproject.net.GridType;
24import org.onosproject.net.OchSignal;
25import org.onosproject.net.OchSignalType;
26import org.onosproject.net.Port;
27import org.onosproject.net.PortNumber;
28import org.onosproject.net.device.DeviceService;
29import org.onosproject.net.driver.AbstractHandlerBehaviour;
30import org.onosproject.net.flow.DefaultFlowEntry;
31import org.onosproject.net.flow.DefaultFlowRule;
32import org.onosproject.net.flow.DefaultTrafficSelector;
33import org.onosproject.net.flow.DefaultTrafficTreatment;
34import org.onosproject.net.flow.FlowEntry;
35import org.onosproject.net.flow.FlowId;
36import org.onosproject.net.flow.FlowRule;
37import org.onosproject.net.flow.FlowRuleProgrammable;
38import org.onosproject.net.flow.TrafficSelector;
39import org.onosproject.net.flow.TrafficTreatment;
40import org.onosproject.net.flow.criteria.Criteria;
41import org.slf4j.Logger;
42import org.slf4j.LoggerFactory;
43import org.snmp4j.PDU;
44import org.snmp4j.event.ResponseEvent;
45import org.snmp4j.smi.Integer32;
46import org.snmp4j.smi.OID;
47import org.snmp4j.smi.UnsignedInteger32;
48import org.snmp4j.smi.VariableBinding;
49import org.snmp4j.util.TreeEvent;
50
51import java.io.IOException;
52import java.util.Arrays;
53import java.util.Collection;
54import java.util.Collections;
55import java.util.LinkedList;
56import java.util.List;
57import java.util.Objects;
58import java.util.stream.Collectors;
59
60import static com.google.common.base.Preconditions.checkArgument;
61
62// TODO: need to convert between OChSignal and XC channel number
Marc De Leenheer57a5af02016-12-02 20:54:41 -080063public class LumentumSdnRoadmFlowRuleProgrammable extends AbstractHandlerBehaviour implements FlowRuleProgrammable {
Marc De Leenheerc662d322016-02-18 16:05:10 -080064
65 private static final Logger log =
Marc De Leenheer57a5af02016-12-02 20:54:41 -080066 LoggerFactory.getLogger(LumentumSdnRoadmFlowRuleProgrammable.class);
Marc De Leenheer2898a7f2016-03-02 21:42:59 -080067
68 // Default values
69 private static final int DEFAULT_TARGET_GAIN_PREAMP = 150;
70 private static final int DEFAULT_TARGET_GAIN_BOOSTER = 200;
71 private static final int DISABLE_CHANNEL_TARGET_POWER = -650;
72 private static final int DEFAULT_CHANNEL_TARGET_POWER = -30;
73 private static final int DISABLE_CHANNEL_ABSOLUTE_ATTENUATION = 160;
74 private static final int DEFAULT_CHANNEL_ABSOLUTE_ATTENUATION = 50;
Marc De Leenheereb80f4f2016-03-05 17:04:59 -080075 private static final int DISABLE_CHANNEL_ADD_DROP_PORT_INDEX = 1;
Marc De Leenheerc662d322016-02-18 16:05:10 -080076 private static final int OUT_OF_SERVICE = 1;
77 private static final int IN_SERVICE = 2;
Marc De Leenheer2898a7f2016-03-02 21:42:59 -080078 private static final int OPEN_LOOP = 1;
79 private static final int CLOSED_LOOP = 2;
Marc De Leenheereb80f4f2016-03-05 17:04:59 -080080 // First 20 ports are add/mux ports, next 20 are drop/demux
81 private static final int DROP_PORT_OFFSET = 20;
Marc De Leenheer2898a7f2016-03-02 21:42:59 -080082
83 // OIDs
Marc De Leenheerc662d322016-02-18 16:05:10 -080084 private static final String CTRL_AMP_MODULE_SERVICE_STATE_PREAMP = ".1.3.6.1.4.1.46184.1.4.4.1.2.1";
85 private static final String CTRL_AMP_MODULE_SERVICE_STATE_BOOSTER = ".1.3.6.1.4.1.46184.1.4.4.1.2.2";
Marc De Leenheer2898a7f2016-03-02 21:42:59 -080086 private static final String CTRL_AMP_MODULE_TARGET_GAIN_PREAMP = ".1.3.6.1.4.1.46184.1.4.4.1.8.1";
87 private static final String CTRL_AMP_MODULE_TARGET_GAIN_BOOSTER = ".1.3.6.1.4.1.46184.1.4.4.1.8.2";
Marc De Leenheerc662d322016-02-18 16:05:10 -080088 private static final String CTRL_CHANNEL_STATE = ".1.3.6.1.4.1.46184.1.4.2.1.3.";
Marc De Leenheer2898a7f2016-03-02 21:42:59 -080089 private static final String CTRL_CHANNEL_MODE = ".1.3.6.1.4.1.46184.1.4.2.1.4.";
90 private static final String CTRL_CHANNEL_TARGET_POWER = ".1.3.6.1.4.1.46184.1.4.2.1.6.";
Marc De Leenheerc662d322016-02-18 16:05:10 -080091 private static final String CTRL_CHANNEL_ADD_DROP_PORT_INDEX = ".1.3.6.1.4.1.46184.1.4.2.1.13.";
92 private static final String CTRL_CHANNEL_ABSOLUTE_ATTENUATION = ".1.3.6.1.4.1.46184.1.4.2.1.5.";
93
94 private LumentumSnmpDevice snmp;
95
96 @Override
97 public Collection<FlowEntry> getFlowEntries() {
98 try {
99 snmp = new LumentumSnmpDevice(handler().data().deviceId());
100 } catch (IOException e) {
101 log.error("Failed to connect to device: ", e);
102 return Collections.emptyList();
103 }
104
105 // Line in is last but one port, line out is last
106 DeviceService deviceService = this.handler().get(DeviceService.class);
107 List<Port> ports = deviceService.getPorts(data().deviceId());
108 if (ports.size() < 2) {
109 return Collections.emptyList();
110 }
111 PortNumber lineIn = ports.get(ports.size() - 2).number();
112 PortNumber lineOut = ports.get(ports.size() - 1).number();
113
114 Collection<FlowEntry> entries = Lists.newLinkedList();
115
116 // Add rules
117 OID addOid = new OID(CTRL_CHANNEL_STATE + "1");
118 entries.addAll(
119 fetchRules(addOid, true, lineOut).stream()
120 .map(fr -> new DefaultFlowEntry(fr, FlowEntry.FlowEntryState.ADDED, 0, 0, 0))
121 .collect(Collectors.toList())
122 );
123
124 // Drop rules
125 OID dropOid = new OID(CTRL_CHANNEL_STATE + "2");
126 entries.addAll(
127 fetchRules(dropOid, false, lineIn).stream()
128 .map(fr -> new DefaultFlowEntry(fr, FlowEntry.FlowEntryState.ADDED, 0, 0, 0))
129 .collect(Collectors.toList())
130 );
131
132 return entries;
133 }
134
135 @Override
136 public Collection<FlowRule> applyFlowRules(Collection<FlowRule> rules) {
137 try {
138 snmp = new LumentumSnmpDevice(data().deviceId());
139 } catch (IOException e) {
140 log.error("Failed to connect to device: ", e);
141 }
142
143 // Line ports
144 DeviceService deviceService = this.handler().get(DeviceService.class);
145 List<Port> ports = deviceService.getPorts(data().deviceId());
146 List<PortNumber> linePorts = ports.subList(ports.size() - 2, ports.size()).stream()
147 .map(p -> p.number())
148 .collect(Collectors.toList());
149
150 // Apply the valid rules on the device
151 Collection<FlowRule> added = rules.stream()
152 .map(r -> new CrossConnectFlowRule(r, linePorts))
153 .filter(xc -> installCrossConnect(xc))
154 .collect(Collectors.toList());
155
156 // Cache the cookie/priority
157 CrossConnectCache cache = this.handler().get(CrossConnectCache.class);
Sho SHIMIZUa09e1bb2016-08-01 14:25:25 -0700158 added.forEach(xc -> cache.set(
159 Objects.hash(data().deviceId(), xc.selector(), xc.treatment()),
160 xc.id(),
161 xc.priority()));
Marc De Leenheerc662d322016-02-18 16:05:10 -0800162
163 return added;
164 }
165
166 @Override
167 public Collection<FlowRule> removeFlowRules(Collection<FlowRule> rules) {
168 try {
169 snmp = new LumentumSnmpDevice(data().deviceId());
170 } catch (IOException e) {
171 log.error("Failed to connect to device: ", e);
172 }
173
174 // Line ports
175 DeviceService deviceService = this.handler().get(DeviceService.class);
176 List<Port> ports = deviceService.getPorts(data().deviceId());
177 List<PortNumber> linePorts = ports.subList(ports.size() - 2, ports.size()).stream()
178 .map(p -> p.number())
179 .collect(Collectors.toList());
180
181 // Apply the valid rules on the device
182 Collection<FlowRule> removed = rules.stream()
183 .map(r -> new CrossConnectFlowRule(r, linePorts))
184 .filter(xc -> removeCrossConnect(xc))
185 .collect(Collectors.toList());
186
187 // Remove flow rule from cache
188 CrossConnectCache cache = this.handler().get(CrossConnectCache.class);
Sho SHIMIZUa09e1bb2016-08-01 14:25:25 -0700189 removed.forEach(xc -> cache.remove(
190 Objects.hash(data().deviceId(), xc.selector(), xc.treatment())));
Marc De Leenheerc662d322016-02-18 16:05:10 -0800191
192 return removed;
193 }
194
195 // Installs cross connect on device
196 private boolean installCrossConnect(CrossConnectFlowRule xc) {
197
198 int channel = toChannel(xc.ochSignal());
199 long addDrop = xc.addDrop().toLong();
Marc De Leenheereb80f4f2016-03-05 17:04:59 -0800200 if (!xc.isAddRule()) {
201 addDrop -= DROP_PORT_OFFSET;
202 }
Marc De Leenheerc662d322016-02-18 16:05:10 -0800203
204 // Create the PDU object
205 PDU pdu = new PDU();
206 pdu.setType(PDU.SET);
207
Marc De Leenheer2898a7f2016-03-02 21:42:59 -0800208 // Enable preamp & booster
Marc De Leenheerc662d322016-02-18 16:05:10 -0800209 List<OID> oids = Arrays.asList(new OID(CTRL_AMP_MODULE_SERVICE_STATE_PREAMP),
210 new OID(CTRL_AMP_MODULE_SERVICE_STATE_BOOSTER));
211 oids.forEach(
212 oid -> pdu.add(new VariableBinding(oid, new Integer32(IN_SERVICE)))
213 );
214
Marc De Leenheer2898a7f2016-03-02 21:42:59 -0800215 // Set target gain on preamp & booster
216 OID ctrlAmpModuleTargetGainPreamp = new OID(CTRL_AMP_MODULE_TARGET_GAIN_PREAMP);
217 pdu.add(new VariableBinding(ctrlAmpModuleTargetGainPreamp, new Integer32(DEFAULT_TARGET_GAIN_PREAMP)));
218 OID ctrlAmpModuleTargetGainBooster = new OID(CTRL_AMP_MODULE_TARGET_GAIN_BOOSTER);
219 pdu.add(new VariableBinding(ctrlAmpModuleTargetGainBooster, new Integer32(DEFAULT_TARGET_GAIN_BOOSTER)));
220
Marc De Leenheerc662d322016-02-18 16:05:10 -0800221 // Make cross connect
222 OID ctrlChannelAddDropPortIndex = new OID(CTRL_CHANNEL_ADD_DROP_PORT_INDEX +
223 (xc.isAddRule() ? "1." : "2.") + channel);
224 pdu.add(new VariableBinding(ctrlChannelAddDropPortIndex, new UnsignedInteger32(addDrop)));
225
Marc De Leenheer2898a7f2016-03-02 21:42:59 -0800226 // Add rules use closed loop, drop rules open loop
227 // Add rules are set to target power, drop rules are attenuated
228 if (xc.isAddRule()) {
229 OID ctrlChannelMode = new OID(CTRL_CHANNEL_MODE + "1." + channel);
230 pdu.add(new VariableBinding(ctrlChannelMode, new Integer32(CLOSED_LOOP)));
231
232 OID ctrlChannelTargetPower = new OID(CTRL_CHANNEL_TARGET_POWER + "1." + channel);
233 pdu.add(new VariableBinding(ctrlChannelTargetPower, new Integer32(DEFAULT_CHANNEL_TARGET_POWER)));
234 } else {
235 OID ctrlChannelMode = new OID(CTRL_CHANNEL_MODE + "2." + channel);
236 pdu.add(new VariableBinding(ctrlChannelMode, new Integer32(OPEN_LOOP)));
237
238 OID ctrlChannelAbsoluteAttenuation = new OID(CTRL_CHANNEL_ABSOLUTE_ATTENUATION + "2." + channel);
239 pdu.add(new VariableBinding(
240 ctrlChannelAbsoluteAttenuation, new UnsignedInteger32(DEFAULT_CHANNEL_ABSOLUTE_ATTENUATION)));
241 }
Marc De Leenheerc662d322016-02-18 16:05:10 -0800242
Marc De Leenheer268c4f92016-03-03 14:38:02 -0800243 // Final step is to enable the channel
244 OID ctrlChannelState = new OID(CTRL_CHANNEL_STATE + (xc.isAddRule() ? "1." : "2.") + channel);
245 pdu.add(new VariableBinding(ctrlChannelState, new Integer32(IN_SERVICE)));
246
Marc De Leenheerc662d322016-02-18 16:05:10 -0800247 try {
248 ResponseEvent response = snmp.set(pdu);
249
250 // TODO: parse response
251 } catch (IOException e) {
252 log.error("Failed to create cross connect, unable to connect to device: ", e);
253 }
254
255 return true;
256 }
257
258 // Removes cross connect on device
259 private boolean removeCrossConnect(CrossConnectFlowRule xc) {
260
261 int channel = toChannel(xc.ochSignal());
262
263 // Create the PDU object
264 PDU pdu = new PDU();
265 pdu.setType(PDU.SET);
266
267 // Disable the channel
268 OID ctrlChannelState = new OID(CTRL_CHANNEL_STATE + (xc.isAddRule() ? "1." : "2.") + channel);
269 pdu.add(new VariableBinding(ctrlChannelState, new Integer32(OUT_OF_SERVICE)));
270
271 // Put cross connect back into default port 1
272 OID ctrlChannelAddDropPortIndex = new OID(CTRL_CHANNEL_ADD_DROP_PORT_INDEX +
273 (xc.isAddRule() ? "1." : "2.") + channel);
Marc De Leenheereb80f4f2016-03-05 17:04:59 -0800274 pdu.add(new VariableBinding(ctrlChannelAddDropPortIndex,
275 new UnsignedInteger32(DISABLE_CHANNEL_ADD_DROP_PORT_INDEX)));
Marc De Leenheerc662d322016-02-18 16:05:10 -0800276
Marc De Leenheer2898a7f2016-03-02 21:42:59 -0800277 // Put port/channel back to open loop
278 OID ctrlChannelMode = new OID(CTRL_CHANNEL_MODE + (xc.isAddRule() ? "1." : "2.") + channel);
279 pdu.add(new VariableBinding(ctrlChannelMode, new Integer32(OPEN_LOOP)));
280
281 // Add rules are set to target power, drop rules are attenuated
282 if (xc.isAddRule()) {
283 OID ctrlChannelTargetPower = new OID(CTRL_CHANNEL_TARGET_POWER + "1." + channel);
284 pdu.add(new VariableBinding(ctrlChannelTargetPower, new Integer32(DISABLE_CHANNEL_TARGET_POWER)));
285 } else {
286 OID ctrlChannelAbsoluteAttenuation = new OID(CTRL_CHANNEL_ABSOLUTE_ATTENUATION + "2." + channel);
287 pdu.add(new VariableBinding(
288 ctrlChannelAbsoluteAttenuation, new UnsignedInteger32(DISABLE_CHANNEL_ABSOLUTE_ATTENUATION)));
289 }
Marc De Leenheerc662d322016-02-18 16:05:10 -0800290
291 try {
292 ResponseEvent response = snmp.set(pdu);
293
294 // TODO: parse response
295 } catch (IOException e) {
296 log.error("Failed to remove cross connect, unable to connect to device: ", e);
297 return false;
298 }
299
300 return true;
301 }
302
303 /**
304 * Convert OCh signal to Lumentum channel ID.
305 *
306 * @param ochSignal OCh signal
307 * @return Lumentum channel ID
308 */
309 public static int toChannel(OchSignal ochSignal) {
310 // FIXME: move to cross connect validation
311 checkArgument(ochSignal.channelSpacing() == ChannelSpacing.CHL_50GHZ);
312 checkArgument(LumentumSnmpDevice.START_CENTER_FREQ.compareTo(ochSignal.centralFrequency()) <= 0);
313 checkArgument(LumentumSnmpDevice.END_CENTER_FREQ.compareTo(ochSignal.centralFrequency()) >= 0);
314
315 return ochSignal.spacingMultiplier() + LumentumSnmpDevice.MULTIPLIER_SHIFT;
316 }
317
318 /**
319 * Convert Lumentum channel ID to OCh signal.
320 *
321 * @param channel Lumentum channel ID
322 * @return OCh signal
323 */
324 public static OchSignal toOchSignal(int channel) {
325 checkArgument(1 <= channel);
326 checkArgument(channel <= 96);
327
328 return new OchSignal(GridType.DWDM, ChannelSpacing.CHL_50GHZ,
329 channel - LumentumSnmpDevice.MULTIPLIER_SHIFT, 4);
330 }
331
332 // Returns the currently configured add/drop port for the given channel.
333 private PortNumber getAddDropPort(int channel, boolean isAddPort) {
334 OID oid = new OID(CTRL_CHANNEL_ADD_DROP_PORT_INDEX + (isAddPort ? "1" : "2"));
335
336 for (TreeEvent event : snmp.get(oid)) {
337 if (event == null) {
338 return null;
339 }
340
341 VariableBinding[] varBindings = event.getVariableBindings();
342
343 for (VariableBinding varBinding : varBindings) {
344 if (varBinding.getOid().last() == channel) {
345 int port = varBinding.getVariable().toInt();
Marc De Leenheereb80f4f2016-03-05 17:04:59 -0800346 if (!isAddPort) {
347 port += DROP_PORT_OFFSET;
348 }
Marc De Leenheerc662d322016-02-18 16:05:10 -0800349 return PortNumber.portNumber(port);
350
351 }
352 }
353
354 }
355
356 return null;
357 }
358
359 // Returns the currently installed flow entries on the device.
360 private List<FlowRule> fetchRules(OID oid, boolean isAdd, PortNumber linePort) {
361 List<FlowRule> rules = new LinkedList<>();
362
363 for (TreeEvent event : snmp.get(oid)) {
364 if (event == null) {
365 continue;
366 }
367
368 VariableBinding[] varBindings = event.getVariableBindings();
369 for (VariableBinding varBinding : varBindings) {
370 CrossConnectCache cache = this.handler().get(CrossConnectCache.class);
371
372 if (varBinding.getVariable().toInt() == IN_SERVICE) {
373 int channel = varBinding.getOid().removeLast();
374
375 PortNumber addDropPort = getAddDropPort(channel, isAdd);
376 if (addDropPort == null) {
377 continue;
378 }
379
380 TrafficSelector selector = DefaultTrafficSelector.builder()
381 .matchInPort(isAdd ? addDropPort : linePort)
382 .add(Criteria.matchOchSignalType(OchSignalType.FIXED_GRID))
383 .add(Criteria.matchLambda(toOchSignal(channel)))
384 .build();
385 TrafficTreatment treatment = DefaultTrafficTreatment.builder()
386 .setOutput(isAdd ? linePort : addDropPort)
387 .build();
388
389 // Lookup flow ID and priority
390 int hash = Objects.hash(data().deviceId(), selector, treatment);
391 Pair<FlowId, Integer> lookup = cache.get(hash);
392 if (lookup == null) {
393 continue;
394 }
395
396 FlowRule fr = DefaultFlowRule.builder()
397 .forDevice(data().deviceId())
398 .makePermanent()
399 .withSelector(selector)
400 .withTreatment(treatment)
401 .withPriority(lookup.getRight())
402 .withCookie(lookup.getLeft().value())
403 .build();
404 rules.add(fr);
405 }
406 }
407 }
408
409 return rules;
410 }
411}