blob: b7804bbe19d7e13c6529edfb896e4426df130c04 [file] [log] [blame]
Yi Tsenge616d752018-11-27 10:53:27 -08001/*
2 * Copyright 2018-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.onosproject.provider.general.device.impl;
18
19import com.google.common.annotations.Beta;
Carmelo Casconeab5d41e2019-03-06 18:02:34 -080020import com.google.common.collect.Maps;
Yi Tsenge616d752018-11-27 10:53:27 -080021import com.google.common.util.concurrent.Striped;
22import gnmi.Gnmi.Notification;
23import gnmi.Gnmi.Path;
24import gnmi.Gnmi.PathElem;
25import gnmi.Gnmi.SubscribeRequest;
26import gnmi.Gnmi.Subscription;
27import gnmi.Gnmi.SubscriptionList;
28import gnmi.Gnmi.SubscriptionMode;
29import gnmi.Gnmi.Update;
Yi Tsenge616d752018-11-27 10:53:27 -080030import org.onosproject.gnmi.api.GnmiController;
31import org.onosproject.gnmi.api.GnmiEvent;
32import org.onosproject.gnmi.api.GnmiEventListener;
33import org.onosproject.gnmi.api.GnmiUpdate;
34import org.onosproject.gnmi.api.GnmiUtils;
35import org.onosproject.mastership.MastershipEvent;
36import org.onosproject.mastership.MastershipListener;
37import org.onosproject.mastership.MastershipService;
Yi Tseng59d5f3e2018-11-27 23:09:41 -080038import org.onosproject.net.DefaultAnnotations;
Yi Tsenge616d752018-11-27 10:53:27 -080039import org.onosproject.net.DeviceId;
40import org.onosproject.net.Port;
Carmelo Casconeab5d41e2019-03-06 18:02:34 -080041import org.onosproject.net.PortNumber;
Yi Tsenge616d752018-11-27 10:53:27 -080042import org.onosproject.net.device.DefaultPortDescription;
43import org.onosproject.net.device.DeviceEvent;
44import org.onosproject.net.device.DeviceListener;
45import org.onosproject.net.device.DeviceProviderService;
46import org.onosproject.net.device.DeviceService;
47import org.onosproject.net.device.PortDescription;
48import org.slf4j.Logger;
49import org.slf4j.LoggerFactory;
50
Yi Tsenge616d752018-11-27 10:53:27 -080051import java.util.List;
Carmelo Casconeab5d41e2019-03-06 18:02:34 -080052import java.util.Map;
53import java.util.Objects;
54import java.util.Set;
Yi Tsenge616d752018-11-27 10:53:27 -080055import java.util.concurrent.locks.Lock;
Carmelo Casconeab5d41e2019-03-06 18:02:34 -080056import java.util.stream.Collectors;
Yi Tsenge616d752018-11-27 10:53:27 -080057
58/**
59 * Entity that manages gNMI subscription for devices using OpenConfig models and
60 * that reports relevant events to the core.
61 */
62@Beta
63class GnmiDeviceStateSubscriber {
64
Carmelo Casconeab5d41e2019-03-06 18:02:34 -080065 private static final String LAST_CHANGE = "last-changed";
Yi Tseng59d5f3e2018-11-27 23:09:41 -080066
Yi Tsenge616d752018-11-27 10:53:27 -080067 private static Logger log = LoggerFactory.getLogger(GnmiDeviceStateSubscriber.class);
68
69 private final GnmiController gnmiController;
70 private final DeviceService deviceService;
71 private final DeviceProviderService providerService;
72 private final MastershipService mastershipService;
73
Yi Tsenge616d752018-11-27 10:53:27 -080074 private final InternalGnmiEventListener gnmiEventListener = new InternalGnmiEventListener();
75 private final InternalDeviceListener deviceEventListener = new InternalDeviceListener();
76 private final InternalMastershipListener mastershipListener = new InternalMastershipListener();
Carmelo Casconeab5d41e2019-03-06 18:02:34 -080077 private final Map<DeviceId, Set<PortNumber>> deviceSubscribed = Maps.newHashMap();
Yi Tsenge616d752018-11-27 10:53:27 -080078
79 private final Striped<Lock> deviceLocks = Striped.lock(30);
80
81 GnmiDeviceStateSubscriber(GnmiController gnmiController, DeviceService deviceService,
82 MastershipService mastershipService,
83 DeviceProviderService providerService) {
84 this.gnmiController = gnmiController;
85 this.deviceService = deviceService;
86 this.mastershipService = mastershipService;
87 this.providerService = providerService;
88 }
89
90 public void activate() {
91 deviceService.addListener(deviceEventListener);
92 mastershipService.addListener(mastershipListener);
93 gnmiController.addListener(gnmiEventListener);
94 // Subscribe to existing devices.
Carmelo Casconeab5d41e2019-03-06 18:02:34 -080095 deviceService.getDevices().forEach(d -> checkSubscription(d.id()));
Yi Tsenge616d752018-11-27 10:53:27 -080096 }
97
98 public void deactivate() {
Carmelo Casconeab5d41e2019-03-06 18:02:34 -080099 deviceSubscribed.keySet().forEach(this::unsubscribeIfNeeded);
Yi Tsenge616d752018-11-27 10:53:27 -0800100 deviceService.removeListener(deviceEventListener);
101 mastershipService.removeListener(mastershipListener);
102 gnmiController.removeListener(gnmiEventListener);
103 }
104
Carmelo Casconeab5d41e2019-03-06 18:02:34 -0800105 private void checkSubscription(DeviceId deviceId) {
Carmelo Casconec2be50a2019-04-10 00:15:39 -0700106 if (gnmiController.get(deviceId) == null) {
Carmelo Casconeab5d41e2019-03-06 18:02:34 -0800107 // Ignore devices for which a gNMI client does not exist.
108 return;
109 }
Yi Tsenge616d752018-11-27 10:53:27 -0800110 deviceLocks.get(deviceId).lock();
111 try {
Carmelo Casconeab5d41e2019-03-06 18:02:34 -0800112 if (shouldHaveSubscription(deviceId)) {
Yi Tsenge616d752018-11-27 10:53:27 -0800113 subscribeIfNeeded(deviceId);
Carmelo Casconeab5d41e2019-03-06 18:02:34 -0800114 } else {
115 unsubscribeIfNeeded(deviceId);
Yi Tsenge616d752018-11-27 10:53:27 -0800116 }
117 } finally {
118 deviceLocks.get(deviceId).unlock();
119 }
120 }
121
Carmelo Casconeab5d41e2019-03-06 18:02:34 -0800122 private boolean shouldHaveSubscription(DeviceId deviceId) {
123 return deviceService.getDevice(deviceId) != null
124 && deviceService.isAvailable(deviceId)
125 && mastershipService.isLocalMaster(deviceId)
126 && !deviceService.getPorts(deviceId).isEmpty();
127 }
128
Yi Tsenge616d752018-11-27 10:53:27 -0800129 private Path interfaceOperStatusPath(String interfaceName) {
130 return Path.newBuilder()
131 .addElem(PathElem.newBuilder().setName("interfaces").build())
132 .addElem(PathElem.newBuilder()
133 .setName("interface").putKey("name", interfaceName).build())
134 .addElem(PathElem.newBuilder().setName("state").build())
135 .addElem(PathElem.newBuilder().setName("oper-status").build())
136 .build();
137 }
138
139 private void unsubscribeIfNeeded(DeviceId deviceId) {
Carmelo Casconec2be50a2019-04-10 00:15:39 -0700140 gnmiController.get(deviceId).unsubscribe();
Carmelo Casconeab5d41e2019-03-06 18:02:34 -0800141 if (deviceSubscribed.remove(deviceId) != null) {
142 log.info("Cancelled gNMI subscription for {}", deviceId);
Yi Tsenge616d752018-11-27 10:53:27 -0800143 }
Yi Tsenge616d752018-11-27 10:53:27 -0800144 }
145
146 private void subscribeIfNeeded(DeviceId deviceId) {
Carmelo Casconeab5d41e2019-03-06 18:02:34 -0800147
148 Set<PortNumber> ports = deviceService.getPorts(deviceId).stream()
149 .map(Port::number)
150 .collect(Collectors.toSet());
151
152 if (Objects.equals(ports, deviceSubscribed.get(deviceId))) {
153 // Already subscribed for the same ports.
Yi Tsenge616d752018-11-27 10:53:27 -0800154 return;
155 }
156
Carmelo Cascone67a317d2019-03-18 17:08:22 -0700157 // Subscribe for the new set of ports.
158 deviceSubscribed.put(deviceId, ports);
Yi Tsenge616d752018-11-27 10:53:27 -0800159
Carmelo Cascone67a317d2019-03-18 17:08:22 -0700160 // Send subscription request.
161 final SubscriptionList subscriptionList = SubscriptionList.newBuilder()
162 .setMode(SubscriptionList.Mode.STREAM)
163 .setUpdatesOnly(true)
164 .addAllSubscription(ports.stream().map(
165 port -> Subscription.newBuilder()
166 .setPath(interfaceOperStatusPath(port.name()))
167 .setMode(SubscriptionMode.ON_CHANGE)
168 .build()).collect(Collectors.toList()))
Yi Tsenge616d752018-11-27 10:53:27 -0800169 .build();
Carmelo Casconec2be50a2019-04-10 00:15:39 -0700170 gnmiController.get(deviceId).subscribe(
Carmelo Cascone67a317d2019-03-18 17:08:22 -0700171 SubscribeRequest.newBuilder()
172 .setSubscribe(subscriptionList)
173 .build());
Yi Tsenge616d752018-11-27 10:53:27 -0800174
Carmelo Casconeab5d41e2019-03-06 18:02:34 -0800175 log.info("Started gNMI subscription for {} ports on {}", ports.size(), deviceId);
Yi Tsenge616d752018-11-27 10:53:27 -0800176 }
177
178 private void handleGnmiUpdate(GnmiUpdate eventSubject) {
179 Notification notification = eventSubject.update();
180 if (notification == null) {
181 log.warn("Cannot handle gNMI event without update data, abort");
182 log.debug("gNMI update:\n{}", eventSubject);
183 return;
184 }
185
186 List<Update> updateList = notification.getUpdateList();
187 updateList.forEach(update -> {
188 Path path = update.getPath();
189 PathElem lastElem = path.getElem(path.getElemCount() - 1);
190
191 // Use last element to identify which state updated
192 if ("oper-status".equals(lastElem.getName())) {
Yi Tseng59d5f3e2018-11-27 23:09:41 -0800193 handleOperStatusUpdate(eventSubject.deviceId(), update,
194 notification.getTimestamp());
Yi Tsenge616d752018-11-27 10:53:27 -0800195 } else {
196 log.debug("Unrecognized update {}", GnmiUtils.pathToString(path));
197 }
198 });
199 }
200
Yi Tseng59d5f3e2018-11-27 23:09:41 -0800201 private void handleOperStatusUpdate(DeviceId deviceId, Update update, long timestamp) {
Yi Tsenge616d752018-11-27 10:53:27 -0800202 Path path = update.getPath();
203 // first element should be "interface"
204 String interfaceName = path.getElem(1).getKeyOrDefault("name", null);
205 if (interfaceName == null) {
206 log.error("No interface present in gNMI update, abort");
207 log.debug("gNMI update:\n{}", update);
208 return;
209 }
210
211 List<Port> portsFromDevice = deviceService.getPorts(deviceId);
212 portsFromDevice.forEach(port -> {
213 if (!port.number().name().equals(interfaceName)) {
214 return;
215 }
216
Yi Tseng59d5f3e2018-11-27 23:09:41 -0800217 DefaultAnnotations portAnnotations = DefaultAnnotations.builder()
218 .putAll(port.annotations())
219 .set(LAST_CHANGE, String.valueOf(timestamp))
220 .build();
221
Yi Tsenge616d752018-11-27 10:53:27 -0800222 // Port/Interface name is identical in OpenConfig model, but not in ONOS
223 // This might cause some problem if we use one name to different port
224 PortDescription portDescription = DefaultPortDescription.builder()
225 .portSpeed(port.portSpeed())
226 .withPortNumber(port.number())
227 .isEnabled(update.getVal().getStringVal().equals("UP"))
228 .type(port.type())
Yi Tseng59d5f3e2018-11-27 23:09:41 -0800229 .annotations(portAnnotations)
Yi Tsenge616d752018-11-27 10:53:27 -0800230 .build();
231 providerService.portStatusChanged(deviceId, portDescription);
232 });
233 }
234
235 class InternalGnmiEventListener implements GnmiEventListener {
236
237 @Override
238 public void event(GnmiEvent event) {
Carmelo Casconeab5d41e2019-03-06 18:02:34 -0800239 if (!deviceSubscribed.containsKey(event.subject().deviceId())) {
240 log.warn("Received gNMI event from {}, but we did'nt expect to " +
241 "be subscribed to it! Discarding event...",
Yi Tsenge616d752018-11-27 10:53:27 -0800242 event.subject().deviceId());
Carmelo Casconeab5d41e2019-03-06 18:02:34 -0800243 return;
Yi Tsenge616d752018-11-27 10:53:27 -0800244 }
Carmelo Casconeab5d41e2019-03-06 18:02:34 -0800245
Yi Tsenge616d752018-11-27 10:53:27 -0800246 log.debug("Received gNMI event {}", event.toString());
247 if (event.type() == GnmiEvent.Type.UPDATE) {
Carmelo Casconeab5d41e2019-03-06 18:02:34 -0800248 handleGnmiUpdate((GnmiUpdate) event.subject());
Yi Tsenge616d752018-11-27 10:53:27 -0800249 } else {
250 log.debug("Unsupported gNMI event type: {}", event.type());
251 }
252 }
253 }
254
255 class InternalMastershipListener implements MastershipListener {
256
257 @Override
258 public void event(MastershipEvent event) {
Carmelo Casconeab5d41e2019-03-06 18:02:34 -0800259 checkSubscription(event.subject());
Yi Tsenge616d752018-11-27 10:53:27 -0800260 }
261 }
262
263 class InternalDeviceListener implements DeviceListener {
264
265 @Override
266 public void event(DeviceEvent event) {
267 switch (event.type()) {
268 case DEVICE_ADDED:
269 case DEVICE_AVAILABILITY_CHANGED:
270 case DEVICE_UPDATED:
271 case DEVICE_REMOVED:
Carmelo Casconeab5d41e2019-03-06 18:02:34 -0800272 case PORT_ADDED:
273 case PORT_REMOVED:
274 checkSubscription(event.subject().id());
Yi Tsenge616d752018-11-27 10:53:27 -0800275 break;
276 default:
277 break;
278 }
279 }
280 }
281}