blob: 9f8c67c8dcc1e47fc65725a00916c5672230092b [file] [log] [blame]
Jian Liecae4382018-06-28 17:41:12 +09001/*
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 */
16package org.onosproject.openstacknetworking.impl;
17
18import com.google.common.base.Strings;
19import com.google.common.collect.ImmutableSet;
Jian Liec5c32b2018-07-13 14:28:58 +090020import com.google.common.collect.Sets;
Jian Liecae4382018-06-28 17:41:12 +090021import org.onlab.packet.IpAddress;
22import org.onlab.packet.MacAddress;
Jian Li078cd202018-07-23 18:58:20 +090023import org.onosproject.cluster.ClusterService;
24import org.onosproject.cluster.LeadershipService;
25import org.onosproject.cluster.NodeId;
26import org.onosproject.core.ApplicationId;
Jian Liecae4382018-06-28 17:41:12 +090027import org.onosproject.core.CoreService;
28import org.onosproject.event.ListenerRegistry;
Jian Li63430202018-08-30 16:24:09 +090029import org.onosproject.net.DeviceId;
Jian Liecae4382018-06-28 17:41:12 +090030import org.onosproject.net.Host;
Jian Liec5c32b2018-07-13 14:28:58 +090031import org.onosproject.net.HostLocation;
Jian Li63430202018-08-30 16:24:09 +090032import org.onosproject.net.PortNumber;
Jian Liecae4382018-06-28 17:41:12 +090033import org.onosproject.net.host.HostEvent;
34import org.onosproject.net.host.HostListener;
35import org.onosproject.net.host.HostService;
36import org.onosproject.openstacknetworking.api.Constants;
37import org.onosproject.openstacknetworking.api.InstancePort;
38import org.onosproject.openstacknetworking.api.InstancePortAdminService;
39import org.onosproject.openstacknetworking.api.InstancePortEvent;
40import org.onosproject.openstacknetworking.api.InstancePortListener;
41import org.onosproject.openstacknetworking.api.InstancePortService;
42import org.onosproject.openstacknetworking.api.InstancePortStore;
43import org.onosproject.openstacknetworking.api.InstancePortStoreDelegate;
Jian Lic38e9032018-08-09 17:08:38 +090044import org.onosproject.openstacknetworking.api.OpenstackRouterService;
Ray Milkeyd84f89b2018-08-17 14:54:17 -070045import org.osgi.service.component.annotations.Activate;
46import org.osgi.service.component.annotations.Component;
47import org.osgi.service.component.annotations.Deactivate;
48import org.osgi.service.component.annotations.Reference;
49import org.osgi.service.component.annotations.ReferenceCardinality;
Jian Liecae4382018-06-28 17:41:12 +090050import org.slf4j.Logger;
51
Jian Li078cd202018-07-23 18:58:20 +090052import java.util.Objects;
Jian Liecae4382018-06-28 17:41:12 +090053import java.util.Set;
Jian Lidc5d5012019-04-04 13:54:41 +090054import java.util.concurrent.ExecutorService;
Jian Liecae4382018-06-28 17:41:12 +090055import java.util.stream.Collectors;
56
57import static com.google.common.base.Preconditions.checkArgument;
58import static com.google.common.base.Preconditions.checkNotNull;
Jian Lidc5d5012019-04-04 13:54:41 +090059import static java.util.concurrent.Executors.newSingleThreadExecutor;
60import static org.onlab.util.Tools.groupedThreads;
Jian Liec5c32b2018-07-13 14:28:58 +090061import static org.onosproject.openstacknetworking.api.Constants.ANNOTATION_NETWORK_ID;
62import static org.onosproject.openstacknetworking.api.Constants.ANNOTATION_PORT_ID;
Jian Liecae4382018-06-28 17:41:12 +090063import static org.onosproject.openstacknetworking.api.InstancePort.State.ACTIVE;
Jian Liec5c32b2018-07-13 14:28:58 +090064import static org.onosproject.openstacknetworking.api.InstancePort.State.INACTIVE;
65import static org.onosproject.openstacknetworking.api.InstancePort.State.MIGRATED;
66import static org.onosproject.openstacknetworking.api.InstancePort.State.MIGRATING;
Jian Liecae4382018-06-28 17:41:12 +090067import static org.slf4j.LoggerFactory.getLogger;
68
69/**
70 * Provides implementation of administering and interfacing instance ports.
71 * It also provides instance port events for the hosts mapped to OpenStack VM interface.
72 */
Jian Li5ecfd1a2018-12-10 11:41:03 +090073@Component(
74 immediate = true,
75 service = { InstancePortService.class, InstancePortAdminService.class }
76)
Jian Liecae4382018-06-28 17:41:12 +090077public class InstancePortManager
78 extends ListenerRegistry<InstancePortEvent, InstancePortListener>
79 implements InstancePortService, InstancePortAdminService {
80
81 protected final Logger log = getLogger(getClass());
82
Jian Lib2d7e1d2020-09-06 14:06:53 +090083 private static final String OPENSTACK_PROVIDER = "org.onosproject.openstacknetworking";
Jian Liecae4382018-06-28 17:41:12 +090084 private static final String MSG_INSTANCE_PORT = "Instance port %s %s";
85 private static final String MSG_CREATED = "created";
86 private static final String MSG_UPDATED = "updated";
87 private static final String MSG_REMOVED = "removed";
88
89 private static final String ERR_NULL_INSTANCE_PORT = "Instance port cannot be null";
90 private static final String ERR_NULL_INSTANCE_PORT_ID = "Instance port ID cannot be null";
91 private static final String ERR_NULL_MAC_ADDRESS = "MAC address cannot be null";
92 private static final String ERR_NULL_IP_ADDRESS = "IP address cannot be null";
93 private static final String ERR_NULL_NETWORK_ID = "Network ID cannot be null";
Jian Li63430202018-08-30 16:24:09 +090094 private static final String ERR_NULL_DEVICE_ID = "Device ID cannot be null";
95 private static final String ERR_NULL_PORT_NUMBER = "Port number cannot be null";
Jian Liecae4382018-06-28 17:41:12 +090096
97 private static final String ERR_IN_USE = " still in use";
98
Ray Milkeyd84f89b2018-08-17 14:54:17 -070099 @Reference(cardinality = ReferenceCardinality.MANDATORY)
Jian Liecae4382018-06-28 17:41:12 +0900100 protected CoreService coreService;
101
Ray Milkeyd84f89b2018-08-17 14:54:17 -0700102 @Reference(cardinality = ReferenceCardinality.MANDATORY)
Jian Liecae4382018-06-28 17:41:12 +0900103 protected InstancePortStore instancePortStore;
104
Ray Milkeyd84f89b2018-08-17 14:54:17 -0700105 @Reference(cardinality = ReferenceCardinality.MANDATORY)
Jian Li078cd202018-07-23 18:58:20 +0900106 protected LeadershipService leadershipService;
107
Ray Milkeyd84f89b2018-08-17 14:54:17 -0700108 @Reference(cardinality = ReferenceCardinality.MANDATORY)
Jian Li078cd202018-07-23 18:58:20 +0900109 protected ClusterService clusterService;
110
Ray Milkeyd84f89b2018-08-17 14:54:17 -0700111 @Reference(cardinality = ReferenceCardinality.MANDATORY)
Jian Liecae4382018-06-28 17:41:12 +0900112 protected HostService hostService;
113
Ray Milkeyd84f89b2018-08-17 14:54:17 -0700114 @Reference(cardinality = ReferenceCardinality.MANDATORY)
Jian Lic38e9032018-08-09 17:08:38 +0900115 protected OpenstackRouterService routerService;
116
Jian Lidc5d5012019-04-04 13:54:41 +0900117 private final ExecutorService eventExecutor = newSingleThreadExecutor(
118 groupedThreads(this.getClass().getSimpleName(), "event-handler"));
Jian Liecae4382018-06-28 17:41:12 +0900119 private final InstancePortStoreDelegate
120 delegate = new InternalInstancePortStoreDelegate();
121 private final InternalHostListener
122 hostListener = new InternalHostListener();
123
Jian Li078cd202018-07-23 18:58:20 +0900124 private ApplicationId appId;
125 private NodeId localNodeId;
126
Jian Liecae4382018-06-28 17:41:12 +0900127 @Activate
128 protected void activate() {
Jian Li078cd202018-07-23 18:58:20 +0900129 appId = coreService.registerApplication(Constants.OPENSTACK_NETWORKING_APP_ID);
130 localNodeId = clusterService.getLocalNode().id();
Jian Liecae4382018-06-28 17:41:12 +0900131 instancePortStore.setDelegate(delegate);
132 hostService.addListener(hostListener);
Jian Li078cd202018-07-23 18:58:20 +0900133 leadershipService.runForLeadership(appId.name());
134
Jian Liecae4382018-06-28 17:41:12 +0900135 log.info("Started");
136 }
137
138 @Deactivate
139 protected void deactivate() {
140 hostService.removeListener(hostListener);
141 instancePortStore.unsetDelegate(delegate);
Jian Li078cd202018-07-23 18:58:20 +0900142 leadershipService.withdraw(appId.name());
143
Jian Liecae4382018-06-28 17:41:12 +0900144 log.info("Stopped");
145 }
146
147 @Override
148 public void createInstancePort(InstancePort instancePort) {
149 checkNotNull(instancePort, ERR_NULL_INSTANCE_PORT);
150 checkArgument(!Strings.isNullOrEmpty(instancePort.portId()),
151 ERR_NULL_INSTANCE_PORT_ID);
152
153 instancePortStore.createInstancePort(instancePort);
154 log.info(String.format(MSG_INSTANCE_PORT, instancePort.portId(),
155 MSG_CREATED));
156 }
157
158 @Override
159 public void updateInstancePort(InstancePort instancePort) {
160 checkNotNull(instancePort, ERR_NULL_INSTANCE_PORT);
161 checkArgument(!Strings.isNullOrEmpty(instancePort.portId()),
162 ERR_NULL_INSTANCE_PORT_ID);
163
Jian Lida03ce92018-07-24 21:41:53 +0900164 // in case OpenStack removes the port prior to OVS, we will not update
165 // the instance port as it does not exist in the store
166 if (instancePortStore.instancePort(instancePort.portId()) == null) {
Jian Li5ecfd1a2018-12-10 11:41:03 +0900167 log.warn("Unable to update instance port {}, as it does not exist",
168 instancePort.portId());
Jian Lida03ce92018-07-24 21:41:53 +0900169 return;
170 }
171
Jian Liecae4382018-06-28 17:41:12 +0900172 instancePortStore.updateInstancePort(instancePort);
173 log.info(String.format(MSG_INSTANCE_PORT, instancePort.portId(), MSG_UPDATED));
174 }
175
176 @Override
177 public void removeInstancePort(String portId) {
178 checkArgument(!Strings.isNullOrEmpty(portId), ERR_NULL_INSTANCE_PORT_ID);
179
180 synchronized (this) {
181 if (isInstancePortInUse(portId)) {
182 final String error =
183 String.format(MSG_INSTANCE_PORT, portId, ERR_IN_USE);
184 throw new IllegalStateException(error);
185 }
186 InstancePort instancePort = instancePortStore.removeInstancePort(portId);
187 if (instancePort != null) {
188 log.info(String.format(MSG_INSTANCE_PORT, instancePort.portId(), MSG_REMOVED));
189 }
190 }
191 }
192
193 @Override
194 public void clear() {
195 instancePortStore.clear();
196 }
197
198 @Override
199 public InstancePort instancePort(MacAddress macAddress) {
200 checkNotNull(macAddress, ERR_NULL_MAC_ADDRESS);
201
202 return instancePortStore.instancePorts().stream()
203 .filter(port -> port.macAddress().equals(macAddress))
204 .findFirst().orElse(null);
205 }
206
207 @Override
208 public InstancePort instancePort(IpAddress ipAddress, String osNetId) {
209 checkNotNull(ipAddress, ERR_NULL_IP_ADDRESS);
210 checkNotNull(osNetId, ERR_NULL_NETWORK_ID);
211
212 return instancePortStore.instancePorts().stream()
213 .filter(port -> port.networkId().equals(osNetId))
214 .filter(port -> port.ipAddress().equals(ipAddress))
215 .findFirst().orElse(null);
216 }
217
218 @Override
219 public InstancePort instancePort(String portId) {
220 checkArgument(!Strings.isNullOrEmpty(portId), ERR_NULL_INSTANCE_PORT_ID);
221
222 return instancePortStore.instancePort(portId);
223 }
224
225 @Override
Jian Li63430202018-08-30 16:24:09 +0900226 public InstancePort instancePort(DeviceId deviceId, PortNumber portNumber) {
227 checkNotNull(deviceId, ERR_NULL_DEVICE_ID);
228 checkNotNull(portNumber, ERR_NULL_PORT_NUMBER);
229
230 return instancePortStore.instancePorts().stream()
231 .filter(port -> port.deviceId().equals(deviceId))
232 .filter(port -> port.portNumber().equals(portNumber))
233 .findFirst().orElse(null);
234 }
235
236 @Override
Daniel Park4fa1f5e2018-10-17 12:41:52 +0900237 public Set<InstancePort> instancePort(DeviceId deviceId) {
238 Set<InstancePort> ports = instancePortStore.instancePorts().stream()
239 .filter(port -> port.deviceId().equals(deviceId))
240 .collect(Collectors.toSet());
241
242 return ImmutableSet.copyOf(ports);
243 }
244
245 @Override
Jian Liecae4382018-06-28 17:41:12 +0900246 public Set<InstancePort> instancePorts() {
247 Set<InstancePort> ports = instancePortStore.instancePorts();
248
249 return ImmutableSet.copyOf(ports);
250 }
251
252 @Override
253 public Set<InstancePort> instancePorts(String osNetId) {
254 checkNotNull(osNetId, ERR_NULL_NETWORK_ID);
255
256 Set<InstancePort> ports = instancePortStore.instancePorts().stream()
257 .filter(port -> port.networkId().equals(osNetId))
258 .collect(Collectors.toSet());
259
260 return ImmutableSet.copyOf(ports);
261 }
262
Jian Lic38e9032018-08-09 17:08:38 +0900263 @Override
264 public IpAddress floatingIp(String osPortId) {
265 checkNotNull(osPortId, ERR_NULL_INSTANCE_PORT_ID);
266
267 return routerService.floatingIps().stream()
268 .filter(fip -> osPortId.equals(fip.getPortId()))
269 .filter(fip -> fip.getFloatingIpAddress() != null)
270 .map(fip -> IpAddress.valueOf(fip.getFloatingIpAddress()))
271 .findFirst().orElse(null);
272 }
273
Jian Liecae4382018-06-28 17:41:12 +0900274 private boolean isInstancePortInUse(String portId) {
275 // TODO add checking logic
276 return false;
277 }
278
279 private class InternalInstancePortStoreDelegate implements InstancePortStoreDelegate {
280
281 @Override
282 public void notify(InstancePortEvent event) {
283 if (event != null) {
284 log.trace("send instance port event {}", event);
285 process(event);
286 }
287 }
288 }
289
290 /**
291 * An internal listener that listens host event generated by HostLocationTracker
292 * in DistributedHostStore. The role of this listener is to convert host event
293 * to instance port event and post to the subscribers that have interested on
294 * this type of event.
295 */
296 private class InternalHostListener implements HostListener {
297
298 @Override
299 public boolean isRelevant(HostEvent event) {
300 Host host = event.subject();
301 if (!isValidHost(host)) {
302 log.debug("Invalid host detected, ignore it {}", host);
303 return false;
304 }
Jian Li078cd202018-07-23 18:58:20 +0900305
Jian Lib73a95a2020-09-04 16:34:49 +0900306 boolean isProvider =
307 OPENSTACK_PROVIDER.equals(event.subject().providerId().id());
308
Jian Li078cd202018-07-23 18:58:20 +0900309 // do not allow to proceed without leadership
310 NodeId leader = leadershipService.getLeader(appId.name());
Jian Lib73a95a2020-09-04 16:34:49 +0900311 return Objects.equals(localNodeId, leader) && isProvider;
Jian Liecae4382018-06-28 17:41:12 +0900312 }
313
314 @Override
315 public void event(HostEvent event) {
316 InstancePort instPort = DefaultInstancePort.from(event.subject(), ACTIVE);
317
318 switch (event.type()) {
319 case HOST_UPDATED:
Jian Lidc5d5012019-04-04 13:54:41 +0900320 eventExecutor.execute(() -> processHostUpdate(instPort));
Jian Liecae4382018-06-28 17:41:12 +0900321 break;
322 case HOST_ADDED:
Jian Lidc5d5012019-04-04 13:54:41 +0900323 eventExecutor.execute(() -> processHostAddition(instPort));
Jian Liecae4382018-06-28 17:41:12 +0900324 break;
325 case HOST_REMOVED:
Jian Lidc5d5012019-04-04 13:54:41 +0900326 eventExecutor.execute(() -> processHostRemoval(instPort));
Jian Liecae4382018-06-28 17:41:12 +0900327 break;
328 case HOST_MOVED:
Jian Lidc5d5012019-04-04 13:54:41 +0900329 eventExecutor.execute(() -> processHostMove(event, instPort));
Jian Liecae4382018-06-28 17:41:12 +0900330 break;
331 default:
332 break;
333 }
334 }
335
Jian Lidc5d5012019-04-04 13:54:41 +0900336 private void processHostUpdate(InstancePort instPort) {
337 InstancePort existingPort = instancePort(instPort.portId());
338 if (existingPort == null) {
339 createInstancePort(instPort);
340 } else {
341 updateInstancePort(instPort);
342 }
343 }
344
Jian Li6a47fd02018-11-27 21:51:03 +0900345 private void processHostAddition(InstancePort instPort) {
346 InstancePort existingPort = instancePort(instPort.portId());
347 if (existingPort == null) {
348 // first time to add instance
349 createInstancePort(instPort);
350 } else {
351 if (existingPort.state() == INACTIVE) {
352
353 if (instPort.deviceId().equals(existingPort.deviceId())) {
354 // VM RESTART case
355 // if the ID of switch where VM is attached to is
356 // identical, we can assume that the VM was
357 // restarted in the same location;
358 // note that the switch port number where VM is
359 // attached can be varied per each restart
360 updateInstancePort(instPort);
361 } else {
362 // VM COLD MIGRATION case
363 // if the ID of switch where VM is attached to is
364 // varied, we can assume that the VM was migrated
365 // to a new location
366 updateInstancePort(instPort.updateState(MIGRATING));
367 InstancePort updated = instPort.updateState(MIGRATED);
368 updateInstancePort(updated.updatePrevLocation(
369 existingPort.deviceId(), existingPort.portNumber()));
370 }
371 }
372 }
373 }
374
375 private void processHostRemoval(InstancePort instPort) {
376 /* in case the instance port cannot be found in the store,
377 this indicates that the instance port was removed due to
378 the removal of openstack port; in some cases, openstack
379 port removal message arrives before ovs port removal message */
380 if (instancePortStore.instancePort(instPort.portId()) == null) {
381 log.debug("instance port was removed before ovs port removal");
382 return;
383 }
384
385 /* we will remove instance port from persistent store,
386 only if we receive port removal signal from neutron.
387 by default, we update the instance port state to INACTIVE
388 to indicate the instance is terminated */
389 updateInstancePort(instPort.updateState(INACTIVE));
390 }
391
392 private void processHostMove(HostEvent event, InstancePort instPort) {
393 Host oldHost = event.prevSubject();
394 Host currHost = event.subject();
395
396 // in the middle of VM migration
397 if (oldHost.locations().size() < currHost.locations().size()) {
398 updateInstancePort(instPort.updateState(MIGRATING));
399 }
400
401 // finish of VM migration
402 if (oldHost.locations().size() > currHost.locations().size()) {
403 Set<HostLocation> diff =
404 Sets.difference(oldHost.locations(), currHost.locations());
405 HostLocation location = diff.stream().findFirst().orElse(null);
406
407 if (location != null) {
408 InstancePort updated = instPort.updateState(MIGRATED);
409 updateInstancePort(updated.updatePrevLocation(
410 location.deviceId(), location.port()));
411 }
412 }
413 }
414
Jian Liecae4382018-06-28 17:41:12 +0900415 private boolean isValidHost(Host host) {
416 return !host.ipAddresses().isEmpty() &&
417 host.annotations().value(ANNOTATION_NETWORK_ID) != null &&
418 host.annotations().value(ANNOTATION_PORT_ID) != null;
419 }
420 }
421}