/* * Copyright (c) 2013 Big Switch Networks, Inc. * * Licensed under the Eclipse Public License, Version 1.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.eclipse.org/legal/epl-v10.html * * 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.sdnplatform.devicegroup; import java.util.ArrayList; import java.util.Collection; import java.util.Collections; import java.util.List; import java.util.Map; import java.util.regex.Matcher; import java.util.regex.Pattern; import org.codehaus.jackson.annotate.JsonIgnore; import org.openflow.protocol.OFPhysicalPort; import org.openflow.util.HexString; import org.sdnplatform.core.IControllerService; import org.sdnplatform.core.IOFSwitch; import org.sdnplatform.devicemanager.IDevice; import org.sdnplatform.devicemanager.SwitchPort; import org.sdnplatform.packet.Ethernet; /** * Represents a DeviceGroup membership rule (e.g., NetVirt interface rule) that * defines membership of a define in a device group. * @author readams */ public class MembershipRule<T extends IDeviceGroup> implements Comparable<MembershipRule<T>> { protected T parentDeviceGroup; protected String name; protected static final String UNTAGGED_VLAN_NAME = "untagged"; /* * Non-unique name of this rule. */ protected String ruleName; protected String friendlyName; protected String description; protected int priority; protected boolean multipleAllowed; protected boolean vlanTagOnEgress; protected boolean active; protected String mac; protected String ipSubnet; protected String switchId; protected String ports; protected List<String> portList; // exploded unmodifiable list of ports protected String vlans; protected List<Integer> vlanList; // exploded unmodifiable list of vlans protected String tags; protected List<String> tagList; // exploded unmodifiable list of tags /* Used to garbage collect stale config data */ protected boolean marked; /** * @param name This membership rule's unique name. * @param parentDeviceGroup the parent of the interface rule */ public MembershipRule(String name, T parentDeviceGroup) { super(); this.setName(name); this.setParentDeviceGroup(parentDeviceGroup); } @JsonIgnore public T getParentDeviceGroup() { return parentDeviceGroup; } public void setParentDeviceGroup(T parentDeviceGroup) { if (parentDeviceGroup == null) throw new NullPointerException("parentDeviceGroup cannot be null"); this.parentDeviceGroup = parentDeviceGroup; } public String getName() { return name; } public void setName(String name) { if (name == null) throw new NullPointerException("name cannot be null"); this.name = name; this.friendlyName = name.replaceAll("[^|]*\\|", ""); } public String getRuleName() { return ruleName; } public void setRuleName(String ruleName) { if (ruleName == null) throw new NullPointerException("ruleName cannot be null"); this.ruleName = ruleName; } public String getDescription() { return description; } public void setDescription(String description) { this.description = description; } public int getPriority() { return priority; } public void setPriority(int priority) { this.priority = priority; } public boolean isMultipleAllowed() { return multipleAllowed; } public void setMultipleAllowed(boolean multipleAllowed) { this.multipleAllowed = multipleAllowed; } public boolean getVlanTagOnEgress() { return vlanTagOnEgress; } public void setVlanTagOnEgress(boolean vlanTagOnEgress) { this.vlanTagOnEgress = vlanTagOnEgress; } public boolean isActive() { return active; } public void setActive(boolean active) { this.active = active; } public String getMac() { return mac; } static final protected Pattern macPattern = Pattern.compile("([A-Fa-f\\d]{2}:?){5}[A-Fa-f\\d]{2}"); public void setMac(String mac) throws IllegalArgumentException { if (mac != null) { Matcher m = macPattern.matcher(mac); if (!m.matches()) { throw new IllegalArgumentException("Invalid mac: " + mac); } this.mac = mac.toLowerCase(); } else { this.mac = null; } } public String getIpSubnet() { return ipSubnet; } static final protected Pattern ipSubnetPattern = Pattern.compile("(\\d{1,3}\\.){3}\\d{1,3}/\\d{1,2}?"); public void setIpSubnet(String ipSubnet) throws IllegalArgumentException { if (ipSubnet != null) { Matcher m = ipSubnetPattern.matcher(ipSubnet); if (!m.matches()) { throw new IllegalArgumentException("Invalid ip subnet: " + ipSubnet); } } this.ipSubnet = ipSubnet; } public String getSwitchId() { return switchId; } static final protected Pattern switchIdPattern = Pattern.compile("([A-Fa-f\\d]{2}:?){7}[A-Fa-f\\d]{2}"); public void setSwitchId(String switchId) throws IllegalArgumentException { if (switchId != null) { Matcher m = switchIdPattern.matcher(switchId); if (!m.matches()) { throw new IllegalArgumentException("Invalid switchId: " + switchId); } this.switchId = switchId.toLowerCase(); } else { this.switchId = null; } } /** * Returns the ports to match in original form. I.e., a string specifying * port ranges and enumerations * @return */ public String getPorts() { return ports; } static final protected Pattern portRangePattern = Pattern.compile("([A-Za-z0-9-\\.:/]*?)(\\d+)-(\\d+)"); /** * Explode the port rule into a list of real port names * @return a (possibly-null) unmodifiable list of port names */ @JsonIgnore public List<String> getPortList() { return this.portList; } /** * Set the ports to match on for this rule. Requires that a switchId is * set as well. Used the port *name* for matching. Supports ranges and * enumerations: * GigabitEthernet1-6,Foobar2,Foobar3 * * @param ports * @throws IllegalArgumentException of the string is not a valid * port list */ public void setPorts(String ports) throws IllegalArgumentException { if (ports == null) { this.portList = null; this.ports = null; return; } String[] portranges = ports.split(","); // list might end up being larger than portranges.size List<String> portl = new ArrayList<String>(portranges.length); for (String p : portranges) { Matcher m = portRangePattern.matcher(p); if (m.matches()) { try { int start = Integer.parseInt(m.group(2)); int end = Integer.parseInt(m.group(3)); String prefix = m.group(1); for (int i = start; i <= end && i>=0; i++) { portl.add(prefix + i); } } catch (NumberFormatException e) { throw new IllegalArgumentException( "Could not process port range pattern " + ports); } } else { portl.add(p); } } this.ports = ports; if (portl.size() == 0) this.portList = null; else this.portList = Collections.unmodifiableList(portl); } /** * Returns the vlans to match as the original string * @return */ public String getVlans() { return vlans; } static final protected Pattern vlanRangePattern = Pattern.compile("(\\d+)-(\\d+)"); /** * Explode the vlans rule into a list of vlan IDs * @return a (possibly-null) unmodifiable list of vlans */ @JsonIgnore public List<Integer> getVlanList() { return this.vlanList; } /** * Set the vlans to match on for this rule. * Supports ranges and enumerations: * 1-5,10,23-42,4242 * * @param vlans * @throws IllegalArgumentException of the string is not a valid * vlan list * TODO: should we fail if a specified vlan is out of bounds? * Right now we silently ignore it! */ public void setVlans(String vlans) throws IllegalArgumentException { if (vlans == null) { this.vlans = null; this.vlanList = null; return; } String[] vlanranges = vlans.split(","); List<Integer> vlanl = new ArrayList<Integer>(vlanranges.length); for (String p : vlanranges) { Matcher m = vlanRangePattern.matcher(p); if (m.matches()) { try { int start = Integer.parseInt(m.group(1)); int end = Integer.parseInt(m.group(2)); if (start < 0) start = 0; if (end > 4095) end = 4095; for (int i = start; i <= end; i++) { vlanl.add(i); } } catch (NumberFormatException e) { throw new IllegalArgumentException("Could not process port " + "range pattern " + ports); } } else if (p.contentEquals(UNTAGGED_VLAN_NAME)) { int vlanInt = Ethernet.VLAN_UNTAGGED; vlanInt = vlanInt & 0xffff; vlanl.add(new Integer(vlanInt)); } else { try { int vlan = Integer.parseInt(p); if (vlan >= 0 && vlan <= 4095) vlanl.add(vlan); } catch (NumberFormatException e) { throw new IllegalArgumentException("Invalid vlan id " + p); } } } this.vlans = vlans; if (vlanl.size() == 0) this.vlanList = null; else this.vlanList = Collections.unmodifiableList(vlanl); } /** * Return tags to match for this rule as specified by setTags * @return */ public String getTags() { return tags; } static final protected Pattern tagsPattern = Pattern.compile("([\\.a-zA-Z0-9_-]*)=([a-zA-Z0-9_-]+)"); /** * Explode the tags rule into a list of tags * @return a (possibly-null) unmodifiable list of tags */ public List<String> getTagList() { return tagList; } /** * Set the tags to match on for this rule. You can specify a list * of key=value pairs. * * @param tags * @throws IllegalArgumentException of the string is not a valid * list of tags */ public void setTags(String tags) throws IllegalArgumentException { if (tags == null) { this.tagList = null; this.tags = null; return; } String[] tagMembers = tags.split(","); List<String> tagGroup = new ArrayList<String>(tagMembers.length); for (String p : tagMembers) { p = p.trim(); Matcher m = tagsPattern.matcher(p); if (m.matches()) { String fullname = m.group(1); String ns = "default"; String name = ""; int separatorIndex = fullname.lastIndexOf('.'); if (fullname.length() == 0) throw new IllegalArgumentException("Invalid tag: " + p); if (separatorIndex == (fullname.length() - 1)) { throw new IllegalArgumentException("Invalid tag: " + p); } else if (separatorIndex == -1) { name = fullname; } else { ns = fullname.substring(0, separatorIndex); name = fullname.substring(separatorIndex+1); } tagGroup.add(ns + "|" + name + "|" + m.group(2)); } else { throw new IllegalArgumentException("Invalid tag: " + p); } } this.tags = tags; // tagGroup will never be empty this.tagList = Collections.unmodifiableList(tagGroup); } /** * Used to clean up unused rules on config change. * @return */ public boolean isMarked() { return marked; } /** * Used to clean up unused rules on config change. All rules are set * to marked==false when the config changes. Then rules are updated. * Any rule that is left unmarked after reading of rules is complete * needs to be removed. * @return */ public void setMarked(boolean marked) { this.marked = marked; } public static boolean isInteger(String s) { int radix = 10; if(s.isEmpty()) return false; for(int i = 0; i < s.length(); i++) { if(i == 0 && s.charAt(i) == '-') continue; if(Character.digit(s.charAt(i),radix) < 0) return false; } return true; } /** * Return a fixed interface name associated with this rule * TODO: should this move the NetVirtManagerImpl or NetVirtInterface? */ @JsonIgnore public String getFixedInterfaceName() { boolean viface = true; if (switchId != null && mac == null && ipSubnet == null) { viface = false; } if (isInteger(friendlyName)) { if (viface) return "VEth" + friendlyName; else return "Eth" + friendlyName; } else { return friendlyName; } } /** * Return a list of sub-interface names that can be derived from * the rule itself, independent of devices. This is called * once upon interface creation and when switches * connect. * TODO: should this move the NetVirtManagerImpl or NetVirtInterface? */ public List<String> getFixedSubInterfaceNames(IOFSwitch sw) { String mainIfaceName = ""; boolean viface = true; List<String> ifNames; if (switchId != null && mac == null && ipSubnet == null) { viface = false; } // Nothing to do if called on switch connect and this is // not a switch based rule. if (viface || !sw.getStringId().equals(switchId)) return null; mainIfaceName = getFixedInterfaceName(); /* * There is a switch-based rule and the switch exists, * return per-port sub-interface names. */ Collection<OFPhysicalPort> ports = sw.getEnabledPorts(); List<String> ruleports = getPortList(); ifNames = new ArrayList<String>(1); for (OFPhysicalPort ofp : ports) { if (ruleports == null || ruleports.contains(ofp.getName())) { ifNames.add(mainIfaceName + "/" + ofp.getName()); } } return ifNames; } /** * Return the appropriate full interface name for specified device * assuming the device matches this interface rule. The name is * returned in an array of name elements. For example: * {"Eth5", "34"} or {"MyMacInterface", "00:00:00:00:00:01"} * @param d the device to look up * @param controllerProvider a sdnplatform provider * @return the interface name specified in an array of components * TODO: should this move the NetVirtManagerImpl or NetVirtInterface? */ public String[] getInterfaceNameForDevice(IDevice d, IControllerService controllerProvider) { String prefix = ""; boolean viface = true; if (switchId != null && mac == null && ipSubnet == null) { viface = false; } // Check if the interface name is an integer if (isInteger(friendlyName)) { if (viface) prefix = "VEth"; else prefix = "Eth"; } String subiname = null; if (viface) { subiname = d.getMACAddressString(); } else { /* * If this is "physical" interface we need to find the matching * switch attachment point, and return the "nice" name for the * port as the subinterface */ SwitchPort[] aps = d.getAttachmentPoints(); Map<Long, IOFSwitch> switches = controllerProvider.getSwitches(); for (SwitchPort ap : aps) { long switchIdLong = ap.getSwitchDPID(); long ruleswitch = HexString.toLong(switchId); if (switchIdLong == ruleswitch) { IOFSwitch sw = switches.get(switchIdLong); if (sw == null) continue; OFPhysicalPort p = sw.getPort((short)ap.getPort()); if (p!=null && sw.portEnabled(p)) { subiname = p.getName(); } } if (subiname != null) break; } if (subiname == null) { subiname = "[disconnected]"; } } return new String[] {prefix + friendlyName, subiname}; } @Override public boolean equals(Object arg0) { if (arg0 instanceof MembershipRule) { MembershipRule<?> other = (MembershipRule<?>)arg0; return (this.name.equals(other.name) && this.parentDeviceGroup.equals(other.parentDeviceGroup)); } return false; } @Override public int hashCode() { return (this.name + "|" + this.parentDeviceGroup.getName()).hashCode(); } /** * Checks if this rule and other rule have the same matching fields, * priority, etc. I.e., whether they would be exactly the same from a * matching point of view. */ public boolean matchingFieldsEquals(MembershipRule<T> other) { if (this == other) return true; if (other == null) return false; if (active != other.active) return false; if (ipSubnet == null) { if (other.ipSubnet != null) return false; } else if (!ipSubnet.equals(other.ipSubnet)) return false; if (mac == null) { if (other.mac != null) return false; } else if (!mac.equals(other.mac)) return false; if (multipleAllowed != other.multipleAllowed) return false; if (name == null) { if (other.name != null) return false; } else if (!name.equals(other.name)) return false; if (ports == null) { if (other.ports != null) return false; } else if (!ports.equals(other.ports)) return false; if (switchId == null) { if (other.switchId != null) return false; } else if (!switchId.equals(other.switchId)) return false; if (tags == null) { if (other.tags != null) return false; } else if (!tags.equals(other.tags)) return false; if (vlanTagOnEgress != other.vlanTagOnEgress) return false; if (vlans == null) { if (other.vlans != null) return false; } else if (!vlans.equals(other.vlans)) return false; return true; } @Override public int compareTo(MembershipRule<T> o) { // higher priority is sorted first if (parentDeviceGroup.equals(o.parentDeviceGroup)) { if (priority != o.priority) return (new Integer(o.priority)).compareTo(priority); return (name.compareTo(o.name)); } else { return (parentDeviceGroup.compareTo(o.parentDeviceGroup)); } } @Override public String toString() { StringBuffer sb = new StringBuffer(); sb.append(friendlyName); sb.append(" [Parent: " + parentDeviceGroup.getName() + "]"); sb.append(" [Active: " + active + "]"); sb.append(" [Priority: " + priority + "]"); if (mac != null) sb.append(" [MAC: " + mac + "]"); if (vlans != null) sb.append(" [VLAN: " + vlans + "]"); if (ipSubnet != null) sb.append(" [IP Subnet: " + ipSubnet + "]"); if (switchId != null) sb.append(" [Switch: " + switchId + "]"); if (ports != null) sb.append(" [Ports: " + ports + "]"); return sb.toString(); } }