blob: 9c44ae733a6f17978e08ae24360859c901dd2333 [file] [log] [blame]
Mao Lu1f524702017-02-22 17:05:12 +08001/*
Brian O'Connora09fe5b2017-08-03 21:12:30 -07002 * Copyright 2016-present Open Networking Foundation
Mao Lu1f524702017-02-22 17:05:12 +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 */
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;
MaoLu937cf422017-03-03 23:31:46 -0800117 // SWITCH
118 private static final long SWITCH_POWER_LOW_THRES = -6000L;
119 private static final long SWITCH_POWER_HIGH_THRES = 6000L;
Mao Lu1f524702017-02-22 17:05:12 +0800120
121 /**
122 * Create a new OplinkPowerConfigUtil.
123 * @param behaviour driver handler behaviour
124 */
125 public OplinkPowerConfigUtil(HandlerBehaviour behaviour) {
126 this.behaviour = behaviour;
127 }
128
129 /**
130 * Obtains specified port/channel target power.
131 *
132 * @param port the port number
133 * @param component the port component
134 * @return target power value in .01 dBm
135 */
136 public Long getTargetPower(PortNumber port, Object component) {
137 switch (getComponentType(component)) {
138 case PORT:
139 return getPortPower(port, OpticalAnnotations.TARGET_POWER);
140 case CHANNEL:
141 return getChannelAttenuation(port, (OchSignal) component);
142 default:
143 return null;
144 }
145 }
146
147 /**
148 * Obtains specified port/channel current power.
149 *
150 * @param port the port number
151 * @param component the port component
152 * @return current power value in .01 dBm
153 */
154 public Long getCurrentPower(PortNumber port, Object component) {
155 switch (getComponentType(component)) {
156 case PORT:
157 return getPortPower(port, OpticalAnnotations.CURRENT_POWER);
158 case CHANNEL:
159 return getCurrentChannelPower(port, (OchSignal) component);
160 default:
161 return null;
162 }
163 }
164
165 /**
166 * Sets specified port target power or channel attenuation.
167 *
168 * @param port the port number
169 * @param component the port component
170 * @param power target power in .01 dBm
171 */
172 public void setTargetPower(PortNumber port, Object component, long power) {
173 switch (getComponentType(component)) {
174 case PORT:
175 setPortPower(port, power);
176 break;
177 case CHANNEL:
178 setChannelAttenuation(port, (OchSignal) component, power);
179 break;
180 default:
181 break;
182 }
183 }
184
185 /**
186 * Returns the acceptable target range for an output port/channel, null otherwise.
187 *
188 * @param port the port number
189 * @param component the port component
190 * @return power range
191 */
192 public Range<Long> getTargetPowerRange(PortNumber port, Object component) {
193 switch (getComponentType(component)) {
194 case PORT:
195 return getTargetPortPowerRange(port);
196 case CHANNEL:
197 return getChannelAttenuationRange(port);
198 default:
199 return null;
200 }
201 }
202
203 /**
204 * Returns the working input power range for an input port, null otherwise.
205 *
206 * @param port the port number
207 * @param component the port component
208 * @return power range
209 */
210 public Range<Long> getInputPowerRange(PortNumber port, Object component) {
211 switch (getComponentType(component)) {
212 case PORT:
213 return getInputPortPowerRange(port);
214 default:
215 return null;
216 }
217 }
218
219 /**
220 * Returns specified component type.
221 *
222 * @param component the port component
223 * @return component type
224 */
225 private ComponentType getComponentType(Object component) {
226 if (component == null || component instanceof Direction) {
227 return ComponentType.PORT;
228 } else if (component instanceof OchSignal) {
229 return ComponentType.CHANNEL;
230 }
231 return ComponentType.NONE;
232 }
233
234 /**
235 * Returns current switch known to this OF controller.
236 *
237 * @return current switch
238 */
239 private OpenFlowSwitch getOpenFlowDevice() {
240 final DriverHandler handler = behaviour.handler();
241 final OpenFlowController controller = handler.get(OpenFlowController.class);
242 final Dpid dpid = Dpid.dpid(handler.data().deviceId().uri());
243 OpenFlowSwitch sw = controller.getSwitch(dpid);
244 if (sw == null || !sw.isConnected()) {
245 log.warn("OpenFlow handshaker driver not found or device is not connected, dpid = {}", dpid);
246 return null;
247 }
248 return sw;
249 }
250
251 /**
252 * Find oplink port description type from optical ports.
253 *
254 * @param opsw switch
255 * @param portNum the port number
256 * @return port oplink port description type
257 */
258 private PortDescType getPortDescType(OpenFlowOpticalSwitch opsw, PortNumber portNum) {
259 for (PortDescPropertyType type : opsw.getPortTypes()) {
260 List<? extends OFObject> portsOf = opsw.getPortsOf(type);
261 for (OFObject op : portsOf) {
262 if (op instanceof OFPortOptical) {
263 OFPortOptical opticalPort = (OFPortOptical) op;
264 if ((long) opticalPort.getPortNo().getPortNumber() == portNum.toLong()) {
265 return PortDescType.values()[opticalPort.getDesc().get(0).getPortType()];
266 }
267 }
268 }
269 }
270 return PortDescType.NONE;
271 }
272
273 /**
274 * Returns the target port power range.
275 *
276 * @param portNum the port number
277 * @return power range
278 */
279 private Range<Long> getTargetPortPowerRange(PortNumber portNum) {
280 OpenFlowSwitch ofs = getOpenFlowDevice();
281 if (ofs == null) {
282 return null;
283 }
284 PortDescType portType = getPortDescType((OpenFlowOpticalSwitch) ofs, portNum);
285 Type devType = ofs.deviceType();
286 // FIXME
287 // Short time hard code.
288 // The power range will be obtained from physical device in the future.
289 switch (devType) {
290 case OPTICAL_AMPLIFIER:
291 if (portType == PortDescType.PA_LINE_OUT || portType == PortDescType.BA_LINE_OUT) {
292 return Range.closed(EDFA_POWER_OUT_LOW_THRES, EDFA_POWER_OUT_HIGH_THRES);
293 }
294 break;
295 case ROADM:
296 if (portType == PortDescType.PA_LINE_OUT) {
297 return Range.closed(ROADM_POWER_LINE_OUT_LOW_THRES, ROADM_POWER_LINE_OUT_HIGH_THRES);
298 } else if (portType == PortDescType.EXP_OUT || portType == PortDescType.AUX_OUT) {
299 return Range.closed(ROADM_POWER_OTHER_OUT_LOW_THRES, ROADM_POWER_OTHER_OUT_HIGH_THRES);
300 }
301 break;
MaoLu937cf422017-03-03 23:31:46 -0800302 case FIBER_SWITCH:
303 return Range.closed(SWITCH_POWER_LOW_THRES, SWITCH_POWER_HIGH_THRES);
Mao Lu1f524702017-02-22 17:05:12 +0800304 default:
305 log.warn("Unexpected device type: {}", devType);
306 break;
307 }
308 // Unexpected port or device type. Do not need warning here for port polling.
309 return null;
310 }
311
312 /**
313 * Returns the input port power range.
314 *
315 * @param portNum the port number
316 * @return power range
317 */
318 private Range<Long> getInputPortPowerRange(PortNumber portNum) {
319 OpenFlowSwitch ofs = getOpenFlowDevice();
320 if (ofs == null) {
321 return null;
322 }
323 PortDescType portType = getPortDescType((OpenFlowOpticalSwitch) ofs, portNum);
324 Type devType = ofs.deviceType();
325 // FIXME
326 // Short time hard code.
327 // The port type and power range will be obtained from physical device in the future.
328 switch (devType) {
329 case OPTICAL_AMPLIFIER:
330 if (portType == PortDescType.PA_LINE_IN) {
331 return Range.closed(EDFA_POWER_IN_WEST_LOW_THRES, EDFA_POWER_IN_WEST_HIGH_THRES);
332 } else if (portType == PortDescType.BA_LINE_IN) {
333 return Range.closed(EDFA_POWER_IN_EAST_LOW_THRES, EDFA_POWER_IN_EAST_HIGH_THRES);
334 }
335 break;
336 case ROADM:
337 if (portType == PortDescType.PA_LINE_IN) {
338 return Range.closed(ROADM_POWER_LINE_IN_LOW_THRES, ROADM_POWER_LINE_IN_HIGH_THRES);
339 } else if (portType == PortDescType.EXP_IN || portType == PortDescType.AUX_IN) {
340 return Range.closed(ROADM_POWER_OTHER_IN_LOW_THRES, ROADM_POWER_OTHER_IN_HIGH_THRES);
341 }
342 break;
MaoLu937cf422017-03-03 23:31:46 -0800343 case FIBER_SWITCH:
344 return Range.closed(SWITCH_POWER_LOW_THRES, SWITCH_POWER_HIGH_THRES);
Mao Lu1f524702017-02-22 17:05:12 +0800345 default:
346 log.warn("Unexpected device type: {}", devType);
347 break;
348 }
349 // Unexpected port or device type. Do not need warning here for port polling.
350 return null;
351 }
352
353 /**
354 * Returns the acceptable attenuation range for a connection (represented as
355 * a flow with attenuation instruction). Port can be either the input or
356 * output port of the connection. Returns null if the connection does not
357 * support attenuation.
358 *
359 * @param portNum the port number
360 * @return attenuation range
361 */
362 private Range<Long> getChannelAttenuationRange(PortNumber portNum) {
363 OpenFlowSwitch ofs = getOpenFlowDevice();
364 if (ofs == null) {
365 return null;
366 }
367 if (ofs.deviceType() != Type.ROADM) {
368 return null;
369 }
370 PortDescType portType = getPortDescType((OpenFlowOpticalSwitch) ofs, portNum);
371 // Short time hard code.
372 // The port type and attenuation range will be obtained from physical device in the future.
373 if (portType == PortDescType.PA_LINE_OUT || portType == PortDescType.EXP_IN ||
374 portType == PortDescType.AUX_IN) {
375 return Range.closed(ROADM_MIN_ATTENUATION, ROADM_MAX_ATTENUATION);
376 }
377 // Unexpected port. Do not need warning here for port polling.
378 return null;
379 }
380
381 /**
382 * Find specified port power from port description.
383 *
384 * @param portNum the port number
385 * @param annotation annotation in port description
386 * @return power value in 0.01 dBm
387 */
388 private Long getPortPower(PortNumber portNum, String annotation) {
389 // Check if switch is connected, otherwise do not return value in store, which is obsolete.
390 if (getOpenFlowDevice() == null) {
391 // Warning already exists in method getOpenFlowDevice()
392 return null;
393 }
394 final DriverHandler handler = behaviour.handler();
395 DeviceService deviceService = handler.get(DeviceService.class);
396 Port port = deviceService.getPort(handler.data().deviceId(), portNum);
397 if (port == null) {
398 log.warn("Unexpected port: {}", portNum);
399 return null;
400 }
401 String power = port.annotations().value(annotation);
402 if (power == null) {
MaoLudd5a00b2017-03-14 11:19:48 -0700403 // Do not need warning here for port polling.
404 log.debug("Cannot get {} from port {}.", annotation, portNum);
Mao Lu1f524702017-02-22 17:05:12 +0800405 return null;
406 }
407 return Long.valueOf(power);
408 }
409
410 /**
411 * Sets specified port power value.
412 *
413 * @param portNum the port number
414 * @param power power value
415 */
416 private void setPortPower(PortNumber portNum, long power) {
417 OpenFlowSwitch device = getOpenFlowDevice();
418 // Check if switch is connected
419 if (device == null) {
420 return;
421 }
422 device.sendMsg(device.factory().buildOplinkPortPowerSet()
423 .setXid(xidCounter.getAndIncrement())
424 .setPort((int) portNum.toLong())
425 .setPowerValue((int) power)
426 .build());
427 }
428
429 /**
430 * Gets specified channel attenuation.
431 *
432 * @param portNum the port number
433 * @param och channel signal
434 * @return atteuation in 0.01 dB
435 */
436 private Long getChannelAttenuation(PortNumber portNum, OchSignal och) {
437 FlowEntry flowEntry = findFlow(portNum, och);
438 if (flowEntry == null) {
439 return null;
440 }
441 List<Instruction> instructions = flowEntry.treatment().allInstructions();
442 for (Instruction ins : instructions) {
443 if (ins.type() != Instruction.Type.EXTENSION) {
444 continue;
445 }
446 ExtensionTreatment ext = ((Instructions.ExtensionInstructionWrapper) ins).extensionInstruction();
447 if (ext.type() == ExtensionTreatmentType.ExtensionTreatmentTypes.OPLINK_ATTENUATION.type()) {
448 return (long) ((OplinkAttenuation) ext).getAttenuation();
449 }
450 }
451 return null;
452 }
453
454 /**
455 * Sets specified channle attenuation.
456 *
457 * @param portNum the port number
458 * @param och channel signal
459 * @param power attenuation in 0.01 dB
460 */
461 private void setChannelAttenuation(PortNumber portNum, OchSignal och, long power) {
462 FlowEntry flowEntry = findFlow(portNum, och);
463 if (flowEntry == null) {
464 log.warn("Target channel power not set");
465 return;
466 }
467 final DriverHandler handler = behaviour.handler();
468 for (Instruction ins : flowEntry.treatment().allInstructions()) {
469 if (ins.type() != Instruction.Type.EXTENSION) {
470 continue;
471 }
472 ExtensionTreatment ext = ((Instructions.ExtensionInstructionWrapper) ins).extensionInstruction();
473 if (ext.type() == ExtensionTreatmentType.ExtensionTreatmentTypes.OPLINK_ATTENUATION.type()) {
474 ((OplinkAttenuation) ext).setAttenuation((int) power);
475 FlowRuleService service = handler.get(FlowRuleService.class);
476 service.applyFlowRules(flowEntry);
477 return;
478 }
479 }
480 addAttenuation(flowEntry, power);
481 }
482
483 /**
484 * Gets specified channle current power.
485 *
486 * @param portNum the port number
487 * @param och channel signal
488 * @return power value in 0.01 dBm
489 */
490 private Long getCurrentChannelPower(PortNumber portNum, OchSignal och) {
491 FlowEntry flowEntry = findFlow(portNum, och);
492 if (flowEntry != null) {
493 // TODO put somewhere else if possible
494 // We put channel power in packets
495 return flowEntry.packets();
496 }
497 return null;
498 }
499
500 /**
501 * Find matching flow on device.
502 *
503 * @param portNum the port number
504 * @param och channel signal
505 * @return flow entry
506 */
507 private FlowEntry findFlow(PortNumber portNum, OchSignal och) {
508 final DriverHandler handler = behaviour.handler();
509 FlowRuleService service = handler.get(FlowRuleService.class);
510 Iterable<FlowEntry> flowEntries = service.getFlowEntries(handler.data().deviceId());
511
512 // Return first matching flow
513 for (FlowEntry entry : flowEntries) {
514 TrafficSelector selector = entry.selector();
515 OchSignalCriterion entrySigid =
516 (OchSignalCriterion) selector.getCriterion(Criterion.Type.OCH_SIGID);
517 // Check channel
518 if (entrySigid != null && och.equals(entrySigid.lambda())) {
519 // Check input port
520 PortCriterion entryPort =
521 (PortCriterion) selector.getCriterion(Criterion.Type.IN_PORT);
522 if (entryPort != null && portNum.equals(entryPort.port())) {
523 return entry;
524 }
525
526 // Check output port
527 TrafficTreatment treatment = entry.treatment();
528 for (Instruction instruction : treatment.allInstructions()) {
529 if (instruction.type() == Instruction.Type.OUTPUT &&
530 ((Instructions.OutputInstruction) instruction).port().equals(portNum)) {
531 return entry;
532 }
533 }
534 }
535 }
536 log.warn("No matching flow found");
537 return null;
538 }
539
540 /**
541 * Replace flow with new flow containing Oplink attenuation extension instruction. Also resets metrics.
542 *
543 * @param flowEntry flow entry
544 * @param power power value
545 */
546 private void addAttenuation(FlowEntry flowEntry, long power) {
547 FlowRule.Builder flowBuilder = new DefaultFlowRule.Builder()
548 .withCookie(flowEntry.id().value())
549 .withPriority(flowEntry.priority())
550 .forDevice(flowEntry.deviceId())
551 .forTable(flowEntry.tableId());
552 if (flowEntry.isPermanent()) {
553 flowBuilder.makePermanent();
554 } else {
555 flowBuilder.makeTemporary(flowEntry.timeout());
556 }
557 flowBuilder.withSelector(flowEntry.selector());
558 // Copy original instructions and add attenuation instruction
559 TrafficTreatment.Builder treatmentBuilder = DefaultTrafficTreatment.builder();
560 flowEntry.treatment().allInstructions().forEach(ins -> treatmentBuilder.add(ins));
561 final DriverHandler handler = behaviour.handler();
562 treatmentBuilder.add(Instructions.extension(new OplinkAttenuation((int) power), handler.data().deviceId()));
563 flowBuilder.withTreatment(treatmentBuilder.build());
564
565 FlowRuleService service = handler.get(FlowRuleService.class);
566 service.applyFlowRules(flowBuilder.build());
567 }
568}