blob: 712409df4274170d7ec31898c190f90348bc6e5c [file] [log] [blame]
/*
* Copyright 2016-present Open Networking Foundation
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.onosproject.openstacknetworking.impl;
import com.google.common.base.Strings;
import org.apache.felix.scr.annotations.Activate;
import org.apache.felix.scr.annotations.Component;
import org.apache.felix.scr.annotations.Deactivate;
import org.apache.felix.scr.annotations.Reference;
import org.apache.felix.scr.annotations.ReferenceCardinality;
import org.apache.felix.scr.annotations.Service;
import org.onlab.packet.IpAddress;
import org.onlab.packet.MacAddress;
import org.onlab.packet.VlanId;
import org.onlab.util.Tools;
import org.onosproject.core.CoreService;
import org.onosproject.mastership.MastershipService;
import org.onosproject.net.ConnectPoint;
import org.onosproject.net.DefaultAnnotations;
import org.onosproject.net.Device;
import org.onosproject.net.Host;
import org.onosproject.net.HostId;
import org.onosproject.net.HostLocation;
import org.onosproject.net.Port;
import org.onosproject.net.device.DeviceEvent;
import org.onosproject.net.device.DeviceListener;
import org.onosproject.net.device.DeviceService;
import org.onosproject.net.host.DefaultHostDescription;
import org.onosproject.net.host.HostDescription;
import org.onosproject.net.host.HostProvider;
import org.onosproject.net.host.HostProviderRegistry;
import org.onosproject.net.host.HostProviderService;
import org.onosproject.net.host.HostService;
import org.onosproject.net.provider.AbstractProvider;
import org.onosproject.net.provider.ProviderId;
import org.onosproject.openstacknetworking.api.OpenstackNetworkService;
import org.onosproject.openstacknode.api.OpenstackNode;
import org.onosproject.openstacknode.api.OpenstackNodeEvent;
import org.onosproject.openstacknode.api.OpenstackNodeListener;
import org.onosproject.openstacknode.api.OpenstackNodeService;
import org.openstack4j.model.network.Network;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.util.Set;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.stream.Collectors;
import static org.onlab.util.Tools.groupedThreads;
import static org.onosproject.net.AnnotationKeys.PORT_NAME;
import static org.onosproject.openstacknetworking.api.Constants.*;
import static org.onosproject.openstacknetworking.impl.HostBasedInstancePort.ANNOTATION_CREATE_TIME;
import static org.onosproject.openstacknetworking.impl.HostBasedInstancePort.ANNOTATION_NETWORK_ID;
import static org.onosproject.openstacknetworking.impl.HostBasedInstancePort.ANNOTATION_PORT_ID;
@Service
@Component(immediate = true)
public final class OpenstackSwitchingHostProvider extends AbstractProvider implements HostProvider {
private final Logger log = LoggerFactory.getLogger(getClass());
private static final String PORT_NAME_PREFIX_VM = "tap";
private static final String ERR_ADD_HOST = "Failed to add host: ";
@Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
protected CoreService coreService;
@Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
protected DeviceService deviceService;
@Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
protected HostProviderRegistry hostProviderRegistry;
@Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
protected HostService hostService;
@Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
protected MastershipService mastershipService;
@Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
protected OpenstackNetworkService osNetworkService;
@Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
protected OpenstackNodeService osNodeService;
private final ExecutorService deviceEventExecutor =
Executors.newSingleThreadExecutor(groupedThreads("openstacknetworking", "device-event"));
private final ExecutorService configEventExecutor =
Executors.newSingleThreadExecutor(groupedThreads("openstacknetworking", "config-event"));
private final InternalDeviceListener internalDeviceListener = new InternalDeviceListener();
private final InternalOpenstackNodeListener internalNodeListener = new InternalOpenstackNodeListener();
private HostProviderService hostProvider;
/**
* Creates OpenStack switching host provider.
*/
public OpenstackSwitchingHostProvider() {
super(new ProviderId("sona", OPENSTACK_NETWORKING_APP_ID));
}
@Activate
protected void activate() {
coreService.registerApplication(OPENSTACK_NETWORKING_APP_ID);
deviceService.addListener(internalDeviceListener);
osNodeService.addListener(internalNodeListener);
hostProvider = hostProviderRegistry.register(this);
log.info("Started");
}
@Deactivate
protected void deactivate() {
hostProviderRegistry.unregister(this);
osNodeService.removeListener(internalNodeListener);
deviceService.removeListener(internalDeviceListener);
deviceEventExecutor.shutdown();
configEventExecutor.shutdown();
log.info("Stopped");
}
@Override
public void triggerProbe(Host host) {
// no probe is required
}
private void processPortAdded(Port port) {
// TODO check the node state is COMPLETE
org.openstack4j.model.network.Port osPort = osNetworkService.port(port);
if (osPort == null) {
log.warn(ERR_ADD_HOST + "OpenStack port for {} not found", port);
return;
}
Network osNet = osNetworkService.network(osPort.getNetworkId());
if (osNet == null) {
log.warn(ERR_ADD_HOST + "OpenStack network {} not found",
osPort.getNetworkId());
return;
}
if (osPort.getFixedIps().isEmpty()) {
log.warn(ERR_ADD_HOST + "no fixed IP for port {}", osPort.getId());
return;
}
MacAddress macAddr = MacAddress.valueOf(osPort.getMacAddress());
Set<IpAddress> fixedIps = osPort.getFixedIps().stream()
.map(ip -> IpAddress.valueOf(ip.getIpAddress()))
.collect(Collectors.toSet());
ConnectPoint connectPoint = new ConnectPoint(port.element().id(), port.number());
DefaultAnnotations.Builder annotations = DefaultAnnotations.builder()
.set(ANNOTATION_NETWORK_ID, osPort.getNetworkId())
.set(ANNOTATION_PORT_ID, osPort.getId())
.set(ANNOTATION_CREATE_TIME, String.valueOf(System.currentTimeMillis()));
HostDescription hostDesc = new DefaultHostDescription(
macAddr,
VlanId.NONE,
new HostLocation(connectPoint, System.currentTimeMillis()),
fixedIps,
annotations.build());
HostId hostId = HostId.hostId(macAddr);
hostProvider.hostDetected(hostId, hostDesc, false);
}
private void processPortRemoved(Port port) {
ConnectPoint connectPoint = new ConnectPoint(port.element().id(), port.number());
hostService.getConnectedHosts(connectPoint).forEach(host -> {
hostProvider.hostVanished(host.id());
});
}
private class InternalDeviceListener implements DeviceListener {
@Override
public boolean isRelevant(DeviceEvent event) {
Device device = event.subject();
if (!mastershipService.isLocalMaster(device.id())) {
// do not allow to proceed without mastership
return false;
}
Port port = event.port();
if (port == null) {
return false;
}
String portName = port.annotations().value(PORT_NAME);
if (Strings.isNullOrEmpty(portName) ||
!portName.startsWith(PORT_NAME_PREFIX_VM)) {
// handles Nova created port event only
return false;
}
return true;
}
@Override
public void event(DeviceEvent event) {
switch (event.type()) {
case PORT_UPDATED:
if (!event.port().isEnabled()) {
deviceEventExecutor.execute(() -> {
log.debug("Instance port {} is removed from {}",
event.port().annotations().value(PORT_NAME),
event.subject().id());
processPortRemoved(event.port());
});
} else if (event.port().isEnabled()) {
deviceEventExecutor.execute(() -> {
log.debug("Instance Port {} is detected from {}",
event.port().annotations().value(PORT_NAME),
event.subject().id());
processPortAdded(event.port());
});
}
break;
case PORT_ADDED:
deviceEventExecutor.execute(() -> {
log.debug("Instance port {} is detected from {}",
event.port().annotations().value(PORT_NAME),
event.subject().id());
processPortAdded(event.port());
});
break;
case PORT_REMOVED:
deviceEventExecutor.execute(() -> {
log.debug("Instance port {} is removed from {}",
event.port().annotations().value(PORT_NAME),
event.subject().id());
processPortRemoved(event.port());
});
default:
break;
}
}
}
private class InternalOpenstackNodeListener implements OpenstackNodeListener {
@Override
public void event(OpenstackNodeEvent event) {
OpenstackNode osNode = event.subject();
// TODO check leadership of the node and make only the leader process
switch (event.type()) {
case OPENSTACK_NODE_COMPLETE:
deviceEventExecutor.execute(() -> {
log.info("COMPLETE node {} is detected", osNode.hostname());
processCompleteNode(event.subject());
});
break;
case OPENSTACK_NODE_INCOMPLETE:
log.warn("{} is changed to INCOMPLETE state", osNode);
break;
case OPENSTACK_NODE_CREATED:
case OPENSTACK_NODE_UPDATED:
case OPENSTACK_NODE_REMOVED:
default:
break;
}
}
private void processCompleteNode(OpenstackNode osNode) {
deviceService.getPorts(osNode.intgBridge()).stream()
.filter(port -> port.annotations().value(PORT_NAME)
.startsWith(PORT_NAME_PREFIX_VM) &&
port.isEnabled())
.forEach(port -> {
log.debug("Instance port {} is detected from {}",
port.annotations().value(PORT_NAME),
osNode.hostname());
processPortAdded(port);
});
Tools.stream(hostService.getHosts())
.filter(host -> deviceService.getPort(
host.location().deviceId(),
host.location().port()) == null)
.forEach(host -> {
log.info("Remove stale host {}", host.id());
hostProvider.hostVanished(host.id());
});
}
}
}