blob: a24df098eef0b865af568f26a1deb8c682833dbe [file] [log] [blame]
Mao Lu1f524702017-02-22 17:05:12 +08001/*
2 * Copyright 2016-present 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 */
16
17package org.onosproject.driver.optical.power;
18
19import java.util.concurrent.atomic.AtomicInteger;
20import java.util.List;
21
22import com.google.common.collect.Range;
23
24import org.onosproject.driver.extensions.OplinkAttenuation;
25import org.onosproject.net.OchSignal;
26import org.onosproject.net.Direction;
27import org.onosproject.net.Port;
28import org.onosproject.net.PortNumber;
29import org.onosproject.net.device.DeviceService;
30import org.onosproject.net.driver.DriverHandler;
31import org.onosproject.net.driver.HandlerBehaviour;
32import org.onosproject.net.flow.DefaultFlowRule;
33import org.onosproject.net.flow.DefaultTrafficTreatment;
34import org.onosproject.net.flow.FlowEntry;
35import org.onosproject.net.flow.FlowRule;
36import org.onosproject.net.flow.FlowRuleService;
37import org.onosproject.net.flow.TrafficSelector;
38import org.onosproject.net.flow.TrafficTreatment;
39import org.onosproject.net.flow.criteria.Criterion;
40import org.onosproject.net.flow.criteria.OchSignalCriterion;
41import org.onosproject.net.flow.criteria.PortCriterion;
42import org.onosproject.net.flow.instructions.ExtensionTreatment;
43import org.onosproject.net.flow.instructions.ExtensionTreatmentType;
44import org.onosproject.net.flow.instructions.Instruction;
45import org.onosproject.net.flow.instructions.Instructions;
46import org.onosproject.net.optical.OpticalAnnotations;
47import org.onosproject.openflow.controller.Dpid;
48import org.onosproject.openflow.controller.OpenFlowController;
49import org.onosproject.openflow.controller.OpenFlowOpticalSwitch;
50import org.onosproject.openflow.controller.OpenFlowSwitch;
51import org.onosproject.openflow.controller.PortDescPropertyType;
52import org.projectfloodlight.openflow.protocol.OFObject;
53import org.projectfloodlight.openflow.protocol.OFPortOptical;
54
55import org.slf4j.Logger;
56
57import static org.slf4j.LoggerFactory.getLogger;
58import static org.onosproject.net.Device.Type;
59
60/**
61 * Oplink power config utility.
62 */
63public class OplinkPowerConfigUtil {
64
65 // Parent driver handler behaviour
66 private HandlerBehaviour behaviour;
67 // Transaction id to use.
68 private final AtomicInteger xidCounter = new AtomicInteger(0);
69 // Log
70 private final Logger log = getLogger(getClass());
71
72 // Component type
73 private enum ComponentType {
74 NONE,
75 PORT,
76 CHANNEL
77 }
78
79 // Port properties for oplink devices, currently supports EDFA and ROADM.
80 // This type is mapped to OFPortDescPropOpticalTransport#getPortType() value.
81 private enum PortDescType {
82 NONE,
83 PA_LINE_IN,
84 PA_LINE_OUT,
85 BA_LINE_IN,
86 BA_LINE_OUT,
87 EXP_IN,
88 EXP_OUT,
89 AUX_IN,
90 AUX_OUT,
91 }
92
93 /**
94 * Power threshold of each port, in 0.01 dB
95 * Note:
96 * These threshold configurations are just in use for a short time.
97 * In the future, the power threshold would be obtained from physical device.
98 */
99 // EDFA
100 private static final long EDFA_POWER_IN_WEST_LOW_THRES = -1900L;
101 private static final long EDFA_POWER_IN_WEST_HIGH_THRES = 0L;
102 private static final long EDFA_POWER_IN_EAST_LOW_THRES = -3100L;
103 private static final long EDFA_POWER_IN_EAST_HIGH_THRES = 700L;
104 private static final long EDFA_POWER_OUT_LOW_THRES = 0L;
105 private static final long EDFA_POWER_OUT_HIGH_THRES = 1900L;
106 // ROADM
107 private static final long ROADM_POWER_LINE_IN_LOW_THRES = -3000L;
108 private static final long ROADM_POWER_LINE_IN_HIGH_THRES = 2350L;
109 private static final long ROADM_POWER_LINE_OUT_LOW_THRES = 0L;
110 private static final long ROADM_POWER_LINE_OUT_HIGH_THRES = 2350L;
111 private static final long ROADM_POWER_OTHER_IN_LOW_THRES = -1500L;
112 private static final long ROADM_POWER_OTHER_IN_HIGH_THRES = 2000L;
113 private static final long ROADM_POWER_OTHER_OUT_LOW_THRES = -600L;
114 private static final long ROADM_POWER_OTHER_OUT_HIGH_THRES = 1500L;
115 private static final long ROADM_MIN_ATTENUATION = 0L;
116 private static final long ROADM_MAX_ATTENUATION = 2500L;
117
118 /**
119 * Create a new OplinkPowerConfigUtil.
120 * @param behaviour driver handler behaviour
121 */
122 public OplinkPowerConfigUtil(HandlerBehaviour behaviour) {
123 this.behaviour = behaviour;
124 }
125
126 /**
127 * Obtains specified port/channel target power.
128 *
129 * @param port the port number
130 * @param component the port component
131 * @return target power value in .01 dBm
132 */
133 public Long getTargetPower(PortNumber port, Object component) {
134 switch (getComponentType(component)) {
135 case PORT:
136 return getPortPower(port, OpticalAnnotations.TARGET_POWER);
137 case CHANNEL:
138 return getChannelAttenuation(port, (OchSignal) component);
139 default:
140 return null;
141 }
142 }
143
144 /**
145 * Obtains specified port/channel current power.
146 *
147 * @param port the port number
148 * @param component the port component
149 * @return current power value in .01 dBm
150 */
151 public Long getCurrentPower(PortNumber port, Object component) {
152 switch (getComponentType(component)) {
153 case PORT:
154 return getPortPower(port, OpticalAnnotations.CURRENT_POWER);
155 case CHANNEL:
156 return getCurrentChannelPower(port, (OchSignal) component);
157 default:
158 return null;
159 }
160 }
161
162 /**
163 * Sets specified port target power or channel attenuation.
164 *
165 * @param port the port number
166 * @param component the port component
167 * @param power target power in .01 dBm
168 */
169 public void setTargetPower(PortNumber port, Object component, long power) {
170 switch (getComponentType(component)) {
171 case PORT:
172 setPortPower(port, power);
173 break;
174 case CHANNEL:
175 setChannelAttenuation(port, (OchSignal) component, power);
176 break;
177 default:
178 break;
179 }
180 }
181
182 /**
183 * Returns the acceptable target range for an output port/channel, null otherwise.
184 *
185 * @param port the port number
186 * @param component the port component
187 * @return power range
188 */
189 public Range<Long> getTargetPowerRange(PortNumber port, Object component) {
190 switch (getComponentType(component)) {
191 case PORT:
192 return getTargetPortPowerRange(port);
193 case CHANNEL:
194 return getChannelAttenuationRange(port);
195 default:
196 return null;
197 }
198 }
199
200 /**
201 * Returns the working input power range for an input port, null otherwise.
202 *
203 * @param port the port number
204 * @param component the port component
205 * @return power range
206 */
207 public Range<Long> getInputPowerRange(PortNumber port, Object component) {
208 switch (getComponentType(component)) {
209 case PORT:
210 return getInputPortPowerRange(port);
211 default:
212 return null;
213 }
214 }
215
216 /**
217 * Returns specified component type.
218 *
219 * @param component the port component
220 * @return component type
221 */
222 private ComponentType getComponentType(Object component) {
223 if (component == null || component instanceof Direction) {
224 return ComponentType.PORT;
225 } else if (component instanceof OchSignal) {
226 return ComponentType.CHANNEL;
227 }
228 return ComponentType.NONE;
229 }
230
231 /**
232 * Returns current switch known to this OF controller.
233 *
234 * @return current switch
235 */
236 private OpenFlowSwitch getOpenFlowDevice() {
237 final DriverHandler handler = behaviour.handler();
238 final OpenFlowController controller = handler.get(OpenFlowController.class);
239 final Dpid dpid = Dpid.dpid(handler.data().deviceId().uri());
240 OpenFlowSwitch sw = controller.getSwitch(dpid);
241 if (sw == null || !sw.isConnected()) {
242 log.warn("OpenFlow handshaker driver not found or device is not connected, dpid = {}", dpid);
243 return null;
244 }
245 return sw;
246 }
247
248 /**
249 * Find oplink port description type from optical ports.
250 *
251 * @param opsw switch
252 * @param portNum the port number
253 * @return port oplink port description type
254 */
255 private PortDescType getPortDescType(OpenFlowOpticalSwitch opsw, PortNumber portNum) {
256 for (PortDescPropertyType type : opsw.getPortTypes()) {
257 List<? extends OFObject> portsOf = opsw.getPortsOf(type);
258 for (OFObject op : portsOf) {
259 if (op instanceof OFPortOptical) {
260 OFPortOptical opticalPort = (OFPortOptical) op;
261 if ((long) opticalPort.getPortNo().getPortNumber() == portNum.toLong()) {
262 return PortDescType.values()[opticalPort.getDesc().get(0).getPortType()];
263 }
264 }
265 }
266 }
267 return PortDescType.NONE;
268 }
269
270 /**
271 * Returns the target port power range.
272 *
273 * @param portNum the port number
274 * @return power range
275 */
276 private Range<Long> getTargetPortPowerRange(PortNumber portNum) {
277 OpenFlowSwitch ofs = getOpenFlowDevice();
278 if (ofs == null) {
279 return null;
280 }
281 PortDescType portType = getPortDescType((OpenFlowOpticalSwitch) ofs, portNum);
282 Type devType = ofs.deviceType();
283 // FIXME
284 // Short time hard code.
285 // The power range will be obtained from physical device in the future.
286 switch (devType) {
287 case OPTICAL_AMPLIFIER:
288 if (portType == PortDescType.PA_LINE_OUT || portType == PortDescType.BA_LINE_OUT) {
289 return Range.closed(EDFA_POWER_OUT_LOW_THRES, EDFA_POWER_OUT_HIGH_THRES);
290 }
291 break;
292 case ROADM:
293 if (portType == PortDescType.PA_LINE_OUT) {
294 return Range.closed(ROADM_POWER_LINE_OUT_LOW_THRES, ROADM_POWER_LINE_OUT_HIGH_THRES);
295 } else if (portType == PortDescType.EXP_OUT || portType == PortDescType.AUX_OUT) {
296 return Range.closed(ROADM_POWER_OTHER_OUT_LOW_THRES, ROADM_POWER_OTHER_OUT_HIGH_THRES);
297 }
298 break;
299 default:
300 log.warn("Unexpected device type: {}", devType);
301 break;
302 }
303 // Unexpected port or device type. Do not need warning here for port polling.
304 return null;
305 }
306
307 /**
308 * Returns the input port power range.
309 *
310 * @param portNum the port number
311 * @return power range
312 */
313 private Range<Long> getInputPortPowerRange(PortNumber portNum) {
314 OpenFlowSwitch ofs = getOpenFlowDevice();
315 if (ofs == null) {
316 return null;
317 }
318 PortDescType portType = getPortDescType((OpenFlowOpticalSwitch) ofs, portNum);
319 Type devType = ofs.deviceType();
320 // FIXME
321 // Short time hard code.
322 // The port type and power range will be obtained from physical device in the future.
323 switch (devType) {
324 case OPTICAL_AMPLIFIER:
325 if (portType == PortDescType.PA_LINE_IN) {
326 return Range.closed(EDFA_POWER_IN_WEST_LOW_THRES, EDFA_POWER_IN_WEST_HIGH_THRES);
327 } else if (portType == PortDescType.BA_LINE_IN) {
328 return Range.closed(EDFA_POWER_IN_EAST_LOW_THRES, EDFA_POWER_IN_EAST_HIGH_THRES);
329 }
330 break;
331 case ROADM:
332 if (portType == PortDescType.PA_LINE_IN) {
333 return Range.closed(ROADM_POWER_LINE_IN_LOW_THRES, ROADM_POWER_LINE_IN_HIGH_THRES);
334 } else if (portType == PortDescType.EXP_IN || portType == PortDescType.AUX_IN) {
335 return Range.closed(ROADM_POWER_OTHER_IN_LOW_THRES, ROADM_POWER_OTHER_IN_HIGH_THRES);
336 }
337 break;
338 default:
339 log.warn("Unexpected device type: {}", devType);
340 break;
341 }
342 // Unexpected port or device type. Do not need warning here for port polling.
343 return null;
344 }
345
346 /**
347 * Returns the acceptable attenuation range for a connection (represented as
348 * a flow with attenuation instruction). Port can be either the input or
349 * output port of the connection. Returns null if the connection does not
350 * support attenuation.
351 *
352 * @param portNum the port number
353 * @return attenuation range
354 */
355 private Range<Long> getChannelAttenuationRange(PortNumber portNum) {
356 OpenFlowSwitch ofs = getOpenFlowDevice();
357 if (ofs == null) {
358 return null;
359 }
360 if (ofs.deviceType() != Type.ROADM) {
361 return null;
362 }
363 PortDescType portType = getPortDescType((OpenFlowOpticalSwitch) ofs, portNum);
364 // Short time hard code.
365 // The port type and attenuation range will be obtained from physical device in the future.
366 if (portType == PortDescType.PA_LINE_OUT || portType == PortDescType.EXP_IN ||
367 portType == PortDescType.AUX_IN) {
368 return Range.closed(ROADM_MIN_ATTENUATION, ROADM_MAX_ATTENUATION);
369 }
370 // Unexpected port. Do not need warning here for port polling.
371 return null;
372 }
373
374 /**
375 * Find specified port power from port description.
376 *
377 * @param portNum the port number
378 * @param annotation annotation in port description
379 * @return power value in 0.01 dBm
380 */
381 private Long getPortPower(PortNumber portNum, String annotation) {
382 // Check if switch is connected, otherwise do not return value in store, which is obsolete.
383 if (getOpenFlowDevice() == null) {
384 // Warning already exists in method getOpenFlowDevice()
385 return null;
386 }
387 final DriverHandler handler = behaviour.handler();
388 DeviceService deviceService = handler.get(DeviceService.class);
389 Port port = deviceService.getPort(handler.data().deviceId(), portNum);
390 if (port == null) {
391 log.warn("Unexpected port: {}", portNum);
392 return null;
393 }
394 String power = port.annotations().value(annotation);
395 if (power == null) {
396 log.warn("Cannot get {} from port {}.", annotation, portNum);
397 return null;
398 }
399 return Long.valueOf(power);
400 }
401
402 /**
403 * Sets specified port power value.
404 *
405 * @param portNum the port number
406 * @param power power value
407 */
408 private void setPortPower(PortNumber portNum, long power) {
409 OpenFlowSwitch device = getOpenFlowDevice();
410 // Check if switch is connected
411 if (device == null) {
412 return;
413 }
414 device.sendMsg(device.factory().buildOplinkPortPowerSet()
415 .setXid(xidCounter.getAndIncrement())
416 .setPort((int) portNum.toLong())
417 .setPowerValue((int) power)
418 .build());
419 }
420
421 /**
422 * Gets specified channel attenuation.
423 *
424 * @param portNum the port number
425 * @param och channel signal
426 * @return atteuation in 0.01 dB
427 */
428 private Long getChannelAttenuation(PortNumber portNum, OchSignal och) {
429 FlowEntry flowEntry = findFlow(portNum, och);
430 if (flowEntry == null) {
431 return null;
432 }
433 List<Instruction> instructions = flowEntry.treatment().allInstructions();
434 for (Instruction ins : instructions) {
435 if (ins.type() != Instruction.Type.EXTENSION) {
436 continue;
437 }
438 ExtensionTreatment ext = ((Instructions.ExtensionInstructionWrapper) ins).extensionInstruction();
439 if (ext.type() == ExtensionTreatmentType.ExtensionTreatmentTypes.OPLINK_ATTENUATION.type()) {
440 return (long) ((OplinkAttenuation) ext).getAttenuation();
441 }
442 }
443 return null;
444 }
445
446 /**
447 * Sets specified channle attenuation.
448 *
449 * @param portNum the port number
450 * @param och channel signal
451 * @param power attenuation in 0.01 dB
452 */
453 private void setChannelAttenuation(PortNumber portNum, OchSignal och, long power) {
454 FlowEntry flowEntry = findFlow(portNum, och);
455 if (flowEntry == null) {
456 log.warn("Target channel power not set");
457 return;
458 }
459 final DriverHandler handler = behaviour.handler();
460 for (Instruction ins : flowEntry.treatment().allInstructions()) {
461 if (ins.type() != Instruction.Type.EXTENSION) {
462 continue;
463 }
464 ExtensionTreatment ext = ((Instructions.ExtensionInstructionWrapper) ins).extensionInstruction();
465 if (ext.type() == ExtensionTreatmentType.ExtensionTreatmentTypes.OPLINK_ATTENUATION.type()) {
466 ((OplinkAttenuation) ext).setAttenuation((int) power);
467 FlowRuleService service = handler.get(FlowRuleService.class);
468 service.applyFlowRules(flowEntry);
469 return;
470 }
471 }
472 addAttenuation(flowEntry, power);
473 }
474
475 /**
476 * Gets specified channle current power.
477 *
478 * @param portNum the port number
479 * @param och channel signal
480 * @return power value in 0.01 dBm
481 */
482 private Long getCurrentChannelPower(PortNumber portNum, OchSignal och) {
483 FlowEntry flowEntry = findFlow(portNum, och);
484 if (flowEntry != null) {
485 // TODO put somewhere else if possible
486 // We put channel power in packets
487 return flowEntry.packets();
488 }
489 return null;
490 }
491
492 /**
493 * Find matching flow on device.
494 *
495 * @param portNum the port number
496 * @param och channel signal
497 * @return flow entry
498 */
499 private FlowEntry findFlow(PortNumber portNum, OchSignal och) {
500 final DriverHandler handler = behaviour.handler();
501 FlowRuleService service = handler.get(FlowRuleService.class);
502 Iterable<FlowEntry> flowEntries = service.getFlowEntries(handler.data().deviceId());
503
504 // Return first matching flow
505 for (FlowEntry entry : flowEntries) {
506 TrafficSelector selector = entry.selector();
507 OchSignalCriterion entrySigid =
508 (OchSignalCriterion) selector.getCriterion(Criterion.Type.OCH_SIGID);
509 // Check channel
510 if (entrySigid != null && och.equals(entrySigid.lambda())) {
511 // Check input port
512 PortCriterion entryPort =
513 (PortCriterion) selector.getCriterion(Criterion.Type.IN_PORT);
514 if (entryPort != null && portNum.equals(entryPort.port())) {
515 return entry;
516 }
517
518 // Check output port
519 TrafficTreatment treatment = entry.treatment();
520 for (Instruction instruction : treatment.allInstructions()) {
521 if (instruction.type() == Instruction.Type.OUTPUT &&
522 ((Instructions.OutputInstruction) instruction).port().equals(portNum)) {
523 return entry;
524 }
525 }
526 }
527 }
528 log.warn("No matching flow found");
529 return null;
530 }
531
532 /**
533 * Replace flow with new flow containing Oplink attenuation extension instruction. Also resets metrics.
534 *
535 * @param flowEntry flow entry
536 * @param power power value
537 */
538 private void addAttenuation(FlowEntry flowEntry, long power) {
539 FlowRule.Builder flowBuilder = new DefaultFlowRule.Builder()
540 .withCookie(flowEntry.id().value())
541 .withPriority(flowEntry.priority())
542 .forDevice(flowEntry.deviceId())
543 .forTable(flowEntry.tableId());
544 if (flowEntry.isPermanent()) {
545 flowBuilder.makePermanent();
546 } else {
547 flowBuilder.makeTemporary(flowEntry.timeout());
548 }
549 flowBuilder.withSelector(flowEntry.selector());
550 // Copy original instructions and add attenuation instruction
551 TrafficTreatment.Builder treatmentBuilder = DefaultTrafficTreatment.builder();
552 flowEntry.treatment().allInstructions().forEach(ins -> treatmentBuilder.add(ins));
553 final DriverHandler handler = behaviour.handler();
554 treatmentBuilder.add(Instructions.extension(new OplinkAttenuation((int) power), handler.data().deviceId()));
555 flowBuilder.withTreatment(treatmentBuilder.build());
556
557 FlowRuleService service = handler.get(FlowRuleService.class);
558 service.applyFlowRules(flowBuilder.build());
559 }
560}