/*
* 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.addressspace;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Date;
import java.util.EnumSet;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import java.util.Set;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.locks.ReentrantReadWriteLock;
import org.openflow.util.HexString;
import org.sdnplatform.core.IControllerService;
import org.sdnplatform.core.IHAListener;
import org.sdnplatform.core.IControllerService.Role;
import org.sdnplatform.core.annotations.LogMessageDoc;
import org.sdnplatform.core.annotations.LogMessageDocs;
import org.sdnplatform.core.module.ModuleContext;
import org.sdnplatform.core.module.ModuleException;
import org.sdnplatform.core.module.IModule;
import org.sdnplatform.core.module.IPlatformService;
import org.sdnplatform.core.util.SingletonTask;
import org.sdnplatform.devicegroup.DeviceGroupMatcher;
import org.sdnplatform.devicegroup.MembershipRule;
import org.sdnplatform.devicemanager.IDevice;
import org.sdnplatform.devicemanager.IEntityClass;
import org.sdnplatform.devicemanager.IEntityClassListener;
import org.sdnplatform.devicemanager.IEntityClassifierService;
import org.sdnplatform.devicemanager.SwitchPort;
import org.sdnplatform.devicemanager.IDeviceService.DeviceField;
import org.sdnplatform.devicemanager.internal.Entity;
import org.sdnplatform.packet.Ethernet;
import org.sdnplatform.restserver.IRestApiService;
import org.sdnplatform.storage.IResultSet;
import org.sdnplatform.storage.IStorageSourceListener;
import org.sdnplatform.storage.IStorageSourceService;
import org.sdnplatform.storage.StorageException;
import org.sdnplatform.tagmanager.ITagManagerService;
import org.sdnplatform.threadpool.IThreadPoolService;
import org.sdnplatform.topology.ITopologyService;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/**
* @author gregor
*
* TODO: unify vlan semantics: -1 vs. NULL vs. 0 vs. untagged vs. not-set
* (this is a sdnplatform wide task)
*
* TODO: find out the right interaction with Sandeep
*/
public class AddressSpaceManagerImpl implements
IAddressSpaceManagerService,
IHAListener,
IModule,
IStorageSourceListener,
IEntityClassifierService {
// Table names
public static final String ADDRESS_SPACE_TABLE_NAME =
"controller_addressspace";
public static final String ADDRESS_SPACE_IDENTIFIER_RULE_TABLE_NAME =
"controller_addressspaceidentifierrule";
// Column names
public static final String NAME_COLUMN_NAME = "name";
public static final String ACTIVE_COLUMN_NAME = "active";
public static final String PRIORITY_COLUMN_NAME = "priority";
public static final String SEPARATOR_COLUMN_NAME = "vlan_tag_on_egress";
public static final String ID_COLUMN_NAME = "name";
public static final String ADDRESS_SPACE_COLUMN_NAME =
"address_space_id";
public static final String DESCRIPTION_COLUMN_NAME = "description";
public static final String RULE_COLUMN_NAME = "rule";
public static final String MAC_COLUMN_NAME = "mac";
public static final String SWITCH_COLUMN_NAME = "switch";
public static final String PORTS_COLUMN_NAME = "ports";
public static final String VLANS_COLUMN_NAME = "vlans";
public static final String TAGS_COLUMN_NAME = "tag";
protected static final int UPDATE_TASK_BATCH_DELAY_MS = 750;
protected static Logger logger =
LoggerFactory.getLogger(AddressSpaceManagerImpl.class);
/**
* This is the set of address spaces that exist in the system, mapping
* ID to BetterEntityClass object
*/
protected Map<String, BetterEntityClass> addressSpaceMap;
/**
* This is the set of Address Space identifier rules that exist in the
* system, mapping name to address space identifier rule object
*/
protected Map<String,MembershipRule<BetterEntityClass>> identifierRuleMap;
/**
* Address Space manager event listeners
*/
protected Set<IEntityClassListener> entityClassListeners;
/**
* Asynchronous task for responding to address space configuration changes
* notifications.
*/
SingletonTask configUpdateTask;
protected IStorageSourceService storageSource;
protected IRestApiService restApi;
protected IThreadPoolService threadPool;
protected IControllerService controllerProvider;
protected ITagManagerService tagManager;
protected ITopologyService topology;
protected ReentrantReadWriteLock configLock;
/**
* This list maps vlans to addressSpaces. We use it for fast address space
* lookups on internal links
*/
protected ArrayList<BetterEntityClass> entityClasses;
protected DeviceGroupMatcher<BetterEntityClass> deviceGroupMatcher;
protected static final EnumSet<DeviceField> keyFields;
protected BetterEntityClass defaultEntityClass;
protected boolean addressSpaceGlobalActiveState;
protected boolean enableNetworkService;
static {
keyFields = EnumSet.of(DeviceField.SWITCH,
DeviceField.PORT,
DeviceField.VLAN,
DeviceField.MAC);
}
protected Map<String, BetterEntityClass> getAddressSpaceMap () {
return addressSpaceMap;
}
protected Map<String,MembershipRule<BetterEntityClass>>
getIdentifierRuleMap () {
return identifierRuleMap;
}
public String getName () {
return "addressSpaceManager";
}
// **********************
// IStorageSourceListener
// **********************
@Override
public void rowsModified(String tableName, Set<Object> rowKeys) {
queueConfigUpdate();
}
@Override
public void rowsDeleted(String tableName, Set<Object> rowKeys) {
queueConfigUpdate();
}
/**
* Queue a task to update the configuration state
*/
protected void queueConfigUpdate() {
configUpdateTask.reschedule(UPDATE_TASK_BATCH_DELAY_MS,
TimeUnit.MILLISECONDS);
}
// ********************
// Service Dependencies
// ********************
/**
* A shim IDevice implementations that just wraps a single entity
* We pass instances of of this class to
* {@link DeviceGroupMatcher#matchDevice(IDevice)}
*
* TODO: alternatively, we could add a DeviceGroupMatcher.matchEntity()
*/
protected class FakeDevice implements IDevice {
protected Entity entity;
String macAddressString;
public FakeDevice(Entity entity) {
this.entity = entity;
}
@Override
public Long getDeviceKey() {
return null;
}
@Override
public long getMACAddress() {
return entity.getMacAddress();
}
@Override
public String getMACAddressString() {
if (macAddressString == null) {
macAddressString =
HexString.toHexString(entity.getMacAddress(), 6);
}
return macAddressString;
}
@Override
public Short[] getVlanId() {
if (entity.getVlan() != null) {
return new Short[]{ entity.getVlan() };
} else {
return new Short[] { Short.valueOf((short)-1) };
}
}
@Override
public Integer[] getIPv4Addresses() {
// no support for IP based rules
throw new UnsupportedOperationException();
}
@Override
public SwitchPort[] getAttachmentPoints() {
if (entity.getSwitchDPID() != null &&
entity.getSwitchPort() != null) {
return new SwitchPort[] {
new SwitchPort(entity.getSwitchDPID().longValue(),
entity.getSwitchPort().shortValue())
};
}
return new SwitchPort[0];
}
@Override
public SwitchPort[] getAttachmentPoints(boolean includeError) {
return getAttachmentPoints();
}
@Override
public Date getLastSeen() {
throw new UnsupportedOperationException();
}
@Override
public IEntityClass getEntityClass() {
throw new UnsupportedOperationException();
}
@Override
public Short[] getSwitchPortVlanIds(SwitchPort swp) {
throw new UnsupportedOperationException();
}
}
/**
* Match entity against address space membership rules and return
* the appropriate BetterEntityClass. This class matches entities at the
* edge of an OF domain.
* Call only while holding configLock!
* @param entity the entity to match
* @return entityClass or null
*/
@LogMessageDocs({
@LogMessageDoc(level="ERROR",
message="Failed to assign an address space {device} "
+ "{exception}",
explanation="Could not assign an address-space to the device.",
recommendation=LogMessageDoc.REPORT_CONTROLLER_BUG)
})
protected BetterEntityClass doMatchEntity(Entity entity) {
// An attachment point port. Match entity against
// address space membership rules
FakeDevice d = new FakeDevice(entity);
List<MembershipRule<BetterEntityClass>> rules;
try {
rules = deviceGroupMatcher.matchDevice(d);
if (rules == null) {
if (entity.getVlan() == null) {
// no rules matched && untagged:
// default entity class
return defaultEntityClass;
} else if (entityClasses.get(entity.getVlan()) == null) {
// no rules && tagged && no address space associated
// with this vlan: default entity class
return defaultEntityClass;
} else {
// no rules && tagged && there is an address space
// associated with the vlan: don't allow
return null;
}
} else {
// we matched rules. We don't allow "allow multiple"
// so we just look at the first rule
return rules.get(0).getParentDeviceGroup();
}
} catch (Exception e) {
logger.error("Failed to assign an address space " + d, e);
return null;
}
}
/**
* Drop all state. Used for going to SLAVE mode
*/
protected void clearConfigState() {
configLock.writeLock().lock();
try {
deviceGroupMatcher.clear();
addressSpaceMap.clear();
identifierRuleMap.clear();
entityClasses.clear();
addressSpaceGlobalActiveState = false;
} finally {
configLock.writeLock().unlock();
}
}
private boolean hasAddressSpaceOptionChanged (String oldS, String newS,
boolean ignoreCase) {
if ((oldS == null) && (newS == null)) {
return false;
}
if ((oldS == null) && (newS != null)) {
return true;
}
if ((oldS != null) && (newS == null)) {
return true;
}
if ((ignoreCase) && (!oldS.equalsIgnoreCase(newS))) {
return true;
}
if ((!ignoreCase) && (!oldS.equals(newS))) {
return true;
}
return false;
}
@LogMessageDocs({
@LogMessageDoc(level="WARN",
message="Unsupported mac address based match ignored",
explanation="An address-space configuration in the data-base "
+"uses unsupported MAC based matches",
recommendation=LogMessageDoc.GENERIC_ACTION),
@LogMessageDoc(level="WARN",
message="Vlan tag value {vlan}" +
" does not match vlan-tag-on-egress value {vlan}. " +
" Entry ignored.",
explanation="Address-space misconfiguration. An address-space" +
"uses a different vlan for vlan-tag-on-egress than" +
"it uses to match devices.",
recommendation=LogMessageDoc.GENERIC_ACTION),
@LogMessageDoc(level="WARN",
message="Error loading address space {address-space}" +
" rule {rule-name} from storage, entry ignored." +
" {exception}",
explanation="Could not read the mentioned address-spaces" +
"identifier-rule from storage.",
recommendation=LogMessageDoc.GENERIC_ACTION)
})
private void readAddressSpaceIdentifierRuleConfig (
IResultSet iruleResultSet,
HashSet<BetterEntityClass> addressSpaceWithRuleChangesSet)
{
String addressSpaceName = iruleResultSet.getString(
ADDRESS_SPACE_COLUMN_NAME);
if (addressSpaceName == null)
return;
BetterEntityClass addressSpace = addressSpaceMap.get(addressSpaceName);
if (addressSpace == null || addressSpace == defaultEntityClass)
return;
String identifierName = iruleResultSet.getString(NAME_COLUMN_NAME);
MembershipRule<BetterEntityClass> idRule =
identifierRuleMap.get(identifierName);
if (idRule == null) {
idRule = new MembershipRule<BetterEntityClass>(identifierName,
addressSpace);
addressSpaceWithRuleChangesSet.add(addressSpace);
logger.debug("New address space interface rule added: {}", idRule);
}
try {
/* For each of the possible address-space identifier rule
* match options, we compare the match field with the old
* value, and if the new value is differnet, then we
* add the addressSpace to addressSpaceWithRuleChangesSet
*/
idRule.setDescription(
iruleResultSet.getString(DESCRIPTION_COLUMN_NAME));
idRule.setRuleName(
iruleResultSet.getString(RULE_COLUMN_NAME));
boolean oldState = idRule.isActive();
idRule.setActive(iruleResultSet.getBoolean(ACTIVE_COLUMN_NAME));
if (oldState != idRule.isActive()) {
/*
* Active status of rule has changed
*/
addressSpaceWithRuleChangesSet.add(
idRule.getParentDeviceGroup());
}
int oldPriority = idRule.getPriority();
idRule.setPriority(iruleResultSet.getInt(PRIORITY_COLUMN_NAME));
if (oldPriority != idRule.getPriority()) {
/*
* Priority of rule has changed
*/
addressSpaceWithRuleChangesSet.add(
idRule.getParentDeviceGroup());
}
String oldSwitchId = idRule.getSwitchId();
idRule.setSwitchId(iruleResultSet.getString(SWITCH_COLUMN_NAME));
if (hasAddressSpaceOptionChanged(oldSwitchId, idRule.getSwitchId(),
true)) {
/*
* Switch ID match rule has changed
*/
addressSpaceWithRuleChangesSet.add(
idRule.getParentDeviceGroup());
}
String oldPorts = idRule.getPorts();
idRule.setPorts(iruleResultSet.getString(PORTS_COLUMN_NAME));
if (hasAddressSpaceOptionChanged(oldPorts,
idRule.getPorts(), false)) {
/*
* Ports match rule has changed
*/
addressSpaceWithRuleChangesSet.add(
idRule.getParentDeviceGroup());
}
String oldTags = idRule.getTags();
idRule.setTags(iruleResultSet.getString(TAGS_COLUMN_NAME));
if (hasAddressSpaceOptionChanged(oldTags,
idRule.getTags(), false)) {
/*
* Tags match rule has changed
*/
addressSpaceWithRuleChangesSet.add(
idRule.getParentDeviceGroup());
}
String oldMac = idRule.getMac();
String newMac = iruleResultSet.getString(MAC_COLUMN_NAME);
if (newMac != null) {
logger.warn("Unsupported mac address based match ignored");
} else {
idRule.setMac(newMac);
if (hasAddressSpaceOptionChanged(oldMac, newMac, true)) {
/*
* MAC match of rule has changed
*/
addressSpaceWithRuleChangesSet.add(
idRule.getParentDeviceGroup());
}
}
String oldVlans = idRule.getVlans();
String newVlans = iruleResultSet.getString(VLANS_COLUMN_NAME);
if (newVlans != null &&
!(new Short(newVlans)).equals(
idRule.getParentDeviceGroup().getVlan())) {
logger.warn("Vlan tag value " + newVlans +
" does not match vlan-tag-on-egress value " +
idRule.getParentDeviceGroup().getVlan() +
". Entry ignored");
} else {
idRule.setVlans(newVlans);
if (hasAddressSpaceOptionChanged(oldVlans, newVlans, false)) {
/*
* VLAN match rule has changed
*/
addressSpaceWithRuleChangesSet.add(
idRule.getParentDeviceGroup());
}
}
} catch (Exception e) {
logger.warn("Error loading address space " + addressSpaceName +
" rule " + identifierName +
" from storage, entry ignored. " + e);
return;
}
idRule.setMarked(true);
identifierRuleMap.put(idRule.getName(), idRule);
/*
* XXX NetVirt pre-creates interfaces and sub-interfaces upon configuration
* read. Check if we need to do some thing similar, when we read
* address space configuration as well.
*/
// createNetVirtInterfaces(idRule);
logger.debug("Configured address space identifier Rule " + idRule);
return;
}
private int processAddressSpaceVlanTagOnEgressConfig (
BetterEntityClass addressSpace,
IResultSet addressSpaceResultSet,
int maxPriInModifiedSet) {
Short oldVlan = addressSpace.getVlan();
Short newVlan = null;
if (addressSpaceResultSet.containsColumn(SEPARATOR_COLUMN_NAME)) {
newVlan = addressSpaceResultSet.getShort(SEPARATOR_COLUMN_NAME);
}
addressSpace.setVlan(newVlan);
if (oldVlan == null && newVlan == null) {
return maxPriInModifiedSet;
}
if (oldVlan != null && newVlan != null) {
if (oldVlan.equals(newVlan)) {
return maxPriInModifiedSet;
}
}
if (maxPriInModifiedSet < addressSpace.getPriority()) {
maxPriInModifiedSet = addressSpace.getPriority();
}
if (newVlan != null) {
if (entityClasses.get(newVlan) != null) {
// Another address-space is already using this vlan.
throw new StorageException("Vlan " + newVlan.toString() +
" is already in use by another address-space " +
entityClasses.get(newVlan).getName());
}
}
return maxPriInModifiedSet;
}
@LogMessageDocs({
@LogMessageDoc(level="WARN",
message="Configuration of default address space {default-name}"
+ " is not allowed",
explanation="Address-space \"default\" is configured in the " +
"database but configuration of it is not allowed",
recommendation=LogMessageDoc.GENERIC_ACTION),
@LogMessageDoc(level="WARN",
message="Error loading address space {address-space}" +
" from storage, entry ignored {exception}",
explanation="Could not read the mentioned address-spaces" +
"configuration from storage.",
recommendation=LogMessageDoc.GENERIC_ACTION)
})
private int readAddressSpaceDefinitionConfig (
IResultSet addressSpaceResultSet,
int maxPriInModifiedSet) {
String addressSpaceName =
addressSpaceResultSet.getString(NAME_COLUMN_NAME);
if (addressSpaceName.equals(DEFAULT_ADDRESS_SPACE_NAME)) {
// Do not allow configuration of the default address space
logger.warn("Configuration of default address space {} "
+ "is not allowed",
DEFAULT_ADDRESS_SPACE_NAME);
return maxPriInModifiedSet;
}
// check if address space is active
if (!addressSpaceResultSet.getBoolean(ACTIVE_COLUMN_NAME)) {
if (logger.isDebugEnabled()) {
logger.debug("Skipping inactive address-space {}",
addressSpaceName);
}
return maxPriInModifiedSet;
}
// skip if address space does not have a transport /egress vlan
if (!addressSpaceResultSet.containsColumn(SEPARATOR_COLUMN_NAME)) {
if (logger.isDebugEnabled()) {
logger.debug("Skipping address-space {} with vlan-tag-on-egress",
addressSpaceName);
}
return maxPriInModifiedSet;
}
BetterEntityClass addressSpace = addressSpaceMap.get(addressSpaceName);
if (addressSpace == null) {
/* New BetterEntityClass was created */
addressSpace = new BetterEntityClass(addressSpaceName, null);
if (maxPriInModifiedSet < addressSpace.getPriority()) {
maxPriInModifiedSet = addressSpace.getPriority();
}
}
try {
int oldPriority = addressSpace.getPriority();
addressSpace.setPriority(
addressSpaceResultSet.getInt(PRIORITY_COLUMN_NAME));
if (oldPriority != addressSpace.getPriority()) {
if (maxPriInModifiedSet < oldPriority) {
maxPriInModifiedSet = oldPriority;
}
if (maxPriInModifiedSet < addressSpace.getPriority()) {
maxPriInModifiedSet = addressSpace.getPriority();
}
}
boolean oldState = addressSpace.isActive();
addressSpace.setActive(
addressSpaceResultSet.getBoolean(ACTIVE_COLUMN_NAME));
if (oldState != addressSpace.isActive()) {
/*
* Active status of address-space has changed
*/
if (maxPriInModifiedSet < addressSpace.getPriority()) {
maxPriInModifiedSet = addressSpace.getPriority();
}
}
maxPriInModifiedSet =
processAddressSpaceVlanTagOnEgressConfig(addressSpace,
addressSpaceResultSet,
maxPriInModifiedSet);
addressSpace.setDescription(
addressSpaceResultSet.getString(DESCRIPTION_COLUMN_NAME));
} catch (Exception e) {
logger.warn("Error loading address space " +
addressSpaceName +
" from storage, entry ignored. " + e);
return maxPriInModifiedSet;
}
// Store address space in class wide structures
addressSpace.setMarked(true);
addressSpaceMap.put(addressSpace.getName(), addressSpace);
if (addressSpace.getVlan() == null) {
entityClasses.set(0, addressSpace);
} else {
entityClasses.set(addressSpace.getVlan(), addressSpace);
}
if (logger.isDebugEnabled()) {
logger.debug("address space {} ({}): Reading AS definition complete ",
addressSpace.getName(), addressSpace.getVlan());
}
return maxPriInModifiedSet;
}
private void findAndUpdateModifiedAddressSpaces (
HashSet<BetterEntityClass> addressSpaceDeletesSet,
HashSet<BetterEntityClass> addressSpaceWithRuleChangesSet,
int maxPriInModifiedSet) {
/*
* FIXME: these comments are copied mostly verbatim from NetVirtManagerImpl
* and don't make a lot of sense for address space manager....
*
* Certain clients such as deviceManager need to be informed about
* all the address spaces that are affected. Eventually, all entities
* in all the affected address-spaces must be reclassified as the
* new configuration can render existing entities to be mapped to
* different entityClasses.
*
* DELETED address spaces
* =============
* For the flows in address spaces that were deleted either those flows need to
* be deleted or they need to be moved to some other address space if the devices
* were member of multiple address spaces. If the flow need to be moved to some
* other address space then the flow-mods in the switches need not be touched buy
* the flow-cache should be updated so that these flows are moved to
* other application instance name. Here we need to submit flow query
* for all the address spaces that were deleted.
* <p>
* ADDED address spaces
* ===========
* For the new address spaces that were created we need to check if any flow
* from a lower priority address space need to be migrated to a newly created
* address space. To accomplish this we need to submit flow query for all the
* existing address spaces that are in lower priority than the highest-priority
* addressSpace that was created.
*
* We will use the logic stated above to create a set of address spaces for which
* flow queries need to be submitted for reconciliation.
*/
HashSet<String> addressSpacesChanged = new HashSet<String>();
/*
* Find the address-space with highest priority in id rule changed
*/
int maxPriInChangedSet = 0;
for (BetterEntityClass addressSpace :
addressSpaceWithRuleChangesSet) {
if (addressSpace.getPriority() > maxPriInChangedSet) {
maxPriInChangedSet = addressSpace.getPriority();
}
}
/* Get higher priority of the two */
int maxPriInAddedAndChanged = Math.max(maxPriInModifiedSet,
maxPriInChangedSet);
/*
* Now add all the address spaces that were deleted as we need to
* reconcile flows in those address spaces
*/
for (BetterEntityClass addressSpace : addressSpaceDeletesSet) {
addressSpacesChanged.add(addressSpace.getName());
if (addressSpace.getPriority() > maxPriInAddedAndChanged) {
maxPriInAddedAndChanged = addressSpace.getPriority();
}
}
/*
* Get all address spaces with priority less than or equal to
* maxPriInAddedAndChanged
*/
for (String addSpaceName : addressSpaceMap.keySet() ) {
BetterEntityClass addressSpace = addressSpaceMap.get(addSpaceName);
if (addressSpace.getPriority() <= maxPriInAddedAndChanged) {
addressSpacesChanged.add(addSpaceName);
}
}
/*
* Notify interested clients, address-spaces that need
* re-classification.
*/
notifyAddressSpaceConfigurationChanged(addressSpacesChanged);
return;
}
/*
* IEntityClassifierService
*/
@Override
public void addListener (IEntityClassListener listener) {
entityClassListeners.add(listener);
}
private void notifyAddressSpaceConfigurationChanged (
HashSet<String> entityClassNames) {
/*
* If the list is empty, then don't bother.
*/
if (entityClassNames.isEmpty()) return;
/*
* Now submit flow query to get the flows in each of these address
* spaces.
*/
if (logger.isTraceEnabled()) {
logger.trace("Set of address spaces to query for flow " +
"reconciliation: {}", entityClassNames);
}
for (IEntityClassListener listener : entityClassListeners) {
listener.entityClassChanged(entityClassNames);
}
}
/**
* Read the AddressSpace configuration information from storage, merging
* with the existing configuration.
*/
protected void readAddressSpaceConfigFromStorage() throws StorageException {
IResultSet addressSpaceResultSet = storageSource.executeQuery(
ADDRESS_SPACE_TABLE_NAME,
null, null, null);
IResultSet iruleResultSet = storageSource.executeQuery(
ADDRESS_SPACE_IDENTIFIER_RULE_TABLE_NAME,
new String[] {
NAME_COLUMN_NAME,
ADDRESS_SPACE_COLUMN_NAME,
SEPARATOR_COLUMN_NAME,
DESCRIPTION_COLUMN_NAME,
RULE_COLUMN_NAME,
ACTIVE_COLUMN_NAME,
PRIORITY_COLUMN_NAME,
MAC_COLUMN_NAME,
SWITCH_COLUMN_NAME,
PORTS_COLUMN_NAME,
VLANS_COLUMN_NAME,
TAGS_COLUMN_NAME
},
null, null);
/*
* We will maintain a set of address spaces that were deleted and a set
* new address spaces that were created so that we can reconcile flows
* in a subset of address spaces
*/
HashSet<BetterEntityClass> addressSpaceDeletesSet =
new HashSet<BetterEntityClass>();
HashSet<BetterEntityClass> addressSpaceWithRuleChangesSet =
new HashSet<BetterEntityClass>();
int maxPriInModifiedSet = 0;
configLock.writeLock().lock();
try {
addressSpaceMap.put(DEFAULT_ADDRESS_SPACE_NAME, defaultEntityClass);
// Clear out the vlan to address space array
entityClasses.clear();
for (int i = 0; i < 4096; i++) {
entityClasses.add(null);
}
addressSpaceGlobalActiveState = true;
// Flush rule-matching data structures
deviceGroupMatcher.clear();
// Read in all the BetterEntityClass
for (BetterEntityClass addressSpace : addressSpaceMap.values()) {
if (addressSpace == defaultEntityClass)
continue;
addressSpace.setMarked(false);
}
while (addressSpaceResultSet.next()) {
maxPriInModifiedSet = readAddressSpaceDefinitionConfig(
addressSpaceResultSet,
maxPriInModifiedSet);
}
// clear out state related to address-spaces that no longer exist
Iterator<Entry<String, BetterEntityClass>> addressSpaceMapIter =
addressSpaceMap.entrySet().iterator();
while (addressSpaceMapIter.hasNext()) {
BetterEntityClass addressSpace =
addressSpaceMapIter.next().getValue();
if (addressSpace == defaultEntityClass)
continue; // don't delete default address space
if (addressSpace.isMarked() == false) {
/* This address space was deleted */
addressSpaceDeletesSet.add(addressSpace);
addressSpaceMapIter.remove();
}
}
// Read in all the address-space identifier rules
for (MembershipRule<BetterEntityClass> idRule :
identifierRuleMap.values()) {
idRule.setMarked(false);
}
while (iruleResultSet.next()) {
readAddressSpaceIdentifierRuleConfig(iruleResultSet,
addressSpaceWithRuleChangesSet);
}
/*
* clear out old rules state no longer exist and set up lookup data
* structures
*/
Iterator<Entry<String, MembershipRule<BetterEntityClass>>>
interfaceRuleMapIter = identifierRuleMap.entrySet().iterator();
while (interfaceRuleMapIter.hasNext()) {
MembershipRule<BetterEntityClass> idRule =
interfaceRuleMapIter.next().getValue();
if (idRule.isMarked() == false) {
interfaceRuleMapIter.remove();
addressSpaceWithRuleChangesSet.add(
idRule.getParentDeviceGroup());
continue;
}
deviceGroupMatcher.addRuleIfActive(idRule);
}
} finally {
configLock.writeLock().unlock();
}
findAndUpdateModifiedAddressSpaces(
addressSpaceDeletesSet, addressSpaceWithRuleChangesSet,
maxPriInModifiedSet);
return;
}
/**
* @see org.sdnplatform.devicemanager.IEntityClassifierService#classifyEntity(org.sdnplatform.devicemanager.internal.Entity)
*/
@Override
public IEntityClass classifyEntity(Entity entity) {
Long switchDpid = entity.getSwitchDPID();
Integer port = entity.getSwitchPort();
BetterEntityClass entityClass = null;
short vlan = (entity.getVlan()!=null) ?
entity.getVlan().shortValue() : 0;
configLock.readLock().lock();
try {
if (!addressSpaceGlobalActiveState) {
return null;
}
/*
* If this is an internal port, we simply use the entities VLAN tag
* to identify the address space.
*/
if (switchDpid!= null && port!=null &&
!topology.isAttachmentPointPort(switchDpid,
port.shortValue())) {
entityClass = entityClasses.get(vlan);
if (entityClass == null)
entityClass = defaultEntityClass;
if (logger.isTraceEnabled()) {
logger.trace("Internal port. Entity={} entityClass={}",
entity, entityClass);
}
}
else {
entityClass = doMatchEntity(entity);
if (logger.isTraceEnabled()) {
logger.trace("External port. Entity={} entityClass={}",
entity, entityClass);
}
}
} finally {
configLock.readLock().unlock();
}
return entityClass;
}
/**
* @see org.sdnplatform.devicemanager.IEntityClassifierService#getKeyFields()
*/
@Override
public final EnumSet<DeviceField> getKeyFields() {
/*
* Make sure nobody can accidentally change our keyFiels by returning
* a clone since there's no ImmutableEnumSet :-(
*/
return keyFields.clone();
}
/**
* @see org.sdnplatform.devicemanager.IEntityClassifierService#reclassifyEntity(org.sdnplatform.devicemanager.IDevice, org.sdnplatform.devicemanager.internal.Entity)
*/
@Override
public IEntityClass reclassifyEntity(IDevice curDevice, Entity entity) {
return classifyEntity(entity);
}
@Override
public Short getSwitchPortVlanMode(SwitchPort swp, String addressSpaceName,
Short currentVlan,
boolean tunnelEnabled) {
// TODO:
// This is a temporary work around to support networkservice with addressspace.
// Once duplicate ip is properly support, this should be removed.
if (enableNetworkService) {
return Short.valueOf(Ethernet.VLAN_UNTAGGED);
}
// TODO: we really should cache these lookups. But then we need
// to listen and react to Tag changes. Sigh.
if (addressSpaceName == null)
throw new NullPointerException("address-space cannot be null");
if (swp == null)
throw new NullPointerException("swp cannot be null");
if (currentVlan == null)
throw new NullPointerException("currentVlan cannot be null");
configLock.readLock().lock();
try {
BetterEntityClass sourceAS = addressSpaceMap.get(addressSpaceName);
if (sourceAS == null)
return null;
if (!topology.isAttachmentPointPort(swp.getSwitchDPID(),
(short)swp.getPort(),
tunnelEnabled)) {
// internal link. packet is always allowed. we use the VLAN
// of the source address-space or the current vlan if it's the
// default address-space.
if (sourceAS == defaultEntityClass)
return currentVlan;
else
return sourceAS.getVlan();
}
/* need to special case default address space. Only check with
* the VLAN the packet currently has.
*/
if (sourceAS == defaultEntityClass) {
Entity e;
if (currentVlan.equals(Ethernet.VLAN_UNTAGGED)) {
e = new Entity(0L, null, null,
swp.getSwitchDPID(), swp.getPort(), null);
} else {
e = new Entity(0L, currentVlan, null,
swp.getSwitchDPID(), swp.getPort(), null);
}
BetterEntityClass bec = doMatchEntity(e);
if (sourceAS.equals(bec))
return currentVlan;
else return null;
}
// Check if the VLAN is native
// Query for the switch-port without the vlan.
Entity e = new Entity(0L, null, null,
swp.getSwitchDPID(), swp.getPort(), null);
BetterEntityClass bec = doMatchEntity(e);
if (sourceAS.equals(bec)) {
return Short.valueOf(Ethernet.VLAN_UNTAGGED);
}
// address-space is not native. does it have a vlan?
if (sourceAS.getVlan() == null)
return null;
// address-space is not native. It has a vlan. check if it's allowed
// tagged
e = new Entity(0L, sourceAS.getVlan(), null,
swp.getSwitchDPID(), swp.getPort(), null);
bec = doMatchEntity(e);
if (bec != null && sourceAS.equals(bec)) {
return sourceAS.getVlan();
}
}
finally {
configLock.readLock().unlock();
}
return null;
}
@Override
public IEntityClass getEntityClassByName(String addressSpaceName) {
configLock.readLock().lock();
try {
return addressSpaceMap.get(addressSpaceName);
} finally {
configLock.readLock().unlock();
}
}
/**
* @see org.sdnplatform.devicemanager.IEntityClassifierService#deviceUpdate(org.sdnplatform.devicemanager.IDevice, java.util.Collection)
*/
@Override
public void deviceUpdate(IDevice oldDevice,
Collection<? extends IDevice> newDevices) {
// TODO
throw(new UnsupportedOperationException("Not implemented"));
}
@Override
public Collection<Class<? extends IPlatformService>> getModuleServices() {
Collection<Class<? extends IPlatformService>> l =
new ArrayList<Class<? extends IPlatformService>>();
l.add(IAddressSpaceManagerService.class);
l.add(IEntityClassifierService.class);
return l;
}
@Override
public Map<Class<? extends IPlatformService>, IPlatformService> getServiceImpls() {
Map<Class<? extends IPlatformService>,
IPlatformService> m =
new HashMap<Class<? extends IPlatformService>,
IPlatformService>();
// We are the class that implements the service
m.put(IAddressSpaceManagerService.class, this);
m.put(IEntityClassifierService.class, this);
return m;
}
@Override
public Collection<Class<? extends IPlatformService>> getModuleDependencies() {
Collection<Class<? extends IPlatformService>> l =
new ArrayList<Class<? extends IPlatformService>>();
// TODO!!
l.add(IControllerService.class);
//l.add(IDeviceService.class);
// TODO: Those will come later
l.add(IStorageSourceService.class);
l.add(ITagManagerService.class);
l.add(ITopologyService.class);
//l.add(IRestApiService.class);
//l.add(IFlowReconcileService.class);
//l.add(IThreadPoolService.class);
return l;
}
@Override
public void init(ModuleContext context)
throws ModuleException {
controllerProvider =
context.getServiceImpl(IControllerService.class);
tagManager = context.getServiceImpl(ITagManagerService.class);
topology = context.getServiceImpl(ITopologyService.class);
storageSource = context.getServiceImpl(IStorageSourceService.class);
restApi = context.getServiceImpl(IRestApiService.class);
threadPool = context.getServiceImpl(IThreadPoolService.class);
entityClassListeners = new HashSet<IEntityClassListener>();
addressSpaceMap = new ConcurrentHashMap<String, BetterEntityClass>();
identifierRuleMap = new HashMap<String,
MembershipRule<BetterEntityClass>>();
defaultEntityClass = new BetterEntityClass();
defaultEntityClass.setActive(true);
defaultEntityClass.setPriority(Integer.MIN_VALUE);
configLock = new ReentrantReadWriteLock();
entityClasses = new ArrayList<BetterEntityClass>(4096);
// we reference tagManager here but the constructor won't call any
// of its methods so we are ok.
deviceGroupMatcher = new DeviceGroupMatcher<BetterEntityClass>(tagManager,
controllerProvider);
String nsProp = System.getProperty("org.sdnplatform.addressspace.EnableNetworkService");
if (nsProp != null) {
if (Integer.parseInt(nsProp) != 0) {
enableNetworkService = true;
}
}
}
@Override
public void startUp(ModuleContext context) {
ScheduledExecutorService ses = threadPool.getScheduledExecutor();
configUpdateTask = new SingletonTask(ses, new Runnable() {
@Override
public void run() {
readAddressSpaceConfigFromStorage();
}
});
storageSource.createTable(ADDRESS_SPACE_TABLE_NAME, null);
storageSource.setTablePrimaryKeyName(ADDRESS_SPACE_TABLE_NAME,
NAME_COLUMN_NAME);
storageSource.createTable(ADDRESS_SPACE_IDENTIFIER_RULE_TABLE_NAME,
null);
storageSource.setTablePrimaryKeyName(
ADDRESS_SPACE_IDENTIFIER_RULE_TABLE_NAME, NAME_COLUMN_NAME);
storageSource.addListener(ADDRESS_SPACE_TABLE_NAME, this);
storageSource.addListener(ADDRESS_SPACE_IDENTIFIER_RULE_TABLE_NAME,
this);
readAddressSpaceConfigFromStorage();
controllerProvider.addHAListener(this);
}
@Override
@LogMessageDocs({
@LogMessageDoc(level="ERROR",
message="New role {role} is currently not supported",
explanation="The address-space logic received a notification "
+ "that the controller changed to a new role that"
+ "is currently not supported",
recommendation=LogMessageDoc.REPORT_CONTROLLER_BUG)
})
public void roleChanged(Role oldRole, Role newRole) {
switch(newRole) {
case MASTER:
if (oldRole == Role.SLAVE) {
readAddressSpaceConfigFromStorage();
}
break;
case SLAVE:
logger.debug("Clearing config state due to HA " +
"role change to SLAVE");
clearConfigState();
break;
default:
logger.error("New role {} is currently not supported", newRole);
break;
}
}
@Override
public void
controllerNodeIPsChanged(
Map<String, String> curControllerNodeIPs,
Map<String, String> addedControllerNodeIPs,
Map<String, String> removedControllerNodeIPs) {
// ignore
}
}