blob: 5388e9b17181c40549e15e0e73dd02eb348d189d [file] [log] [blame]
Hyunsun Moonb974fca2016-06-30 21:20:39 -07001/*
2 * Copyright 2016-present Open Networking Laboratory
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.switching;
17
18import com.google.common.base.Strings;
19import com.google.common.collect.Maps;
20import com.google.common.collect.Sets;
21import 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.Ip4Address;
28import org.onlab.packet.IpPrefix;
29import org.onlab.packet.VlanId;
30import org.onlab.util.Tools;
31import org.onosproject.core.CoreService;
32import org.onosproject.dhcp.DhcpService;
33import org.onosproject.dhcp.IpAssignment;
34import org.onosproject.mastership.MastershipService;
35import org.onosproject.net.ConnectPoint;
36import org.onosproject.net.DefaultAnnotations;
37import org.onosproject.net.Device;
38import org.onosproject.net.Host;
39import org.onosproject.net.HostId;
40import org.onosproject.net.HostLocation;
41import org.onosproject.net.Port;
42import org.onosproject.net.device.DeviceEvent;
43import org.onosproject.net.device.DeviceListener;
44import org.onosproject.net.device.DeviceService;
45import org.onosproject.net.host.DefaultHostDescription;
46import org.onosproject.net.host.HostDescription;
47import org.onosproject.net.host.HostProvider;
48import org.onosproject.net.host.HostProviderRegistry;
49import org.onosproject.net.host.HostProviderService;
50import org.onosproject.net.host.HostService;
51import org.onosproject.net.provider.AbstractProvider;
52import org.onosproject.net.provider.ProviderId;
53import org.onosproject.openstackinterface.OpenstackInterfaceService;
54import org.onosproject.openstackinterface.OpenstackNetwork;
55import org.onosproject.openstackinterface.OpenstackPort;
56import org.onosproject.openstackinterface.OpenstackSubnet;
57import org.onosproject.openstacknetworking.OpenstackPortInfo;
58import org.onosproject.openstacknetworking.OpenstackSwitchingService;
Hyunsun Moon05d9b262016-07-03 18:38:44 -070059import org.onosproject.openstacknode.OpenstackNode;
60import org.onosproject.openstacknode.OpenstackNodeEvent;
61import org.onosproject.openstacknode.OpenstackNodeListener;
62import org.onosproject.openstacknode.OpenstackNodeService;
Hyunsun Moonb974fca2016-06-30 21:20:39 -070063import org.slf4j.Logger;
64import org.slf4j.LoggerFactory;
65
66import java.util.Date;
67import java.util.Map;
68import java.util.concurrent.ExecutorService;
69import java.util.concurrent.Executors;
70
71import static org.onlab.util.Tools.groupedThreads;
72import static org.onosproject.dhcp.IpAssignment.AssignmentStatus.Option_RangeNotEnforced;
73
74import static com.google.common.base.Preconditions.checkArgument;
75import static com.google.common.base.Preconditions.checkNotNull;
76import static org.onosproject.net.AnnotationKeys.PORT_NAME;
77import static org.onosproject.openstacknetworking.switching.Constants.*;
78
79@Service
80@Component(immediate = true)
81/**
82 * Populates forwarding rules for VMs created by Openstack.
83 */
84public final class OpenstackSwitchingManager extends AbstractProvider
85 implements OpenstackSwitchingService, HostProvider {
86
87 private final Logger log = LoggerFactory.getLogger(getClass());
88
89 @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
90 protected CoreService coreService;
91
92 @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
93 protected DeviceService deviceService;
94
95 @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
96 protected HostProviderRegistry hostProviderRegistry;
97
98 @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
99 protected DhcpService dhcpService;
100
101 @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
102 protected HostService hostService;
103
104 @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
105 protected MastershipService mastershipService;
106
107 @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
108 protected OpenstackInterfaceService openstackService;
109
Hyunsun Moon05d9b262016-07-03 18:38:44 -0700110 @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
111 protected OpenstackNodeService openstackNodeService;
112
Hyunsun Moonb974fca2016-06-30 21:20:39 -0700113 private final ExecutorService deviceEventExecutor =
114 Executors.newSingleThreadExecutor(groupedThreads("onos/openstackswitching", "device-event"));
115 private final ExecutorService configEventExecutor =
116 Executors.newSingleThreadExecutor(groupedThreads("onos/openstackswitching", "config-event"));
117 private final InternalDeviceListener internalDeviceListener = new InternalDeviceListener();
Hyunsun Moon05d9b262016-07-03 18:38:44 -0700118 private final InternalOpenstackNodeListener internalNodeListener = new InternalOpenstackNodeListener();
Hyunsun Moonb974fca2016-06-30 21:20:39 -0700119
120 private HostProviderService hostProvider;
121
122 /**
123 * Creates OpenStack switching host provider.
124 */
125 public OpenstackSwitchingManager() {
126 super(new ProviderId("host", APP_ID));
127 }
128
129 @Activate
130 protected void activate() {
131 coreService.registerApplication(APP_ID);
132 deviceService.addListener(internalDeviceListener);
Hyunsun Moon05d9b262016-07-03 18:38:44 -0700133 openstackNodeService.addListener(internalNodeListener);
Hyunsun Moonb974fca2016-06-30 21:20:39 -0700134 hostProvider = hostProviderRegistry.register(this);
135
136 log.info("Started");
137 }
138
139 @Deactivate
140 protected void deactivate() {
141 hostProviderRegistry.unregister(this);
142 deviceService.removeListener(internalDeviceListener);
Hyunsun Moon05d9b262016-07-03 18:38:44 -0700143 openstackNodeService.removeListener(internalNodeListener);
Hyunsun Moonb974fca2016-06-30 21:20:39 -0700144
145 deviceEventExecutor.shutdown();
146 configEventExecutor.shutdown();
147
148 log.info("Stopped");
149 }
150
151 @Override
152 public void triggerProbe(Host host) {
153 // no probe is required
154 }
155
156 @Override
157 // TODO remove this and openstackPortInfo
158 public Map<String, OpenstackPortInfo> openstackPortInfo() {
159 Map<String, OpenstackPortInfo> portInfoMap = Maps.newHashMap();
160
161 Tools.stream(hostService.getHosts()).filter(this::isValidHost).forEach(host -> {
162 Port port = deviceService.getPort(
163 host.location().deviceId(),
164 host.location().port());
165
166 OpenstackPortInfo portInfo = OpenstackPortInfo.builder()
167 .setDeviceId(host.location().deviceId())
168 .setHostMac(host.mac())
169 .setNetworkId(host.annotations().value(NETWORK_ID))
170 .setGatewayIP(Ip4Address.valueOf(host.annotations().value(GATEWAY_IP)))
171 .setVni(Long.valueOf(host.annotations().value(VXLAN_ID)))
172 .setHostIp(host.ipAddresses().stream().findFirst().get().getIp4Address())
173 .build();
174
175 portInfoMap.put(port.annotations().value(PORT_NAME), portInfo);
176 });
177
178 return portInfoMap;
179 }
180
181 // TODO remove this and openstackPortInfo
182 private boolean isValidHost(Host host) {
183 return !host.ipAddresses().isEmpty() &&
184 host.annotations().value(VXLAN_ID) != null &&
185 host.annotations().value(NETWORK_ID) != null &&
186 host.annotations().value(TENANT_ID) != null &&
187 host.annotations().value(GATEWAY_IP) != null &&
188 host.annotations().value(PORT_ID) != null;
189 }
190
191 private void processPortAdded(Port port) {
Hyunsun Moon05d9b262016-07-03 18:38:44 -0700192 // TODO check the node state is COMPLETE
Hyunsun Moonb974fca2016-06-30 21:20:39 -0700193 OpenstackPort osPort = openstackService.port(port);
194 if (osPort == null) {
195 log.warn("Failed to get OpenStack port for {}", port);
196 return;
197 }
198
199 OpenstackNetwork osNet = openstackService.network(osPort.networkId());
200 if (osNet == null) {
201 log.warn("Failed to get OpenStack network {}",
202 osPort.networkId());
203 return;
204 }
205
206 registerDhcpInfo(osPort);
207 ConnectPoint connectPoint = new ConnectPoint(port.element().id(), port.number());
208 // TODO remove this and openstackPortInfo
209 String gatewayIp = osNet.subnets().stream().findFirst().get().gatewayIp();
210
211 // Added CREATE_TIME intentionally to trigger HOST_UPDATED event for the
212 // existing instances.
213 DefaultAnnotations.Builder annotations = DefaultAnnotations.builder()
214 .set(NETWORK_ID, osPort.networkId())
215 .set(PORT_ID, osPort.id())
216 .set(VXLAN_ID, osNet.segmentId())
217 .set(TENANT_ID, osNet.tenantId())
218 // TODO remove this and openstackPortInfo
219 .set(GATEWAY_IP, gatewayIp)
220 .set(CREATE_TIME, String.valueOf(System.currentTimeMillis()));
221
222 HostDescription hostDesc = new DefaultHostDescription(
223 osPort.macAddress(),
224 VlanId.NONE,
225 new HostLocation(connectPoint, System.currentTimeMillis()),
226 Sets.newHashSet(osPort.fixedIps().values()),
227 annotations.build());
228
229 HostId hostId = HostId.hostId(osPort.macAddress());
230 hostProvider.hostDetected(hostId, hostDesc, false);
231 }
232
233 private void processPortRemoved(Port port) {
234 ConnectPoint connectPoint = new ConnectPoint(port.element().id(), port.number());
Hyunsun Moon05d9b262016-07-03 18:38:44 -0700235 removeHosts(connectPoint);
236 }
237
238 private void removeHosts(ConnectPoint connectPoint) {
Hyunsun Moonb974fca2016-06-30 21:20:39 -0700239 hostService.getConnectedHosts(connectPoint).stream()
240 .forEach(host -> {
241 dhcpService.removeStaticMapping(host.mac());
242 hostProvider.hostVanished(host.id());
243 });
244 }
245
246 private void registerDhcpInfo(OpenstackPort openstackPort) {
247 checkNotNull(openstackPort);
248 checkArgument(!openstackPort.fixedIps().isEmpty());
249
250 OpenstackSubnet openstackSubnet = openstackService.subnets().stream()
251 .filter(n -> n.networkId().equals(openstackPort.networkId()))
252 .findFirst().orElse(null);
253 if (openstackSubnet == null) {
254 log.warn("Failed to find subnet for {}", openstackPort);
255 return;
256 }
257
258 Ip4Address ipAddress = openstackPort.fixedIps().values().stream().findFirst().get();
259 IpPrefix subnetPrefix = IpPrefix.valueOf(openstackSubnet.cidr());
260 Ip4Address broadcast = Ip4Address.makeMaskedAddress(
261 ipAddress,
262 subnetPrefix.prefixLength());
263
264 // TODO: supports multiple DNS servers
265 Ip4Address domainServer = openstackSubnet.dnsNameservers().isEmpty() ?
266 DNS_SERVER_IP : openstackSubnet.dnsNameservers().get(0);
267
268 IpAssignment ipAssignment = IpAssignment.builder()
269 .ipAddress(ipAddress)
270 .leasePeriod(DHCP_INFINITE_LEASE)
271 .timestamp(new Date())
272 .subnetMask(Ip4Address.makeMaskPrefix(subnetPrefix.prefixLength()))
273 .broadcast(broadcast)
274 .domainServer(domainServer)
275 .assignmentStatus(Option_RangeNotEnforced)
276 .routerAddress(Ip4Address.valueOf(openstackSubnet.gatewayIp()))
277 .build();
278
279 dhcpService.setStaticMapping(openstackPort.macAddress(), ipAssignment);
280 }
281
282 private class InternalDeviceListener implements DeviceListener {
283
284 @Override
285 public void event(DeviceEvent event) {
286 Device device = event.subject();
287 if (!mastershipService.isLocalMaster(device.id())) {
288 // do not allow to proceed without mastership
289 return;
290 }
291
292 Port port = event.port();
293 if (port == null) {
294 return;
295 }
296
297 String portName = port.annotations().value(PORT_NAME);
298 if (Strings.isNullOrEmpty(portName) ||
299 !portName.startsWith(PORTNAME_PREFIX_VM)) {
300 // handles VM connected port event only
301 return;
302 }
303
304 switch (event.type()) {
305 case PORT_UPDATED:
306 if (!event.port().isEnabled()) {
307 deviceEventExecutor.execute(() -> processPortRemoved(event.port()));
308 }
309 break;
310 case PORT_ADDED:
311 deviceEventExecutor.execute(() -> processPortAdded(event.port()));
312 break;
313 default:
314 break;
315 }
316 }
317 }
Hyunsun Moon05d9b262016-07-03 18:38:44 -0700318
319 private class InternalOpenstackNodeListener implements OpenstackNodeListener {
320
321 @Override
322 public void event(OpenstackNodeEvent event) {
323 OpenstackNode node = event.node();
324 // TODO check leadership of the node and make only the leader process
325
326 switch (event.type()) {
327 case COMPLETE:
328 log.info("COMPLETE node {} detected", node.hostname());
329
330 // adds existing VMs running on the complete state node
331 deviceService.getPorts(node.intBridge()).stream()
332 .filter(port -> port.annotations().value(PORT_NAME)
333 .startsWith(PORTNAME_PREFIX_VM) &&
334 port.isEnabled())
335 .forEach(port -> {
336 deviceEventExecutor.execute(() -> processPortAdded(port));
337 log.info("VM is detected on {}", port);
338 });
339
340 // removes stale VMs
341 hostService.getHosts().forEach(host -> {
342 if (deviceService.getPort(host.location().deviceId(),
343 host.location().port()) == null) {
344 deviceEventExecutor.execute(() -> removeHosts(host.location()));
345 log.info("Removed stale VM {}", host.location());
346 }
347 });
348 break;
349 case INCOMPLETE:
350 log.warn("{} is changed to INCOMPLETE state", node);
351 break;
352 case INIT:
353 case DEVICE_CREATED:
354 default:
355 break;
356 }
357 }
358 }
Hyunsun Moonb974fca2016-06-30 21:20:39 -0700359}