blob: a5276426e64482910c04b1ded485c3479d500d62 [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.GnmiClient;
31import org.onosproject.gnmi.api.GnmiController;
32import org.onosproject.gnmi.api.GnmiEvent;
33import org.onosproject.gnmi.api.GnmiEventListener;
34import org.onosproject.gnmi.api.GnmiUpdate;
35import org.onosproject.gnmi.api.GnmiUtils;
36import org.onosproject.mastership.MastershipEvent;
37import org.onosproject.mastership.MastershipListener;
38import org.onosproject.mastership.MastershipService;
Yi Tseng59d5f3e2018-11-27 23:09:41 -080039import org.onosproject.net.DefaultAnnotations;
Yi Tsenge616d752018-11-27 10:53:27 -080040import org.onosproject.net.DeviceId;
41import org.onosproject.net.Port;
Carmelo Casconeab5d41e2019-03-06 18:02:34 -080042import org.onosproject.net.PortNumber;
Yi Tsenge616d752018-11-27 10:53:27 -080043import org.onosproject.net.device.DefaultPortDescription;
44import org.onosproject.net.device.DeviceEvent;
45import org.onosproject.net.device.DeviceListener;
46import org.onosproject.net.device.DeviceProviderService;
47import org.onosproject.net.device.DeviceService;
48import org.onosproject.net.device.PortDescription;
49import org.slf4j.Logger;
50import org.slf4j.LoggerFactory;
51
Yi Tsenge616d752018-11-27 10:53:27 -080052import java.util.List;
Carmelo Casconeab5d41e2019-03-06 18:02:34 -080053import java.util.Map;
54import java.util.Objects;
55import java.util.Set;
Yi Tsenge616d752018-11-27 10:53:27 -080056import java.util.concurrent.locks.Lock;
Carmelo Casconeab5d41e2019-03-06 18:02:34 -080057import java.util.stream.Collectors;
Yi Tsenge616d752018-11-27 10:53:27 -080058
59/**
60 * Entity that manages gNMI subscription for devices using OpenConfig models and
61 * that reports relevant events to the core.
62 */
63@Beta
64class GnmiDeviceStateSubscriber {
65
Carmelo Casconeab5d41e2019-03-06 18:02:34 -080066 private static final String LAST_CHANGE = "last-changed";
Yi Tseng59d5f3e2018-11-27 23:09:41 -080067
Yi Tsenge616d752018-11-27 10:53:27 -080068 private static Logger log = LoggerFactory.getLogger(GnmiDeviceStateSubscriber.class);
69
70 private final GnmiController gnmiController;
71 private final DeviceService deviceService;
72 private final DeviceProviderService providerService;
73 private final MastershipService mastershipService;
74
Yi Tsenge616d752018-11-27 10:53:27 -080075 private final InternalGnmiEventListener gnmiEventListener = new InternalGnmiEventListener();
76 private final InternalDeviceListener deviceEventListener = new InternalDeviceListener();
77 private final InternalMastershipListener mastershipListener = new InternalMastershipListener();
Carmelo Casconeab5d41e2019-03-06 18:02:34 -080078 private final Map<DeviceId, Set<PortNumber>> deviceSubscribed = Maps.newHashMap();
Yi Tsenge616d752018-11-27 10:53:27 -080079
80 private final Striped<Lock> deviceLocks = Striped.lock(30);
81
82 GnmiDeviceStateSubscriber(GnmiController gnmiController, DeviceService deviceService,
83 MastershipService mastershipService,
84 DeviceProviderService providerService) {
85 this.gnmiController = gnmiController;
86 this.deviceService = deviceService;
87 this.mastershipService = mastershipService;
88 this.providerService = providerService;
89 }
90
91 public void activate() {
92 deviceService.addListener(deviceEventListener);
93 mastershipService.addListener(mastershipListener);
94 gnmiController.addListener(gnmiEventListener);
95 // Subscribe to existing devices.
Carmelo Casconeab5d41e2019-03-06 18:02:34 -080096 deviceService.getDevices().forEach(d -> checkSubscription(d.id()));
Yi Tsenge616d752018-11-27 10:53:27 -080097 }
98
99 public void deactivate() {
Carmelo Casconeab5d41e2019-03-06 18:02:34 -0800100 deviceSubscribed.keySet().forEach(this::unsubscribeIfNeeded);
Yi Tsenge616d752018-11-27 10:53:27 -0800101 deviceService.removeListener(deviceEventListener);
102 mastershipService.removeListener(mastershipListener);
103 gnmiController.removeListener(gnmiEventListener);
104 }
105
Carmelo Casconeab5d41e2019-03-06 18:02:34 -0800106 private void checkSubscription(DeviceId deviceId) {
107 if (gnmiController.getClient(deviceId) == null) {
108 // Ignore devices for which a gNMI client does not exist.
109 return;
110 }
Yi Tsenge616d752018-11-27 10:53:27 -0800111 deviceLocks.get(deviceId).lock();
112 try {
Carmelo Casconeab5d41e2019-03-06 18:02:34 -0800113 if (shouldHaveSubscription(deviceId)) {
Yi Tsenge616d752018-11-27 10:53:27 -0800114 subscribeIfNeeded(deviceId);
Carmelo Casconeab5d41e2019-03-06 18:02:34 -0800115 } else {
116 unsubscribeIfNeeded(deviceId);
Yi Tsenge616d752018-11-27 10:53:27 -0800117 }
118 } finally {
119 deviceLocks.get(deviceId).unlock();
120 }
121 }
122
Carmelo Casconeab5d41e2019-03-06 18:02:34 -0800123 private boolean shouldHaveSubscription(DeviceId deviceId) {
124 return deviceService.getDevice(deviceId) != null
125 && deviceService.isAvailable(deviceId)
126 && mastershipService.isLocalMaster(deviceId)
127 && !deviceService.getPorts(deviceId).isEmpty();
128 }
129
Yi Tsenge616d752018-11-27 10:53:27 -0800130 private Path interfaceOperStatusPath(String interfaceName) {
131 return Path.newBuilder()
132 .addElem(PathElem.newBuilder().setName("interfaces").build())
133 .addElem(PathElem.newBuilder()
134 .setName("interface").putKey("name", interfaceName).build())
135 .addElem(PathElem.newBuilder().setName("state").build())
136 .addElem(PathElem.newBuilder().setName("oper-status").build())
137 .build();
138 }
139
140 private void unsubscribeIfNeeded(DeviceId deviceId) {
Carmelo Casconeab5d41e2019-03-06 18:02:34 -0800141 gnmiController.getClient(deviceId).unsubscribe();
142 if (deviceSubscribed.remove(deviceId) != null) {
143 log.info("Cancelled gNMI subscription for {}", deviceId);
Yi Tsenge616d752018-11-27 10:53:27 -0800144 }
Yi Tsenge616d752018-11-27 10:53:27 -0800145 }
146
147 private void subscribeIfNeeded(DeviceId deviceId) {
Carmelo Casconeab5d41e2019-03-06 18:02:34 -0800148
149 Set<PortNumber> ports = deviceService.getPorts(deviceId).stream()
150 .map(Port::number)
151 .collect(Collectors.toSet());
152
153 if (Objects.equals(ports, deviceSubscribed.get(deviceId))) {
154 // Already subscribed for the same ports.
Yi Tsenge616d752018-11-27 10:53:27 -0800155 return;
156 }
157
158 GnmiClient client = gnmiController.getClient(deviceId);
Yi Tsenge616d752018-11-27 10:53:27 -0800159
Yi Tsenge616d752018-11-27 10:53:27 -0800160 SubscriptionList.Builder subscriptionList = SubscriptionList.newBuilder();
161 subscriptionList.setMode(SubscriptionList.Mode.STREAM);
162 subscriptionList.setUpdatesOnly(true);
163
164 ports.forEach(port -> {
Carmelo Casconeab5d41e2019-03-06 18:02:34 -0800165 String portName = port.name();
Yi Tsenge616d752018-11-27 10:53:27 -0800166 // Subscribe /interface/interface[name=port-name]/state/oper-status
167 Path subscribePath = interfaceOperStatusPath(portName);
168 Subscription interfaceOperStatusSub =
169 Subscription.newBuilder()
170 .setPath(subscribePath)
171 .setMode(SubscriptionMode.ON_CHANGE)
172 .build();
173 // TODO: more state subscription
174 subscriptionList.addSubscription(interfaceOperStatusSub);
175 });
176
177 SubscribeRequest subscribeRequest = SubscribeRequest.newBuilder()
178 .setSubscribe(subscriptionList.build())
179 .build();
180
181 client.subscribe(subscribeRequest);
182
Carmelo Casconeab5d41e2019-03-06 18:02:34 -0800183 log.info("Started gNMI subscription for {} ports on {}", ports.size(), deviceId);
184
185 deviceSubscribed.put(deviceId, ports);
Yi Tsenge616d752018-11-27 10:53:27 -0800186 }
187
188 private void handleGnmiUpdate(GnmiUpdate eventSubject) {
189 Notification notification = eventSubject.update();
190 if (notification == null) {
191 log.warn("Cannot handle gNMI event without update data, abort");
192 log.debug("gNMI update:\n{}", eventSubject);
193 return;
194 }
195
196 List<Update> updateList = notification.getUpdateList();
197 updateList.forEach(update -> {
198 Path path = update.getPath();
199 PathElem lastElem = path.getElem(path.getElemCount() - 1);
200
201 // Use last element to identify which state updated
202 if ("oper-status".equals(lastElem.getName())) {
Yi Tseng59d5f3e2018-11-27 23:09:41 -0800203 handleOperStatusUpdate(eventSubject.deviceId(), update,
204 notification.getTimestamp());
Yi Tsenge616d752018-11-27 10:53:27 -0800205 } else {
206 log.debug("Unrecognized update {}", GnmiUtils.pathToString(path));
207 }
208 });
209 }
210
Yi Tseng59d5f3e2018-11-27 23:09:41 -0800211 private void handleOperStatusUpdate(DeviceId deviceId, Update update, long timestamp) {
Yi Tsenge616d752018-11-27 10:53:27 -0800212 Path path = update.getPath();
213 // first element should be "interface"
214 String interfaceName = path.getElem(1).getKeyOrDefault("name", null);
215 if (interfaceName == null) {
216 log.error("No interface present in gNMI update, abort");
217 log.debug("gNMI update:\n{}", update);
218 return;
219 }
220
221 List<Port> portsFromDevice = deviceService.getPorts(deviceId);
222 portsFromDevice.forEach(port -> {
223 if (!port.number().name().equals(interfaceName)) {
224 return;
225 }
226
Yi Tseng59d5f3e2018-11-27 23:09:41 -0800227 DefaultAnnotations portAnnotations = DefaultAnnotations.builder()
228 .putAll(port.annotations())
229 .set(LAST_CHANGE, String.valueOf(timestamp))
230 .build();
231
Yi Tsenge616d752018-11-27 10:53:27 -0800232 // Port/Interface name is identical in OpenConfig model, but not in ONOS
233 // This might cause some problem if we use one name to different port
234 PortDescription portDescription = DefaultPortDescription.builder()
235 .portSpeed(port.portSpeed())
236 .withPortNumber(port.number())
237 .isEnabled(update.getVal().getStringVal().equals("UP"))
238 .type(port.type())
Yi Tseng59d5f3e2018-11-27 23:09:41 -0800239 .annotations(portAnnotations)
Yi Tsenge616d752018-11-27 10:53:27 -0800240 .build();
241 providerService.portStatusChanged(deviceId, portDescription);
242 });
243 }
244
245 class InternalGnmiEventListener implements GnmiEventListener {
246
247 @Override
248 public void event(GnmiEvent event) {
Carmelo Casconeab5d41e2019-03-06 18:02:34 -0800249 if (!deviceSubscribed.containsKey(event.subject().deviceId())) {
250 log.warn("Received gNMI event from {}, but we did'nt expect to " +
251 "be subscribed to it! Discarding event...",
Yi Tsenge616d752018-11-27 10:53:27 -0800252 event.subject().deviceId());
Carmelo Casconeab5d41e2019-03-06 18:02:34 -0800253 return;
Yi Tsenge616d752018-11-27 10:53:27 -0800254 }
Carmelo Casconeab5d41e2019-03-06 18:02:34 -0800255
Yi Tsenge616d752018-11-27 10:53:27 -0800256 log.debug("Received gNMI event {}", event.toString());
257 if (event.type() == GnmiEvent.Type.UPDATE) {
Carmelo Casconeab5d41e2019-03-06 18:02:34 -0800258 handleGnmiUpdate((GnmiUpdate) event.subject());
Yi Tsenge616d752018-11-27 10:53:27 -0800259 } else {
260 log.debug("Unsupported gNMI event type: {}", event.type());
261 }
262 }
263 }
264
265 class InternalMastershipListener implements MastershipListener {
266
267 @Override
268 public void event(MastershipEvent event) {
Carmelo Casconeab5d41e2019-03-06 18:02:34 -0800269 checkSubscription(event.subject());
Yi Tsenge616d752018-11-27 10:53:27 -0800270 }
271 }
272
273 class InternalDeviceListener implements DeviceListener {
274
275 @Override
276 public void event(DeviceEvent event) {
277 switch (event.type()) {
278 case DEVICE_ADDED:
279 case DEVICE_AVAILABILITY_CHANGED:
280 case DEVICE_UPDATED:
281 case DEVICE_REMOVED:
Carmelo Casconeab5d41e2019-03-06 18:02:34 -0800282 case PORT_ADDED:
283 case PORT_REMOVED:
284 checkSubscription(event.subject().id());
Yi Tsenge616d752018-11-27 10:53:27 -0800285 break;
286 default:
287 break;
288 }
289 }
290 }
291}