blob: f04071fae810d1c77aa55d922b58dbc72bc7c2cf [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;
54import java.util.stream.Collectors;
55
56import static com.google.common.base.Preconditions.checkArgument;
57import static com.google.common.base.Preconditions.checkNotNull;
Jian Liec5c32b2018-07-13 14:28:58 +090058import static org.onosproject.openstacknetworking.api.Constants.ANNOTATION_NETWORK_ID;
59import static org.onosproject.openstacknetworking.api.Constants.ANNOTATION_PORT_ID;
Jian Liecae4382018-06-28 17:41:12 +090060import static org.onosproject.openstacknetworking.api.InstancePort.State.ACTIVE;
Jian Liec5c32b2018-07-13 14:28:58 +090061import static org.onosproject.openstacknetworking.api.InstancePort.State.INACTIVE;
62import static org.onosproject.openstacknetworking.api.InstancePort.State.MIGRATED;
63import static org.onosproject.openstacknetworking.api.InstancePort.State.MIGRATING;
Jian Liecae4382018-06-28 17:41:12 +090064import static org.slf4j.LoggerFactory.getLogger;
65
66/**
67 * Provides implementation of administering and interfacing instance ports.
68 * It also provides instance port events for the hosts mapped to OpenStack VM interface.
69 */
Ray Milkeyd84f89b2018-08-17 14:54:17 -070070@Component(immediate = true, service = { InstancePortService.class, InstancePortAdminService.class })
Jian Liecae4382018-06-28 17:41:12 +090071public class InstancePortManager
72 extends ListenerRegistry<InstancePortEvent, InstancePortListener>
73 implements InstancePortService, InstancePortAdminService {
74
75 protected final Logger log = getLogger(getClass());
76
77 private static final String MSG_INSTANCE_PORT = "Instance port %s %s";
78 private static final String MSG_CREATED = "created";
79 private static final String MSG_UPDATED = "updated";
80 private static final String MSG_REMOVED = "removed";
81
82 private static final String ERR_NULL_INSTANCE_PORT = "Instance port cannot be null";
83 private static final String ERR_NULL_INSTANCE_PORT_ID = "Instance port ID cannot be null";
84 private static final String ERR_NULL_MAC_ADDRESS = "MAC address cannot be null";
85 private static final String ERR_NULL_IP_ADDRESS = "IP address cannot be null";
86 private static final String ERR_NULL_NETWORK_ID = "Network ID cannot be null";
Jian Li63430202018-08-30 16:24:09 +090087 private static final String ERR_NULL_DEVICE_ID = "Device ID cannot be null";
88 private static final String ERR_NULL_PORT_NUMBER = "Port number cannot be null";
Jian Liecae4382018-06-28 17:41:12 +090089
90 private static final String ERR_IN_USE = " still in use";
91
Ray Milkeyd84f89b2018-08-17 14:54:17 -070092 @Reference(cardinality = ReferenceCardinality.MANDATORY)
Jian Liecae4382018-06-28 17:41:12 +090093 protected CoreService coreService;
94
Ray Milkeyd84f89b2018-08-17 14:54:17 -070095 @Reference(cardinality = ReferenceCardinality.MANDATORY)
Jian Liecae4382018-06-28 17:41:12 +090096 protected InstancePortStore instancePortStore;
97
Ray Milkeyd84f89b2018-08-17 14:54:17 -070098 @Reference(cardinality = ReferenceCardinality.MANDATORY)
Jian Li078cd202018-07-23 18:58:20 +090099 protected LeadershipService leadershipService;
100
Ray Milkeyd84f89b2018-08-17 14:54:17 -0700101 @Reference(cardinality = ReferenceCardinality.MANDATORY)
Jian Li078cd202018-07-23 18:58:20 +0900102 protected ClusterService clusterService;
103
Ray Milkeyd84f89b2018-08-17 14:54:17 -0700104 @Reference(cardinality = ReferenceCardinality.MANDATORY)
Jian Liecae4382018-06-28 17:41:12 +0900105 protected HostService hostService;
106
Ray Milkeyd84f89b2018-08-17 14:54:17 -0700107 @Reference(cardinality = ReferenceCardinality.MANDATORY)
Jian Lic38e9032018-08-09 17:08:38 +0900108 protected OpenstackRouterService routerService;
109
Jian Liecae4382018-06-28 17:41:12 +0900110 private final InstancePortStoreDelegate
111 delegate = new InternalInstancePortStoreDelegate();
112 private final InternalHostListener
113 hostListener = new InternalHostListener();
114
Jian Li078cd202018-07-23 18:58:20 +0900115 private ApplicationId appId;
116 private NodeId localNodeId;
117
Jian Liecae4382018-06-28 17:41:12 +0900118 @Activate
119 protected void activate() {
Jian Li078cd202018-07-23 18:58:20 +0900120 appId = coreService.registerApplication(Constants.OPENSTACK_NETWORKING_APP_ID);
121 localNodeId = clusterService.getLocalNode().id();
Jian Liecae4382018-06-28 17:41:12 +0900122 instancePortStore.setDelegate(delegate);
123 hostService.addListener(hostListener);
Jian Li078cd202018-07-23 18:58:20 +0900124 leadershipService.runForLeadership(appId.name());
125
Jian Liecae4382018-06-28 17:41:12 +0900126 log.info("Started");
127 }
128
129 @Deactivate
130 protected void deactivate() {
131 hostService.removeListener(hostListener);
132 instancePortStore.unsetDelegate(delegate);
Jian Li078cd202018-07-23 18:58:20 +0900133 leadershipService.withdraw(appId.name());
134
Jian Liecae4382018-06-28 17:41:12 +0900135 log.info("Stopped");
136 }
137
138 @Override
139 public void createInstancePort(InstancePort instancePort) {
140 checkNotNull(instancePort, ERR_NULL_INSTANCE_PORT);
141 checkArgument(!Strings.isNullOrEmpty(instancePort.portId()),
142 ERR_NULL_INSTANCE_PORT_ID);
143
144 instancePortStore.createInstancePort(instancePort);
145 log.info(String.format(MSG_INSTANCE_PORT, instancePort.portId(),
146 MSG_CREATED));
147 }
148
149 @Override
150 public void updateInstancePort(InstancePort instancePort) {
151 checkNotNull(instancePort, ERR_NULL_INSTANCE_PORT);
152 checkArgument(!Strings.isNullOrEmpty(instancePort.portId()),
153 ERR_NULL_INSTANCE_PORT_ID);
154
Jian Lida03ce92018-07-24 21:41:53 +0900155 // in case OpenStack removes the port prior to OVS, we will not update
156 // the instance port as it does not exist in the store
157 if (instancePortStore.instancePort(instancePort.portId()) == null) {
158 log.warn("Unable to update instance port {}, as it does not exist", instancePort.portId());
159 return;
160 }
161
Jian Liecae4382018-06-28 17:41:12 +0900162 instancePortStore.updateInstancePort(instancePort);
163 log.info(String.format(MSG_INSTANCE_PORT, instancePort.portId(), MSG_UPDATED));
164 }
165
166 @Override
167 public void removeInstancePort(String portId) {
168 checkArgument(!Strings.isNullOrEmpty(portId), ERR_NULL_INSTANCE_PORT_ID);
169
170 synchronized (this) {
171 if (isInstancePortInUse(portId)) {
172 final String error =
173 String.format(MSG_INSTANCE_PORT, portId, ERR_IN_USE);
174 throw new IllegalStateException(error);
175 }
176 InstancePort instancePort = instancePortStore.removeInstancePort(portId);
177 if (instancePort != null) {
178 log.info(String.format(MSG_INSTANCE_PORT, instancePort.portId(), MSG_REMOVED));
179 }
180 }
181 }
182
183 @Override
184 public void clear() {
185 instancePortStore.clear();
186 }
187
188 @Override
189 public InstancePort instancePort(MacAddress macAddress) {
190 checkNotNull(macAddress, ERR_NULL_MAC_ADDRESS);
191
192 return instancePortStore.instancePorts().stream()
193 .filter(port -> port.macAddress().equals(macAddress))
194 .findFirst().orElse(null);
195 }
196
197 @Override
198 public InstancePort instancePort(IpAddress ipAddress, String osNetId) {
199 checkNotNull(ipAddress, ERR_NULL_IP_ADDRESS);
200 checkNotNull(osNetId, ERR_NULL_NETWORK_ID);
201
202 return instancePortStore.instancePorts().stream()
203 .filter(port -> port.networkId().equals(osNetId))
204 .filter(port -> port.ipAddress().equals(ipAddress))
205 .findFirst().orElse(null);
206 }
207
208 @Override
209 public InstancePort instancePort(String portId) {
210 checkArgument(!Strings.isNullOrEmpty(portId), ERR_NULL_INSTANCE_PORT_ID);
211
212 return instancePortStore.instancePort(portId);
213 }
214
215 @Override
Jian Li63430202018-08-30 16:24:09 +0900216 public InstancePort instancePort(DeviceId deviceId, PortNumber portNumber) {
217 checkNotNull(deviceId, ERR_NULL_DEVICE_ID);
218 checkNotNull(portNumber, ERR_NULL_PORT_NUMBER);
219
220 return instancePortStore.instancePorts().stream()
221 .filter(port -> port.deviceId().equals(deviceId))
222 .filter(port -> port.portNumber().equals(portNumber))
223 .findFirst().orElse(null);
224 }
225
226 @Override
Daniel Park4fa1f5e2018-10-17 12:41:52 +0900227 public Set<InstancePort> instancePort(DeviceId deviceId) {
228 Set<InstancePort> ports = instancePortStore.instancePorts().stream()
229 .filter(port -> port.deviceId().equals(deviceId))
230 .collect(Collectors.toSet());
231
232 return ImmutableSet.copyOf(ports);
233 }
234
235 @Override
Jian Liecae4382018-06-28 17:41:12 +0900236 public Set<InstancePort> instancePorts() {
237 Set<InstancePort> ports = instancePortStore.instancePorts();
238
239 return ImmutableSet.copyOf(ports);
240 }
241
242 @Override
243 public Set<InstancePort> instancePorts(String osNetId) {
244 checkNotNull(osNetId, ERR_NULL_NETWORK_ID);
245
246 Set<InstancePort> ports = instancePortStore.instancePorts().stream()
247 .filter(port -> port.networkId().equals(osNetId))
248 .collect(Collectors.toSet());
249
250 return ImmutableSet.copyOf(ports);
251 }
252
Jian Lic38e9032018-08-09 17:08:38 +0900253 @Override
254 public IpAddress floatingIp(String osPortId) {
255 checkNotNull(osPortId, ERR_NULL_INSTANCE_PORT_ID);
256
257 return routerService.floatingIps().stream()
258 .filter(fip -> osPortId.equals(fip.getPortId()))
259 .filter(fip -> fip.getFloatingIpAddress() != null)
260 .map(fip -> IpAddress.valueOf(fip.getFloatingIpAddress()))
261 .findFirst().orElse(null);
262 }
263
Jian Liecae4382018-06-28 17:41:12 +0900264 private boolean isInstancePortInUse(String portId) {
265 // TODO add checking logic
266 return false;
267 }
268
269 private class InternalInstancePortStoreDelegate implements InstancePortStoreDelegate {
270
271 @Override
272 public void notify(InstancePortEvent event) {
273 if (event != null) {
274 log.trace("send instance port event {}", event);
275 process(event);
276 }
277 }
278 }
279
280 /**
281 * An internal listener that listens host event generated by HostLocationTracker
282 * in DistributedHostStore. The role of this listener is to convert host event
283 * to instance port event and post to the subscribers that have interested on
284 * this type of event.
285 */
286 private class InternalHostListener implements HostListener {
287
288 @Override
289 public boolean isRelevant(HostEvent event) {
290 Host host = event.subject();
291 if (!isValidHost(host)) {
292 log.debug("Invalid host detected, ignore it {}", host);
293 return false;
294 }
Jian Li078cd202018-07-23 18:58:20 +0900295
296 // do not allow to proceed without leadership
297 NodeId leader = leadershipService.getLeader(appId.name());
298 return Objects.equals(localNodeId, leader);
Jian Liecae4382018-06-28 17:41:12 +0900299 }
300
301 @Override
302 public void event(HostEvent event) {
303 InstancePort instPort = DefaultInstancePort.from(event.subject(), ACTIVE);
304
305 switch (event.type()) {
306 case HOST_UPDATED:
307 updateInstancePort(instPort);
308 break;
309 case HOST_ADDED:
Jian Li6a47fd02018-11-27 21:51:03 +0900310 processHostAddition(instPort);
Jian Liecae4382018-06-28 17:41:12 +0900311 break;
312 case HOST_REMOVED:
Jian Li6a47fd02018-11-27 21:51:03 +0900313 processHostRemoval(instPort);
Jian Liecae4382018-06-28 17:41:12 +0900314 break;
315 case HOST_MOVED:
Jian Li6a47fd02018-11-27 21:51:03 +0900316 processHostMove(event, instPort);
Jian Liecae4382018-06-28 17:41:12 +0900317 break;
318 default:
319 break;
320 }
321 }
322
Jian Li6a47fd02018-11-27 21:51:03 +0900323 private void processHostAddition(InstancePort instPort) {
324 InstancePort existingPort = instancePort(instPort.portId());
325 if (existingPort == null) {
326 // first time to add instance
327 createInstancePort(instPort);
328 } else {
329 if (existingPort.state() == INACTIVE) {
330
331 if (instPort.deviceId().equals(existingPort.deviceId())) {
332 // VM RESTART case
333 // if the ID of switch where VM is attached to is
334 // identical, we can assume that the VM was
335 // restarted in the same location;
336 // note that the switch port number where VM is
337 // attached can be varied per each restart
338 updateInstancePort(instPort);
339 } else {
340 // VM COLD MIGRATION case
341 // if the ID of switch where VM is attached to is
342 // varied, we can assume that the VM was migrated
343 // to a new location
344 updateInstancePort(instPort.updateState(MIGRATING));
345 InstancePort updated = instPort.updateState(MIGRATED);
346 updateInstancePort(updated.updatePrevLocation(
347 existingPort.deviceId(), existingPort.portNumber()));
348 }
349 }
350 }
351 }
352
353 private void processHostRemoval(InstancePort instPort) {
354 /* in case the instance port cannot be found in the store,
355 this indicates that the instance port was removed due to
356 the removal of openstack port; in some cases, openstack
357 port removal message arrives before ovs port removal message */
358 if (instancePortStore.instancePort(instPort.portId()) == null) {
359 log.debug("instance port was removed before ovs port removal");
360 return;
361 }
362
363 /* we will remove instance port from persistent store,
364 only if we receive port removal signal from neutron.
365 by default, we update the instance port state to INACTIVE
366 to indicate the instance is terminated */
367 updateInstancePort(instPort.updateState(INACTIVE));
368 }
369
370 private void processHostMove(HostEvent event, InstancePort instPort) {
371 Host oldHost = event.prevSubject();
372 Host currHost = event.subject();
373
374 // in the middle of VM migration
375 if (oldHost.locations().size() < currHost.locations().size()) {
376 updateInstancePort(instPort.updateState(MIGRATING));
377 }
378
379 // finish of VM migration
380 if (oldHost.locations().size() > currHost.locations().size()) {
381 Set<HostLocation> diff =
382 Sets.difference(oldHost.locations(), currHost.locations());
383 HostLocation location = diff.stream().findFirst().orElse(null);
384
385 if (location != null) {
386 InstancePort updated = instPort.updateState(MIGRATED);
387 updateInstancePort(updated.updatePrevLocation(
388 location.deviceId(), location.port()));
389 }
390 }
391 }
392
Jian Liecae4382018-06-28 17:41:12 +0900393 private boolean isValidHost(Host host) {
394 return !host.ipAddresses().isEmpty() &&
395 host.annotations().value(ANNOTATION_NETWORK_ID) != null &&
396 host.annotations().value(ANNOTATION_PORT_ID) != null;
397 }
398 }
399}