blob: 0582e841c1c5088048503807d021a28c3476c122 [file] [log] [blame]
Daniele Moro94660a02019-12-02 12:02:07 -08001/*
Joey Armstronga68e7852024-01-28 13:27:02 -05002 * Copyright 2019-2024 Open Networking Foundation (ONF) and the ONF Contributors
Daniele Moro94660a02019-12-02 12:02:07 -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
Daniele Moroad7057d2020-01-15 10:54:37 -080017package org.opencord.bng.impl;
Daniele Moro94660a02019-12-02 12:02:07 -080018
19import com.google.common.collect.ImmutableSet;
20import com.google.common.collect.Maps;
21import org.apache.commons.lang3.tuple.Pair;
22import org.onlab.packet.EthType;
23import org.onlab.packet.IpAddress;
24import org.onlab.packet.MacAddress;
25import org.onlab.packet.VlanId;
26import org.onosproject.core.ApplicationId;
27import org.onosproject.core.CoreService;
28import org.onosproject.net.ConnectPoint;
29import org.onosproject.net.DefaultAnnotations;
30import org.onosproject.net.Device;
31import org.onosproject.net.DeviceId;
32import org.onosproject.net.Host;
33import org.onosproject.net.HostId;
34import org.onosproject.net.HostLocation;
35import org.onosproject.net.behaviour.BngProgrammable;
36import org.onosproject.net.behaviour.BngProgrammable.BngProgrammableException;
37import org.onosproject.net.config.ConfigFactory;
38import org.onosproject.net.config.NetworkConfigEvent;
39import org.onosproject.net.config.NetworkConfigListener;
40import org.onosproject.net.config.NetworkConfigRegistry;
41import org.onosproject.net.device.DeviceEvent;
42import org.onosproject.net.device.DeviceListener;
43import org.onosproject.net.device.DeviceService;
44import org.onosproject.net.host.DefaultHostDescription;
45import org.onosproject.net.host.HostDescription;
46import org.onosproject.net.host.HostProvider;
47import org.onosproject.net.host.HostProviderRegistry;
48import org.onosproject.net.host.HostProviderService;
49import org.onosproject.net.link.LinkService;
50import org.onosproject.net.provider.ProviderId;
Daniele Moroad7057d2020-01-15 10:54:37 -080051import org.opencord.bng.BngAttachment;
52import org.opencord.bng.BngService;
53import org.opencord.bng.PppoeBngAttachment;
Daniele Moro94660a02019-12-02 12:02:07 -080054import org.opencord.bng.config.BngConfig;
55import org.osgi.service.component.annotations.Activate;
56import org.osgi.service.component.annotations.Component;
57import org.osgi.service.component.annotations.Deactivate;
58import org.osgi.service.component.annotations.Reference;
59import org.osgi.service.component.annotations.ReferenceCardinality;
60import org.slf4j.Logger;
61import org.slf4j.LoggerFactory;
62
63import java.util.Map;
64import java.util.Optional;
65import java.util.Set;
66import java.util.concurrent.atomic.AtomicBoolean;
67import java.util.stream.Collectors;
68
69import static org.onosproject.net.config.basics.SubjectFactories.APP_SUBJECT_FACTORY;
70
71/**
72 * Implements the network level BNG service API to manage attachments.
73 */
Daniele Moroad7057d2020-01-15 10:54:37 -080074@Component(immediate = true)
Daniele Moro94660a02019-12-02 12:02:07 -080075public class BngManager implements HostProvider, BngService {
76 public static final String BNG_APP = "org.opencord.bng";
77
78 private static final ProviderId PROVIDER_ID = new ProviderId("bngapp", BngManager.BNG_APP);
79
80 private final Logger log = LoggerFactory.getLogger(getClass());
81 private final AtomicBoolean bnguInitialized = new AtomicBoolean(false);
82
83 @Reference(cardinality = ReferenceCardinality.MANDATORY)
84 protected LinkService linkService;
85
86 @Reference(cardinality = ReferenceCardinality.MANDATORY)
87 protected DeviceService deviceService;
88
89 @Reference(cardinality = ReferenceCardinality.MANDATORY)
90 protected CoreService coreService;
91
92 @Reference(cardinality = ReferenceCardinality.MANDATORY)
93 protected NetworkConfigRegistry cfgService;
94
95 @Reference(cardinality = ReferenceCardinality.MANDATORY)
96 protected HostProviderRegistry providerRegistry;
97
98 private ConfigFactory<ApplicationId, BngConfig> cfgFactory = new ConfigFactory<>(
99 APP_SUBJECT_FACTORY,
100 BngConfig.class,
101 BngConfig.KEY) {
102 @Override
103 public BngConfig createConfig() {
104 return new BngConfig();
105 }
106 };
107 private BngProgrammable bngProgrammable;
108 private DeviceId bngDeviceId;
109 private InternalDeviceListener deviceListener;
110 private InternalConfigListener cfgListener;
111 private HostProviderService hostProviderService;
112 // TODO: add support for other attachment type
113 private Map<String, Pair<BngAttachment, HostId>> registeredAttachment;
114 private ApplicationId appId;
115
116 @Activate
117 protected void activate() {
118 appId = coreService.registerApplication(BNG_APP);
119 hostProviderService = providerRegistry.register(this);
120 registeredAttachment = Maps.newHashMap();
121 bngProgrammable = null;
122 bngDeviceId = null;
123 deviceListener = new InternalDeviceListener();
124 cfgListener = new InternalConfigListener();
125 cfgService.addListener(cfgListener);
126 cfgService.registerConfigFactory(cfgFactory);
127
128 // Update the BNG relay configuration
129 updateConfig();
130
131 deviceService.addListener(deviceListener);
132
133 log.info("BNG app activated");
134 }
135
136 @Deactivate
137 protected void deactivate() {
138 providerRegistry.unregister(this);
139 if (bngProgrammableAvailable()) {
140 try {
141 bngProgrammable.cleanUp(appId);
142 } catch (BngProgrammableException e) {
Daniele Moro9818fc92020-03-23 16:18:45 +0100143 log.error("Error cleaning-up the BNG-U, {}", e.getMessage());
Daniele Moro94660a02019-12-02 12:02:07 -0800144 }
145 }
146 deviceService.removeListener(deviceListener);
147 cfgService.removeListener(cfgListener);
148 cfgService.unregisterConfigFactory(cfgFactory);
149 registeredAttachment = null;
150 bnguInitialized.set(false);
151 log.info("BNG app deactivated");
152 }
153
154 @Override
155 public void setupAttachment(String attachmentKey, BngAttachment attachment) {
156 // FIXME: the update case is not completely clear. Should the programAttachment method clean up the counters?
157 assert attachment.type().equals(BngProgrammable.Attachment.AttachmentType.PPPoE);
158 boolean updating = false;
159 var alreadyRegAttachment = registeredAttachment.get(attachmentKey);
160 if (alreadyRegAttachment == null) {
161 log.info("Registering a new attachment: {}", attachment.toString());
162 } else if (!attachment.equals(alreadyRegAttachment.getLeft())) {
163 log.info("Updating the attachment: {}", attachment.toString());
164 updating = true;
165 } else {
166 log.info("Attachment already registered: {}", attachment.toString());
167 return;
168 }
Daniele Moro9818fc92020-03-23 16:18:45 +0100169 // FIXME: it could register anyway the attachment but do not program it on the BNG-U.
Daniele Moro94660a02019-12-02 12:02:07 -0800170 if (attachment.type() != BngProgrammable.Attachment.AttachmentType.PPPoE) {
171 log.warn("Attachment type not supported, rejecting attachment: {}", attachmentKey);
172 return;
173 }
174 var pppoeAttachment = (PppoeBngAttachment) attachment;
175 // Retrieve the connect point on the ASG device
176 var asgConnectPoint = getAsgConnectPoint(pppoeAttachment.oltConnectPoint()).orElseThrow();
177 final HostId hostId = HostId.hostId(attachment.macAddress(), attachment.sTag());
178 final HostDescription hostDescription = createHostDescription(
179 attachment.cTag(), attachment.sTag(),
180 attachment.macAddress(), attachment.ipAddress(),
181 asgConnectPoint, pppoeAttachment.oltConnectPoint(), pppoeAttachment.onuSerial());
182
183 // Make sure that bngProgrammable is available and if so that the attachment is connected to the bngProgrammable
184 if (bngProgrammableAvailable() && isCorrectlyConnected(asgConnectPoint)) {
185 try {
186 programAttachment(attachment, hostId, hostDescription, updating);
187 } catch (BngProgrammableException ex) {
188 log.error("Attachment not created: " + ex.getMessage());
189 }
190 } else {
191 // If the BNG user plane is not available, or the attachment is not connected to
Daniele Moro9818fc92020-03-23 16:18:45 +0100192 // the correct BNG user plane, accept anyway the attachment.
193 // Check if the attachment is correctly connected to the BNG user plane when that device will show up.
Daniele Moroad7057d2020-01-15 10:54:37 -0800194 log.info("BNG user plane not available, attachment accepted but not programmed");
Daniele Moro94660a02019-12-02 12:02:07 -0800195 }
196 log.info("PPPoE Attachment created/updated: {}", pppoeAttachment);
197 registeredAttachment.put(attachmentKey, Pair.of(pppoeAttachment, hostId));
198 }
199
200 private Optional<ConnectPoint> getAsgConnectPoint(ConnectPoint oltConnectPoint) {
201 try {
Daniele Moro9818fc92020-03-23 16:18:45 +0100202 // Here I suppose that each OLT can be connected to a SINGLE ASG that is BNG user plane capable
Daniele Moro94660a02019-12-02 12:02:07 -0800203 return Optional.of(linkService.getDeviceEgressLinks(oltConnectPoint.deviceId()).stream()
204 .filter(link -> isBngProgrammable(link.dst().deviceId()))
205 .map(link -> link.dst())
206 .collect(Collectors.toList())
207 .get(0));
208
209 } catch (IndexOutOfBoundsException ex) {
210 return Optional.empty();
211 }
212 }
213
214 /**
215 * Setup of an attachment. Before calling this method, make sure that BNG
216 * programmable is available.
217 *
218 * @param attachment
219 * @param hostId
220 * @param hostDescription
221 * @param update
222 * @throws BngProgrammableException
223 */
224 private void programAttachment(BngAttachment attachment, HostId hostId,
225 HostDescription hostDescription, boolean update)
226 throws BngProgrammableException {
227 assert bngProgrammableAvailable();
228 bngProgrammable.setupAttachment(attachment);
229 if (!update) {
230 bngProgrammable.resetCounters(attachment);
231 }
232 // Trigger host creation in ONOS
233 hostProviderService.hostDetected(hostId, hostDescription, true);
234 }
235
236 /**
237 * Create an host description from the attachment information.
238 *
239 * @param cTag Vlan C-TAG.
240 * @param sTag Vlan S-TAG.
241 * @param hostMac MAC address of the attachment.
242 * @param hostIp IP address of the attachment.
243 * @param asgConnectPoint Attachment connect point from the ASG switch
244 * perspective.
245 * @param oltConnectPoint Attachment connect point from the OLT
246 * perspective.
247 * @param onuSerialNumber ONU Serial Number.
248 * @return Host description of the attachment
249 */
250 private HostDescription createHostDescription(VlanId cTag, VlanId sTag,
251 MacAddress hostMac,
252 IpAddress hostIp,
253 ConnectPoint asgConnectPoint,
254 ConnectPoint oltConnectPoint,
255 String onuSerialNumber) {
Daniele Moro86c2c7e2020-03-27 18:50:42 +0100256 Set<HostLocation> hostLocation = Set.of(new HostLocation(oltConnectPoint.deviceId(),
257 oltConnectPoint.port(),
258 System.currentTimeMillis()));
259 Set<HostLocation> auxLocation = Set.of(new HostLocation(asgConnectPoint.deviceId(),
260 asgConnectPoint.port(),
261 System.currentTimeMillis()));
Daniele Moro94660a02019-12-02 12:02:07 -0800262 var annotations = DefaultAnnotations.builder()
Daniele Moro94660a02019-12-02 12:02:07 -0800263 .set(ONU_ANNOTATION, onuSerialNumber)
264 .build();
265 Set<IpAddress> ips = hostIp != null
266 ? ImmutableSet.of(hostIp) : ImmutableSet.of();
267 return new DefaultHostDescription(hostMac, sTag,
Daniele Moro86c2c7e2020-03-27 18:50:42 +0100268 hostLocation, auxLocation,
Daniele Moro94660a02019-12-02 12:02:07 -0800269 ips, cTag, EthType.EtherType.QINQ.ethType(),
270 false, annotations);
271 }
272
273 @Override
274 public void removeAttachment(String attachmentKey) {
275 assert attachmentKey != null;
276 if (!registeredAttachment.containsKey(attachmentKey)) {
277 log.info("Attachment cannot be removed if it wasn't registered");
278 return;
279 }
280 var regAttachment = registeredAttachment.get(attachmentKey).getLeft();
281
282 final HostId hostToBeRemoved = HostId.hostId(regAttachment.macAddress(), regAttachment.sTag());
283 registeredAttachment.remove(attachmentKey);
Daniele Moro9818fc92020-03-23 16:18:45 +0100284 // Try to remove host even if the BNG user plane is not available
Daniele Moro94660a02019-12-02 12:02:07 -0800285 hostProviderService.hostVanished(hostToBeRemoved);
286 if (bngProgrammableAvailable()) {
Daniele Moroa3813672020-01-21 18:10:03 -0800287 try {
288 bngProgrammable.removeAttachment(regAttachment);
289 } catch (BngProgrammableException ex) {
290 log.error("Exception when removing the attachment: " + ex.getMessage());
291 }
Daniele Moro94660a02019-12-02 12:02:07 -0800292 } else {
Daniele Moro9818fc92020-03-23 16:18:45 +0100293 log.info("BNG-U not available!");
Daniele Moro94660a02019-12-02 12:02:07 -0800294 }
295 log.info("Attachment {} removed successfully!", regAttachment);
296 }
297
298
299 @Override
300 public Map<String, BngAttachment> getAttachments() {
301 return Maps.asMap(registeredAttachment.keySet(),
302 key -> registeredAttachment.get(key).getLeft());
303 }
304
305 @Override
306 public BngAttachment getAttachment(String attachmentKey) {
307 return registeredAttachment.getOrDefault(attachmentKey, Pair.of(null, null))
308 .getLeft();
309 }
310
311 /**
312 * Check if the given connect point is part of the BNG user plane device.
313 * Before calling this method, make sure that bngProgrammable is available.
314 *
315 * @param asgConnectPoint The connect point to check
316 * @return
317 */
318 private boolean isCorrectlyConnected(ConnectPoint asgConnectPoint) {
319 assert bngProgrammableAvailable();
320 return asgConnectPoint.deviceId().equals(bngProgrammable.data().deviceId());
321 }
322
323 /**
324 * Setup of the BNG user plane device. This method will cleanup the BNG
325 * pipeline, initialize it and then submit all the attachment already
326 * registered.
327 *
328 * @param deviceId BNG user plane device ID
329 */
330 private void setBngDevice(DeviceId deviceId) {
331 synchronized (bnguInitialized) {
332 if (bnguInitialized.get()) {
Daniele Moro9818fc92020-03-23 16:18:45 +0100333 log.debug("BNG-U {} already initialized", deviceId);
334 return;
335 }
336 if (!deviceService.isAvailable(deviceId)) {
337 log.info("BNG-U is currently unavailable, skip setup");
Daniele Moro94660a02019-12-02 12:02:07 -0800338 return;
339 }
340 if (!isBngProgrammable(deviceId)) {
341 log.warn("{} is not BNG-U", deviceId);
342 return;
343 }
344 if (bngProgrammable != null && !bngProgrammable.data().deviceId().equals(deviceId)) {
345 log.error("Change of the BNG-U while BNG-U device is available is not supported!");
346 return;
347 }
348
349 bngProgrammable = deviceService.getDevice(deviceId).as(BngProgrammable.class);
Daniele Moro9818fc92020-03-23 16:18:45 +0100350 log.info("Setup BNG-U: {}", deviceId);
Daniele Moro94660a02019-12-02 12:02:07 -0800351
352 // Initialize behavior
353 try {
354 bngProgrammable.cleanUp(appId);
355 bngProgrammable.init(appId);
356 // FIXME: we can improve this re-registration, keeping track of which attachment
357 // already has the flow rules submitted in the flow rule subsystem.
358 // In this way we do not need to cleanUp the bngProgrammable every time it come back online.
359 // If there is any already registered attachment, try to re-setup their attachment.
360 resubmitRegisteredAttachment();
Daniele Moro94660a02019-12-02 12:02:07 -0800361 bnguInitialized.set(true);
Daniele Moro9818fc92020-03-23 16:18:45 +0100362 log.info("BNG-U setup successful!");
Daniele Moro94660a02019-12-02 12:02:07 -0800363 } catch (BngProgrammableException e) {
Daniele Moro9818fc92020-03-23 16:18:45 +0100364 log.error("Error setup BNG-U, {}", e.getMessage());
Daniele Moro94660a02019-12-02 12:02:07 -0800365 }
366 }
367 }
368
369 /**
370 * Resubmit all the attachments to the BNG user plane device. Before calling
371 * this method, make sure that bngProgrammable is available
372 *
373 * @throws BngProgrammableException when error in BNG user plane device.
374 */
375 private void resubmitRegisteredAttachment() throws BngProgrammableException {
376 assert bngProgrammableAvailable();
377 for (var registeredAttachemnt : registeredAttachment.entrySet()) {
378 var attachment = registeredAttachemnt.getValue().getLeft();
379 var host = registeredAttachemnt.getValue().getRight();
380 var attachentKey = registeredAttachemnt.getKey();
381 var asgConnectPoint = getAsgConnectPoint(attachment.oltConnectPoint());
382 if (attachment.type() != BngProgrammable.Attachment.AttachmentType.PPPoE) {
383 log.info("Unsupported attachment: {}", attachentKey);
384 continue;
385 }
386 if (asgConnectPoint.isPresent() && isCorrectlyConnected(asgConnectPoint.orElseThrow())) {
387 HostDescription hostDescription = createHostDescription(
388 attachment.cTag(), attachment.sTag(),
389 attachment.macAddress(), attachment.ipAddress(),
390 asgConnectPoint.orElseThrow(), attachment.oltConnectPoint(),
391 attachment.onuSerial());
392 // When resubmitting registered attachment act as the attachment is being setting up.
393 programAttachment(attachment, host, hostDescription, false);
394 } else {
395 log.info("Attachment is not connected to a valid BNG user plane: {}", attachment);
396 }
397 }
398 }
399
400 /**
401 * Unset the BNG user plane device. If available it will be cleaned-up.
402 */
403 private void unsetBngDevice() {
404 synchronized (bnguInitialized) {
405 if (bngProgrammable != null) {
Daniele Moro9818fc92020-03-23 16:18:45 +0100406 log.info("BNG-U cleanup");
Daniele Moro94660a02019-12-02 12:02:07 -0800407 try {
408 bngProgrammable.cleanUp(appId);
409 } catch (BngProgrammableException e) {
Daniele Moro9818fc92020-03-23 16:18:45 +0100410 log.error("Error in BNG-U, {}", e.getMessage());
Daniele Moro94660a02019-12-02 12:02:07 -0800411 }
412 bngProgrammable = null;
413 bnguInitialized.set(false);
414 }
415 }
416 }
417
418 /**
419 * Check if the device is registered and is BNG user plane.
420 *
421 * @param deviceId
422 * @return
423 */
424 private boolean isBngProgrammable(DeviceId deviceId) {
425 final Device device = deviceService.getDevice(deviceId);
426 return device != null && device.is(BngProgrammable.class);
427 }
428
429 /**
430 * Check if the BNG user plane is available.
431 *
432 * @return
433 * @throws BngProgrammableException
434 */
435 private boolean bngProgrammableAvailable() {
436 return bngProgrammable != null;
437 }
438
439 private void bngUpdateConfig(BngConfig config) {
440 if (config.isValid()) {
441 bngDeviceId = config.getBnguDeviceId();
442 setBngDevice(bngDeviceId);
443 }
444 }
445
446 @Override
447 public DeviceId getBngDeviceId() {
448 return bngDeviceId;
449 }
450
451 /**
452 * Updates BNG app configuration.
453 */
454 private void updateConfig() {
455 BngConfig bngConfig = cfgService.getConfig(appId, BngConfig.class);
456 if (bngConfig != null) {
457 bngUpdateConfig(bngConfig);
458 }
459 }
460
461 @Override
462 public void triggerProbe(Host host) {
463 // Do nothing here
464 }
465
466 @Override
467 public ProviderId id() {
468 return PROVIDER_ID;
469 }
470
471 /**
472 * React to new devices. The first device recognized to have BNG-U
473 * functionality is taken as BNG-U device.
474 */
475 private class InternalDeviceListener implements DeviceListener {
476 @Override
477 public void event(DeviceEvent event) {
478 DeviceId deviceId = event.subject().id();
479 if (deviceId.equals(bngDeviceId)) {
480 switch (event.type()) {
481 case DEVICE_ADDED:
482 case DEVICE_UPDATED:
483 case DEVICE_AVAILABILITY_CHANGED:
Daniele Moro9818fc92020-03-23 16:18:45 +0100484 if (deviceService.isAvailable(deviceId)) {
485 log.debug("Event: {}, setting BNG-U", event.type());
486 setBngDevice(deviceId);
487 }
Daniele Moro94660a02019-12-02 12:02:07 -0800488 break;
489 case DEVICE_REMOVED:
490 case DEVICE_SUSPENDED:
491 unsetBngDevice();
492 break;
493 case PORT_ADDED:
494 case PORT_UPDATED:
495 case PORT_REMOVED:
496 case PORT_STATS_UPDATED:
497 break;
498 default:
499 log.warn("Unknown device event type {}", event.type());
500 }
501 }
502 }
503 }
504
505
506 /**
507 * Listener for network config events.
508 */
509 private class InternalConfigListener implements NetworkConfigListener {
510 @Override
511 public void event(NetworkConfigEvent event) {
512 switch (event.type()) {
513 case CONFIG_UPDATED:
514 case CONFIG_ADDED:
515 event.config().ifPresent(config -> {
516 bngUpdateConfig((BngConfig) config);
517 log.info("{} updated", config.getClass().getSimpleName());
518 });
519 break;
520 case CONFIG_REMOVED:
521 event.prevConfig().ifPresent(config -> {
522 unsetBngDevice();
523 log.info("{} removed", config.getClass().getSimpleName());
524 });
525 break;
526 case CONFIG_REGISTERED:
527 case CONFIG_UNREGISTERED:
528 break;
529 default:
530 log.warn("Unsupported event type {}", event.type());
531 break;
532 }
533 }
534
535 @Override
536 public boolean isRelevant(NetworkConfigEvent event) {
537 if (event.configClass().equals(BngConfig.class)) {
538 return true;
539 }
540 log.debug("Ignore irrelevant event class {}", event.configClass().getName());
541 return false;
542 }
543 }
544}