blob: 21f9b24878392e198ebdeb2951a9d1c756301a47 [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;
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
Andrea Campanellabb8775b2016-04-12 10:32:14 -070061public class LumentumFlowRuleProgrammable extends AbstractHandlerBehaviour implements FlowRuleProgrammable {
Marc De Leenheerc662d322016-02-18 16:05:10 -080062
63 private static final Logger log =
Andrea Campanellabb8775b2016-04-12 10:32:14 -070064 LoggerFactory.getLogger(LumentumFlowRuleProgrammable.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 Leenheereb80f4f2016-03-05 17:04:59 -080073 private static final int DISABLE_CHANNEL_ADD_DROP_PORT_INDEX = 1;
Marc De Leenheerc662d322016-02-18 16:05:10 -080074 private static final int OUT_OF_SERVICE = 1;
75 private static final int IN_SERVICE = 2;
Marc De Leenheer2898a7f2016-03-02 21:42:59 -080076 private static final int OPEN_LOOP = 1;
77 private static final int CLOSED_LOOP = 2;
Marc De Leenheereb80f4f2016-03-05 17:04:59 -080078 // First 20 ports are add/mux ports, next 20 are drop/demux
79 private static final int DROP_PORT_OFFSET = 20;
Marc De Leenheer2898a7f2016-03-02 21:42:59 -080080
81 // OIDs
Marc De Leenheerc662d322016-02-18 16:05:10 -080082 private static final String CTRL_AMP_MODULE_SERVICE_STATE_PREAMP = ".1.3.6.1.4.1.46184.1.4.4.1.2.1";
83 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 -080084 private static final String CTRL_AMP_MODULE_TARGET_GAIN_PREAMP = ".1.3.6.1.4.1.46184.1.4.4.1.8.1";
85 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 -080086 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 -080087 private static final String CTRL_CHANNEL_MODE = ".1.3.6.1.4.1.46184.1.4.2.1.4.";
88 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 -080089 private static final String CTRL_CHANNEL_ADD_DROP_PORT_INDEX = ".1.3.6.1.4.1.46184.1.4.2.1.13.";
90 private static final String CTRL_CHANNEL_ABSOLUTE_ATTENUATION = ".1.3.6.1.4.1.46184.1.4.2.1.5.";
91
92 private LumentumSnmpDevice snmp;
93
94 @Override
95 public Collection<FlowEntry> getFlowEntries() {
96 try {
97 snmp = new LumentumSnmpDevice(handler().data().deviceId());
98 } catch (IOException e) {
99 log.error("Failed to connect to device: ", e);
100 return Collections.emptyList();
101 }
102
103 // Line in is last but one port, line out is last
104 DeviceService deviceService = this.handler().get(DeviceService.class);
105 List<Port> ports = deviceService.getPorts(data().deviceId());
106 if (ports.size() < 2) {
107 return Collections.emptyList();
108 }
109 PortNumber lineIn = ports.get(ports.size() - 2).number();
110 PortNumber lineOut = ports.get(ports.size() - 1).number();
111
112 Collection<FlowEntry> entries = Lists.newLinkedList();
113
114 // Add rules
115 OID addOid = new OID(CTRL_CHANNEL_STATE + "1");
116 entries.addAll(
117 fetchRules(addOid, true, lineOut).stream()
118 .map(fr -> new DefaultFlowEntry(fr, FlowEntry.FlowEntryState.ADDED, 0, 0, 0))
119 .collect(Collectors.toList())
120 );
121
122 // Drop rules
123 OID dropOid = new OID(CTRL_CHANNEL_STATE + "2");
124 entries.addAll(
125 fetchRules(dropOid, false, lineIn).stream()
126 .map(fr -> new DefaultFlowEntry(fr, FlowEntry.FlowEntryState.ADDED, 0, 0, 0))
127 .collect(Collectors.toList())
128 );
129
130 return entries;
131 }
132
133 @Override
134 public Collection<FlowRule> applyFlowRules(Collection<FlowRule> rules) {
135 try {
136 snmp = new LumentumSnmpDevice(data().deviceId());
137 } catch (IOException e) {
138 log.error("Failed to connect to device: ", e);
139 }
140
141 // Line ports
142 DeviceService deviceService = this.handler().get(DeviceService.class);
143 List<Port> ports = deviceService.getPorts(data().deviceId());
144 List<PortNumber> linePorts = ports.subList(ports.size() - 2, ports.size()).stream()
145 .map(p -> p.number())
146 .collect(Collectors.toList());
147
148 // Apply the valid rules on the device
149 Collection<FlowRule> added = rules.stream()
150 .map(r -> new CrossConnectFlowRule(r, linePorts))
151 .filter(xc -> installCrossConnect(xc))
152 .collect(Collectors.toList());
153
154 // Cache the cookie/priority
155 CrossConnectCache cache = this.handler().get(CrossConnectCache.class);
Sho SHIMIZUa09e1bb2016-08-01 14:25:25 -0700156 added.forEach(xc -> cache.set(
157 Objects.hash(data().deviceId(), xc.selector(), xc.treatment()),
158 xc.id(),
159 xc.priority()));
Marc De Leenheerc662d322016-02-18 16:05:10 -0800160
161 return added;
162 }
163
164 @Override
165 public Collection<FlowRule> removeFlowRules(Collection<FlowRule> rules) {
166 try {
167 snmp = new LumentumSnmpDevice(data().deviceId());
168 } catch (IOException e) {
169 log.error("Failed to connect to device: ", e);
170 }
171
172 // Line ports
173 DeviceService deviceService = this.handler().get(DeviceService.class);
174 List<Port> ports = deviceService.getPorts(data().deviceId());
175 List<PortNumber> linePorts = ports.subList(ports.size() - 2, ports.size()).stream()
176 .map(p -> p.number())
177 .collect(Collectors.toList());
178
179 // Apply the valid rules on the device
180 Collection<FlowRule> removed = rules.stream()
181 .map(r -> new CrossConnectFlowRule(r, linePorts))
182 .filter(xc -> removeCrossConnect(xc))
183 .collect(Collectors.toList());
184
185 // Remove flow rule from cache
186 CrossConnectCache cache = this.handler().get(CrossConnectCache.class);
Sho SHIMIZUa09e1bb2016-08-01 14:25:25 -0700187 removed.forEach(xc -> cache.remove(
188 Objects.hash(data().deviceId(), xc.selector(), xc.treatment())));
Marc De Leenheerc662d322016-02-18 16:05:10 -0800189
190 return removed;
191 }
192
193 // Installs cross connect on device
194 private boolean installCrossConnect(CrossConnectFlowRule xc) {
195
196 int channel = toChannel(xc.ochSignal());
197 long addDrop = xc.addDrop().toLong();
Marc De Leenheereb80f4f2016-03-05 17:04:59 -0800198 if (!xc.isAddRule()) {
199 addDrop -= DROP_PORT_OFFSET;
200 }
Marc De Leenheerc662d322016-02-18 16:05:10 -0800201
202 // Create the PDU object
203 PDU pdu = new PDU();
204 pdu.setType(PDU.SET);
205
Marc De Leenheer2898a7f2016-03-02 21:42:59 -0800206 // Enable preamp & booster
Marc De Leenheerc662d322016-02-18 16:05:10 -0800207 List<OID> oids = Arrays.asList(new OID(CTRL_AMP_MODULE_SERVICE_STATE_PREAMP),
208 new OID(CTRL_AMP_MODULE_SERVICE_STATE_BOOSTER));
209 oids.forEach(
210 oid -> pdu.add(new VariableBinding(oid, new Integer32(IN_SERVICE)))
211 );
212
Marc De Leenheer2898a7f2016-03-02 21:42:59 -0800213 // Set target gain on preamp & booster
214 OID ctrlAmpModuleTargetGainPreamp = new OID(CTRL_AMP_MODULE_TARGET_GAIN_PREAMP);
215 pdu.add(new VariableBinding(ctrlAmpModuleTargetGainPreamp, new Integer32(DEFAULT_TARGET_GAIN_PREAMP)));
216 OID ctrlAmpModuleTargetGainBooster = new OID(CTRL_AMP_MODULE_TARGET_GAIN_BOOSTER);
217 pdu.add(new VariableBinding(ctrlAmpModuleTargetGainBooster, new Integer32(DEFAULT_TARGET_GAIN_BOOSTER)));
218
Marc De Leenheerc662d322016-02-18 16:05:10 -0800219 // 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
Marc De Leenheer268c4f92016-03-03 14:38:02 -0800241 // Final step is to enable the channel
242 OID ctrlChannelState = new OID(CTRL_CHANNEL_STATE + (xc.isAddRule() ? "1." : "2.") + channel);
243 pdu.add(new VariableBinding(ctrlChannelState, new Integer32(IN_SERVICE)));
244
Marc De Leenheerc662d322016-02-18 16:05:10 -0800245 try {
246 ResponseEvent response = snmp.set(pdu);
247
248 // TODO: parse response
249 } catch (IOException e) {
250 log.error("Failed to create cross connect, unable to connect to device: ", e);
251 }
252
253 return true;
254 }
255
256 // Removes cross connect on device
257 private boolean removeCrossConnect(CrossConnectFlowRule xc) {
258
259 int channel = toChannel(xc.ochSignal());
260
261 // Create the PDU object
262 PDU pdu = new PDU();
263 pdu.setType(PDU.SET);
264
265 // Disable the channel
266 OID ctrlChannelState = new OID(CTRL_CHANNEL_STATE + (xc.isAddRule() ? "1." : "2.") + channel);
267 pdu.add(new VariableBinding(ctrlChannelState, new Integer32(OUT_OF_SERVICE)));
268
269 // Put cross connect back into default port 1
270 OID ctrlChannelAddDropPortIndex = new OID(CTRL_CHANNEL_ADD_DROP_PORT_INDEX +
271 (xc.isAddRule() ? "1." : "2.") + channel);
Marc De Leenheereb80f4f2016-03-05 17:04:59 -0800272 pdu.add(new VariableBinding(ctrlChannelAddDropPortIndex,
273 new UnsignedInteger32(DISABLE_CHANNEL_ADD_DROP_PORT_INDEX)));
Marc De Leenheerc662d322016-02-18 16:05:10 -0800274
Marc De Leenheer2898a7f2016-03-02 21:42:59 -0800275 // Put port/channel back to open loop
276 OID ctrlChannelMode = new OID(CTRL_CHANNEL_MODE + (xc.isAddRule() ? "1." : "2.") + channel);
277 pdu.add(new VariableBinding(ctrlChannelMode, new Integer32(OPEN_LOOP)));
278
279 // Add rules are set to target power, drop rules are attenuated
280 if (xc.isAddRule()) {
281 OID ctrlChannelTargetPower = new OID(CTRL_CHANNEL_TARGET_POWER + "1." + channel);
282 pdu.add(new VariableBinding(ctrlChannelTargetPower, new Integer32(DISABLE_CHANNEL_TARGET_POWER)));
283 } else {
284 OID ctrlChannelAbsoluteAttenuation = new OID(CTRL_CHANNEL_ABSOLUTE_ATTENUATION + "2." + channel);
285 pdu.add(new VariableBinding(
286 ctrlChannelAbsoluteAttenuation, new UnsignedInteger32(DISABLE_CHANNEL_ABSOLUTE_ATTENUATION)));
287 }
Marc De Leenheerc662d322016-02-18 16:05:10 -0800288
289 try {
290 ResponseEvent response = snmp.set(pdu);
291
292 // TODO: parse response
293 } catch (IOException e) {
294 log.error("Failed to remove cross connect, unable to connect to device: ", e);
295 return false;
296 }
297
298 return true;
299 }
300
301 /**
302 * Convert OCh signal to Lumentum channel ID.
303 *
304 * @param ochSignal OCh signal
305 * @return Lumentum channel ID
306 */
307 public static int toChannel(OchSignal ochSignal) {
308 // FIXME: move to cross connect validation
309 checkArgument(ochSignal.channelSpacing() == ChannelSpacing.CHL_50GHZ);
310 checkArgument(LumentumSnmpDevice.START_CENTER_FREQ.compareTo(ochSignal.centralFrequency()) <= 0);
311 checkArgument(LumentumSnmpDevice.END_CENTER_FREQ.compareTo(ochSignal.centralFrequency()) >= 0);
312
313 return ochSignal.spacingMultiplier() + LumentumSnmpDevice.MULTIPLIER_SHIFT;
314 }
315
316 /**
317 * Convert Lumentum channel ID to OCh signal.
318 *
319 * @param channel Lumentum channel ID
320 * @return OCh signal
321 */
322 public static OchSignal toOchSignal(int channel) {
323 checkArgument(1 <= channel);
324 checkArgument(channel <= 96);
325
326 return new OchSignal(GridType.DWDM, ChannelSpacing.CHL_50GHZ,
327 channel - LumentumSnmpDevice.MULTIPLIER_SHIFT, 4);
328 }
329
330 // Returns the currently configured add/drop port for the given channel.
331 private PortNumber getAddDropPort(int channel, boolean isAddPort) {
332 OID oid = new OID(CTRL_CHANNEL_ADD_DROP_PORT_INDEX + (isAddPort ? "1" : "2"));
333
334 for (TreeEvent event : snmp.get(oid)) {
335 if (event == null) {
336 return null;
337 }
338
339 VariableBinding[] varBindings = event.getVariableBindings();
340
341 for (VariableBinding varBinding : varBindings) {
342 if (varBinding.getOid().last() == channel) {
343 int port = varBinding.getVariable().toInt();
Marc De Leenheereb80f4f2016-03-05 17:04:59 -0800344 if (!isAddPort) {
345 port += DROP_PORT_OFFSET;
346 }
Marc De Leenheerc662d322016-02-18 16:05:10 -0800347 return PortNumber.portNumber(port);
348
349 }
350 }
351
352 }
353
354 return null;
355 }
356
357 // Returns the currently installed flow entries on the device.
358 private List<FlowRule> fetchRules(OID oid, boolean isAdd, PortNumber linePort) {
359 List<FlowRule> rules = new LinkedList<>();
360
361 for (TreeEvent event : snmp.get(oid)) {
362 if (event == null) {
363 continue;
364 }
365
366 VariableBinding[] varBindings = event.getVariableBindings();
367 for (VariableBinding varBinding : varBindings) {
368 CrossConnectCache cache = this.handler().get(CrossConnectCache.class);
369
370 if (varBinding.getVariable().toInt() == IN_SERVICE) {
371 int channel = varBinding.getOid().removeLast();
372
373 PortNumber addDropPort = getAddDropPort(channel, isAdd);
374 if (addDropPort == null) {
375 continue;
376 }
377
378 TrafficSelector selector = DefaultTrafficSelector.builder()
379 .matchInPort(isAdd ? addDropPort : linePort)
380 .add(Criteria.matchOchSignalType(OchSignalType.FIXED_GRID))
381 .add(Criteria.matchLambda(toOchSignal(channel)))
382 .build();
383 TrafficTreatment treatment = DefaultTrafficTreatment.builder()
384 .setOutput(isAdd ? linePort : addDropPort)
385 .build();
386
387 // Lookup flow ID and priority
388 int hash = Objects.hash(data().deviceId(), selector, treatment);
389 Pair<FlowId, Integer> lookup = cache.get(hash);
390 if (lookup == null) {
391 continue;
392 }
393
394 FlowRule fr = DefaultFlowRule.builder()
395 .forDevice(data().deviceId())
396 .makePermanent()
397 .withSelector(selector)
398 .withTreatment(treatment)
399 .withPriority(lookup.getRight())
400 .withCookie(lookup.getLeft().value())
401 .build();
402 rules.add(fr);
403 }
404 }
405 }
406
407 return rules;
408 }
409}