blob: c1a5fe82bf50dc3872d1b68ab319976edf81ff6f [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);
65 private static final int DEFAULT_ATTENUATION = 160;
66 private static final int OUT_OF_SERVICE = 1;
67 private static final int IN_SERVICE = 2;
68 private static final String CTRL_AMP_MODULE_SERVICE_STATE_PREAMP = ".1.3.6.1.4.1.46184.1.4.4.1.2.1";
69 private static final String CTRL_AMP_MODULE_SERVICE_STATE_BOOSTER = ".1.3.6.1.4.1.46184.1.4.4.1.2.2";
70 private static final String CTRL_CHANNEL_STATE = ".1.3.6.1.4.1.46184.1.4.2.1.3.";
71 private static final String CTRL_CHANNEL_ADD_DROP_PORT_INDEX = ".1.3.6.1.4.1.46184.1.4.2.1.13.";
72 private static final String CTRL_CHANNEL_ABSOLUTE_ATTENUATION = ".1.3.6.1.4.1.46184.1.4.2.1.5.";
73
74 private LumentumSnmpDevice snmp;
75
76 @Override
77 public Collection<FlowEntry> getFlowEntries() {
78 try {
79 snmp = new LumentumSnmpDevice(handler().data().deviceId());
80 } catch (IOException e) {
81 log.error("Failed to connect to device: ", e);
82 return Collections.emptyList();
83 }
84
85 // Line in is last but one port, line out is last
86 DeviceService deviceService = this.handler().get(DeviceService.class);
87 List<Port> ports = deviceService.getPorts(data().deviceId());
88 if (ports.size() < 2) {
89 return Collections.emptyList();
90 }
91 PortNumber lineIn = ports.get(ports.size() - 2).number();
92 PortNumber lineOut = ports.get(ports.size() - 1).number();
93
94 Collection<FlowEntry> entries = Lists.newLinkedList();
95
96 // Add rules
97 OID addOid = new OID(CTRL_CHANNEL_STATE + "1");
98 entries.addAll(
99 fetchRules(addOid, true, lineOut).stream()
100 .map(fr -> new DefaultFlowEntry(fr, FlowEntry.FlowEntryState.ADDED, 0, 0, 0))
101 .collect(Collectors.toList())
102 );
103
104 // Drop rules
105 OID dropOid = new OID(CTRL_CHANNEL_STATE + "2");
106 entries.addAll(
107 fetchRules(dropOid, false, lineIn).stream()
108 .map(fr -> new DefaultFlowEntry(fr, FlowEntry.FlowEntryState.ADDED, 0, 0, 0))
109 .collect(Collectors.toList())
110 );
111
112 return entries;
113 }
114
115 @Override
116 public Collection<FlowRule> applyFlowRules(Collection<FlowRule> rules) {
117 try {
118 snmp = new LumentumSnmpDevice(data().deviceId());
119 } catch (IOException e) {
120 log.error("Failed to connect to device: ", e);
121 }
122
123 // Line ports
124 DeviceService deviceService = this.handler().get(DeviceService.class);
125 List<Port> ports = deviceService.getPorts(data().deviceId());
126 List<PortNumber> linePorts = ports.subList(ports.size() - 2, ports.size()).stream()
127 .map(p -> p.number())
128 .collect(Collectors.toList());
129
130 // Apply the valid rules on the device
131 Collection<FlowRule> added = rules.stream()
132 .map(r -> new CrossConnectFlowRule(r, linePorts))
133 .filter(xc -> installCrossConnect(xc))
134 .collect(Collectors.toList());
135
136 // Cache the cookie/priority
137 CrossConnectCache cache = this.handler().get(CrossConnectCache.class);
138 added.stream()
139 .forEach(xc -> cache.set(
140 Objects.hash(data().deviceId(), xc.selector(), xc.treatment()),
141 xc.id(),
142 xc.priority()));
143
144 return added;
145 }
146
147 @Override
148 public Collection<FlowRule> removeFlowRules(Collection<FlowRule> rules) {
149 try {
150 snmp = new LumentumSnmpDevice(data().deviceId());
151 } catch (IOException e) {
152 log.error("Failed to connect to device: ", e);
153 }
154
155 // Line ports
156 DeviceService deviceService = this.handler().get(DeviceService.class);
157 List<Port> ports = deviceService.getPorts(data().deviceId());
158 List<PortNumber> linePorts = ports.subList(ports.size() - 2, ports.size()).stream()
159 .map(p -> p.number())
160 .collect(Collectors.toList());
161
162 // Apply the valid rules on the device
163 Collection<FlowRule> removed = rules.stream()
164 .map(r -> new CrossConnectFlowRule(r, linePorts))
165 .filter(xc -> removeCrossConnect(xc))
166 .collect(Collectors.toList());
167
168 // Remove flow rule from cache
169 CrossConnectCache cache = this.handler().get(CrossConnectCache.class);
170 removed.stream()
171 .forEach(xc -> cache.remove(
172 Objects.hash(data().deviceId(), xc.selector(), xc.treatment())));
173
174 return removed;
175 }
176
177 // Installs cross connect on device
178 private boolean installCrossConnect(CrossConnectFlowRule xc) {
179
180 int channel = toChannel(xc.ochSignal());
181 long addDrop = xc.addDrop().toLong();
182
183 // Create the PDU object
184 PDU pdu = new PDU();
185 pdu.setType(PDU.SET);
186
187 // Enable preamp & booster for service
188 List<OID> oids = Arrays.asList(new OID(CTRL_AMP_MODULE_SERVICE_STATE_PREAMP),
189 new OID(CTRL_AMP_MODULE_SERVICE_STATE_BOOSTER));
190 oids.forEach(
191 oid -> pdu.add(new VariableBinding(oid, new Integer32(IN_SERVICE)))
192 );
193
194 // Enable the channel
195 OID ctrlChannelState = new OID(CTRL_CHANNEL_STATE + (xc.isAddRule() ? "1." : "2.") + channel);
196 pdu.add(new VariableBinding(ctrlChannelState, new Integer32(IN_SERVICE)));
197
198 // Make cross connect
199 OID ctrlChannelAddDropPortIndex = new OID(CTRL_CHANNEL_ADD_DROP_PORT_INDEX +
200 (xc.isAddRule() ? "1." : "2.") + channel);
201 pdu.add(new VariableBinding(ctrlChannelAddDropPortIndex, new UnsignedInteger32(addDrop)));
202
203 // Set attenuation to zero
204 OID ctrlChannelAbsoluteAttenuation = new OID(CTRL_CHANNEL_ABSOLUTE_ATTENUATION +
205 (xc.isAddRule() ? "1." : "2.") + channel);
206 pdu.add(new VariableBinding(ctrlChannelAbsoluteAttenuation, new UnsignedInteger32(0)));
207
208 try {
209 ResponseEvent response = snmp.set(pdu);
210
211 // TODO: parse response
212 } catch (IOException e) {
213 log.error("Failed to create cross connect, unable to connect to device: ", e);
214 }
215
216 return true;
217 }
218
219 // Removes cross connect on device
220 private boolean removeCrossConnect(CrossConnectFlowRule xc) {
221
222 int channel = toChannel(xc.ochSignal());
223
224 // Create the PDU object
225 PDU pdu = new PDU();
226 pdu.setType(PDU.SET);
227
228 // Disable the channel
229 OID ctrlChannelState = new OID(CTRL_CHANNEL_STATE + (xc.isAddRule() ? "1." : "2.") + channel);
230 pdu.add(new VariableBinding(ctrlChannelState, new Integer32(OUT_OF_SERVICE)));
231
232 // Put cross connect back into default port 1
233 OID ctrlChannelAddDropPortIndex = new OID(CTRL_CHANNEL_ADD_DROP_PORT_INDEX +
234 (xc.isAddRule() ? "1." : "2.") + channel);
235 pdu.add(new VariableBinding(ctrlChannelAddDropPortIndex, new UnsignedInteger32(OUT_OF_SERVICE)));
236
237 // Set attenuation to default value
238 OID ctrlChannelAbsoluteAttenuation = new OID(CTRL_CHANNEL_ABSOLUTE_ATTENUATION +
239 (xc.isAddRule() ? "1." : "2.") + channel);
240 pdu.add(new VariableBinding(ctrlChannelAbsoluteAttenuation, new UnsignedInteger32(DEFAULT_ATTENUATION))
241 );
242
243 try {
244 ResponseEvent response = snmp.set(pdu);
245
246 // TODO: parse response
247 } catch (IOException e) {
248 log.error("Failed to remove cross connect, unable to connect to device: ", e);
249 return false;
250 }
251
252 return true;
253 }
254
255 /**
256 * Convert OCh signal to Lumentum channel ID.
257 *
258 * @param ochSignal OCh signal
259 * @return Lumentum channel ID
260 */
261 public static int toChannel(OchSignal ochSignal) {
262 // FIXME: move to cross connect validation
263 checkArgument(ochSignal.channelSpacing() == ChannelSpacing.CHL_50GHZ);
264 checkArgument(LumentumSnmpDevice.START_CENTER_FREQ.compareTo(ochSignal.centralFrequency()) <= 0);
265 checkArgument(LumentumSnmpDevice.END_CENTER_FREQ.compareTo(ochSignal.centralFrequency()) >= 0);
266
267 return ochSignal.spacingMultiplier() + LumentumSnmpDevice.MULTIPLIER_SHIFT;
268 }
269
270 /**
271 * Convert Lumentum channel ID to OCh signal.
272 *
273 * @param channel Lumentum channel ID
274 * @return OCh signal
275 */
276 public static OchSignal toOchSignal(int channel) {
277 checkArgument(1 <= channel);
278 checkArgument(channel <= 96);
279
280 return new OchSignal(GridType.DWDM, ChannelSpacing.CHL_50GHZ,
281 channel - LumentumSnmpDevice.MULTIPLIER_SHIFT, 4);
282 }
283
284 // Returns the currently configured add/drop port for the given channel.
285 private PortNumber getAddDropPort(int channel, boolean isAddPort) {
286 OID oid = new OID(CTRL_CHANNEL_ADD_DROP_PORT_INDEX + (isAddPort ? "1" : "2"));
287
288 for (TreeEvent event : snmp.get(oid)) {
289 if (event == null) {
290 return null;
291 }
292
293 VariableBinding[] varBindings = event.getVariableBindings();
294
295 for (VariableBinding varBinding : varBindings) {
296 if (varBinding.getOid().last() == channel) {
297 int port = varBinding.getVariable().toInt();
298 return PortNumber.portNumber(port);
299
300 }
301 }
302
303 }
304
305 return null;
306 }
307
308 // Returns the currently installed flow entries on the device.
309 private List<FlowRule> fetchRules(OID oid, boolean isAdd, PortNumber linePort) {
310 List<FlowRule> rules = new LinkedList<>();
311
312 for (TreeEvent event : snmp.get(oid)) {
313 if (event == null) {
314 continue;
315 }
316
317 VariableBinding[] varBindings = event.getVariableBindings();
318 for (VariableBinding varBinding : varBindings) {
319 CrossConnectCache cache = this.handler().get(CrossConnectCache.class);
320
321 if (varBinding.getVariable().toInt() == IN_SERVICE) {
322 int channel = varBinding.getOid().removeLast();
323
324 PortNumber addDropPort = getAddDropPort(channel, isAdd);
325 if (addDropPort == null) {
326 continue;
327 }
328
329 TrafficSelector selector = DefaultTrafficSelector.builder()
330 .matchInPort(isAdd ? addDropPort : linePort)
331 .add(Criteria.matchOchSignalType(OchSignalType.FIXED_GRID))
332 .add(Criteria.matchLambda(toOchSignal(channel)))
333 .build();
334 TrafficTreatment treatment = DefaultTrafficTreatment.builder()
335 .setOutput(isAdd ? linePort : addDropPort)
336 .build();
337
338 // Lookup flow ID and priority
339 int hash = Objects.hash(data().deviceId(), selector, treatment);
340 Pair<FlowId, Integer> lookup = cache.get(hash);
341 if (lookup == null) {
342 continue;
343 }
344
345 FlowRule fr = DefaultFlowRule.builder()
346 .forDevice(data().deviceId())
347 .makePermanent()
348 .withSelector(selector)
349 .withTreatment(treatment)
350 .withPriority(lookup.getRight())
351 .withCookie(lookup.getLeft().value())
352 .build();
353 rules.add(fr);
354 }
355 }
356 }
357
358 return rules;
359 }
360}