/*
* Copyright (c) 2005-2010 KOM – Multimedia Communications Lab
*
* This file is part of PeerfactSim.KOM.
*
* PeerfactSim.KOM is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* any later version.
*
* PeerfactSim.KOM is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with PeerfactSim.KOM. If not, see .
*
*/
package de.tud.kom.p2psim.impl.topology.views;
import java.util.Arrays;
import java.util.LinkedHashSet;
import java.util.LinkedList;
import java.util.List;
import java.util.Set;
import de.tud.kom.p2psim.api.common.HostProperties;
import de.tud.kom.p2psim.api.linklayer.mac.Link;
import de.tud.kom.p2psim.api.linklayer.mac.MacAddress;
import de.tud.kom.p2psim.api.linklayer.mac.MacLayer;
import de.tud.kom.p2psim.api.linklayer.mac.PhyType;
import de.tud.kom.p2psim.api.topology.obstacles.ObstacleModel;
import de.tud.kom.p2psim.api.topology.waypoints.WaypointModel;
import de.tud.kom.p2psim.impl.topology.PositionVector;
import de.tud.kom.p2psim.impl.topology.views.FiveGTopologyView.CellLink;
import de.tud.kom.p2psim.impl.topology.views.fiveg.FiveGTopologyDatabase;
import de.tud.kom.p2psim.impl.topology.views.fiveg.FiveGTopologyDatabase.Entry;
import de.tudarmstadt.maki.simonstrator.api.Event;
import de.tudarmstadt.maki.simonstrator.api.EventHandler;
import de.tudarmstadt.maki.simonstrator.api.Host;
import de.tudarmstadt.maki.simonstrator.api.Time;
import de.tudarmstadt.maki.simonstrator.api.component.sensor.handover.HandoverSensor;
import de.tudarmstadt.maki.simonstrator.api.component.sensor.location.Location;
import de.tudarmstadt.maki.simonstrator.api.util.XMLConfigurableConstructor;
/**
* This topology view offers a topology for mobile apps - mobile clients
* maintain one to one connections to one or multiple cloud servers or cloudlet
* instances. This view abstracts from real antennas in that the actual range of
* an antenna etc. is just modeled as a property of the link between mobile
* client and the endpoint (cloud/cloudlet) based on a client's position. This
* way, measurements such as the ones by Kaup et al. can be easily incorporated.
*
* TODO add a property to enable usage of access points (lower latency, higher
* BW at certain client positions around the configured positions of APs)
*
. NO, just make this
* configurable via the topology view properties (e.g., a percentage of nodes
* can use such APs) and THEN set the corresponding host property --> if overlay
* code wants to access this information, we might need to add
* {@link HostProperties} to the Simonstrator API.
*
* TODO add distinction for cloudlets and cloud (assuming that cloudlets exhibit
* lower latency), ideally with yet another (TM) host property on the base
* station side:
. NO, again just
* use the groupID of a host as a config setting for this view: e.g., all
* specified groupIDs are Backend hosts vs. direct cloudlets.
*
* FIXME OR: just base this configuration options on the group-ID in the host
* builder?
*
* @author Bjoern Richerzhagen
* @version 1.0, Nov 4, 2015
*/
public class FiveGTopologyView extends AbstractTopologyView {
private Set cloudlets = new LinkedHashSet<>();
private Set clouds = new LinkedHashSet<>();
private Set mobileClients = new LinkedHashSet<>();
/**
* A subset of mobileClients -> contains all clients that may use access
* points.
*/
private Set mobileClientsUsingAccessPoints = new LinkedHashSet<>();
private List mobileClientsList = new LinkedList<>();
private List cloudsAndCloudletsList = new LinkedList<>();
private FiveGTopologyDatabase database = null;
private FiveGTopologyDatabase databaseAccessPoints = null;
/**
* Configuration setting: all group IDs of nodes that act as cloudlets
* (e.g., act as basestations with a lower delay, whatever that means w.r.t.
* the specified model)
*/
private Set groupIDsCloudlet = new LinkedHashSet<>();
/**
* Configuration setting: all group IDs that act as cloud (e.g., backend
* servers). Here, the full latency of a connection from a mobile client to
* a network sever somewhere in the Internet is assumed (e.g., as measured
* by Android end user devices)
*/
private Set groupIDsCloud = new LinkedHashSet<>();
/**
* Configuration setting: all group IDs that act as clients but have access
* to access points.
*/
private Set groupIDsAccessPointUsage = new LinkedHashSet<>();
private List handoverSensors = new LinkedList<>();
private List links = new LinkedList<>();
/**
*
* @param phy
*/
public FiveGTopologyView(PhyType phy) {
super(phy, true);
setHasRealLinkLayer(false);
/*
* AP state is only set, after components moved. In scenarios without
* active movement, this will never happen. Therefore, we trigger the
* respective event ONCE at the start of the simulation
*/
Event.scheduleImmediately(new EventHandler() {
@Override
public void eventOccurred(Object content, int type) {
checkAPAssociations();
}
}, null, 0);
}
@XMLConfigurableConstructor({ "phy" })
public FiveGTopologyView(String phy) {
// UMTS is reset with custom type next.
this(PhyType.UMTS);
setPhy(phy);
}
@Override
public Link getBestNextLink(MacAddress source, MacAddress lastHop,
MacAddress currentHop, MacAddress destination) {
return getLinkBetween(source, destination);
}
@Override
public void changedWaypointModel(WaypointModel model) {
// I don't care, I love it.
}
@Override
public void changedObstacleModel(ObstacleModel model) {
// I don't care, I love it.
}
@Override
protected void addedMac(MacLayer mac) {
String groupId = mac.getHost().getProperties().getGroupID();
if (groupIDsCloud.contains(groupId)) {
clouds.add(mac.getMacAddress());
cloudsAndCloudletsList.add(mac.getMacAddress());
} else if (groupIDsCloudlet.contains(groupId)) {
cloudlets.add(mac.getMacAddress());
cloudsAndCloudletsList.add(mac.getMacAddress());
} else {
mobileClients.add(mac.getMacAddress());
mobileClientsList.add(mac.getMacAddress());
if (groupIDsAccessPointUsage.contains(groupId)) {
assert databaseAccessPoints != null : "An AP Database is needed if AP-Functionality is desired.";
mobileClientsUsingAccessPoints.add(mac.getMacAddress());
HandoverSensor5G hs = new HandoverSensor5G(mac.getHost(),
mac.getMacAddress());
mac.getHost().registerComponent(hs);
handoverSensors.add(hs);
}
}
}
@Override
protected void updateOutdatedLink(CellLink link) {
if (link.isInvalidLink()) {
// cloud <-> cloud or client <-> client link
return;
}
PositionVector pos = getCachedPosition(link.getMobileClient());
int segId = database.getSegmentID(pos.getX(), pos.getY());
int apSegId = -1;
if (link.supportsAccessPoints()) {
/*
* Check, if an AP-segment is available
*/
apSegId = databaseAccessPoints.getSegmentID(pos.getX(), pos.getY());
if (link.getSegmentId() != segId
|| link.getApSegmentId() != apSegId) {
// Update
link.setLinkData(database.getEntryFor(segId, link.isCloudlet()),
databaseAccessPoints.getEntryFor(apSegId,
link.isCloudlet()));
}
} else if (link.getSegmentId() != segId) {
link.setLinkData(database.getEntryFor(segId, link.isCloudlet()),
null);
}
}
/**
* Check, if a node moved into a new segment. If so, we need to update the
* HandoverSensor to trigger the listeners. We only need to check nodes that
* are included in the list of ap-enabled nodes (i.e., they already have a
* HandoverSensor-instance).
*/
protected void checkAPAssociations() {
for (HandoverSensor5G sensor : handoverSensors) {
PositionVector pos = getCachedPosition(sensor.macAddr);
int segId = databaseAccessPoints.getSegmentID(pos.getX(),
pos.getY());
if (segId != sensor.apSegmentId) {
sensor.apSegmentId = segId;
// Connected to AP, if segment is provided in the DB
sensor.setConnectedToAccessPoint(
databaseAccessPoints.getEntryFor(segId, false) != null);
}
}
/*
* FIXME the not-so-elegant approach of updating all max-BWs
*/
for (MacAddress mobileClient : mobileClientsList) {
updateMaxMacBandwidth(mobileClient);
}
}
long lastMovementTimestamp = 0;
@Override
public void onLocationChanged(Host host, Location location) {
super.onLocationChanged(host, location);
if (lastMovementTimestamp != Time.getCurrentTime()) {
lastMovementTimestamp = Time.getCurrentTime();
// Force re-assignment to cells.
for (CellLink cellLink : links) {
cellLink.setOutdated(true);
}
checkAPAssociations();
}
}
/**
* FIXME this is currently needed to keep the max upload bandwidth reported
* by MOBILE-mac (UMTS) consistent with the bandwidth of the given cell. It
* always reports the BW that can be achieved when sending via MOBILE to the
* cloud (NOT to cloudlets).
*
* @param macAddr
*/
private void updateMaxMacBandwidth(MacAddress macAddr) {
// FIXME workaround for MAC bandwidth updates.
// Sets the initial max upload BW to the cloud (not to cloudlets!)
MacLayer mac = getMac(macAddr);
PositionVector pos = mac.getHost().getTopologyComponent()
.getRealPosition();
int segId = database.getSegmentID(pos.getX(), pos.getY());
Entry usedEntry = database.getEntryFor(segId, false);
if (mobileClientsUsingAccessPoints.contains(macAddr)) {
int apSegId = databaseAccessPoints.getSegmentID(pos.getX(),
pos.getY());
Entry apEntry = databaseAccessPoints.getEntryFor(apSegId, false);
usedEntry = (apEntry != null ? apEntry : usedEntry);
}
mac.getMaxBandwidth().setUpBW(usedEntry.getBandwidth(true));
mac.getMaxBandwidth().setDownBW(usedEntry.getBandwidth(false));
}
@Override
protected CellLink createLink(MacAddress source, MacAddress destination) {
boolean sourceIsClient = mobileClients.contains(source);
boolean destinationIsClient = mobileClients.contains(destination);
if (sourceIsClient == destinationIsClient) {
// both client or both not client -> not connected
return new CellLink(source, destination);
}
MacAddress mobileClient = sourceIsClient ? source : destination;
CellLink link = new CellLink(source, destination, true,
getPhyType().getDefaultMTU(), mobileClient,
cloudlets.contains(sourceIsClient ? destination : source),
mobileClientsUsingAccessPoints.contains(mobileClient));
links.add(link);
updateOutdatedLink(link);
return link;
}
@Override
protected List updateNeighborhood(MacAddress source) {
if (cloudlets.contains(source) || clouds.contains(source)) {
return mobileClientsList;
} else {
assert mobileClients.contains(source);
// Is a client, has all clouds and cloudlets as neighbors
return cloudsAndCloudletsList;
}
}
/**
* Access to the {@link FiveGTopologyDatabase} that takes care of the
* UMTS-links in the 5G-TopoView.
*
* @return
*/
public FiveGTopologyDatabase getUMTSTopologyDatabase() {
return database;
}
/**
* Access to the {@link FiveGTopologyDatabase} used to determine Wi-Fi AP
* connectivity in the 5G TopoView.
*
* @return
*/
public FiveGTopologyDatabase getAccessPointTopologyDatabase() {
return databaseAccessPoints;
}
/*
* Configuration options
*/
/**
* GroupIDs (as specified in the host builder section) that should act as
* clouds (e.g., full measured latency and bandwidth is applied)
*
* @param cloudGroupIDs
*/
@SuppressWarnings("unchecked")
public void setCloudGroups(String[] cloudGroupIDs) {
this.groupIDsCloud = new LinkedHashSet<>(Arrays.asList(cloudGroupIDs));
}
/**
* GroupIDs that act as cloudlets (e.g., lower latency, higher BW) - models
* regional computations facilities.
*
* @param cloudletGroups
*/
@SuppressWarnings("unchecked")
public void setCloudletGroups(String[] cloudletGroups) {
this.groupIDsCloudlet = new LinkedHashSet<>(
Arrays.asList(cloudletGroups));
}
/**
* GroupIDs that act as clients and have access to access points.
*
* @param accessPointGroups
*/
@SuppressWarnings("unchecked")
public void setAccessPointGroups(String[] accessPointGroups) {
this.groupIDsAccessPointUsage = new LinkedHashSet<>(
Arrays.asList(accessPointGroups));
}
/**
* Set the measurement database providing link quality data for clients
* based on their position.
*
* @param database
*/
public void setDatabase(FiveGTopologyDatabase database) {
assert this.database == null;
this.database = database;
}
/**
* Another layer of a database, this time for access points.
*
* @param accessPoints
*/
public void setAccessPoints(FiveGTopologyDatabase accessPoints) {
assert this.databaseAccessPoints == null;
this.databaseAccessPoints = accessPoints;
}
/**
* A custom link object supporting dynamic updates of the properties without
* relying on a movement model notification - the client's position is used
* to determine the link performance.
*
* @author Bjoern Richerzhagen
* @version 1.0, Nov 4, 2015
*/
public class CellLink extends DefaultLink {
/**
* Address of the mobile client
*/
private final MacAddress mobileClient;
private FiveGTopologyDatabase.Entry linkData;
private FiveGTopologyDatabase.Entry apLinkData;
private final boolean isUpload;
private final boolean supportsAccessPoints;
private final boolean isCloudlet;
private final boolean isInvalidLink;
/**
* An unconnected link
*
* @param source
* @param destination
*/
public CellLink(MacAddress source, MacAddress destination) {
super(source, destination, false, -1, -1, -1, -1);
this.mobileClient = null;
this.isUpload = false;
this.isCloudlet = false;
this.isInvalidLink = true;
this.supportsAccessPoints = false;
}
public CellLink(MacAddress source, MacAddress destination,
boolean isConnected, int mtu, MacAddress mobileClient,
boolean isCloudlet, boolean supportsAccessPoints) {
super(source, destination, isConnected, -1, -1, -1, mtu);
this.mobileClient = mobileClient;
this.isCloudlet = isCloudlet;
if (mobileClient.equals(source)) {
isUpload = true;
} else {
assert mobileClient.equals(destination);
isUpload = false;
}
this.isInvalidLink = false;
this.supportsAccessPoints = supportsAccessPoints;
}
public boolean isInvalidLink() {
return isInvalidLink;
}
public boolean supportsAccessPoints() {
return supportsAccessPoints;
}
public int getSegmentId() {
return linkData == null ? -1 : linkData.getSegmentID();
}
public int getApSegmentId() {
assert supportsAccessPoints;
return apLinkData == null ? -1 : apLinkData.getSegmentID();
}
public void setLinkData(FiveGTopologyDatabase.Entry linkData,
FiveGTopologyDatabase.Entry apLinkData) {
// Initialization
if (this.linkData == null && this.apLinkData == null) {
this.linkData = linkData;
this.apLinkData = apLinkData;
if (apLinkData != null) {
apLinkData.onHostEntersSegment(mobileClient);
} else {
linkData.onHostEntersSegment(mobileClient);
}
// For the first update on link creation.
return;
}
// Update
if (apLinkData != null) {
assert supportsAccessPoints;
if (this.apLinkData != null) {
// was in AP, moves into AP
this.apLinkData.onHostLeavesSegment(mobileClient);
apLinkData.onHostEntersSegment(mobileClient);
} else {
// was NOT in AP, moves into AP
this.linkData.onHostLeavesSegment(mobileClient);
apLinkData.onHostEntersSegment(mobileClient);
}
} else {
if (this.apLinkData != null) {
// was in AP, moves OUT OF AP
this.apLinkData.onHostLeavesSegment(mobileClient);
linkData.onHostEntersSegment(mobileClient);
} else {
// was NOT in AP, moves into NON-AP segment
this.linkData.onHostLeavesSegment(mobileClient);
linkData.onHostEntersSegment(mobileClient);
}
}
this.linkData = linkData;
assert (apLinkData != null && supportsAccessPoints)
|| apLinkData == null;
this.apLinkData = apLinkData;
}
public MacAddress getMobileClient() {
return mobileClient;
}
public boolean isCloudlet() {
return isCloudlet;
}
public boolean isUpload() {
return isUpload;
}
@Override
public long getBandwidth(boolean isBroadcast) {
assert (apLinkData != null && supportsAccessPoints)
|| apLinkData == null;
long tmp = apLinkData != null ? apLinkData.getBandwidth(isUpload)
: linkData.getBandwidth(isUpload);
return tmp;
}
@Override
public double getDropProbability() {
assert (apLinkData != null && supportsAccessPoints)
|| apLinkData == null;
return apLinkData != null ? apLinkData.getDropProbability(isUpload)
: linkData.getDropProbability(isUpload);
}
@Override
public long getLatency() {
assert (apLinkData != null && supportsAccessPoints)
|| apLinkData == null;
return apLinkData != null ? apLinkData.getLatency(isUpload)
: linkData.getLatency(isUpload);
}
@Override
public boolean isConnected() {
if (apLinkData != null) {
return apLinkData.isAvailable();
}
if (linkData != null) {
return linkData.isAvailable();
}
return false;
}
}
/**
* Implementation of a {@link HandoverSensor} within the 5G topo-view.
*
* @author Bjoern Richerzhagen
* @version 1.0, Nov 20, 2015
*/
public class HandoverSensor5G implements HandoverSensor {
private List listeners = new LinkedList<>();
private boolean isConnectedToAccessPoint;
public int apSegmentId = -1;
public final MacAddress macAddr;
private Host host;
public HandoverSensor5G(Host host, MacAddress macAddr) {
this.host = host;
this.macAddr = macAddr;
}
@Override
public boolean isConnectedToAccessPoint() {
return isConnectedToAccessPoint;
}
public void setConnectedToAccessPoint(
boolean isConnectedToAccessPoint) {
if (this.isConnectedToAccessPoint != isConnectedToAccessPoint) {
this.isConnectedToAccessPoint = isConnectedToAccessPoint;
for (HandoverListener listener : listeners) {
listener.onHandover(isConnectedToAccessPoint);
}
}
}
@Override
public void addHandoverListener(HandoverListener listener) {
listeners.add(listener);
}
@Override
public boolean removeHandoverListener(
HandoverListener listenerToRemove) {
return listeners.remove(listenerToRemove);
}
@Override
public void initialize() {
// not needed
}
@Override
public void shutdown() {
// not needed
}
@Override
public Host getHost() {
return host;
}
}
}