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