blob: fa5c75527bad2d4d538b69e307d73b82020a84e1 [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) {
143 log.error("Error cleaning-up the BNG pipeline, {}", e.getMessage());
144 }
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 }
169 // FIXME: it could register anyway the attachment but do not program it on the BNG data-plane device.
170 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
192 // the correct BNG user planee, accept anyway the attachment.
193 // Check if the attachment is correctly connected to the BNG device 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 {
202 // Here I suppose that each OLT can be connected to a SINGLE ASG that is BNG U capable
203 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 Moroa3813672020-01-21 18:10:03 -0800256 Set<HostLocation> hostLocation = Set.of(new HostLocation(oltConnectPoint.deviceId(),
257 oltConnectPoint.port(),
Daniele Moro94660a02019-12-02 12:02:07 -0800258 System.currentTimeMillis()));
Daniele Moroa3813672020-01-21 18:10:03 -0800259 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 Moroa3813672020-01-21 18:10:03 -0800268 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);
284 // Try to remove host even if the BNG user plane device is not available
285 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 {
293 log.info("BNG user plane not available!");
294 }
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()) {
333 log.debug("BNG device {} already initialized", deviceId);
334 return;
335 }
336 if (!isBngProgrammable(deviceId)) {
337 log.warn("{} is not BNG-U", deviceId);
338 return;
339 }
340 if (bngProgrammable != null && !bngProgrammable.data().deviceId().equals(deviceId)) {
341 log.error("Change of the BNG-U while BNG-U device is available is not supported!");
342 return;
343 }
344
345 bngProgrammable = deviceService.getDevice(deviceId).as(BngProgrammable.class);
346 log.info("Program BNG-U device {}", deviceId);
347
348 // Initialize behavior
349 try {
350 bngProgrammable.cleanUp(appId);
351 bngProgrammable.init(appId);
352 // FIXME: we can improve this re-registration, keeping track of which attachment
353 // already has the flow rules submitted in the flow rule subsystem.
354 // In this way we do not need to cleanUp the bngProgrammable every time it come back online.
355 // If there is any already registered attachment, try to re-setup their attachment.
356 resubmitRegisteredAttachment();
357
358 bnguInitialized.set(true);
359 } catch (BngProgrammableException e) {
360 log.error("Error in BNG user plane, {}", e.getMessage());
361 }
362 }
363 }
364
365 /**
366 * Resubmit all the attachments to the BNG user plane device. Before calling
367 * this method, make sure that bngProgrammable is available
368 *
369 * @throws BngProgrammableException when error in BNG user plane device.
370 */
371 private void resubmitRegisteredAttachment() throws BngProgrammableException {
372 assert bngProgrammableAvailable();
373 for (var registeredAttachemnt : registeredAttachment.entrySet()) {
374 var attachment = registeredAttachemnt.getValue().getLeft();
375 var host = registeredAttachemnt.getValue().getRight();
376 var attachentKey = registeredAttachemnt.getKey();
377 var asgConnectPoint = getAsgConnectPoint(attachment.oltConnectPoint());
378 if (attachment.type() != BngProgrammable.Attachment.AttachmentType.PPPoE) {
379 log.info("Unsupported attachment: {}", attachentKey);
380 continue;
381 }
382 if (asgConnectPoint.isPresent() && isCorrectlyConnected(asgConnectPoint.orElseThrow())) {
383 HostDescription hostDescription = createHostDescription(
384 attachment.cTag(), attachment.sTag(),
385 attachment.macAddress(), attachment.ipAddress(),
386 asgConnectPoint.orElseThrow(), attachment.oltConnectPoint(),
387 attachment.onuSerial());
388 // When resubmitting registered attachment act as the attachment is being setting up.
389 programAttachment(attachment, host, hostDescription, false);
390 } else {
391 log.info("Attachment is not connected to a valid BNG user plane: {}", attachment);
392 }
393 }
394 }
395
396 /**
397 * Unset the BNG user plane device. If available it will be cleaned-up.
398 */
399 private void unsetBngDevice() {
400 synchronized (bnguInitialized) {
401 if (bngProgrammable != null) {
402 try {
403 bngProgrammable.cleanUp(appId);
404 } catch (BngProgrammableException e) {
405 log.error("Error in BNG user plane, {}", e.getMessage());
406 }
407 bngProgrammable = null;
408 bnguInitialized.set(false);
409 }
410 }
411 }
412
413 /**
414 * Check if the device is registered and is BNG user plane.
415 *
416 * @param deviceId
417 * @return
418 */
419 private boolean isBngProgrammable(DeviceId deviceId) {
420 final Device device = deviceService.getDevice(deviceId);
421 return device != null && device.is(BngProgrammable.class);
422 }
423
424 /**
425 * Check if the BNG user plane is available.
426 *
427 * @return
428 * @throws BngProgrammableException
429 */
430 private boolean bngProgrammableAvailable() {
431 return bngProgrammable != null;
432 }
433
434 private void bngUpdateConfig(BngConfig config) {
435 if (config.isValid()) {
436 bngDeviceId = config.getBnguDeviceId();
437 setBngDevice(bngDeviceId);
438 }
439 }
440
441 @Override
442 public DeviceId getBngDeviceId() {
443 return bngDeviceId;
444 }
445
446 /**
447 * Updates BNG app configuration.
448 */
449 private void updateConfig() {
450 BngConfig bngConfig = cfgService.getConfig(appId, BngConfig.class);
451 if (bngConfig != null) {
452 bngUpdateConfig(bngConfig);
453 }
454 }
455
456 @Override
457 public void triggerProbe(Host host) {
458 // Do nothing here
459 }
460
461 @Override
462 public ProviderId id() {
463 return PROVIDER_ID;
464 }
465
466 /**
467 * React to new devices. The first device recognized to have BNG-U
468 * functionality is taken as BNG-U device.
469 */
470 private class InternalDeviceListener implements DeviceListener {
471 @Override
472 public void event(DeviceEvent event) {
473 DeviceId deviceId = event.subject().id();
474 if (deviceId.equals(bngDeviceId)) {
475 switch (event.type()) {
476 case DEVICE_ADDED:
477 case DEVICE_UPDATED:
478 case DEVICE_AVAILABILITY_CHANGED:
479 // FIXME: do I need the IF?
480 //if (deviceService.isAvailable(deviceId)) {
481 log.warn("Event: {}, SETTING BNG device", event.type());
482 setBngDevice(deviceId);
483 //}
484 break;
485 case DEVICE_REMOVED:
486 case DEVICE_SUSPENDED:
487 unsetBngDevice();
488 break;
489 case PORT_ADDED:
490 case PORT_UPDATED:
491 case PORT_REMOVED:
492 case PORT_STATS_UPDATED:
493 break;
494 default:
495 log.warn("Unknown device event type {}", event.type());
496 }
497 }
498 }
499 }
500
501
502 /**
503 * Listener for network config events.
504 */
505 private class InternalConfigListener implements NetworkConfigListener {
506 @Override
507 public void event(NetworkConfigEvent event) {
508 switch (event.type()) {
509 case CONFIG_UPDATED:
510 case CONFIG_ADDED:
511 event.config().ifPresent(config -> {
512 bngUpdateConfig((BngConfig) config);
513 log.info("{} updated", config.getClass().getSimpleName());
514 });
515 break;
516 case CONFIG_REMOVED:
517 event.prevConfig().ifPresent(config -> {
518 unsetBngDevice();
519 log.info("{} removed", config.getClass().getSimpleName());
520 });
521 break;
522 case CONFIG_REGISTERED:
523 case CONFIG_UNREGISTERED:
524 break;
525 default:
526 log.warn("Unsupported event type {}", event.type());
527 break;
528 }
529 }
530
531 @Override
532 public boolean isRelevant(NetworkConfigEvent event) {
533 if (event.configClass().equals(BngConfig.class)) {
534 return true;
535 }
536 log.debug("Ignore irrelevant event class {}", event.configClass().getName());
537 return false;
538 }
539 }
540}