blob: 01e109907f00253d8cdbebab207ca057fcc5b827 [file] [log] [blame]
Hyunsun Moon44aac662017-02-18 02:07:01 +09001/*
Brian O'Connora09fe5b2017-08-03 21:12:30 -07002 * Copyright 2016-present Open Networking Foundation
Hyunsun Moon44aac662017-02-18 02:07:01 +09003 *
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 org.apache.felix.scr.annotations.Activate;
20import org.apache.felix.scr.annotations.Component;
21import org.apache.felix.scr.annotations.Deactivate;
22import org.apache.felix.scr.annotations.Reference;
23import org.apache.felix.scr.annotations.ReferenceCardinality;
24import org.apache.felix.scr.annotations.Service;
25import org.onlab.packet.IpAddress;
26import org.onlab.packet.MacAddress;
27import org.onlab.packet.VlanId;
28import org.onlab.util.Tools;
29import org.onosproject.core.CoreService;
30import org.onosproject.mastership.MastershipService;
31import org.onosproject.net.ConnectPoint;
32import org.onosproject.net.DefaultAnnotations;
33import org.onosproject.net.Device;
34import org.onosproject.net.Host;
35import org.onosproject.net.HostId;
36import org.onosproject.net.HostLocation;
37import org.onosproject.net.Port;
38import org.onosproject.net.device.DeviceEvent;
39import org.onosproject.net.device.DeviceListener;
40import org.onosproject.net.device.DeviceService;
41import org.onosproject.net.host.DefaultHostDescription;
42import org.onosproject.net.host.HostDescription;
43import org.onosproject.net.host.HostProvider;
44import org.onosproject.net.host.HostProviderRegistry;
45import org.onosproject.net.host.HostProviderService;
46import org.onosproject.net.host.HostService;
47import org.onosproject.net.provider.AbstractProvider;
48import org.onosproject.net.provider.ProviderId;
49import org.onosproject.openstacknetworking.api.OpenstackNetworkService;
Hyunsun Moon0d457362017-06-27 17:19:41 +090050import org.onosproject.openstacknode.api.OpenstackNode;
51import org.onosproject.openstacknode.api.OpenstackNodeEvent;
52import org.onosproject.openstacknode.api.OpenstackNodeListener;
53import org.onosproject.openstacknode.api.OpenstackNodeService;
Hyunsun Moon44aac662017-02-18 02:07:01 +090054import org.openstack4j.model.network.Network;
daniel park796c2eb2018-03-22 17:01:51 +090055import org.openstack4j.model.network.NetworkType;
Hyunsun Moon44aac662017-02-18 02:07:01 +090056import org.slf4j.Logger;
57import org.slf4j.LoggerFactory;
58
Jian Liec5c32b2018-07-13 14:28:58 +090059import java.util.Optional;
Hyunsun Moon44aac662017-02-18 02:07:01 +090060import java.util.Set;
61import java.util.concurrent.ExecutorService;
62import java.util.concurrent.Executors;
63import java.util.stream.Collectors;
64
65import static org.onlab.util.Tools.groupedThreads;
66import static org.onosproject.net.AnnotationKeys.PORT_NAME;
Jian Liec5c32b2018-07-13 14:28:58 +090067import static org.onosproject.openstacknetworking.api.Constants.ANNOTATION_CREATE_TIME;
68import static org.onosproject.openstacknetworking.api.Constants.ANNOTATION_NETWORK_ID;
69import static org.onosproject.openstacknetworking.api.Constants.ANNOTATION_PORT_ID;
70import static org.onosproject.openstacknetworking.api.Constants.ANNOTATION_SEGMENT_ID;
daniel park796c2eb2018-03-22 17:01:51 +090071import static org.onosproject.openstacknetworking.api.Constants.OPENSTACK_NETWORKING_APP_ID;
Ray Milkey9dc57392018-06-08 08:52:31 -070072import static org.onosproject.openstacknetworking.api.Constants.portNamePrefixMap;
Jian Li51b844c2018-05-31 10:59:03 +090073import static org.onosproject.openstacknode.api.OpenstackNode.NodeType.CONTROLLER;
Hyunsun Moon44aac662017-02-18 02:07:01 +090074
75@Service
76@Component(immediate = true)
Jian Li46b74002018-07-15 18:39:08 +090077public final class OpenstackSwitchingHostProvider
78 extends AbstractProvider implements HostProvider {
Hyunsun Moon44aac662017-02-18 02:07:01 +090079
80 private final Logger log = LoggerFactory.getLogger(getClass());
81
82 private static final String PORT_NAME_PREFIX_VM = "tap";
83 private static final String ERR_ADD_HOST = "Failed to add host: ";
Jian Li9d676fb2018-03-02 15:25:36 +090084 private static final String SONA_HOST_SCHEME = "sona";
Hyunsun Moon44aac662017-02-18 02:07:01 +090085
86 @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
Jian Li9d676fb2018-03-02 15:25:36 +090087 protected CoreService coreService;
Hyunsun Moon44aac662017-02-18 02:07:01 +090088
89 @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
Jian Li9d676fb2018-03-02 15:25:36 +090090 protected DeviceService deviceService;
Hyunsun Moon44aac662017-02-18 02:07:01 +090091
92 @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
Jian Li9d676fb2018-03-02 15:25:36 +090093 protected HostProviderRegistry hostProviderRegistry;
Hyunsun Moon44aac662017-02-18 02:07:01 +090094
95 @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
Jian Li9d676fb2018-03-02 15:25:36 +090096 protected HostService hostService;
Hyunsun Moon44aac662017-02-18 02:07:01 +090097
98 @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
Jian Li9d676fb2018-03-02 15:25:36 +090099 protected MastershipService mastershipService;
Hyunsun Moon44aac662017-02-18 02:07:01 +0900100
101 @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
Jian Li9d676fb2018-03-02 15:25:36 +0900102 protected OpenstackNetworkService osNetworkService;
Hyunsun Moon44aac662017-02-18 02:07:01 +0900103
104 @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
Jian Li9d676fb2018-03-02 15:25:36 +0900105 protected OpenstackNodeService osNodeService;
Hyunsun Moon44aac662017-02-18 02:07:01 +0900106
Jian Li07598ff2018-07-23 18:34:34 +0900107 private HostProviderService hostProviderService;
Jian Li24ec59f2018-05-23 19:01:25 +0900108
Jian Li07598ff2018-07-23 18:34:34 +0900109 private final ExecutorService executor =
Jian Li46b74002018-07-15 18:39:08 +0900110 Executors.newSingleThreadExecutor(groupedThreads(this.getClass().getSimpleName(), "device-event"));
111 private final InternalDeviceListener internalDeviceListener =
112 new InternalDeviceListener();
113 private final InternalOpenstackNodeListener internalNodeListener =
114 new InternalOpenstackNodeListener();
Hyunsun Moon44aac662017-02-18 02:07:01 +0900115
Hyunsun Moon44aac662017-02-18 02:07:01 +0900116 /**
117 * Creates OpenStack switching host provider.
118 */
119 public OpenstackSwitchingHostProvider() {
Jian Li9d676fb2018-03-02 15:25:36 +0900120 super(new ProviderId(SONA_HOST_SCHEME, OPENSTACK_NETWORKING_APP_ID));
Hyunsun Moon44aac662017-02-18 02:07:01 +0900121 }
122
123 @Activate
Ray Milkey9c9cde42018-01-12 14:22:06 -0800124 void activate() {
Hyunsun Moon44aac662017-02-18 02:07:01 +0900125 coreService.registerApplication(OPENSTACK_NETWORKING_APP_ID);
126 deviceService.addListener(internalDeviceListener);
127 osNodeService.addListener(internalNodeListener);
Jian Li07598ff2018-07-23 18:34:34 +0900128 hostProviderService = hostProviderRegistry.register(this);
Hyunsun Moon44aac662017-02-18 02:07:01 +0900129
130 log.info("Started");
131 }
132
133 @Deactivate
Ray Milkey9c9cde42018-01-12 14:22:06 -0800134 void deactivate() {
Hyunsun Moon44aac662017-02-18 02:07:01 +0900135 hostProviderRegistry.unregister(this);
136 osNodeService.removeListener(internalNodeListener);
137 deviceService.removeListener(internalDeviceListener);
138
Jian Li07598ff2018-07-23 18:34:34 +0900139 executor.shutdown();
Hyunsun Moon44aac662017-02-18 02:07:01 +0900140
141 log.info("Stopped");
142 }
143
144 @Override
145 public void triggerProbe(Host host) {
146 // no probe is required
147 }
148
Jian Li9d676fb2018-03-02 15:25:36 +0900149 /**
Jian Li07598ff2018-07-23 18:34:34 +0900150 * A helper method which logs the port addition event and performs port
151 * addition action.
152 *
153 * @param event device event
154 */
155 protected void portAddedHelper(DeviceEvent event) {
156 log.debug("Instance port {} is detected from {}",
157 event.port().annotations().value(PORT_NAME),
158 event.subject().id());
159 processPortAdded(event.port());
160 }
161
162 /**
163 * A helper method which logs the port removal event and performs port
164 * removal action.
165 *
166 * @param event device event
167 */
168 protected void portRemovedHelper(DeviceEvent event) {
169 log.debug("Instance port {} is removed from {}",
170 event.port().annotations().value(PORT_NAME),
171 event.subject().id());
172 processPortRemoved(event.port());
173 }
174
175 /**
Jian Li9d676fb2018-03-02 15:25:36 +0900176 * Processes port addition event.
177 * Once a port addition event is detected, it tries to create a host instance
178 * with openstack augmented host information such as networkId, portId,
179 * createTime, segmentId and notifies to host provider.
180 *
181 * @param port port object used in ONOS
182 */
Jian Li07598ff2018-07-23 18:34:34 +0900183 protected void processPortAdded(Port port) {
Hyunsun Moon44aac662017-02-18 02:07:01 +0900184 // TODO check the node state is COMPLETE
185 org.openstack4j.model.network.Port osPort = osNetworkService.port(port);
186 if (osPort == null) {
187 log.warn(ERR_ADD_HOST + "OpenStack port for {} not found", port);
188 return;
189 }
190
191 Network osNet = osNetworkService.network(osPort.getNetworkId());
192 if (osNet == null) {
193 log.warn(ERR_ADD_HOST + "OpenStack network {} not found",
194 osPort.getNetworkId());
195 return;
196 }
197
198 if (osPort.getFixedIps().isEmpty()) {
199 log.warn(ERR_ADD_HOST + "no fixed IP for port {}", osPort.getId());
200 return;
201 }
202
Jian Liec5c32b2018-07-13 14:28:58 +0900203 MacAddress mac = MacAddress.valueOf(osPort.getMacAddress());
204 HostId hostId = HostId.hostId(mac);
205
206 // typically one openstack port should only be bound to one fix IP address;
207 // however, openstack4j binds multiple fixed IPs to one port, this might
208 // be a defect of openstack4j implementation
209
210 // TODO: we need to find a way to bind multiple ports from multiple
211 // openstack networks into one host sooner or later
Hyunsun Moon44aac662017-02-18 02:07:01 +0900212 Set<IpAddress> fixedIps = osPort.getFixedIps().stream()
Jian Li46b74002018-07-15 18:39:08 +0900213 .map(ip -> IpAddress.valueOf(ip.getIpAddress()))
214 .collect(Collectors.toSet());
Jian Liec5c32b2018-07-13 14:28:58 +0900215
216 // connect point is the combination of switch ID with port number where
217 // the host is attached to
Hyunsun Moon44aac662017-02-18 02:07:01 +0900218 ConnectPoint connectPoint = new ConnectPoint(port.element().id(), port.number());
Jian Li24ec59f2018-05-23 19:01:25 +0900219
Jian Liec5c32b2018-07-13 14:28:58 +0900220 long createTime = System.currentTimeMillis();
Jian Li24ec59f2018-05-23 19:01:25 +0900221
Jian Liee8214a2018-07-21 20:07:28 +0900222 // we check whether the host already attached to same locations
Jian Liec5c32b2018-07-13 14:28:58 +0900223 Host host = hostService.getHost(hostId);
Jian Li46b74002018-07-15 18:39:08 +0900224
225 // build host annotations to include a set of meta info from neutron
226 DefaultAnnotations.Builder annotations = DefaultAnnotations.builder()
227 .set(ANNOTATION_NETWORK_ID, osPort.getNetworkId())
228 .set(ANNOTATION_PORT_ID, osPort.getId())
229 .set(ANNOTATION_CREATE_TIME, String.valueOf(createTime));
230
Jian Liee8214a2018-07-21 20:07:28 +0900231 // FLAT typed network does not require segment ID
Jian Li46b74002018-07-15 18:39:08 +0900232 if (osNet.getNetworkType() != NetworkType.FLAT) {
233 annotations.set(ANNOTATION_SEGMENT_ID, osNet.getProviderSegID());
234 }
235
236 // build host description object
237 HostDescription hostDesc = new DefaultHostDescription(
238 mac,
239 VlanId.NONE,
240 new HostLocation(connectPoint, createTime),
241 fixedIps,
242 annotations.build());
243
Jian Liec5c32b2018-07-13 14:28:58 +0900244 if (host != null) {
245 Set<HostLocation> locations = host.locations().stream()
246 .filter(l -> l.deviceId().equals(connectPoint.deviceId()))
247 .filter(l -> l.port().equals(connectPoint.port()))
248 .collect(Collectors.toSet());
Jian Li46b74002018-07-15 18:39:08 +0900249
250 // newly added location is not in the existing location list,
251 // therefore, we simply add this into the location list
Jian Liec5c32b2018-07-13 14:28:58 +0900252 if (locations.size() == 0) {
Jian Li07598ff2018-07-23 18:34:34 +0900253 hostProviderService.addLocationToHost(hostId,
Jian Li46b74002018-07-15 18:39:08 +0900254 new HostLocation(connectPoint, createTime));
255 }
256
257 // newly added location is in the existing location list,
258 // the hostDetected method invocation in turn triggers host Update event
259 if (locations.size() == 1) {
Jian Li07598ff2018-07-23 18:34:34 +0900260 hostProviderService.hostDetected(hostId, hostDesc, false);
Jian Li24ec59f2018-05-23 19:01:25 +0900261 }
Jian Liec5c32b2018-07-13 14:28:58 +0900262 } else {
Jian Li07598ff2018-07-23 18:34:34 +0900263 hostProviderService.hostDetected(hostId, hostDesc, false);
Jian Li24ec59f2018-05-23 19:01:25 +0900264 }
Hyunsun Moon44aac662017-02-18 02:07:01 +0900265 }
266
Jian Li9d676fb2018-03-02 15:25:36 +0900267 /**
268 * Processes port removal event.
269 * Once a port removal event is detected, it tries to look for a host
270 * instance through host provider by giving connect point information,
271 * and vanishes it.
272 *
Jian Li07598ff2018-07-23 18:34:34 +0900273 * @param port ONOS port
Jian Li9d676fb2018-03-02 15:25:36 +0900274 */
Jian Li07598ff2018-07-23 18:34:34 +0900275 protected void processPortRemoved(Port port) {
Hyunsun Moon44aac662017-02-18 02:07:01 +0900276 ConnectPoint connectPoint = new ConnectPoint(port.element().id(), port.number());
Jian Li24ec59f2018-05-23 19:01:25 +0900277
Jian Liec5c32b2018-07-13 14:28:58 +0900278 Set<Host> hosts = hostService.getConnectedHosts(connectPoint);
Jian Li24ec59f2018-05-23 19:01:25 +0900279
Jian Liec5c32b2018-07-13 14:28:58 +0900280 hosts.forEach(h -> {
281 Optional<HostLocation> hostLocation = h.locations().stream()
282 .filter(l -> l.deviceId().equals(port.element().id()))
283 .filter(l -> l.port().equals(port.number())).findAny();
Jian Li24ec59f2018-05-23 19:01:25 +0900284
Jian Liec5c32b2018-07-13 14:28:58 +0900285 // if the host contains only one filtered location, we remove the host
286 if (h.locations().size() == 1) {
Jian Li07598ff2018-07-23 18:34:34 +0900287 hostProviderService.hostVanished(h.id());
Jian Li24ec59f2018-05-23 19:01:25 +0900288 }
289
Jian Liec5c32b2018-07-13 14:28:58 +0900290 // if the host contains multiple locations, we simply remove the
291 // host location
292 if (h.locations().size() > 1 && hostLocation.isPresent()) {
Jian Li07598ff2018-07-23 18:34:34 +0900293 hostProviderService.removeLocationFromHost(h.id(), hostLocation.get());
Jian Liec5c32b2018-07-13 14:28:58 +0900294 }
295 });
Hyunsun Moon44aac662017-02-18 02:07:01 +0900296 }
297
Jian Li9d676fb2018-03-02 15:25:36 +0900298 /**
299 * An internal device listener which listens the port events generated from
300 * OVS integration bridge.
301 */
Hyunsun Moon44aac662017-02-18 02:07:01 +0900302 private class InternalDeviceListener implements DeviceListener {
303
304 @Override
305 public boolean isRelevant(DeviceEvent event) {
306 Device device = event.subject();
307 if (!mastershipService.isLocalMaster(device.id())) {
308 // do not allow to proceed without mastership
309 return false;
310 }
311 Port port = event.port();
312 if (port == null) {
313 return false;
314 }
315 String portName = port.annotations().value(PORT_NAME);
Jian Li9d676fb2018-03-02 15:25:36 +0900316
317 return !Strings.isNullOrEmpty(portName) &&
Daniel Parkc4d06402018-05-28 15:57:37 +0900318 (portName.startsWith(PORT_NAME_PREFIX_VM) || isDirectPort(portName));
319 }
320
321 private boolean isDirectPort(String portName) {
Jian Li07598ff2018-07-23 18:34:34 +0900322 return portNamePrefixMap().values().stream().anyMatch(portName::startsWith);
Hyunsun Moon44aac662017-02-18 02:07:01 +0900323 }
324
325 @Override
326 public void event(DeviceEvent event) {
Daniel Parkc4d06402018-05-28 15:57:37 +0900327 log.info("Device event occurred with type {}", event.type());
Hyunsun Moon44aac662017-02-18 02:07:01 +0900328 switch (event.type()) {
329 case PORT_UPDATED:
330 if (!event.port().isEnabled()) {
Jian Li07598ff2018-07-23 18:34:34 +0900331 executor.execute(() -> portRemovedHelper(event));
Hyunsun Moon44aac662017-02-18 02:07:01 +0900332 } else if (event.port().isEnabled()) {
Jian Li07598ff2018-07-23 18:34:34 +0900333 executor.execute(() -> portAddedHelper(event));
Hyunsun Moon44aac662017-02-18 02:07:01 +0900334 }
335 break;
336 case PORT_ADDED:
Jian Li07598ff2018-07-23 18:34:34 +0900337 executor.execute(() -> portAddedHelper(event));
Hyunsun Moon44aac662017-02-18 02:07:01 +0900338 break;
Hyunsun Moonb7a9cd22017-02-24 11:12:53 +0900339 case PORT_REMOVED:
Jian Li07598ff2018-07-23 18:34:34 +0900340 executor.execute(() -> portRemovedHelper(event));
Ray Milkeyd6a67c32018-02-02 10:30:35 -0800341 break;
Hyunsun Moon44aac662017-02-18 02:07:01 +0900342 default:
343 break;
344 }
345 }
346 }
347
348 private class InternalOpenstackNodeListener implements OpenstackNodeListener {
349
350 @Override
Jian Li9d676fb2018-03-02 15:25:36 +0900351 public boolean isRelevant(OpenstackNodeEvent event) {
Jian Li51b844c2018-05-31 10:59:03 +0900352
353 if (event.subject().type() == CONTROLLER) {
354 return false;
355 }
Jian Li9d676fb2018-03-02 15:25:36 +0900356 // do not allow to proceed without mastership
357 Device device = deviceService.getDevice(event.subject().intgBridge());
Jian Li7ddea782018-03-30 11:17:42 +0900358 if (device == null) {
359 return false;
360 }
Jian Li9d676fb2018-03-02 15:25:36 +0900361 return mastershipService.isLocalMaster(device.id());
362 }
363
364 @Override
Hyunsun Moon44aac662017-02-18 02:07:01 +0900365 public void event(OpenstackNodeEvent event) {
366 OpenstackNode osNode = event.subject();
Hyunsun Moon44aac662017-02-18 02:07:01 +0900367
368 switch (event.type()) {
Hyunsun Moon0d457362017-06-27 17:19:41 +0900369 case OPENSTACK_NODE_COMPLETE:
Jian Li07598ff2018-07-23 18:34:34 +0900370 log.info("COMPLETE node {} is detected", osNode.hostname());
371 executor.execute(() -> processCompleteNode(event.subject()));
Hyunsun Moon44aac662017-02-18 02:07:01 +0900372 break;
Hyunsun Moon0d457362017-06-27 17:19:41 +0900373 case OPENSTACK_NODE_INCOMPLETE:
Hyunsun Moon44aac662017-02-18 02:07:01 +0900374 log.warn("{} is changed to INCOMPLETE state", osNode);
375 break;
Hyunsun Moon0d457362017-06-27 17:19:41 +0900376 case OPENSTACK_NODE_CREATED:
377 case OPENSTACK_NODE_UPDATED:
378 case OPENSTACK_NODE_REMOVED:
Jian Li9d676fb2018-03-02 15:25:36 +0900379 // not reacts to the events other than complete and incomplete states
380 break;
Hyunsun Moon44aac662017-02-18 02:07:01 +0900381 default:
382 break;
383 }
384 }
385
386 private void processCompleteNode(OpenstackNode osNode) {
Hyunsun Moon0d457362017-06-27 17:19:41 +0900387 deviceService.getPorts(osNode.intgBridge()).stream()
Hyunsun Moon44aac662017-02-18 02:07:01 +0900388 .filter(port -> port.annotations().value(PORT_NAME)
389 .startsWith(PORT_NAME_PREFIX_VM) &&
390 port.isEnabled())
391 .forEach(port -> {
392 log.debug("Instance port {} is detected from {}",
Jian Li46b74002018-07-15 18:39:08 +0900393 port.annotations().value(PORT_NAME),
394 osNode.hostname());
Jian Li07598ff2018-07-23 18:34:34 +0900395 processPortAdded(port);
Hyunsun Moon44aac662017-02-18 02:07:01 +0900396 });
397
Jian Li07598ff2018-07-23 18:34:34 +0900398 portNamePrefixMap().values().forEach(portNamePrefix ->
399 deviceService.getPorts(osNode.intgBridge()).stream()
400 .filter(port -> port.annotations().value(PORT_NAME)
401 .startsWith(portNamePrefix) &&
402 port.isEnabled())
403 .forEach(port -> {
404 log.debug("Instance port {} is detected from {}",
405 port.annotations().value(portNamePrefix),
406 osNode.hostname());
407 processPortAdded(port);
408 }));
Daniel Parkc4d06402018-05-28 15:57:37 +0900409
Hyunsun Moon44aac662017-02-18 02:07:01 +0900410 Tools.stream(hostService.getHosts())
411 .filter(host -> deviceService.getPort(
412 host.location().deviceId(),
413 host.location().port()) == null)
414 .forEach(host -> {
415 log.info("Remove stale host {}", host.id());
Jian Li07598ff2018-07-23 18:34:34 +0900416 hostProviderService.hostVanished(host.id());
Jian Li46b74002018-07-15 18:39:08 +0900417 });
Hyunsun Moon44aac662017-02-18 02:07:01 +0900418 }
419 }
420}