blob: 3ca73618680450767271273d099da3a5f1d822ec [file] [log] [blame]
Hyunsun Moon17f0cf82016-05-16 04:32:45 -07001/*
2 * Copyright 2015-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.cordvtn.impl;
17
Hyunsun Moon17f0cf82016-05-16 04:32:45 -070018import com.google.common.collect.Sets;
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.Ethernet;
26import org.onlab.packet.Ip4Address;
27import org.onlab.packet.IpAddress;
28import org.onlab.packet.MacAddress;
29import org.onlab.packet.VlanId;
30import org.onosproject.cordvtn.api.CordVtnConfig;
31import org.onosproject.cordvtn.api.CordVtnService;
32import org.onosproject.cordvtn.api.Instance;
33import org.onosproject.core.ApplicationId;
34import org.onosproject.core.CoreService;
35import org.onosproject.dhcp.DhcpService;
Hyunsun Moon04b1fe92016-05-18 21:28:06 -070036import org.onosproject.dhcp.IpAssignment;
Hyunsun Moon17f0cf82016-05-16 04:32:45 -070037import org.onosproject.mastership.MastershipService;
38import org.onosproject.net.ConnectPoint;
39import org.onosproject.net.DefaultAnnotations;
40import org.onosproject.net.Host;
41import org.onosproject.net.HostId;
42import org.onosproject.net.HostLocation;
43import org.onosproject.net.Port;
44import org.onosproject.net.config.ConfigFactory;
45import org.onosproject.net.config.NetworkConfigEvent;
46import org.onosproject.net.config.NetworkConfigListener;
47import org.onosproject.net.config.NetworkConfigRegistry;
48import org.onosproject.net.config.basics.SubjectFactories;
49import org.onosproject.net.device.DeviceService;
50import org.onosproject.net.host.DefaultHostDescription;
51import org.onosproject.net.host.HostDescription;
52import org.onosproject.net.host.HostEvent;
53import org.onosproject.net.host.HostListener;
54import org.onosproject.net.host.HostProvider;
55import org.onosproject.net.host.HostProviderRegistry;
56import org.onosproject.net.host.HostProviderService;
57import org.onosproject.net.host.HostService;
58import org.onosproject.net.packet.PacketContext;
59import org.onosproject.net.packet.PacketProcessor;
60import org.onosproject.net.packet.PacketService;
61import org.onosproject.net.provider.AbstractProvider;
62import org.onosproject.net.provider.ProviderId;
63import org.onosproject.xosclient.api.OpenStackAccess;
64import org.onosproject.xosclient.api.VtnPort;
65import org.onosproject.xosclient.api.VtnPortApi;
66import org.onosproject.xosclient.api.VtnService;
67import org.onosproject.xosclient.api.VtnServiceApi;
68import org.onosproject.xosclient.api.VtnServiceId;
69import org.onosproject.xosclient.api.XosAccess;
70import org.onosproject.xosclient.api.XosClientService;
71import org.slf4j.Logger;
72
Hyunsun Moon04b1fe92016-05-18 21:28:06 -070073import java.util.Date;
Hyunsun Moon17f0cf82016-05-16 04:32:45 -070074import java.util.Map;
75import java.util.Objects;
76import java.util.Set;
77import java.util.concurrent.ExecutorService;
78import java.util.stream.Collectors;
79import java.util.stream.StreamSupport;
80
81import static com.google.common.base.Preconditions.checkNotNull;
82import static java.util.concurrent.Executors.newSingleThreadScheduledExecutor;
83import static org.onlab.util.Tools.groupedThreads;
84import static org.onosproject.cordvtn.api.Instance.*;
Hyunsun Moon04b1fe92016-05-18 21:28:06 -070085import static org.onosproject.dhcp.IpAssignment.AssignmentStatus.Option_RangeNotEnforced;
86import static org.onosproject.xosclient.api.VtnService.NetworkType.MANAGEMENT;
Hyunsun Moon17f0cf82016-05-16 04:32:45 -070087import static org.onosproject.xosclient.api.VtnService.NetworkType.PRIVATE;
88import static org.slf4j.LoggerFactory.getLogger;
89
90/**
91 * Adds or removes instances to network services.
92 */
93@Component(immediate = true)
94@Service(value = CordVtnInstanceManager.class)
95public class CordVtnInstanceManager extends AbstractProvider implements HostProvider {
96
97 protected final Logger log = getLogger(getClass());
98
99 private static final String XOS_ACCESS_ERROR = "XOS access is not configured";
100 private static final String OPENSTACK_ACCESS_ERROR = "OpenStack access is not configured";
101 private static final Ip4Address DEFAULT_DNS = Ip4Address.valueOf("8.8.8.8");
Hyunsun Moon04b1fe92016-05-18 21:28:06 -0700102 private static final int DHCP_INFINITE_LEASE = -1;
Hyunsun Moon17f0cf82016-05-16 04:32:45 -0700103
104 @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
105 protected CoreService coreService;
106
107 @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
108 protected NetworkConfigRegistry configRegistry;
109
110 @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
111 protected HostProviderRegistry hostProviderRegistry;
112
113 @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
114 protected DeviceService deviceService;
115
116 @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
117 protected HostService hostService;
118
119 @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
120 protected PacketService packetService;
121
122 @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
123 protected DhcpService dhcpService;
124
125 @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
126 protected MastershipService mastershipService;
127
128 @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
129 protected XosClientService xosClient;
130
131 private final ConfigFactory configFactory =
132 new ConfigFactory(SubjectFactories.APP_SUBJECT_FACTORY, CordVtnConfig.class, "cordvtn") {
133 @Override
134 public CordVtnConfig createConfig() {
135 return new CordVtnConfig();
136 }
137 };
138
139 private final ExecutorService eventExecutor =
140 newSingleThreadScheduledExecutor(groupedThreads("onos/cordvtn-instance", "event-handler"));
141 private final PacketProcessor packetProcessor = new InternalPacketProcessor();
142 private final HostListener hostListener = new InternalHostListener();
143 private final NetworkConfigListener configListener = new InternalConfigListener();
144
145 private ApplicationId appId;
146 private HostProviderService hostProvider;
147 private CordVtnArpProxy arpProxy; // TODO make it a component service
148 private MacAddress privateGatewayMac = MacAddress.NONE;
149 private XosAccess xosAccess = null;
150 private OpenStackAccess osAccess = null;
151
152 /**
153 * Creates an cordvtn host location provider.
154 */
155 public CordVtnInstanceManager() {
156 super(new ProviderId("host", CordVtnService.CORDVTN_APP_ID));
157 }
158
159 @Activate
160 protected void activate() {
161 appId = coreService.registerApplication(CordVtnService.CORDVTN_APP_ID);
162
163 arpProxy = new CordVtnArpProxy(appId, packetService, hostService);
164 packetService.addProcessor(packetProcessor, PacketProcessor.director(0));
165 arpProxy.requestPacket();
166
167 hostService.addListener(hostListener);
168 hostProvider = hostProviderRegistry.register(this);
169
170 configRegistry.registerConfigFactory(configFactory);
171 configRegistry.addListener(configListener);
172
173 log.info("Started");
174 }
175
176 @Deactivate
177 protected void deactivate() {
178 hostProviderRegistry.unregister(this);
179 hostService.removeListener(hostListener);
180
181 packetService.removeProcessor(packetProcessor);
182
183 configRegistry.unregisterConfigFactory(configFactory);
184 configRegistry.removeListener(configListener);
185
186 eventExecutor.shutdown();
187 log.info("Stopped");
188 }
189
190 @Override
191 public void triggerProbe(Host host) {
192 /*
193 * Note: In CORD deployment, we assume that all hosts are configured.
194 * Therefore no probe is required.
195 */
196 }
197
198 /**
199 * Adds a service instance at a given connect point.
200 *
201 * @param connectPoint connect point of the instance
202 */
203 public void addInstance(ConnectPoint connectPoint) {
204 Port port = deviceService.getPort(connectPoint.deviceId(), connectPoint.port());
205 if (port == null) {
206 log.debug("No port found from {}", connectPoint);
207 return;
208 }
209
210 VtnPort vtnPort = getVtnPort(port.annotations().value("portName"));
211 if (vtnPort == null) {
212 return;
213 }
214
215 VtnService vtnService = getVtnService(vtnPort.serviceId());
216 if (vtnService == null) {
217 return;
218 }
219
220 // Added CREATE_TIME intentionally to trigger HOST_UPDATED event for the
221 // existing instances.
222 DefaultAnnotations.Builder annotations = DefaultAnnotations.builder()
223 .set(SERVICE_TYPE, vtnService.serviceType().toString())
224 .set(SERVICE_ID, vtnPort.serviceId().id())
225 .set(PORT_ID, vtnPort.id().id())
226 .set(CREATE_TIME, String.valueOf(System.currentTimeMillis()));
227
228 HostDescription hostDesc = new DefaultHostDescription(
229 vtnPort.mac(),
230 VlanId.NONE,
231 new HostLocation(connectPoint, System.currentTimeMillis()),
232 Sets.newHashSet(vtnPort.ip()),
233 annotations.build());
234
235 HostId hostId = HostId.hostId(vtnPort.mac());
236 hostProvider.hostDetected(hostId, hostDesc, false);
237 }
238
239 /**
240 * Adds a service instance with given host ID and host description.
241 *
242 * @param hostId host id
243 * @param description host description
244 */
245 public void addInstance(HostId hostId, HostDescription description) {
246 hostProvider.hostDetected(hostId, description, false);
247 }
248
249 /**
250 * Removes a service instance from a given connect point.
251 *
252 * @param connectPoint connect point
253 */
254 public void removeInstance(ConnectPoint connectPoint) {
255 hostService.getConnectedHosts(connectPoint)
256 .stream()
257 .forEach(host -> hostProvider.hostVanished(host.id()));
258 }
259
260 /**
261 * Removes service instance with given host ID.
262 *
263 * @param hostId host id
264 */
265 public void removeInstance(HostId hostId) {
266 hostProvider.hostVanished(hostId);
267 }
268
269 private void instanceDetected(Instance instance) {
270 VtnService service = getVtnService(instance.serviceId());
271 if (service == null) {
272 return;
273 }
274
275 if (service.networkType().equals(PRIVATE)) {
276 arpProxy.addGateway(service.serviceIp(), privateGatewayMac);
277 arpProxy.sendGratuitousArpForGateway(service.serviceIp(), Sets.newHashSet(instance));
278 }
Hyunsun Moon17f0cf82016-05-16 04:32:45 -0700279 if (!instance.isNestedInstance()) {
280 registerDhcpLease(instance, service);
281 }
282 }
283
284 private void instanceRemoved(Instance instance) {
285 VtnService service = getVtnService(instance.serviceId());
286 if (service == null) {
287 return;
288 }
289
290 if (service.networkType().equals(PRIVATE) && getInstances(service.id()).isEmpty()) {
291 arpProxy.removeGateway(service.serviceIp());
292 }
293
294 if (!instance.isNestedInstance()) {
295 dhcpService.removeStaticMapping(instance.mac());
296 }
297 }
298
299 private void registerDhcpLease(Instance instance, VtnService service) {
Hyunsun Moon04b1fe92016-05-18 21:28:06 -0700300 Ip4Address broadcast = Ip4Address.makeMaskedAddress(
301 instance.ipAddress(),
302 service.subnet().prefixLength());
303
304 IpAssignment.Builder ipBuilder = IpAssignment.builder()
305 .ipAddress(instance.ipAddress())
306 .leasePeriod(DHCP_INFINITE_LEASE)
307 .timestamp(new Date())
308 .subnetMask(Ip4Address.makeMaskPrefix(service.subnet().prefixLength()))
309 .broadcast(broadcast)
310 .domainServer(DEFAULT_DNS)
311 .assignmentStatus(Option_RangeNotEnforced);
312
313 if (service.networkType() != MANAGEMENT) {
314 ipBuilder = ipBuilder.routerAddress(service.serviceIp().getIp4Address());
315 }
Hyunsun Moon17f0cf82016-05-16 04:32:45 -0700316
317 log.debug("Set static DHCP mapping for {} {}", instance.mac(), instance.ipAddress());
Hyunsun Moon04b1fe92016-05-18 21:28:06 -0700318 dhcpService.setStaticMapping(instance.mac(), ipBuilder.build());
Hyunsun Moon17f0cf82016-05-16 04:32:45 -0700319 }
320
321 private VtnService getVtnService(VtnServiceId serviceId) {
322 checkNotNull(osAccess, OPENSTACK_ACCESS_ERROR);
323 checkNotNull(xosAccess, XOS_ACCESS_ERROR);
324
325 // TODO remove openstack access when XOS provides all information
326 VtnServiceApi serviceApi = xosClient.getClient(xosAccess).vtnService();
327 VtnService service = serviceApi.service(serviceId, osAccess);
328 if (service == null) {
329 log.warn("Failed to get VtnService for {}", serviceId);
330 }
331 return service;
332 }
333
334 private VtnPort getVtnPort(String portName) {
335 checkNotNull(osAccess, OPENSTACK_ACCESS_ERROR);
336 checkNotNull(xosAccess, XOS_ACCESS_ERROR);
337
338 // TODO remove openstack access when XOS provides all information
339 VtnPortApi portApi = xosClient.getClient(xosAccess).vtnPort();
340 VtnPort vtnPort = portApi.vtnPort(portName, osAccess);
341 if (vtnPort == null) {
342 log.warn("Failed to get port information of {}", portName);
343 }
344 return vtnPort;
345 }
346
347 private Set<Instance> getInstances(VtnServiceId serviceId) {
348 return StreamSupport.stream(hostService.getHosts().spliterator(), false)
349 .filter(host -> Objects.equals(
350 serviceId.id(),
351 host.annotations().value(Instance.SERVICE_ID)))
352 .map(Instance::of)
353 .collect(Collectors.toSet());
354 }
355
356 private void readConfiguration() {
357 CordVtnConfig config = configRegistry.getConfig(appId, CordVtnConfig.class);
358 if (config == null) {
359 log.debug("No configuration found");
360 return;
361 }
362
363 log.info("Load CORD-VTN configurations");
364
365 xosAccess = config.xosAccess();
366 osAccess = config.openstackAccess();
367 privateGatewayMac = config.privateGatewayMac();
368
369 Map<IpAddress, MacAddress> publicGateways = config.publicGateways();
370 publicGateways.entrySet()
371 .stream()
372 .forEach(entry -> {
373 arpProxy.addGateway(entry.getKey(), entry.getValue());
374 log.debug("Added public gateway IP {}, MAC {}",
375 entry.getKey(), entry.getValue());
376 });
377 // TODO notice gateway MAC change to VMs holds this gateway IP
378 }
379
380 private class InternalHostListener implements HostListener {
381
382 @Override
383 public void event(HostEvent event) {
384 Host host = event.subject();
385 if (!mastershipService.isLocalMaster(host.location().deviceId())) {
386 // do not allow to proceed without mastership
387 return;
388 }
389
390 Instance instance = Instance.of(host);
391 switch (event.type()) {
392 case HOST_UPDATED:
393 case HOST_ADDED:
394 eventExecutor.execute(() -> instanceDetected(instance));
395 break;
396 case HOST_REMOVED:
397 eventExecutor.execute(() -> instanceRemoved(instance));
398 break;
399 default:
400 break;
401 }
402 }
403 }
404
405 private class InternalPacketProcessor implements PacketProcessor {
406
407 @Override
408 public void process(PacketContext context) {
409 if (context.isHandled()) {
410 return;
411 }
412 Ethernet ethPacket = context.inPacket().parsed();
413 if (ethPacket == null || ethPacket.getEtherType() != Ethernet.TYPE_ARP) {
414 return;
415 }
416 arpProxy.processArpPacket(context, ethPacket);
417 }
418 }
419
420 private class InternalConfigListener implements NetworkConfigListener {
421
422 @Override
423 public void event(NetworkConfigEvent event) {
424 if (!event.configClass().equals(CordVtnConfig.class)) {
425 return;
426 }
427
428 switch (event.type()) {
429 case CONFIG_ADDED:
430 case CONFIG_UPDATED:
431 readConfiguration();
432 break;
433 default:
434 break;
435 }
436 }
437 }
438}