blob: 645a86dcee37048f94c577b41f26996f0bc6d589 [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;
35import org.onosproject.net.Host;
Jian Liec5c32b2018-07-13 14:28:58 +090036import org.onosproject.net.HostLocation;
Jian Liecae4382018-06-28 17:41:12 +090037import org.onosproject.net.host.HostEvent;
38import org.onosproject.net.host.HostListener;
39import org.onosproject.net.host.HostService;
40import org.onosproject.openstacknetworking.api.Constants;
41import org.onosproject.openstacknetworking.api.InstancePort;
42import org.onosproject.openstacknetworking.api.InstancePortAdminService;
43import org.onosproject.openstacknetworking.api.InstancePortEvent;
44import org.onosproject.openstacknetworking.api.InstancePortListener;
45import org.onosproject.openstacknetworking.api.InstancePortService;
46import org.onosproject.openstacknetworking.api.InstancePortStore;
47import org.onosproject.openstacknetworking.api.InstancePortStoreDelegate;
Jian Lic38e9032018-08-09 17:08:38 +090048import org.onosproject.openstacknetworking.api.OpenstackRouterService;
Jian Liecae4382018-06-28 17:41:12 +090049import org.slf4j.Logger;
50
Jian Li078cd202018-07-23 18:58:20 +090051import java.util.Objects;
Jian Liecae4382018-06-28 17:41:12 +090052import java.util.Set;
53import java.util.stream.Collectors;
54
55import static com.google.common.base.Preconditions.checkArgument;
56import static com.google.common.base.Preconditions.checkNotNull;
Jian Liec5c32b2018-07-13 14:28:58 +090057import static org.onosproject.openstacknetworking.api.Constants.ANNOTATION_NETWORK_ID;
58import static org.onosproject.openstacknetworking.api.Constants.ANNOTATION_PORT_ID;
Jian Liecae4382018-06-28 17:41:12 +090059import static org.onosproject.openstacknetworking.api.InstancePort.State.ACTIVE;
Jian Liec5c32b2018-07-13 14:28:58 +090060import static org.onosproject.openstacknetworking.api.InstancePort.State.INACTIVE;
61import static org.onosproject.openstacknetworking.api.InstancePort.State.MIGRATED;
62import static org.onosproject.openstacknetworking.api.InstancePort.State.MIGRATING;
Jian Liecae4382018-06-28 17:41:12 +090063import static org.slf4j.LoggerFactory.getLogger;
64
65/**
66 * Provides implementation of administering and interfacing instance ports.
67 * It also provides instance port events for the hosts mapped to OpenStack VM interface.
68 */
69@Service
Jian Liec5c32b2018-07-13 14:28:58 +090070@Component(immediate = true)
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";
87
88 private static final String ERR_IN_USE = " still in use";
89
90 @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
91 protected CoreService coreService;
92
93 @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
94 protected InstancePortStore instancePortStore;
95
96 @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
Jian Li078cd202018-07-23 18:58:20 +090097 protected LeadershipService leadershipService;
98
99 @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
100 protected ClusterService clusterService;
101
102 @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
Jian Liecae4382018-06-28 17:41:12 +0900103 protected HostService hostService;
104
Jian Lic38e9032018-08-09 17:08:38 +0900105 @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
106 protected OpenstackRouterService routerService;
107
Jian Liecae4382018-06-28 17:41:12 +0900108 private final InstancePortStoreDelegate
109 delegate = new InternalInstancePortStoreDelegate();
110 private final InternalHostListener
111 hostListener = new InternalHostListener();
112
Jian Li078cd202018-07-23 18:58:20 +0900113 private ApplicationId appId;
114 private NodeId localNodeId;
115
Jian Liecae4382018-06-28 17:41:12 +0900116 @Activate
117 protected void activate() {
Jian Li078cd202018-07-23 18:58:20 +0900118 appId = coreService.registerApplication(Constants.OPENSTACK_NETWORKING_APP_ID);
119 localNodeId = clusterService.getLocalNode().id();
Jian Liecae4382018-06-28 17:41:12 +0900120 instancePortStore.setDelegate(delegate);
121 hostService.addListener(hostListener);
Jian Li078cd202018-07-23 18:58:20 +0900122 leadershipService.runForLeadership(appId.name());
123
Jian Liecae4382018-06-28 17:41:12 +0900124 log.info("Started");
125 }
126
127 @Deactivate
128 protected void deactivate() {
129 hostService.removeListener(hostListener);
130 instancePortStore.unsetDelegate(delegate);
Jian Li078cd202018-07-23 18:58:20 +0900131 leadershipService.withdraw(appId.name());
132
Jian Liecae4382018-06-28 17:41:12 +0900133 log.info("Stopped");
134 }
135
136 @Override
137 public void createInstancePort(InstancePort instancePort) {
138 checkNotNull(instancePort, ERR_NULL_INSTANCE_PORT);
139 checkArgument(!Strings.isNullOrEmpty(instancePort.portId()),
140 ERR_NULL_INSTANCE_PORT_ID);
141
142 instancePortStore.createInstancePort(instancePort);
143 log.info(String.format(MSG_INSTANCE_PORT, instancePort.portId(),
144 MSG_CREATED));
145 }
146
147 @Override
148 public void updateInstancePort(InstancePort instancePort) {
149 checkNotNull(instancePort, ERR_NULL_INSTANCE_PORT);
150 checkArgument(!Strings.isNullOrEmpty(instancePort.portId()),
151 ERR_NULL_INSTANCE_PORT_ID);
152
Jian Lida03ce92018-07-24 21:41:53 +0900153 // in case OpenStack removes the port prior to OVS, we will not update
154 // the instance port as it does not exist in the store
155 if (instancePortStore.instancePort(instancePort.portId()) == null) {
156 log.warn("Unable to update instance port {}, as it does not exist", instancePort.portId());
157 return;
158 }
159
Jian Liecae4382018-06-28 17:41:12 +0900160 instancePortStore.updateInstancePort(instancePort);
161 log.info(String.format(MSG_INSTANCE_PORT, instancePort.portId(), MSG_UPDATED));
162 }
163
164 @Override
165 public void removeInstancePort(String portId) {
166 checkArgument(!Strings.isNullOrEmpty(portId), ERR_NULL_INSTANCE_PORT_ID);
167
168 synchronized (this) {
169 if (isInstancePortInUse(portId)) {
170 final String error =
171 String.format(MSG_INSTANCE_PORT, portId, ERR_IN_USE);
172 throw new IllegalStateException(error);
173 }
174 InstancePort instancePort = instancePortStore.removeInstancePort(portId);
175 if (instancePort != null) {
176 log.info(String.format(MSG_INSTANCE_PORT, instancePort.portId(), MSG_REMOVED));
177 }
178 }
179 }
180
181 @Override
182 public void clear() {
183 instancePortStore.clear();
184 }
185
186 @Override
187 public InstancePort instancePort(MacAddress macAddress) {
188 checkNotNull(macAddress, ERR_NULL_MAC_ADDRESS);
189
190 return instancePortStore.instancePorts().stream()
191 .filter(port -> port.macAddress().equals(macAddress))
192 .findFirst().orElse(null);
193 }
194
195 @Override
196 public InstancePort instancePort(IpAddress ipAddress, String osNetId) {
197 checkNotNull(ipAddress, ERR_NULL_IP_ADDRESS);
198 checkNotNull(osNetId, ERR_NULL_NETWORK_ID);
199
200 return instancePortStore.instancePorts().stream()
201 .filter(port -> port.networkId().equals(osNetId))
202 .filter(port -> port.ipAddress().equals(ipAddress))
203 .findFirst().orElse(null);
204 }
205
206 @Override
207 public InstancePort instancePort(String portId) {
208 checkArgument(!Strings.isNullOrEmpty(portId), ERR_NULL_INSTANCE_PORT_ID);
209
210 return instancePortStore.instancePort(portId);
211 }
212
213 @Override
214 public Set<InstancePort> instancePorts() {
215 Set<InstancePort> ports = instancePortStore.instancePorts();
216
217 return ImmutableSet.copyOf(ports);
218 }
219
220 @Override
221 public Set<InstancePort> instancePorts(String osNetId) {
222 checkNotNull(osNetId, ERR_NULL_NETWORK_ID);
223
224 Set<InstancePort> ports = instancePortStore.instancePorts().stream()
225 .filter(port -> port.networkId().equals(osNetId))
226 .collect(Collectors.toSet());
227
228 return ImmutableSet.copyOf(ports);
229 }
230
Jian Lic38e9032018-08-09 17:08:38 +0900231 @Override
232 public IpAddress floatingIp(String osPortId) {
233 checkNotNull(osPortId, ERR_NULL_INSTANCE_PORT_ID);
234
235 return routerService.floatingIps().stream()
236 .filter(fip -> osPortId.equals(fip.getPortId()))
237 .filter(fip -> fip.getFloatingIpAddress() != null)
238 .map(fip -> IpAddress.valueOf(fip.getFloatingIpAddress()))
239 .findFirst().orElse(null);
240 }
241
Jian Liecae4382018-06-28 17:41:12 +0900242 private boolean isInstancePortInUse(String portId) {
243 // TODO add checking logic
244 return false;
245 }
246
247 private class InternalInstancePortStoreDelegate implements InstancePortStoreDelegate {
248
249 @Override
250 public void notify(InstancePortEvent event) {
251 if (event != null) {
252 log.trace("send instance port event {}", event);
253 process(event);
254 }
255 }
256 }
257
258 /**
259 * An internal listener that listens host event generated by HostLocationTracker
260 * in DistributedHostStore. The role of this listener is to convert host event
261 * to instance port event and post to the subscribers that have interested on
262 * this type of event.
263 */
264 private class InternalHostListener implements HostListener {
265
266 @Override
267 public boolean isRelevant(HostEvent event) {
268 Host host = event.subject();
269 if (!isValidHost(host)) {
270 log.debug("Invalid host detected, ignore it {}", host);
271 return false;
272 }
Jian Li078cd202018-07-23 18:58:20 +0900273
274 // do not allow to proceed without leadership
275 NodeId leader = leadershipService.getLeader(appId.name());
276 return Objects.equals(localNodeId, leader);
Jian Liecae4382018-06-28 17:41:12 +0900277 }
278
279 @Override
280 public void event(HostEvent event) {
281 InstancePort instPort = DefaultInstancePort.from(event.subject(), ACTIVE);
282
283 switch (event.type()) {
284 case HOST_UPDATED:
285 updateInstancePort(instPort);
286 break;
287 case HOST_ADDED:
Jian Liec5c32b2018-07-13 14:28:58 +0900288 InstancePort existingPort = instancePort(instPort.portId());
289 if (existingPort == null) {
290 // first time to add instance
291 createInstancePort(instPort);
292 } else {
Jian Li46b74002018-07-15 18:39:08 +0900293 if (existingPort.state() == INACTIVE) {
Jian Liee8214a2018-07-21 20:07:28 +0900294
295 if (instPort.deviceId().equals(existingPort.deviceId())) {
296
297 // VM RESTART case
298 // if the ID of switch where VM is attached to is
299 // identical, we can assume that the VM was
300 // restarted in the same location;
301 // note that the switch port number where VM is
302 // attached can be varied per each restart
303 updateInstancePort(instPort);
304 } else {
305
306 // VM COLD MIGRATION case
307 // if the ID of switch where VM is attached to is
308 // varied, we can assume that the VM was migrated
309 // to a new location
310 updateInstancePort(instPort.updateState(MIGRATING));
311 InstancePort updated = instPort.updateState(MIGRATED);
312 updateInstancePort(updated.updatePrevLocation(
313 existingPort.deviceId(), existingPort.portNumber()));
314 }
Jian Liec5c32b2018-07-13 14:28:58 +0900315 }
316 }
Jian Liecae4382018-06-28 17:41:12 +0900317 break;
318 case HOST_REMOVED:
Jian Liec5c32b2018-07-13 14:28:58 +0900319 // we will remove instance port from persistent store,
Jian Liee8214a2018-07-21 20:07:28 +0900320 // only if we receive port removal signal from neutron.
Jian Liec5c32b2018-07-13 14:28:58 +0900321 // by default, we update the instance port state to INACTIVE
322 // to indicate the instance is terminated
323 updateInstancePort(instPort.updateState(INACTIVE));
Jian Liecae4382018-06-28 17:41:12 +0900324 break;
325 case HOST_MOVED:
Jian Liec5c32b2018-07-13 14:28:58 +0900326 Host oldHost = event.prevSubject();
327 Host currHost = event.subject();
328
329 // in the middle of VM migration
330 if (oldHost.locations().size() < currHost.locations().size()) {
331 updateInstancePort(instPort.updateState(MIGRATING));
332 }
333
334 // finish of VM migration
335 if (oldHost.locations().size() > currHost.locations().size()) {
336 Set<HostLocation> diff =
337 Sets.difference(oldHost.locations(), currHost.locations());
338 HostLocation location = diff.stream().findFirst().orElse(null);
339
340 if (location != null) {
341 InstancePort updated = instPort.updateState(MIGRATED);
Jian Li46b74002018-07-15 18:39:08 +0900342 updateInstancePort(updated.updatePrevLocation(
Jian Liec5c32b2018-07-13 14:28:58 +0900343 location.deviceId(), location.port()));
344 }
345 }
Jian Liecae4382018-06-28 17:41:12 +0900346 break;
347 default:
348 break;
349 }
350 }
351
352 private boolean isValidHost(Host host) {
353 return !host.ipAddresses().isEmpty() &&
354 host.annotations().value(ANNOTATION_NETWORK_ID) != null &&
355 host.annotations().value(ANNOTATION_PORT_ID) != null;
356 }
357 }
358}