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