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