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