blob: 2d5c67543aa669a70658d9007f0f0a121db0278e [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
Jian Liecae4382018-06-28 17:41:12 +0900227 public Set<InstancePort> instancePorts() {
228 Set<InstancePort> ports = instancePortStore.instancePorts();
229
230 return ImmutableSet.copyOf(ports);
231 }
232
233 @Override
234 public Set<InstancePort> instancePorts(String osNetId) {
235 checkNotNull(osNetId, ERR_NULL_NETWORK_ID);
236
237 Set<InstancePort> ports = instancePortStore.instancePorts().stream()
238 .filter(port -> port.networkId().equals(osNetId))
239 .collect(Collectors.toSet());
240
241 return ImmutableSet.copyOf(ports);
242 }
243
Jian Lic38e9032018-08-09 17:08:38 +0900244 @Override
245 public IpAddress floatingIp(String osPortId) {
246 checkNotNull(osPortId, ERR_NULL_INSTANCE_PORT_ID);
247
248 return routerService.floatingIps().stream()
249 .filter(fip -> osPortId.equals(fip.getPortId()))
250 .filter(fip -> fip.getFloatingIpAddress() != null)
251 .map(fip -> IpAddress.valueOf(fip.getFloatingIpAddress()))
252 .findFirst().orElse(null);
253 }
254
Jian Liecae4382018-06-28 17:41:12 +0900255 private boolean isInstancePortInUse(String portId) {
256 // TODO add checking logic
257 return false;
258 }
259
260 private class InternalInstancePortStoreDelegate implements InstancePortStoreDelegate {
261
262 @Override
263 public void notify(InstancePortEvent event) {
264 if (event != null) {
265 log.trace("send instance port event {}", event);
266 process(event);
267 }
268 }
269 }
270
271 /**
272 * An internal listener that listens host event generated by HostLocationTracker
273 * in DistributedHostStore. The role of this listener is to convert host event
274 * to instance port event and post to the subscribers that have interested on
275 * this type of event.
276 */
277 private class InternalHostListener implements HostListener {
278
279 @Override
280 public boolean isRelevant(HostEvent event) {
281 Host host = event.subject();
282 if (!isValidHost(host)) {
283 log.debug("Invalid host detected, ignore it {}", host);
284 return false;
285 }
Jian Li078cd202018-07-23 18:58:20 +0900286
287 // do not allow to proceed without leadership
288 NodeId leader = leadershipService.getLeader(appId.name());
289 return Objects.equals(localNodeId, leader);
Jian Liecae4382018-06-28 17:41:12 +0900290 }
291
292 @Override
293 public void event(HostEvent event) {
294 InstancePort instPort = DefaultInstancePort.from(event.subject(), ACTIVE);
295
296 switch (event.type()) {
297 case HOST_UPDATED:
298 updateInstancePort(instPort);
299 break;
300 case HOST_ADDED:
Jian Liec5c32b2018-07-13 14:28:58 +0900301 InstancePort existingPort = instancePort(instPort.portId());
302 if (existingPort == null) {
303 // first time to add instance
304 createInstancePort(instPort);
305 } else {
Jian Li46b74002018-07-15 18:39:08 +0900306 if (existingPort.state() == INACTIVE) {
Jian Liee8214a2018-07-21 20:07:28 +0900307
308 if (instPort.deviceId().equals(existingPort.deviceId())) {
309
310 // VM RESTART case
311 // if the ID of switch where VM is attached to is
312 // identical, we can assume that the VM was
313 // restarted in the same location;
314 // note that the switch port number where VM is
315 // attached can be varied per each restart
316 updateInstancePort(instPort);
317 } else {
318
319 // VM COLD MIGRATION case
320 // if the ID of switch where VM is attached to is
321 // varied, we can assume that the VM was migrated
322 // to a new location
323 updateInstancePort(instPort.updateState(MIGRATING));
324 InstancePort updated = instPort.updateState(MIGRATED);
325 updateInstancePort(updated.updatePrevLocation(
326 existingPort.deviceId(), existingPort.portNumber()));
327 }
Jian Liec5c32b2018-07-13 14:28:58 +0900328 }
329 }
Jian Liecae4382018-06-28 17:41:12 +0900330 break;
331 case HOST_REMOVED:
Jian Li0488c732018-09-14 20:53:07 +0900332
333 // in case the instance port cannot be found in the store,
334 // this indicates that the instance port was removed due to
335 // the removal of openstack port; in some cases, openstack
336 // port removal message arrives before ovs port removal message
337 if (instancePortStore.instancePort(instPort.portId()) == null) {
338 log.debug("instance port was removed before ovs port removal");
339 break;
340 }
341
Jian Liec5c32b2018-07-13 14:28:58 +0900342 // we will remove instance port from persistent store,
Jian Liee8214a2018-07-21 20:07:28 +0900343 // only if we receive port removal signal from neutron.
Jian Liec5c32b2018-07-13 14:28:58 +0900344 // by default, we update the instance port state to INACTIVE
345 // to indicate the instance is terminated
346 updateInstancePort(instPort.updateState(INACTIVE));
Jian Liecae4382018-06-28 17:41:12 +0900347 break;
348 case HOST_MOVED:
Jian Liec5c32b2018-07-13 14:28:58 +0900349 Host oldHost = event.prevSubject();
350 Host currHost = event.subject();
351
352 // in the middle of VM migration
353 if (oldHost.locations().size() < currHost.locations().size()) {
354 updateInstancePort(instPort.updateState(MIGRATING));
355 }
356
357 // finish of VM migration
358 if (oldHost.locations().size() > currHost.locations().size()) {
359 Set<HostLocation> diff =
360 Sets.difference(oldHost.locations(), currHost.locations());
361 HostLocation location = diff.stream().findFirst().orElse(null);
362
363 if (location != null) {
364 InstancePort updated = instPort.updateState(MIGRATED);
Jian Li46b74002018-07-15 18:39:08 +0900365 updateInstancePort(updated.updatePrevLocation(
Jian Liec5c32b2018-07-13 14:28:58 +0900366 location.deviceId(), location.port()));
367 }
368 }
Jian Liecae4382018-06-28 17:41:12 +0900369 break;
370 default:
371 break;
372 }
373 }
374
375 private boolean isValidHost(Host host) {
376 return !host.ipAddresses().isEmpty() &&
377 host.annotations().value(ANNOTATION_NETWORK_ID) != null &&
378 host.annotations().value(ANNOTATION_PORT_ID) != null;
379 }
380 }
381}