blob: 24ab53353c0daf15eb79b029429caffceeb44ae0 [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;
59import org.slf4j.Logger;
60import org.slf4j.LoggerFactory;
61
62import java.util.Date;
63import java.util.Map;
64import java.util.concurrent.ExecutorService;
65import java.util.concurrent.Executors;
66
67import static org.onlab.util.Tools.groupedThreads;
68import static org.onosproject.dhcp.IpAssignment.AssignmentStatus.Option_RangeNotEnforced;
69
70import static com.google.common.base.Preconditions.checkArgument;
71import static com.google.common.base.Preconditions.checkNotNull;
72import static org.onosproject.net.AnnotationKeys.PORT_NAME;
73import static org.onosproject.openstacknetworking.switching.Constants.*;
74
75@Service
76@Component(immediate = true)
77/**
78 * Populates forwarding rules for VMs created by Openstack.
79 */
80public final class OpenstackSwitchingManager extends AbstractProvider
81 implements OpenstackSwitchingService, HostProvider {
82
83 private final Logger log = LoggerFactory.getLogger(getClass());
84
85 @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
86 protected CoreService coreService;
87
88 @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
89 protected DeviceService deviceService;
90
91 @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
92 protected HostProviderRegistry hostProviderRegistry;
93
94 @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
95 protected DhcpService dhcpService;
96
97 @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
98 protected HostService hostService;
99
100 @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
101 protected MastershipService mastershipService;
102
103 @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
104 protected OpenstackInterfaceService openstackService;
105
106 private final ExecutorService deviceEventExecutor =
107 Executors.newSingleThreadExecutor(groupedThreads("onos/openstackswitching", "device-event"));
108 private final ExecutorService configEventExecutor =
109 Executors.newSingleThreadExecutor(groupedThreads("onos/openstackswitching", "config-event"));
110 private final InternalDeviceListener internalDeviceListener = new InternalDeviceListener();
111
112 private HostProviderService hostProvider;
113
114 /**
115 * Creates OpenStack switching host provider.
116 */
117 public OpenstackSwitchingManager() {
118 super(new ProviderId("host", APP_ID));
119 }
120
121 @Activate
122 protected void activate() {
123 coreService.registerApplication(APP_ID);
124 deviceService.addListener(internalDeviceListener);
125 hostProvider = hostProviderRegistry.register(this);
126
127 log.info("Started");
128 }
129
130 @Deactivate
131 protected void deactivate() {
132 hostProviderRegistry.unregister(this);
133 deviceService.removeListener(internalDeviceListener);
134
135 deviceEventExecutor.shutdown();
136 configEventExecutor.shutdown();
137
138 log.info("Stopped");
139 }
140
141 @Override
142 public void triggerProbe(Host host) {
143 // no probe is required
144 }
145
146 @Override
147 // TODO remove this and openstackPortInfo
148 public Map<String, OpenstackPortInfo> openstackPortInfo() {
149 Map<String, OpenstackPortInfo> portInfoMap = Maps.newHashMap();
150
151 Tools.stream(hostService.getHosts()).filter(this::isValidHost).forEach(host -> {
152 Port port = deviceService.getPort(
153 host.location().deviceId(),
154 host.location().port());
155
156 OpenstackPortInfo portInfo = OpenstackPortInfo.builder()
157 .setDeviceId(host.location().deviceId())
158 .setHostMac(host.mac())
159 .setNetworkId(host.annotations().value(NETWORK_ID))
160 .setGatewayIP(Ip4Address.valueOf(host.annotations().value(GATEWAY_IP)))
161 .setVni(Long.valueOf(host.annotations().value(VXLAN_ID)))
162 .setHostIp(host.ipAddresses().stream().findFirst().get().getIp4Address())
163 .build();
164
165 portInfoMap.put(port.annotations().value(PORT_NAME), portInfo);
166 });
167
168 return portInfoMap;
169 }
170
171 // TODO remove this and openstackPortInfo
172 private boolean isValidHost(Host host) {
173 return !host.ipAddresses().isEmpty() &&
174 host.annotations().value(VXLAN_ID) != null &&
175 host.annotations().value(NETWORK_ID) != null &&
176 host.annotations().value(TENANT_ID) != null &&
177 host.annotations().value(GATEWAY_IP) != null &&
178 host.annotations().value(PORT_ID) != null;
179 }
180
181 private void processPortAdded(Port port) {
182 OpenstackPort osPort = openstackService.port(port);
183 if (osPort == null) {
184 log.warn("Failed to get OpenStack port for {}", port);
185 return;
186 }
187
188 OpenstackNetwork osNet = openstackService.network(osPort.networkId());
189 if (osNet == null) {
190 log.warn("Failed to get OpenStack network {}",
191 osPort.networkId());
192 return;
193 }
194
195 registerDhcpInfo(osPort);
196 ConnectPoint connectPoint = new ConnectPoint(port.element().id(), port.number());
197 // TODO remove this and openstackPortInfo
198 String gatewayIp = osNet.subnets().stream().findFirst().get().gatewayIp();
199
200 // Added CREATE_TIME intentionally to trigger HOST_UPDATED event for the
201 // existing instances.
202 DefaultAnnotations.Builder annotations = DefaultAnnotations.builder()
203 .set(NETWORK_ID, osPort.networkId())
204 .set(PORT_ID, osPort.id())
205 .set(VXLAN_ID, osNet.segmentId())
206 .set(TENANT_ID, osNet.tenantId())
207 // TODO remove this and openstackPortInfo
208 .set(GATEWAY_IP, gatewayIp)
209 .set(CREATE_TIME, String.valueOf(System.currentTimeMillis()));
210
211 HostDescription hostDesc = new DefaultHostDescription(
212 osPort.macAddress(),
213 VlanId.NONE,
214 new HostLocation(connectPoint, System.currentTimeMillis()),
215 Sets.newHashSet(osPort.fixedIps().values()),
216 annotations.build());
217
218 HostId hostId = HostId.hostId(osPort.macAddress());
219 hostProvider.hostDetected(hostId, hostDesc, false);
220 }
221
222 private void processPortRemoved(Port port) {
223 ConnectPoint connectPoint = new ConnectPoint(port.element().id(), port.number());
224 hostService.getConnectedHosts(connectPoint).stream()
225 .forEach(host -> {
226 dhcpService.removeStaticMapping(host.mac());
227 hostProvider.hostVanished(host.id());
228 });
229 }
230
231 private void registerDhcpInfo(OpenstackPort openstackPort) {
232 checkNotNull(openstackPort);
233 checkArgument(!openstackPort.fixedIps().isEmpty());
234
235 OpenstackSubnet openstackSubnet = openstackService.subnets().stream()
236 .filter(n -> n.networkId().equals(openstackPort.networkId()))
237 .findFirst().orElse(null);
238 if (openstackSubnet == null) {
239 log.warn("Failed to find subnet for {}", openstackPort);
240 return;
241 }
242
243 Ip4Address ipAddress = openstackPort.fixedIps().values().stream().findFirst().get();
244 IpPrefix subnetPrefix = IpPrefix.valueOf(openstackSubnet.cidr());
245 Ip4Address broadcast = Ip4Address.makeMaskedAddress(
246 ipAddress,
247 subnetPrefix.prefixLength());
248
249 // TODO: supports multiple DNS servers
250 Ip4Address domainServer = openstackSubnet.dnsNameservers().isEmpty() ?
251 DNS_SERVER_IP : openstackSubnet.dnsNameservers().get(0);
252
253 IpAssignment ipAssignment = IpAssignment.builder()
254 .ipAddress(ipAddress)
255 .leasePeriod(DHCP_INFINITE_LEASE)
256 .timestamp(new Date())
257 .subnetMask(Ip4Address.makeMaskPrefix(subnetPrefix.prefixLength()))
258 .broadcast(broadcast)
259 .domainServer(domainServer)
260 .assignmentStatus(Option_RangeNotEnforced)
261 .routerAddress(Ip4Address.valueOf(openstackSubnet.gatewayIp()))
262 .build();
263
264 dhcpService.setStaticMapping(openstackPort.macAddress(), ipAssignment);
265 }
266
267 private class InternalDeviceListener implements DeviceListener {
268
269 @Override
270 public void event(DeviceEvent event) {
271 Device device = event.subject();
272 if (!mastershipService.isLocalMaster(device.id())) {
273 // do not allow to proceed without mastership
274 return;
275 }
276
277 Port port = event.port();
278 if (port == null) {
279 return;
280 }
281
282 String portName = port.annotations().value(PORT_NAME);
283 if (Strings.isNullOrEmpty(portName) ||
284 !portName.startsWith(PORTNAME_PREFIX_VM)) {
285 // handles VM connected port event only
286 return;
287 }
288
289 switch (event.type()) {
290 case PORT_UPDATED:
291 if (!event.port().isEnabled()) {
292 deviceEventExecutor.execute(() -> processPortRemoved(event.port()));
293 }
294 break;
295 case PORT_ADDED:
296 deviceEventExecutor.execute(() -> processPortAdded(event.port()));
297 break;
298 default:
299 break;
300 }
301 }
302 }
303}