blob: 3a76aaa0c2ce3c887eb4329076a796573620c04e [file] [log] [blame]
Daniele Moro94660a02019-12-02 12:02:07 -08001/*
2 * Copyright 2019-present Open Networking Foundation
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
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 Morod2d56202020-03-02 18:06:59 -0800256// FIXME: use auxLocation when ONOS bug is fixed.
257// Set<HostLocation> auxLocation = Set.of(new HostLocation(asgConnectPoint.deviceId(),
258// asgConnectPoint.port(),
259// System.currentTimeMillis()));
260// Set<HostLocation> hostLocation = Set.of(new HostLocation(oltConnectPoint.deviceId(),
261// oltConnectPoint.port(),
262// System.currentTimeMillis()));
263
264 Set<HostLocation> hostLocation = Set.of(new HostLocation(asgConnectPoint.deviceId(),
265 asgConnectPoint.port(),
266 System.currentTimeMillis()));
267
Daniele Moro94660a02019-12-02 12:02:07 -0800268 var annotations = DefaultAnnotations.builder()
Daniele Moro94660a02019-12-02 12:02:07 -0800269 .set(ONU_ANNOTATION, onuSerialNumber)
270 .build();
271 Set<IpAddress> ips = hostIp != null
272 ? ImmutableSet.of(hostIp) : ImmutableSet.of();
273 return new DefaultHostDescription(hostMac, sTag,
Daniele Morod2d56202020-03-02 18:06:59 -0800274 hostLocation,
275// auxLocation,
Daniele Moro94660a02019-12-02 12:02:07 -0800276 ips, cTag, EthType.EtherType.QINQ.ethType(),
277 false, annotations);
278 }
279
280 @Override
281 public void removeAttachment(String attachmentKey) {
282 assert attachmentKey != null;
283 if (!registeredAttachment.containsKey(attachmentKey)) {
284 log.info("Attachment cannot be removed if it wasn't registered");
285 return;
286 }
287 var regAttachment = registeredAttachment.get(attachmentKey).getLeft();
288
289 final HostId hostToBeRemoved = HostId.hostId(regAttachment.macAddress(), regAttachment.sTag());
290 registeredAttachment.remove(attachmentKey);
Daniele Moro9818fc92020-03-23 16:18:45 +0100291 // Try to remove host even if the BNG user plane is not available
Daniele Moro94660a02019-12-02 12:02:07 -0800292 hostProviderService.hostVanished(hostToBeRemoved);
293 if (bngProgrammableAvailable()) {
Daniele Moroa3813672020-01-21 18:10:03 -0800294 try {
295 bngProgrammable.removeAttachment(regAttachment);
296 } catch (BngProgrammableException ex) {
297 log.error("Exception when removing the attachment: " + ex.getMessage());
298 }
Daniele Moro94660a02019-12-02 12:02:07 -0800299 } else {
Daniele Moro9818fc92020-03-23 16:18:45 +0100300 log.info("BNG-U not available!");
Daniele Moro94660a02019-12-02 12:02:07 -0800301 }
302 log.info("Attachment {} removed successfully!", regAttachment);
303 }
304
305
306 @Override
307 public Map<String, BngAttachment> getAttachments() {
308 return Maps.asMap(registeredAttachment.keySet(),
309 key -> registeredAttachment.get(key).getLeft());
310 }
311
312 @Override
313 public BngAttachment getAttachment(String attachmentKey) {
314 return registeredAttachment.getOrDefault(attachmentKey, Pair.of(null, null))
315 .getLeft();
316 }
317
318 /**
319 * Check if the given connect point is part of the BNG user plane device.
320 * Before calling this method, make sure that bngProgrammable is available.
321 *
322 * @param asgConnectPoint The connect point to check
323 * @return
324 */
325 private boolean isCorrectlyConnected(ConnectPoint asgConnectPoint) {
326 assert bngProgrammableAvailable();
327 return asgConnectPoint.deviceId().equals(bngProgrammable.data().deviceId());
328 }
329
330 /**
331 * Setup of the BNG user plane device. This method will cleanup the BNG
332 * pipeline, initialize it and then submit all the attachment already
333 * registered.
334 *
335 * @param deviceId BNG user plane device ID
336 */
337 private void setBngDevice(DeviceId deviceId) {
338 synchronized (bnguInitialized) {
339 if (bnguInitialized.get()) {
Daniele Moro9818fc92020-03-23 16:18:45 +0100340 log.debug("BNG-U {} already initialized", deviceId);
341 return;
342 }
343 if (!deviceService.isAvailable(deviceId)) {
344 log.info("BNG-U is currently unavailable, skip setup");
Daniele Moro94660a02019-12-02 12:02:07 -0800345 return;
346 }
347 if (!isBngProgrammable(deviceId)) {
348 log.warn("{} is not BNG-U", deviceId);
349 return;
350 }
351 if (bngProgrammable != null && !bngProgrammable.data().deviceId().equals(deviceId)) {
352 log.error("Change of the BNG-U while BNG-U device is available is not supported!");
353 return;
354 }
355
356 bngProgrammable = deviceService.getDevice(deviceId).as(BngProgrammable.class);
Daniele Moro9818fc92020-03-23 16:18:45 +0100357 log.info("Setup BNG-U: {}", deviceId);
Daniele Moro94660a02019-12-02 12:02:07 -0800358
359 // Initialize behavior
360 try {
361 bngProgrammable.cleanUp(appId);
362 bngProgrammable.init(appId);
363 // FIXME: we can improve this re-registration, keeping track of which attachment
364 // already has the flow rules submitted in the flow rule subsystem.
365 // In this way we do not need to cleanUp the bngProgrammable every time it come back online.
366 // If there is any already registered attachment, try to re-setup their attachment.
367 resubmitRegisteredAttachment();
Daniele Moro94660a02019-12-02 12:02:07 -0800368 bnguInitialized.set(true);
Daniele Moro9818fc92020-03-23 16:18:45 +0100369 log.info("BNG-U setup successful!");
Daniele Moro94660a02019-12-02 12:02:07 -0800370 } catch (BngProgrammableException e) {
Daniele Moro9818fc92020-03-23 16:18:45 +0100371 log.error("Error setup BNG-U, {}", e.getMessage());
Daniele Moro94660a02019-12-02 12:02:07 -0800372 }
373 }
374 }
375
376 /**
377 * Resubmit all the attachments to the BNG user plane device. Before calling
378 * this method, make sure that bngProgrammable is available
379 *
380 * @throws BngProgrammableException when error in BNG user plane device.
381 */
382 private void resubmitRegisteredAttachment() throws BngProgrammableException {
383 assert bngProgrammableAvailable();
384 for (var registeredAttachemnt : registeredAttachment.entrySet()) {
385 var attachment = registeredAttachemnt.getValue().getLeft();
386 var host = registeredAttachemnt.getValue().getRight();
387 var attachentKey = registeredAttachemnt.getKey();
388 var asgConnectPoint = getAsgConnectPoint(attachment.oltConnectPoint());
389 if (attachment.type() != BngProgrammable.Attachment.AttachmentType.PPPoE) {
390 log.info("Unsupported attachment: {}", attachentKey);
391 continue;
392 }
393 if (asgConnectPoint.isPresent() && isCorrectlyConnected(asgConnectPoint.orElseThrow())) {
394 HostDescription hostDescription = createHostDescription(
395 attachment.cTag(), attachment.sTag(),
396 attachment.macAddress(), attachment.ipAddress(),
397 asgConnectPoint.orElseThrow(), attachment.oltConnectPoint(),
398 attachment.onuSerial());
399 // When resubmitting registered attachment act as the attachment is being setting up.
400 programAttachment(attachment, host, hostDescription, false);
401 } else {
402 log.info("Attachment is not connected to a valid BNG user plane: {}", attachment);
403 }
404 }
405 }
406
407 /**
408 * Unset the BNG user plane device. If available it will be cleaned-up.
409 */
410 private void unsetBngDevice() {
411 synchronized (bnguInitialized) {
412 if (bngProgrammable != null) {
Daniele Moro9818fc92020-03-23 16:18:45 +0100413 log.info("BNG-U cleanup");
Daniele Moro94660a02019-12-02 12:02:07 -0800414 try {
415 bngProgrammable.cleanUp(appId);
416 } catch (BngProgrammableException e) {
Daniele Moro9818fc92020-03-23 16:18:45 +0100417 log.error("Error in BNG-U, {}", e.getMessage());
Daniele Moro94660a02019-12-02 12:02:07 -0800418 }
419 bngProgrammable = null;
420 bnguInitialized.set(false);
421 }
422 }
423 }
424
425 /**
426 * Check if the device is registered and is BNG user plane.
427 *
428 * @param deviceId
429 * @return
430 */
431 private boolean isBngProgrammable(DeviceId deviceId) {
432 final Device device = deviceService.getDevice(deviceId);
433 return device != null && device.is(BngProgrammable.class);
434 }
435
436 /**
437 * Check if the BNG user plane is available.
438 *
439 * @return
440 * @throws BngProgrammableException
441 */
442 private boolean bngProgrammableAvailable() {
443 return bngProgrammable != null;
444 }
445
446 private void bngUpdateConfig(BngConfig config) {
447 if (config.isValid()) {
448 bngDeviceId = config.getBnguDeviceId();
449 setBngDevice(bngDeviceId);
450 }
451 }
452
453 @Override
454 public DeviceId getBngDeviceId() {
455 return bngDeviceId;
456 }
457
458 /**
459 * Updates BNG app configuration.
460 */
461 private void updateConfig() {
462 BngConfig bngConfig = cfgService.getConfig(appId, BngConfig.class);
463 if (bngConfig != null) {
464 bngUpdateConfig(bngConfig);
465 }
466 }
467
468 @Override
469 public void triggerProbe(Host host) {
470 // Do nothing here
471 }
472
473 @Override
474 public ProviderId id() {
475 return PROVIDER_ID;
476 }
477
478 /**
479 * React to new devices. The first device recognized to have BNG-U
480 * functionality is taken as BNG-U device.
481 */
482 private class InternalDeviceListener implements DeviceListener {
483 @Override
484 public void event(DeviceEvent event) {
485 DeviceId deviceId = event.subject().id();
486 if (deviceId.equals(bngDeviceId)) {
487 switch (event.type()) {
488 case DEVICE_ADDED:
489 case DEVICE_UPDATED:
490 case DEVICE_AVAILABILITY_CHANGED:
Daniele Moro9818fc92020-03-23 16:18:45 +0100491 if (deviceService.isAvailable(deviceId)) {
492 log.debug("Event: {}, setting BNG-U", event.type());
493 setBngDevice(deviceId);
494 }
Daniele Moro94660a02019-12-02 12:02:07 -0800495 break;
496 case DEVICE_REMOVED:
497 case DEVICE_SUSPENDED:
498 unsetBngDevice();
499 break;
500 case PORT_ADDED:
501 case PORT_UPDATED:
502 case PORT_REMOVED:
503 case PORT_STATS_UPDATED:
504 break;
505 default:
506 log.warn("Unknown device event type {}", event.type());
507 }
508 }
509 }
510 }
511
512
513 /**
514 * Listener for network config events.
515 */
516 private class InternalConfigListener implements NetworkConfigListener {
517 @Override
518 public void event(NetworkConfigEvent event) {
519 switch (event.type()) {
520 case CONFIG_UPDATED:
521 case CONFIG_ADDED:
522 event.config().ifPresent(config -> {
523 bngUpdateConfig((BngConfig) config);
524 log.info("{} updated", config.getClass().getSimpleName());
525 });
526 break;
527 case CONFIG_REMOVED:
528 event.prevConfig().ifPresent(config -> {
529 unsetBngDevice();
530 log.info("{} removed", config.getClass().getSimpleName());
531 });
532 break;
533 case CONFIG_REGISTERED:
534 case CONFIG_UNREGISTERED:
535 break;
536 default:
537 log.warn("Unsupported event type {}", event.type());
538 break;
539 }
540 }
541
542 @Override
543 public boolean isRelevant(NetworkConfigEvent event) {
544 if (event.configClass().equals(BngConfig.class)) {
545 return true;
546 }
547 log.debug("Ignore irrelevant event class {}", event.configClass().getName());
548 return false;
549 }
550 }
551}