Skip to content
Snippets Groups Projects
Device.java 14.80 KiB
/**
 *    Copyright 2011,2012 Big Switch Networks, Inc.
 *    Originally created by David Erickson, Stanford University
 *
 *    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 net.floodlightcontroller.devicemanager.internal;

import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.Date;
import java.util.EnumSet;
import java.util.Iterator;
import java.util.TreeSet;

import org.codehaus.jackson.map.annotate.JsonSerialize;
import org.openflow.util.HexString;
import net.floodlightcontroller.devicemanager.IDeviceService.DeviceField;
import net.floodlightcontroller.devicemanager.web.DeviceSerializer;
import net.floodlightcontroller.devicemanager.IDevice;
import net.floodlightcontroller.devicemanager.IEntityClass;
import net.floodlightcontroller.devicemanager.SwitchPort;
import static net.floodlightcontroller.devicemanager.SwitchPort.ErrorStatus.*;
import net.floodlightcontroller.topology.ITopologyService;

/**
 * Concrete implementation of {@link IDevice}
 * @author readams
 */
@JsonSerialize(using=DeviceSerializer.class)
public class Device implements IDevice {
    protected Long deviceKey;
    protected DeviceManagerImpl deviceManager;

    protected Entity[] entities;
    protected IEntityClass entityClass;

    protected String macAddressString;

    // ************
    // Constructors
    // ************

    /**
     * Create a device from an entities
     * @param deviceManager the device manager for this device
     * @param deviceKey the unique identifier for this device object
     * @param entity the initial entity for the device
     * @param entityClass the entity classes associated with the entity
     */
    public Device(DeviceManagerImpl deviceManager,
                  Long deviceKey,
                  Entity entity,
                  IEntityClass entityClass) {
        this.deviceManager = deviceManager;
        this.deviceKey = deviceKey;
        this.entities = new Entity[] {entity};
        this.macAddressString =
                HexString.toHexString(entity.getMacAddress(), 6);
        this.entityClass = entityClass;
        Arrays.sort(this.entities);
    }

    /**
     * Create a device from a set of entities
     * @param deviceManager the device manager for this device
     * @param deviceKey the unique identifier for this device object
     * @param entities the initial entities for the device
     * @param entityClass the entity class associated with the entities
     */
    public Device(DeviceManagerImpl deviceManager,
                  Long deviceKey,
                  Collection<Entity> entities,
                  IEntityClass entityClass) {
        this.deviceManager = deviceManager;
        this.deviceKey = deviceKey;
        this.entities = entities.toArray(new Entity[entities.size()]);
        this.macAddressString =
                HexString.toHexString(this.entities[0].getMacAddress(), 6);
        this.entityClass = entityClass;
        Arrays.sort(this.entities);
    }

    /**
     * Construct a new device consisting of the entities from the old device
     * plus an additional entity
     * @param device the old device object
     * @param newEntity the entity to add. newEntity must be have the same
     *        entity class as device
     */
    public Device(Device device,
                  Entity newEntity) {
        this.deviceManager = device.deviceManager;
        this.deviceKey = device.deviceKey;
        this.entities = Arrays.<Entity>copyOf(device.entities,
                                              device.entities.length + 1);
        this.entities[this.entities.length - 1] = newEntity;
        Arrays.sort(this.entities);

        this.macAddressString =
                HexString.toHexString(this.entities[0].getMacAddress(), 6);

        this.entityClass = device.entityClass;
    }

    // *******
    // IDevice
    // *******

    @Override
    public Long getDeviceKey() {
        return deviceKey;
    }

    @Override
    public long getMACAddress() {
        // we assume only one MAC per device for now.
        return entities[0].getMacAddress();
    }

    @Override
    public String getMACAddressString() {
        return macAddressString;
    }

    @Override
    public Short[] getVlanId() {
        if (entities.length == 1) {
            if (entities[0].getVlan() != null) {
                return new Short[]{ entities[0].getVlan() };
            } else {
                return new Short[] { Short.valueOf((short)-1) };
            }
        }

        TreeSet<Short> vals = new TreeSet<Short>();
        for (Entity e : entities) {
            if (e.getVlan() == null)
                vals.add((short)-1);
            else
                vals.add(e.getVlan());
        }
        return vals.toArray(new Short[vals.size()]);
    }

    static final EnumSet<DeviceField> ipv4Fields = EnumSet.of(DeviceField.IPV4);

    @Override
    public Integer[] getIPv4Addresses() {
        // XXX - TODO we can cache this result.  Let's find out if this
        // is really a performance bottleneck first though.

        TreeSet<Integer> vals = new TreeSet<Integer>();
        for (Entity e : entities) {
            if (e.getIpv4Address() == null) continue;
            
            // We have an IP address only if among the devices within the class
            // we have the most recent entity with that IP.
            boolean validIP = true;
            Iterator<Device> devices =
                    deviceManager.queryClassByEntity(entityClass, ipv4Fields, e);
            while (devices.hasNext()) {
                Device d = devices.next();
                if (deviceKey.equals(d.getDeviceKey())) 
                    continue;
                for (Entity se : d.entities) {
                    if (se.getIpv4Address() != null &&
                            se.getIpv4Address().equals(e.getIpv4Address()) &&
                            se.getLastSeenTimestamp() != null &&
                            0 < se.getLastSeenTimestamp().
                            compareTo(e.getLastSeenTimestamp())) {
                        validIP = false;
                        break;
                    }
                }
                if (!validIP)
                    break;
            }
            
            if (validIP)
                vals.add(e.getIpv4Address());
        }

        return vals.toArray(new Integer[vals.size()]);
    }

    @Override
    public SwitchPort[] getAttachmentPoints() {
        return getAttachmentPoints(false);
    }

    @Override
    public SwitchPort[] getAttachmentPoints(boolean includeError) {
        // XXX - TODO we can cache this result.  Let's find out if this
        // is really a performance bottleneck first though.

        if (entities.length == 1) {
            Long dpid = entities[0].getSwitchDPID();
            Integer port = entities[0].getSwitchPort();
            if (dpid != null && port != null &&
                    deviceManager.isValidAttachmentPoint(dpid, port)) {
                SwitchPort sp = new SwitchPort(dpid, port);
                return new SwitchPort[] { sp };
            } else {
                return new SwitchPort[0];
            }
        }

        // Find the most recent attachment point for each cluster
        Entity[] clentities = Arrays.<Entity>copyOf(entities, entities.length);
        Arrays.sort(clentities, deviceManager.apComparator);
        ArrayList<SwitchPort> blocked = null;
        ArrayList<SwitchPort> clusterBlocked = null;
        if (includeError) {
            blocked = new ArrayList<SwitchPort>();
            clusterBlocked = new ArrayList<SwitchPort>();
        }

        ITopologyService topology = deviceManager.topology;
        long prevCluster = 0;
        int clEntIndex = -1;
        Entity prev = null;
        long latestLastSeen = 0;
        for (int i = 0; i < clentities.length; i++) {
            Entity cur = clentities[i];
            Long dpid = cur.getSwitchDPID();
            Integer port = cur.getSwitchPort();
            if (dpid == null || port == null ||
                    !deviceManager.isValidAttachmentPoint(dpid, port) ||
                    (prev != null &&
                    topology.isConsistent(prev.getSwitchDPID().longValue(),
                                          prev.getSwitchPort().shortValue(),
                                          dpid.longValue(),
                                          port.shortValue()))
                    )
                continue;
            long curCluster =
                    topology.getL2DomainId(cur.getSwitchDPID());
            if (prevCluster != curCluster) {
                prev = null;
                latestLastSeen = 0;
                clEntIndex += 1;
                if (includeError) {
                    blocked.addAll(clusterBlocked);
                    clusterBlocked.clear();
                }
            }

            if (prev != null &&
                    !(dpid.equals(prev.getSwitchDPID()) &&
                            port.equals(prev.getSwitchPort())) &&
                            !topology.isInSameBroadcastDomain(dpid.longValue(),
                                                              port.shortValue(),
                                                              prev.getSwitchDPID().longValue(),
                                                              prev.getSwitchPort().shortValue()) &&
                                                              !topology.isConsistent(prev.getSwitchDPID().longValue(),
                                                                                     prev.getSwitchPort().shortValue(),
                                                                                     dpid.longValue(), port.shortValue())) {
                long curActive =
                        deviceManager.apComparator.
                        getEffTS(cur, cur.getActiveSince());
                if (latestLastSeen > 0 &&
                        curActive > 0 &&
                        0 < Long.valueOf(latestLastSeen).compareTo(curActive)) {
                    // If the previous and current are both active at the same
                    // time (i.e. the last seen timestamp of previous is
                    // greater than active timestamp of current item, we want
                    // to suppress rapid flapping between the two points. We
                    // choose arbitrarily based on criteria other than
                    // timestamp; the compareTo for entity should fit the bill.
                    Entity block = prev;
                    if (0 < prev.compareTo(cur)) {
                        block = cur;
                        cur = prev;
                    }
                    if (includeError) {
                        boolean alreadyBlocked = false;
                        for (SwitchPort bl : clusterBlocked) {
                            if (dpid.equals(bl.getSwitchDPID()) &&
                                    port.equals(bl.getPort())) {
                                alreadyBlocked = true;
                                break;
                            }
                        }
                        if (!alreadyBlocked) {
                            SwitchPort blap =
                                    new SwitchPort(block.getSwitchDPID(),
                                                   block.getSwitchPort(),
                                                   DUPLICATE_DEVICE);
                            clusterBlocked.add(blap);
                        }
                    }
                } else {
                    if (includeError) {
                        clusterBlocked.clear();
                    }
                    latestLastSeen = 0;
                }
            }

            prev = clentities[clEntIndex] = cur;
            prevCluster = topology.getL2DomainId(prev.getSwitchDPID());

            long prevLastSeen =
                    deviceManager.apComparator.
                    getEffTS(prev,
                             prev.getLastSeenTimestamp());
            if (latestLastSeen < prevLastSeen)
                latestLastSeen = prevLastSeen;
        }

        if (clEntIndex < 0) {
            return new SwitchPort[0];
        }

        ArrayList<SwitchPort> vals = new ArrayList<SwitchPort>(clEntIndex + 1);
        for (int i = 0; i <= clEntIndex; i++) {
            Entity e = clentities[i];
            if (e.getSwitchDPID() != null &&
                    e.getSwitchPort() != null) {
                SwitchPort sp = new SwitchPort(e.getSwitchDPID(),
                                               e.getSwitchPort());
                vals.add(sp);
            }
        }
        if (includeError) {
            vals.addAll(blocked);
            vals.addAll(clusterBlocked);
        }

        return vals.toArray(new SwitchPort[vals.size()]);
    }

    @Override
    public Date getLastSeen() {
        Date d = null;
        for (int i = 0; i < entities.length; i++) {
            if (d == null ||
                    entities[i].getLastSeenTimestamp().compareTo(d) > 0)
                d = entities[i].getLastSeenTimestamp();
        }
        return d;
    }

    // ***************
    // Getters/Setters
    // ***************

    @Override
    public IEntityClass getEntityClass() {
        return entityClass;
    }

    public Entity[] getEntities() {
        return entities;
    }

    // ***************
    // Utility Methods
    // ***************

    /**
     * Check whether the device contains the specified entity
     * @param entity the entity to search for
     * @return the index of the entity, or <0 if not found
     */
    protected int entityIndex(Entity entity) {
        return Arrays.binarySearch(entities, entity);
    }

    // ******
    // Object
    // ******

    @Override
    public int hashCode() {
        final int prime = 31;
        int result = 1;
        result = prime * result + Arrays.hashCode(entities);
        return result;
    }

    @Override
    public boolean equals(Object obj) {
        if (this == obj) return true;
        if (obj == null) return false;
        if (getClass() != obj.getClass()) return false;
        Device other = (Device) obj;
        if (!deviceKey.equals(other.deviceKey)) return false;
        if (!Arrays.equals(entities, other.entities)) return false;
        return true;
    }

    @Override
    public String toString() {
        return "Device [entityClass=" + entityClass.getName() +
                " entities=" + Arrays.toString(entities) + "]";
    }
}