blob: 2ddb18195e6a6331c542cc845900915c403acf4f [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.apache.felix.scr.annotations.Activate;
22import org.apache.felix.scr.annotations.Component;
23import org.apache.felix.scr.annotations.Deactivate;
24import org.apache.felix.scr.annotations.Reference;
25import org.apache.felix.scr.annotations.ReferenceCardinality;
26import org.apache.felix.scr.annotations.Service;
27import org.onlab.packet.IpAddress;
28import org.onlab.packet.MacAddress;
Jian Li078cd202018-07-23 18:58:20 +090029import org.onosproject.cluster.ClusterService;
30import org.onosproject.cluster.LeadershipService;
31import org.onosproject.cluster.NodeId;
32import org.onosproject.core.ApplicationId;
Jian Liecae4382018-06-28 17:41:12 +090033import org.onosproject.core.CoreService;
34import org.onosproject.event.ListenerRegistry;
Jian Li63430202018-08-30 16:24:09 +090035import org.onosproject.net.DeviceId;
Jian Liecae4382018-06-28 17:41:12 +090036import org.onosproject.net.Host;
Jian Liec5c32b2018-07-13 14:28:58 +090037import org.onosproject.net.HostLocation;
Jian Li63430202018-08-30 16:24:09 +090038import org.onosproject.net.PortNumber;
Jian Liecae4382018-06-28 17:41:12 +090039import org.onosproject.net.host.HostEvent;
40import org.onosproject.net.host.HostListener;
41import org.onosproject.net.host.HostService;
42import org.onosproject.openstacknetworking.api.Constants;
43import org.onosproject.openstacknetworking.api.InstancePort;
44import org.onosproject.openstacknetworking.api.InstancePortAdminService;
45import org.onosproject.openstacknetworking.api.InstancePortEvent;
46import org.onosproject.openstacknetworking.api.InstancePortListener;
47import org.onosproject.openstacknetworking.api.InstancePortService;
48import org.onosproject.openstacknetworking.api.InstancePortStore;
49import org.onosproject.openstacknetworking.api.InstancePortStoreDelegate;
Jian Lic38e9032018-08-09 17:08:38 +090050import org.onosproject.openstacknetworking.api.OpenstackRouterService;
Jian Liecae4382018-06-28 17:41:12 +090051import org.slf4j.Logger;
52
Jian Li078cd202018-07-23 18:58:20 +090053import java.util.Objects;
Jian Liecae4382018-06-28 17:41:12 +090054import java.util.Set;
55import java.util.stream.Collectors;
56
57import static com.google.common.base.Preconditions.checkArgument;
58import static com.google.common.base.Preconditions.checkNotNull;
Jian Liec5c32b2018-07-13 14:28:58 +090059import static org.onosproject.openstacknetworking.api.Constants.ANNOTATION_NETWORK_ID;
60import static org.onosproject.openstacknetworking.api.Constants.ANNOTATION_PORT_ID;
Jian Liecae4382018-06-28 17:41:12 +090061import static org.onosproject.openstacknetworking.api.InstancePort.State.ACTIVE;
Jian Liec5c32b2018-07-13 14:28:58 +090062import static org.onosproject.openstacknetworking.api.InstancePort.State.INACTIVE;
63import static org.onosproject.openstacknetworking.api.InstancePort.State.MIGRATED;
64import static org.onosproject.openstacknetworking.api.InstancePort.State.MIGRATING;
Jian Liecae4382018-06-28 17:41:12 +090065import static org.slf4j.LoggerFactory.getLogger;
66
67/**
68 * Provides implementation of administering and interfacing instance ports.
69 * It also provides instance port events for the hosts mapped to OpenStack VM interface.
70 */
71@Service
Jian Liec5c32b2018-07-13 14:28:58 +090072@Component(immediate = true)
Jian Liecae4382018-06-28 17:41:12 +090073public class InstancePortManager
74 extends ListenerRegistry<InstancePortEvent, InstancePortListener>
75 implements InstancePortService, InstancePortAdminService {
76
77 protected final Logger log = getLogger(getClass());
78
79 private static final String MSG_INSTANCE_PORT = "Instance port %s %s";
80 private static final String MSG_CREATED = "created";
81 private static final String MSG_UPDATED = "updated";
82 private static final String MSG_REMOVED = "removed";
83
84 private static final String ERR_NULL_INSTANCE_PORT = "Instance port cannot be null";
85 private static final String ERR_NULL_INSTANCE_PORT_ID = "Instance port ID cannot be null";
86 private static final String ERR_NULL_MAC_ADDRESS = "MAC address cannot be null";
87 private static final String ERR_NULL_IP_ADDRESS = "IP address cannot be null";
88 private static final String ERR_NULL_NETWORK_ID = "Network ID cannot be null";
Jian Li63430202018-08-30 16:24:09 +090089 private static final String ERR_NULL_DEVICE_ID = "Device ID cannot be null";
90 private static final String ERR_NULL_PORT_NUMBER = "Port number cannot be null";
Jian Liecae4382018-06-28 17:41:12 +090091
92 private static final String ERR_IN_USE = " still in use";
93
94 @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
95 protected CoreService coreService;
96
97 @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
98 protected InstancePortStore instancePortStore;
99
100 @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
Jian Li078cd202018-07-23 18:58:20 +0900101 protected LeadershipService leadershipService;
102
103 @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
104 protected ClusterService clusterService;
105
106 @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
Jian Liecae4382018-06-28 17:41:12 +0900107 protected HostService hostService;
108
Jian Lic38e9032018-08-09 17:08:38 +0900109 @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
110 protected OpenstackRouterService routerService;
111
Jian Liecae4382018-06-28 17:41:12 +0900112 private final InstancePortStoreDelegate
113 delegate = new InternalInstancePortStoreDelegate();
114 private final InternalHostListener
115 hostListener = new InternalHostListener();
116
Jian Li078cd202018-07-23 18:58:20 +0900117 private ApplicationId appId;
118 private NodeId localNodeId;
119
Jian Liecae4382018-06-28 17:41:12 +0900120 @Activate
121 protected void activate() {
Jian Li078cd202018-07-23 18:58:20 +0900122 appId = coreService.registerApplication(Constants.OPENSTACK_NETWORKING_APP_ID);
123 localNodeId = clusterService.getLocalNode().id();
Jian Liecae4382018-06-28 17:41:12 +0900124 instancePortStore.setDelegate(delegate);
125 hostService.addListener(hostListener);
Jian Li078cd202018-07-23 18:58:20 +0900126 leadershipService.runForLeadership(appId.name());
127
Jian Liecae4382018-06-28 17:41:12 +0900128 log.info("Started");
129 }
130
131 @Deactivate
132 protected void deactivate() {
133 hostService.removeListener(hostListener);
134 instancePortStore.unsetDelegate(delegate);
Jian Li078cd202018-07-23 18:58:20 +0900135 leadershipService.withdraw(appId.name());
136
Jian Liecae4382018-06-28 17:41:12 +0900137 log.info("Stopped");
138 }
139
140 @Override
141 public void createInstancePort(InstancePort instancePort) {
142 checkNotNull(instancePort, ERR_NULL_INSTANCE_PORT);
143 checkArgument(!Strings.isNullOrEmpty(instancePort.portId()),
144 ERR_NULL_INSTANCE_PORT_ID);
145
146 instancePortStore.createInstancePort(instancePort);
147 log.info(String.format(MSG_INSTANCE_PORT, instancePort.portId(),
148 MSG_CREATED));
149 }
150
151 @Override
152 public void updateInstancePort(InstancePort instancePort) {
153 checkNotNull(instancePort, ERR_NULL_INSTANCE_PORT);
154 checkArgument(!Strings.isNullOrEmpty(instancePort.portId()),
155 ERR_NULL_INSTANCE_PORT_ID);
156
Jian Lida03ce92018-07-24 21:41:53 +0900157 // in case OpenStack removes the port prior to OVS, we will not update
158 // the instance port as it does not exist in the store
159 if (instancePortStore.instancePort(instancePort.portId()) == null) {
160 log.warn("Unable to update instance port {}, as it does not exist", instancePort.portId());
161 return;
162 }
163
Jian Liecae4382018-06-28 17:41:12 +0900164 instancePortStore.updateInstancePort(instancePort);
165 log.info(String.format(MSG_INSTANCE_PORT, instancePort.portId(), MSG_UPDATED));
166 }
167
168 @Override
169 public void removeInstancePort(String portId) {
170 checkArgument(!Strings.isNullOrEmpty(portId), ERR_NULL_INSTANCE_PORT_ID);
171
172 synchronized (this) {
173 if (isInstancePortInUse(portId)) {
174 final String error =
175 String.format(MSG_INSTANCE_PORT, portId, ERR_IN_USE);
176 throw new IllegalStateException(error);
177 }
178 InstancePort instancePort = instancePortStore.removeInstancePort(portId);
179 if (instancePort != null) {
180 log.info(String.format(MSG_INSTANCE_PORT, instancePort.portId(), MSG_REMOVED));
181 }
182 }
183 }
184
185 @Override
186 public void clear() {
187 instancePortStore.clear();
188 }
189
190 @Override
191 public InstancePort instancePort(MacAddress macAddress) {
192 checkNotNull(macAddress, ERR_NULL_MAC_ADDRESS);
193
194 return instancePortStore.instancePorts().stream()
195 .filter(port -> port.macAddress().equals(macAddress))
196 .findFirst().orElse(null);
197 }
198
199 @Override
200 public InstancePort instancePort(IpAddress ipAddress, String osNetId) {
201 checkNotNull(ipAddress, ERR_NULL_IP_ADDRESS);
202 checkNotNull(osNetId, ERR_NULL_NETWORK_ID);
203
204 return instancePortStore.instancePorts().stream()
205 .filter(port -> port.networkId().equals(osNetId))
206 .filter(port -> port.ipAddress().equals(ipAddress))
207 .findFirst().orElse(null);
208 }
209
210 @Override
211 public InstancePort instancePort(String portId) {
212 checkArgument(!Strings.isNullOrEmpty(portId), ERR_NULL_INSTANCE_PORT_ID);
213
214 return instancePortStore.instancePort(portId);
215 }
216
217 @Override
Jian Li63430202018-08-30 16:24:09 +0900218 public InstancePort instancePort(DeviceId deviceId, PortNumber portNumber) {
219 checkNotNull(deviceId, ERR_NULL_DEVICE_ID);
220 checkNotNull(portNumber, ERR_NULL_PORT_NUMBER);
221
222 return instancePortStore.instancePorts().stream()
223 .filter(port -> port.deviceId().equals(deviceId))
224 .filter(port -> port.portNumber().equals(portNumber))
225 .findFirst().orElse(null);
226 }
227
228 @Override
Daniel Park4fa1f5e2018-10-17 12:41:52 +0900229 public Set<InstancePort> instancePort(DeviceId deviceId) {
230 Set<InstancePort> ports = instancePortStore.instancePorts().stream()
231 .filter(port -> port.deviceId().equals(deviceId))
232 .collect(Collectors.toSet());
233
234 return ImmutableSet.copyOf(ports);
235 }
236
237 @Override
Jian Liecae4382018-06-28 17:41:12 +0900238 public Set<InstancePort> instancePorts() {
239 Set<InstancePort> ports = instancePortStore.instancePorts();
240
241 return ImmutableSet.copyOf(ports);
242 }
243
244 @Override
245 public Set<InstancePort> instancePorts(String osNetId) {
246 checkNotNull(osNetId, ERR_NULL_NETWORK_ID);
247
248 Set<InstancePort> ports = instancePortStore.instancePorts().stream()
249 .filter(port -> port.networkId().equals(osNetId))
250 .collect(Collectors.toSet());
251
252 return ImmutableSet.copyOf(ports);
253 }
254
Jian Lic38e9032018-08-09 17:08:38 +0900255 @Override
256 public IpAddress floatingIp(String osPortId) {
257 checkNotNull(osPortId, ERR_NULL_INSTANCE_PORT_ID);
258
259 return routerService.floatingIps().stream()
260 .filter(fip -> osPortId.equals(fip.getPortId()))
261 .filter(fip -> fip.getFloatingIpAddress() != null)
262 .map(fip -> IpAddress.valueOf(fip.getFloatingIpAddress()))
263 .findFirst().orElse(null);
264 }
265
Jian Liecae4382018-06-28 17:41:12 +0900266 private boolean isInstancePortInUse(String portId) {
267 // TODO add checking logic
268 return false;
269 }
270
271 private class InternalInstancePortStoreDelegate implements InstancePortStoreDelegate {
272
273 @Override
274 public void notify(InstancePortEvent event) {
275 if (event != null) {
276 log.trace("send instance port event {}", event);
277 process(event);
278 }
279 }
280 }
281
282 /**
283 * An internal listener that listens host event generated by HostLocationTracker
284 * in DistributedHostStore. The role of this listener is to convert host event
285 * to instance port event and post to the subscribers that have interested on
286 * this type of event.
287 */
288 private class InternalHostListener implements HostListener {
289
290 @Override
291 public boolean isRelevant(HostEvent event) {
292 Host host = event.subject();
293 if (!isValidHost(host)) {
294 log.debug("Invalid host detected, ignore it {}", host);
295 return false;
296 }
Jian Li078cd202018-07-23 18:58:20 +0900297
298 // do not allow to proceed without leadership
299 NodeId leader = leadershipService.getLeader(appId.name());
300 return Objects.equals(localNodeId, leader);
Jian Liecae4382018-06-28 17:41:12 +0900301 }
302
303 @Override
304 public void event(HostEvent event) {
305 InstancePort instPort = DefaultInstancePort.from(event.subject(), ACTIVE);
306
307 switch (event.type()) {
308 case HOST_UPDATED:
309 updateInstancePort(instPort);
310 break;
311 case HOST_ADDED:
Jian Liec5c32b2018-07-13 14:28:58 +0900312 InstancePort existingPort = instancePort(instPort.portId());
313 if (existingPort == null) {
314 // first time to add instance
315 createInstancePort(instPort);
316 } else {
Jian Li46b74002018-07-15 18:39:08 +0900317 if (existingPort.state() == INACTIVE) {
Jian Liee8214a2018-07-21 20:07:28 +0900318
319 if (instPort.deviceId().equals(existingPort.deviceId())) {
320
321 // VM RESTART case
322 // if the ID of switch where VM is attached to is
323 // identical, we can assume that the VM was
324 // restarted in the same location;
325 // note that the switch port number where VM is
326 // attached can be varied per each restart
327 updateInstancePort(instPort);
328 } else {
329
330 // VM COLD MIGRATION case
331 // if the ID of switch where VM is attached to is
332 // varied, we can assume that the VM was migrated
333 // to a new location
334 updateInstancePort(instPort.updateState(MIGRATING));
335 InstancePort updated = instPort.updateState(MIGRATED);
336 updateInstancePort(updated.updatePrevLocation(
337 existingPort.deviceId(), existingPort.portNumber()));
338 }
Jian Liec5c32b2018-07-13 14:28:58 +0900339 }
340 }
Jian Liecae4382018-06-28 17:41:12 +0900341 break;
342 case HOST_REMOVED:
Jian Li0488c732018-09-14 20:53:07 +0900343
344 // in case the instance port cannot be found in the store,
345 // this indicates that the instance port was removed due to
346 // the removal of openstack port; in some cases, openstack
347 // port removal message arrives before ovs port removal message
348 if (instancePortStore.instancePort(instPort.portId()) == null) {
349 log.debug("instance port was removed before ovs port removal");
350 break;
351 }
352
Jian Liec5c32b2018-07-13 14:28:58 +0900353 // we will remove instance port from persistent store,
Jian Liee8214a2018-07-21 20:07:28 +0900354 // only if we receive port removal signal from neutron.
Jian Liec5c32b2018-07-13 14:28:58 +0900355 // by default, we update the instance port state to INACTIVE
356 // to indicate the instance is terminated
357 updateInstancePort(instPort.updateState(INACTIVE));
Jian Liecae4382018-06-28 17:41:12 +0900358 break;
359 case HOST_MOVED:
Jian Liec5c32b2018-07-13 14:28:58 +0900360 Host oldHost = event.prevSubject();
361 Host currHost = event.subject();
362
363 // in the middle of VM migration
364 if (oldHost.locations().size() < currHost.locations().size()) {
365 updateInstancePort(instPort.updateState(MIGRATING));
366 }
367
368 // finish of VM migration
369 if (oldHost.locations().size() > currHost.locations().size()) {
370 Set<HostLocation> diff =
371 Sets.difference(oldHost.locations(), currHost.locations());
372 HostLocation location = diff.stream().findFirst().orElse(null);
373
374 if (location != null) {
375 InstancePort updated = instPort.updateState(MIGRATED);
Jian Li46b74002018-07-15 18:39:08 +0900376 updateInstancePort(updated.updatePrevLocation(
Jian Liec5c32b2018-07-13 14:28:58 +0900377 location.deviceId(), location.port()));
378 }
379 }
Jian Liecae4382018-06-28 17:41:12 +0900380 break;
381 default:
382 break;
383 }
384 }
385
386 private boolean isValidHost(Host host) {
387 return !host.ipAddresses().isEmpty() &&
388 host.annotations().value(ANNOTATION_NETWORK_ID) != null &&
389 host.annotations().value(ANNOTATION_PORT_ID) != null;
390 }
391 }
392}