blob: ba90b7e5e88abea150b3c7cd8acebeb748267b6c [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
83 private static final String MSG_INSTANCE_PORT = "Instance port %s %s";
84 private static final String MSG_CREATED = "created";
85 private static final String MSG_UPDATED = "updated";
86 private static final String MSG_REMOVED = "removed";
87
88 private static final String ERR_NULL_INSTANCE_PORT = "Instance port cannot be null";
89 private static final String ERR_NULL_INSTANCE_PORT_ID = "Instance port ID cannot be null";
90 private static final String ERR_NULL_MAC_ADDRESS = "MAC address cannot be null";
91 private static final String ERR_NULL_IP_ADDRESS = "IP address cannot be null";
92 private static final String ERR_NULL_NETWORK_ID = "Network ID cannot be null";
Jian Li63430202018-08-30 16:24:09 +090093 private static final String ERR_NULL_DEVICE_ID = "Device ID cannot be null";
94 private static final String ERR_NULL_PORT_NUMBER = "Port number cannot be null";
Jian Liecae4382018-06-28 17:41:12 +090095
96 private static final String ERR_IN_USE = " still in use";
97
Ray Milkeyd84f89b2018-08-17 14:54:17 -070098 @Reference(cardinality = ReferenceCardinality.MANDATORY)
Jian Liecae4382018-06-28 17:41:12 +090099 protected CoreService coreService;
100
Ray Milkeyd84f89b2018-08-17 14:54:17 -0700101 @Reference(cardinality = ReferenceCardinality.MANDATORY)
Jian Liecae4382018-06-28 17:41:12 +0900102 protected InstancePortStore instancePortStore;
103
Ray Milkeyd84f89b2018-08-17 14:54:17 -0700104 @Reference(cardinality = ReferenceCardinality.MANDATORY)
Jian Li078cd202018-07-23 18:58:20 +0900105 protected LeadershipService leadershipService;
106
Ray Milkeyd84f89b2018-08-17 14:54:17 -0700107 @Reference(cardinality = ReferenceCardinality.MANDATORY)
Jian Li078cd202018-07-23 18:58:20 +0900108 protected ClusterService clusterService;
109
Ray Milkeyd84f89b2018-08-17 14:54:17 -0700110 @Reference(cardinality = ReferenceCardinality.MANDATORY)
Jian Liecae4382018-06-28 17:41:12 +0900111 protected HostService hostService;
112
Ray Milkeyd84f89b2018-08-17 14:54:17 -0700113 @Reference(cardinality = ReferenceCardinality.MANDATORY)
Jian Lic38e9032018-08-09 17:08:38 +0900114 protected OpenstackRouterService routerService;
115
Jian Lidc5d5012019-04-04 13:54:41 +0900116 private final ExecutorService eventExecutor = newSingleThreadExecutor(
117 groupedThreads(this.getClass().getSimpleName(), "event-handler"));
Jian Liecae4382018-06-28 17:41:12 +0900118 private final InstancePortStoreDelegate
119 delegate = new InternalInstancePortStoreDelegate();
120 private final InternalHostListener
121 hostListener = new InternalHostListener();
122
Jian Li078cd202018-07-23 18:58:20 +0900123 private ApplicationId appId;
124 private NodeId localNodeId;
125
Jian Liecae4382018-06-28 17:41:12 +0900126 @Activate
127 protected void activate() {
Jian Li078cd202018-07-23 18:58:20 +0900128 appId = coreService.registerApplication(Constants.OPENSTACK_NETWORKING_APP_ID);
129 localNodeId = clusterService.getLocalNode().id();
Jian Liecae4382018-06-28 17:41:12 +0900130 instancePortStore.setDelegate(delegate);
131 hostService.addListener(hostListener);
Jian Li078cd202018-07-23 18:58:20 +0900132 leadershipService.runForLeadership(appId.name());
133
Jian Liecae4382018-06-28 17:41:12 +0900134 log.info("Started");
135 }
136
137 @Deactivate
138 protected void deactivate() {
139 hostService.removeListener(hostListener);
140 instancePortStore.unsetDelegate(delegate);
Jian Li078cd202018-07-23 18:58:20 +0900141 leadershipService.withdraw(appId.name());
142
Jian Liecae4382018-06-28 17:41:12 +0900143 log.info("Stopped");
144 }
145
146 @Override
147 public void createInstancePort(InstancePort instancePort) {
148 checkNotNull(instancePort, ERR_NULL_INSTANCE_PORT);
149 checkArgument(!Strings.isNullOrEmpty(instancePort.portId()),
150 ERR_NULL_INSTANCE_PORT_ID);
151
152 instancePortStore.createInstancePort(instancePort);
153 log.info(String.format(MSG_INSTANCE_PORT, instancePort.portId(),
154 MSG_CREATED));
155 }
156
157 @Override
158 public void updateInstancePort(InstancePort instancePort) {
159 checkNotNull(instancePort, ERR_NULL_INSTANCE_PORT);
160 checkArgument(!Strings.isNullOrEmpty(instancePort.portId()),
161 ERR_NULL_INSTANCE_PORT_ID);
162
Jian Lida03ce92018-07-24 21:41:53 +0900163 // in case OpenStack removes the port prior to OVS, we will not update
164 // the instance port as it does not exist in the store
165 if (instancePortStore.instancePort(instancePort.portId()) == null) {
Jian Li5ecfd1a2018-12-10 11:41:03 +0900166 log.warn("Unable to update instance port {}, as it does not exist",
167 instancePort.portId());
Jian Lida03ce92018-07-24 21:41:53 +0900168 return;
169 }
170
Jian Liecae4382018-06-28 17:41:12 +0900171 instancePortStore.updateInstancePort(instancePort);
172 log.info(String.format(MSG_INSTANCE_PORT, instancePort.portId(), MSG_UPDATED));
173 }
174
175 @Override
176 public void removeInstancePort(String portId) {
177 checkArgument(!Strings.isNullOrEmpty(portId), ERR_NULL_INSTANCE_PORT_ID);
178
179 synchronized (this) {
180 if (isInstancePortInUse(portId)) {
181 final String error =
182 String.format(MSG_INSTANCE_PORT, portId, ERR_IN_USE);
183 throw new IllegalStateException(error);
184 }
185 InstancePort instancePort = instancePortStore.removeInstancePort(portId);
186 if (instancePort != null) {
187 log.info(String.format(MSG_INSTANCE_PORT, instancePort.portId(), MSG_REMOVED));
188 }
189 }
190 }
191
192 @Override
193 public void clear() {
194 instancePortStore.clear();
195 }
196
197 @Override
198 public InstancePort instancePort(MacAddress macAddress) {
199 checkNotNull(macAddress, ERR_NULL_MAC_ADDRESS);
200
201 return instancePortStore.instancePorts().stream()
202 .filter(port -> port.macAddress().equals(macAddress))
203 .findFirst().orElse(null);
204 }
205
206 @Override
207 public InstancePort instancePort(IpAddress ipAddress, String osNetId) {
208 checkNotNull(ipAddress, ERR_NULL_IP_ADDRESS);
209 checkNotNull(osNetId, ERR_NULL_NETWORK_ID);
210
211 return instancePortStore.instancePorts().stream()
212 .filter(port -> port.networkId().equals(osNetId))
213 .filter(port -> port.ipAddress().equals(ipAddress))
214 .findFirst().orElse(null);
215 }
216
217 @Override
218 public InstancePort instancePort(String portId) {
219 checkArgument(!Strings.isNullOrEmpty(portId), ERR_NULL_INSTANCE_PORT_ID);
220
221 return instancePortStore.instancePort(portId);
222 }
223
224 @Override
Jian Li63430202018-08-30 16:24:09 +0900225 public InstancePort instancePort(DeviceId deviceId, PortNumber portNumber) {
226 checkNotNull(deviceId, ERR_NULL_DEVICE_ID);
227 checkNotNull(portNumber, ERR_NULL_PORT_NUMBER);
228
229 return instancePortStore.instancePorts().stream()
230 .filter(port -> port.deviceId().equals(deviceId))
231 .filter(port -> port.portNumber().equals(portNumber))
232 .findFirst().orElse(null);
233 }
234
235 @Override
Daniel Park4fa1f5e2018-10-17 12:41:52 +0900236 public Set<InstancePort> instancePort(DeviceId deviceId) {
237 Set<InstancePort> ports = instancePortStore.instancePorts().stream()
238 .filter(port -> port.deviceId().equals(deviceId))
239 .collect(Collectors.toSet());
240
241 return ImmutableSet.copyOf(ports);
242 }
243
244 @Override
Jian Liecae4382018-06-28 17:41:12 +0900245 public Set<InstancePort> instancePorts() {
246 Set<InstancePort> ports = instancePortStore.instancePorts();
247
248 return ImmutableSet.copyOf(ports);
249 }
250
251 @Override
252 public Set<InstancePort> instancePorts(String osNetId) {
253 checkNotNull(osNetId, ERR_NULL_NETWORK_ID);
254
255 Set<InstancePort> ports = instancePortStore.instancePorts().stream()
256 .filter(port -> port.networkId().equals(osNetId))
257 .collect(Collectors.toSet());
258
259 return ImmutableSet.copyOf(ports);
260 }
261
Jian Lic38e9032018-08-09 17:08:38 +0900262 @Override
263 public IpAddress floatingIp(String osPortId) {
264 checkNotNull(osPortId, ERR_NULL_INSTANCE_PORT_ID);
265
266 return routerService.floatingIps().stream()
267 .filter(fip -> osPortId.equals(fip.getPortId()))
268 .filter(fip -> fip.getFloatingIpAddress() != null)
269 .map(fip -> IpAddress.valueOf(fip.getFloatingIpAddress()))
270 .findFirst().orElse(null);
271 }
272
Jian Liecae4382018-06-28 17:41:12 +0900273 private boolean isInstancePortInUse(String portId) {
274 // TODO add checking logic
275 return false;
276 }
277
278 private class InternalInstancePortStoreDelegate implements InstancePortStoreDelegate {
279
280 @Override
281 public void notify(InstancePortEvent event) {
282 if (event != null) {
283 log.trace("send instance port event {}", event);
284 process(event);
285 }
286 }
287 }
288
289 /**
290 * An internal listener that listens host event generated by HostLocationTracker
291 * in DistributedHostStore. The role of this listener is to convert host event
292 * to instance port event and post to the subscribers that have interested on
293 * this type of event.
294 */
295 private class InternalHostListener implements HostListener {
296
297 @Override
298 public boolean isRelevant(HostEvent event) {
299 Host host = event.subject();
300 if (!isValidHost(host)) {
301 log.debug("Invalid host detected, ignore it {}", host);
302 return false;
303 }
Jian Li078cd202018-07-23 18:58:20 +0900304
305 // do not allow to proceed without leadership
306 NodeId leader = leadershipService.getLeader(appId.name());
307 return Objects.equals(localNodeId, leader);
Jian Liecae4382018-06-28 17:41:12 +0900308 }
309
310 @Override
311 public void event(HostEvent event) {
312 InstancePort instPort = DefaultInstancePort.from(event.subject(), ACTIVE);
313
314 switch (event.type()) {
315 case HOST_UPDATED:
Jian Lidc5d5012019-04-04 13:54:41 +0900316 eventExecutor.execute(() -> processHostUpdate(instPort));
Jian Liecae4382018-06-28 17:41:12 +0900317 break;
318 case HOST_ADDED:
Jian Lidc5d5012019-04-04 13:54:41 +0900319 eventExecutor.execute(() -> processHostAddition(instPort));
Jian Liecae4382018-06-28 17:41:12 +0900320 break;
321 case HOST_REMOVED:
Jian Lidc5d5012019-04-04 13:54:41 +0900322 eventExecutor.execute(() -> processHostRemoval(instPort));
Jian Liecae4382018-06-28 17:41:12 +0900323 break;
324 case HOST_MOVED:
Jian Lidc5d5012019-04-04 13:54:41 +0900325 eventExecutor.execute(() -> processHostMove(event, instPort));
Jian Liecae4382018-06-28 17:41:12 +0900326 break;
327 default:
328 break;
329 }
330 }
331
Jian Lidc5d5012019-04-04 13:54:41 +0900332 private void processHostUpdate(InstancePort instPort) {
333 InstancePort existingPort = instancePort(instPort.portId());
334 if (existingPort == null) {
335 createInstancePort(instPort);
336 } else {
337 updateInstancePort(instPort);
338 }
339 }
340
Jian Li6a47fd02018-11-27 21:51:03 +0900341 private void processHostAddition(InstancePort instPort) {
342 InstancePort existingPort = instancePort(instPort.portId());
343 if (existingPort == null) {
344 // first time to add instance
345 createInstancePort(instPort);
346 } else {
347 if (existingPort.state() == INACTIVE) {
348
349 if (instPort.deviceId().equals(existingPort.deviceId())) {
350 // VM RESTART case
351 // if the ID of switch where VM is attached to is
352 // identical, we can assume that the VM was
353 // restarted in the same location;
354 // note that the switch port number where VM is
355 // attached can be varied per each restart
356 updateInstancePort(instPort);
357 } else {
358 // VM COLD MIGRATION case
359 // if the ID of switch where VM is attached to is
360 // varied, we can assume that the VM was migrated
361 // to a new location
362 updateInstancePort(instPort.updateState(MIGRATING));
363 InstancePort updated = instPort.updateState(MIGRATED);
364 updateInstancePort(updated.updatePrevLocation(
365 existingPort.deviceId(), existingPort.portNumber()));
366 }
367 }
368 }
369 }
370
371 private void processHostRemoval(InstancePort instPort) {
372 /* in case the instance port cannot be found in the store,
373 this indicates that the instance port was removed due to
374 the removal of openstack port; in some cases, openstack
375 port removal message arrives before ovs port removal message */
376 if (instancePortStore.instancePort(instPort.portId()) == null) {
377 log.debug("instance port was removed before ovs port removal");
378 return;
379 }
380
381 /* we will remove instance port from persistent store,
382 only if we receive port removal signal from neutron.
383 by default, we update the instance port state to INACTIVE
384 to indicate the instance is terminated */
385 updateInstancePort(instPort.updateState(INACTIVE));
386 }
387
388 private void processHostMove(HostEvent event, InstancePort instPort) {
389 Host oldHost = event.prevSubject();
390 Host currHost = event.subject();
391
392 // in the middle of VM migration
393 if (oldHost.locations().size() < currHost.locations().size()) {
394 updateInstancePort(instPort.updateState(MIGRATING));
395 }
396
397 // finish of VM migration
398 if (oldHost.locations().size() > currHost.locations().size()) {
399 Set<HostLocation> diff =
400 Sets.difference(oldHost.locations(), currHost.locations());
401 HostLocation location = diff.stream().findFirst().orElse(null);
402
403 if (location != null) {
404 InstancePort updated = instPort.updateState(MIGRATED);
405 updateInstancePort(updated.updatePrevLocation(
406 location.deviceId(), location.port()));
407 }
408 }
409 }
410
Jian Liecae4382018-06-28 17:41:12 +0900411 private boolean isValidHost(Host host) {
412 return !host.ipAddresses().isEmpty() &&
413 host.annotations().value(ANNOTATION_NETWORK_ID) != null &&
414 host.annotations().value(ANNOTATION_PORT_ID) != null;
415 }
416 }
417}