Refactored OpenstackSwitching app
[DONE]
- Restructured to activate or deactivate switching and routing app separately
- Fixed to add or remove host when port is detected or vanished
- Use openstack node service to get integration bridges and data IP
[TODO]
- Remove use of OpenstackPortInfo
- Support installing flow rules for exising VMs
- Call security group update method when port update triggered from OpenStack
Change-Id: Ic0b2ac3f7ab07f0e20c97c6edfdd1928b9767baf
diff --git a/apps/openstacknetworking/switching/src/main/java/org/onosproject/openstacknetworking/switching/OpenstackSecurityGroupRulePopulator.java b/apps/openstacknetworking/switching/src/main/java/org/onosproject/openstacknetworking/switching/OpenstackSecurityGroupRulePopulator.java
new file mode 100644
index 0000000..7477404
--- /dev/null
+++ b/apps/openstacknetworking/switching/src/main/java/org/onosproject/openstacknetworking/switching/OpenstackSecurityGroupRulePopulator.java
@@ -0,0 +1,365 @@
+/*
+* Copyright 2016-present Open Networking Laboratory
+*
+* 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.switching;
+
+import com.google.common.collect.Maps;
+import com.google.common.collect.Sets;
+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.onlab.packet.Ethernet;
+import org.onlab.packet.IPv4;
+import org.onlab.packet.Ip4Address;
+import org.onlab.packet.IpPrefix;
+import org.onlab.packet.TpPort;
+import org.onlab.util.Tools;
+import org.onosproject.net.DeviceId;
+import org.onosproject.net.Host;
+import org.onosproject.net.flow.DefaultTrafficSelector;
+import org.onosproject.net.flow.DefaultTrafficTreatment;
+import org.onosproject.net.flow.TrafficSelector;
+import org.onosproject.net.flowobjective.DefaultForwardingObjective;
+import org.onosproject.net.flowobjective.FlowObjectiveService;
+import org.onosproject.net.flowobjective.ForwardingObjective;
+import org.onosproject.openstackinterface.OpenstackInterfaceService;
+import org.onosproject.openstackinterface.OpenstackPort;
+import org.onosproject.openstackinterface.OpenstackSecurityGroup;
+import org.onosproject.openstackinterface.OpenstackSecurityGroupRule;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import java.util.Map;
+import java.util.Objects;
+import java.util.Optional;
+import java.util.Set;
+import java.util.stream.Collectors;
+
+import static org.onosproject.openstacknetworking.switching.Constants.*;
+
+/**
+ * Populates flows rules for Security Groups of VMs.
+ *
+ */
+@Component(immediate = true)
+public class OpenstackSecurityGroupRulePopulator extends AbstractVmHandler {
+
+ private final Logger log = LoggerFactory.getLogger(getClass());
+
+ @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
+ protected OpenstackInterfaceService openstackService;
+
+ @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
+ protected FlowObjectiveService flowObjectiveService;
+
+ private static final String PROTO_ICMP = "ICMP";
+ private static final String PROTO_TCP = "TCP";
+ private static final String PROTO_UDP = "UDP";
+ private static final String ETHTYPE_IPV4 = "IPV4";
+
+ private final Map<Host, Set<SecurityGroupRule>> securityGroupRuleMap = Maps.newConcurrentMap();
+
+ @Activate
+ protected void activate() {
+ super.activate();
+ }
+
+ @Deactivate
+ protected void deactivate() {
+ super.deactivate();
+ }
+
+ // TODO call this when port is updated from OpenStack
+ public void updateSecurityGroup(OpenstackPort osPort) {
+ if (!osPort.status().equals(OpenstackPort.PortStatus.ACTIVE)) {
+ return;
+ }
+
+ Optional<Host> host = getVmByPortId(osPort.id());
+ if (!host.isPresent()) {
+ log.warn("No host found with {}", osPort);
+ return;
+ }
+ eventExecutor.execute(() -> updateSecurityGroupRules(host.get(), true));
+ }
+
+ /**
+ * Populates security group rules for all VMs in the supplied tenant ID.
+ * VMs in the same tenant tend to be engaged to each other by sharing the
+ * same security groups or setting the remote to another security group.
+ * To make the implementation simpler and robust, it tries to reinstall
+ * security group rules for all the VMs in the same tenant whenever a new
+ * VM is detected or port is updated.
+ *
+ * @param tenantId tenant id to update security group rules
+ */
+ private void populateSecurityGroupRules(String tenantId, boolean install) {
+ securityGroupRuleMap.entrySet().stream()
+ .filter(entry -> getTenantId(entry.getKey()).equals(tenantId))
+ .forEach(entry -> {
+ Host local = entry.getKey();
+ entry.getValue().stream().forEach(sgRule -> {
+ setSecurityGroupRule(local.location().deviceId(),
+ sgRule.rule(),
+ getIp(local),
+ sgRule.remoteIp(), install);
+ });
+ });
+ log.debug("Updated security group rules for {}", tenantId);
+ }
+
+ private void setSecurityGroupRule(DeviceId deviceId, OpenstackSecurityGroupRule sgRule,
+ Ip4Address vmIp, IpPrefix remoteIp,
+ boolean install) {
+ ForwardingObjective.Builder foBuilder = buildFlowObjective(sgRule, vmIp, remoteIp);
+ if (foBuilder == null) {
+ return;
+ }
+
+ if (install) {
+ flowObjectiveService.forward(deviceId, foBuilder.add());
+ } else {
+ flowObjectiveService.forward(deviceId, foBuilder.remove());
+ }
+ }
+
+ private ForwardingObjective.Builder buildFlowObjective(OpenstackSecurityGroupRule sgRule,
+ Ip4Address vmIp,
+ IpPrefix remoteIp) {
+ if (remoteIp != null && remoteIp.equals(IpPrefix.valueOf(vmIp, 32))) {
+ // do nothing if the remote IP is my IP
+ return null;
+ }
+
+ TrafficSelector.Builder sBuilder = DefaultTrafficSelector.builder();
+ buildMatchs(sBuilder, sgRule, vmIp, remoteIp);
+
+ return DefaultForwardingObjective.builder()
+ .withSelector(sBuilder.build())
+ .withTreatment(DefaultTrafficTreatment.builder().build())
+ .withPriority(ACL_RULE_PRIORITY)
+ .withFlag(ForwardingObjective.Flag.SPECIFIC)
+ .fromApp(appId);
+ }
+
+ private void buildMatchs(TrafficSelector.Builder sBuilder, OpenstackSecurityGroupRule sgRule,
+ Ip4Address vmIp, IpPrefix remoteIp) {
+ buildMatchEthType(sBuilder, sgRule.ethertype());
+ buildMatchDirection(sBuilder, sgRule.direction(), vmIp);
+ buildMatchProto(sBuilder, sgRule.protocol());
+ buildMatchPort(sBuilder, sgRule.protocol(), sgRule.direction(),
+ sgRule.portRangeMax(), sgRule.portRangeMin());
+ buildMatchRemoteIp(sBuilder, remoteIp, sgRule.direction());
+ }
+
+ private void buildMatchDirection(TrafficSelector.Builder sBuilder,
+ OpenstackSecurityGroupRule.Direction direction,
+ Ip4Address vmIp) {
+ if (direction.equals(OpenstackSecurityGroupRule.Direction.EGRESS)) {
+ sBuilder.matchIPSrc(IpPrefix.valueOf(vmIp, 32));
+ } else {
+ sBuilder.matchIPDst(IpPrefix.valueOf(vmIp, 32));
+ }
+ }
+
+ private void buildMatchEthType(TrafficSelector.Builder sBuilder, String etherType) {
+ // Either IpSrc or IpDst (or both) is set by default, and we need to set EthType as IPv4.
+ sBuilder.matchEthType(Ethernet.TYPE_IPV4);
+ if (etherType != null && !Objects.equals(etherType, "null") &&
+ !etherType.toUpperCase().equals(ETHTYPE_IPV4)) {
+ log.debug("EthType {} is not supported yet in Security Group", etherType);
+ }
+ }
+
+ private void buildMatchRemoteIp(TrafficSelector.Builder sBuilder, IpPrefix remoteIpPrefix,
+ OpenstackSecurityGroupRule.Direction direction) {
+ if (remoteIpPrefix != null && !remoteIpPrefix.getIp4Prefix().equals(IP_PREFIX_ANY)) {
+ if (direction.equals(OpenstackSecurityGroupRule.Direction.EGRESS)) {
+ sBuilder.matchIPDst(remoteIpPrefix);
+ } else {
+ sBuilder.matchIPSrc(remoteIpPrefix);
+ }
+ }
+ }
+
+ private void buildMatchProto(TrafficSelector.Builder sBuilder, String protocol) {
+ if (protocol != null) {
+ switch (protocol.toUpperCase()) {
+ case PROTO_ICMP:
+ sBuilder.matchIPProtocol(IPv4.PROTOCOL_ICMP);
+ break;
+ case PROTO_TCP:
+ sBuilder.matchIPProtocol(IPv4.PROTOCOL_TCP);
+ break;
+ case PROTO_UDP:
+ sBuilder.matchIPProtocol(IPv4.PROTOCOL_UDP);
+ break;
+ default:
+ }
+ }
+ }
+
+ private void buildMatchPort(TrafficSelector.Builder sBuilder, String protocol,
+ OpenstackSecurityGroupRule.Direction direction,
+ int portMin, int portMax) {
+ if (portMin > 0 && portMax > 0 && portMin == portMax) {
+ if (protocol.toUpperCase().equals(PROTO_TCP)) {
+ if (direction.equals(OpenstackSecurityGroupRule.Direction.EGRESS)) {
+ sBuilder.matchTcpDst(TpPort.tpPort(portMax));
+ } else {
+ sBuilder.matchTcpSrc(TpPort.tpPort(portMax));
+ }
+ } else if (protocol.toUpperCase().equals(PROTO_UDP)) {
+ if (direction.equals(OpenstackSecurityGroupRule.Direction.EGRESS)) {
+ sBuilder.matchUdpDst(TpPort.tpPort(portMax));
+ } else {
+ sBuilder.matchUdpSrc(TpPort.tpPort(portMax));
+ }
+ }
+ }
+ }
+
+ private void updateSecurityGroupRulesMap(Host host) {
+ OpenstackPort osPort = openstackService.port(host.annotations().value(PORT_ID));
+ if (osPort == null) {
+ log.debug("Failed to get OpenStack port information for {}", host);
+ return;
+ }
+
+ Set<SecurityGroupRule> rules = Sets.newHashSet();
+ osPort.securityGroups().stream().forEach(sgId -> {
+ OpenstackSecurityGroup osSecGroup = openstackService.securityGroup(sgId);
+ if (osSecGroup != null) {
+ osSecGroup.rules().stream().forEach(rule -> rules.addAll(getSgRules(rule)));
+ } else {
+ // TODO handle the case that the security group removed
+ log.warn("Failed to get security group {}", sgId);
+ }
+ });
+ securityGroupRuleMap.put(host, rules);
+ }
+
+ /**
+ * Returns set of security group rules with individual remote IP by
+ * converting remote group to actual IP address.
+ *
+ * @param sgRule security group rule
+ * @return set of security group rules
+ */
+ private Set<SecurityGroupRule> getSgRules(OpenstackSecurityGroupRule sgRule) {
+ Set<SecurityGroupRule> sgRules = Sets.newHashSet();
+ if (sgRule.remoteGroupId() != null && !sgRule.remoteGroupId().equals("null")) {
+ sgRules = getRemoteIps(sgRule.tenantId(), sgRule.remoteGroupId())
+ .stream()
+ .map(remoteIp -> new SecurityGroupRule(sgRule, remoteIp))
+ .collect(Collectors.toSet());
+ } else {
+ sgRules.add(new SecurityGroupRule(sgRule, sgRule.remoteIpPrefix()));
+ }
+ return sgRules;
+ }
+
+ /**
+ * Returns a set of host IP addresses engaged with supplied security group ID.
+ * It only searches a VM in the same tenant boundary.
+ *
+ * @param tenantId tenant id
+ * @param sgId security group id
+ * @return set of ip addresses in ip prefix format
+ */
+ private Set<IpPrefix> getRemoteIps(String tenantId, String sgId) {
+ Set<IpPrefix> remoteIps = Sets.newHashSet();
+ securityGroupRuleMap.entrySet().stream()
+ .filter(entry -> Objects.equals(getTenantId(entry.getKey()), tenantId))
+ .forEach(entry -> {
+ if (entry.getValue().stream()
+ .anyMatch(rule -> rule.rule().secuityGroupId().equals(sgId))) {
+ remoteIps.add(IpPrefix.valueOf(getIp(entry.getKey()), 32));
+ }
+ });
+ return remoteIps;
+ }
+
+ private void updateSecurityGroupRules(Host host, boolean isHostAdded) {
+ String tenantId = getTenantId(host);
+ populateSecurityGroupRules(tenantId, false);
+
+ if (isHostAdded) {
+ updateSecurityGroupRulesMap(host);
+ } else {
+ securityGroupRuleMap.remove(host);
+ }
+
+ Tools.stream(hostService.getHosts())
+ .filter(h -> Objects.equals(getTenantId(h), getTenantId(host)))
+ .forEach(this::updateSecurityGroupRulesMap);
+
+ populateSecurityGroupRules(tenantId, true);
+ }
+
+ @Override
+ protected void hostDetected(Host host) {
+ updateSecurityGroupRules(host, true);
+ log.info("Applied security group rules for {}", host);
+ }
+
+ @Override
+ protected void hostRemoved(Host host) {
+ updateSecurityGroupRules(host, false);
+ log.info("Applied security group rules for {}", host);
+ }
+
+ private final class SecurityGroupRule {
+ private final OpenstackSecurityGroupRule rule;
+ private final IpPrefix remoteIp;
+
+ private SecurityGroupRule(OpenstackSecurityGroupRule rule, IpPrefix remoteIp) {
+ this.rule = rule;
+ this.remoteIp = remoteIp;
+ }
+
+ private OpenstackSecurityGroupRule rule() {
+ return rule;
+ }
+
+ private IpPrefix remoteIp() {
+ return remoteIp;
+ }
+
+ @Override
+ public boolean equals(Object obj) {
+ if (this == obj) {
+ return true;
+ }
+
+ if (obj instanceof SecurityGroupRule) {
+ SecurityGroupRule that = (SecurityGroupRule) obj;
+ if (Objects.equals(rule, that.rule) &&
+ Objects.equals(remoteIp, that.remoteIp)) {
+ return true;
+ }
+ }
+ return false;
+ }
+
+ @Override
+ public int hashCode() {
+ return Objects.hash(rule, remoteIp);
+ }
+ }
+}