/* * Copyright 2014 Alexey Plotnik * * 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.stem.domain.topology; import com.google.common.collect.Lists; import org.apache.commons.collections4.CollectionUtils; import org.apache.commons.collections4.Predicate; import org.stem.coordination.ZNodeAbstract; import org.stem.domain.Cluster; import java.net.InetSocketAddress; import java.util.*; // TODO: add events when topology changes (node added, node failed, node remover, the same for disks, rack, datacenters, etc) public class Topology extends ZNodeAbstract { //public static final Factory factory = new Factory(); private Cluster cluster; public static enum NodeState { UNAUTHORIZED, RUNNING, UNAVAILABLE } public static enum DiskState { SUSPEND, RUNNING, UNAVAILABLE } private final Index cache; private final EventSubscriber subscriber; private final Map<UUID, Datacenter> dataCenters = new HashMap<>(); public static class Factory { public static Topology create(Cluster cluster) { return new Topology(cluster); } public static Topology create() { return new Topology(); } } private Topology(Cluster owner) { cache = new Index(); subscriber = new EventSubscriber(this); subscriber.addListener(getCacheUpdater()); setOwner(owner); } public void setOwner(Cluster owner) { this.cluster = owner; subscriber.addListener(this.cluster.topologyAutoSaver()); // Add listener after datacenter has been added } private Topology() { cache = new Index(); subscriber = new EventSubscriber(this); subscriber.addListener(getCacheUpdater()); } private TopologyEventListener getCacheUpdater() { return cache; } public void addListener(TopologyEventListener listener) { subscriber.addListener(listener); } public void removeListener(TopologyEventListener listener) { subscriber.addListener(listener); } public void attachCluster(Cluster cluster) { this.cluster = cluster; } @Override public String name() { return "topology"; // TODO: extract to constant } public List<Datacenter> dataCenters() { return Lists.newArrayList(dataCenters.values()); } public void addDatacenter(Datacenter dc) { dataCenters.put(dc.id, dc); dc.attachSubscriber(this.subscriber); subscriber.onDatacenterAdded(dc); for (Rack rack : dc.racks()) { subscriber.onRackAdded(rack); for (StorageNode node : rack.storageNodes()) { subscriber.onStorageNodeAdded(node); for (Disk disk : node.disks()) { subscriber.onDiskAdded(disk); } } } } public void removeDatacenter(Datacenter dc) { dataCenters.remove(dc.id); subscriber.onDatacenterRemoved(dc); } /** * Base class for all objects in topology: Datacenter, Rack, StorageNode or Disk */ public static abstract class Node { public UUID id = UUID.randomUUID(); public String description = ""; protected EventSubscriber subscriber = new EventSubscriber(); public UUID getId() { return id; } public void setId(UUID id) { this.id = id; } public String getDescription() { return description; } public void setDescription(String description) { this.description = description; } protected void attachSubscriber(EventSubscriber subscriber) { this.subscriber = subscriber; } // TODO: make sure we adding just single node, not tree or sub-tree } /** * */ public static class Datacenter extends Node { public final String name; private final Map<UUID, Rack> racks = new HashMap<>(); public Datacenter(String name) { super(); this.name = name; } public String getName() { return name; } public List<Rack> racks() { return Lists.newArrayList(racks.values()); } public void addRack(Rack rack) { rack.attachSubscriber(this.subscriber); racks.put(rack.id, rack); rack.datacenter = this; subscriber.onRackAdded(rack); } public void removeRack(Rack rack) { racks.remove(rack.id); rack.datacenter = null; subscriber.onRackRemoved(rack); } @Override protected void attachSubscriber(EventSubscriber subscriber) { super.attachSubscriber(subscriber); for (Rack rack : racks.values()) { rack.attachSubscriber(subscriber); } } } /** * */ public static class Rack extends Node { private Datacenter datacenter; public final String name; private final Map<UUID, StorageNode> storageNodes = new HashMap<>(); public String getName() { return name; } public Rack(String name) { super(); this.name = name; } public List<StorageNode> storageNodes() { return Lists.newArrayList(storageNodes.values()); } public void addStorageNode(StorageNode node) { if (null == storageNodes.get(node.getId())) { // TODO: What if node exists but with new disks? node.attachSubscriber(this.subscriber); storageNodes.put(node.id, node); node.rack = this; subscriber.onStorageNodeAdded(node); for (Disk disk : node.disks()) { subscriber.onDiskAdded(disk); } } } public void removeStorageNode(StorageNode node) { storageNodes.remove(node.id); node.rack = null; subscriber.onStorageNodeRemoved(node); } @Override protected void attachSubscriber(EventSubscriber subscriber) { super.attachSubscriber(subscriber); for (StorageNode node : storageNodes.values()) { node.attachSubscriber(subscriber); } } } /** * */ public static class StorageNode extends Node { private Rack rack; public final InetSocketAddress address; String hostname; long capacity; private NodeState state = NodeState.UNAUTHORIZED; // TODO: persist in Zookeeper private final Map<UUID, Disk> disks = new HashMap<>(); public List<Disk> disks() { return Lists.newArrayList(disks.values()); } public String getHostname() { return hostname; } public void setHostname(String hostname) { this.hostname = hostname; } public InetSocketAddress getAddress() { return address; } public StorageNode(InetSocketAddress address) { super(); this.address = address; this.hostname = address.getHostName(); } public void addDisk(Disk disk) { disk.attachSubscriber(this.subscriber); disks.put(disk.id, disk); disk.storageNode = this; subscriber.onDiskAdded(disk); } public void removeDisk(Disk disk) { disks.remove(disk.id); disk.storageNode = null; subscriber.onDiskRemoved(disk); } public Datacenter datacenter() { return rack.datacenter; } public Rack rack() { return rack; } @Override protected void attachSubscriber(EventSubscriber subscriber) { super.attachSubscriber(subscriber); for (Disk disk : disks.values()) { disk.attachSubscriber(subscriber); } } public long getUsedBytes() { long result = 0; for (Disk disk : disks.values()) { result += disk.getUsedBytes(); } return result; } public long getTotalBytes() { long result = 0; for (Disk disk : disks.values()) { result += disk.getTotalBytes(); } return result; } } /** * */ public static class Disk extends Node { private StorageNode storageNode; String path; long usedBytes = 0; long totalBytes = 0; private DiskState state; // TODO: persist in Zookeeper public String getPath() { return path; } public void setPath(String path) { this.path = path; } public long getUsedBytes() { return usedBytes; } public void setUsedBytes(long usedBytes) { this.usedBytes = usedBytes; } public long getTotalBytes() { return totalBytes; } public void setTotalBytes(long totalBytes) { this.totalBytes = totalBytes; } public DiskState getState() { return state; } public void setState(DiskState state) { this.state = state; } public Disk() { super(); } public Datacenter datacenter() { return storageNode.rack.datacenter; } public Rack rack() { return storageNode.rack; } @Override public String toString() { return String.format("Disk: id=%s, path=%s", id, path); } } public static class ReplicaSet implements Iterable<Disk> { public final List<Disk> disks; public ReplicaSet(List<Disk> disks) { this.disks = disks; } public boolean isEmpty() { return disks.isEmpty(); } @Override public Iterator<Disk> iterator() { return disks.iterator(); } public boolean contains(Disk disk) { return disks.contains(disk); // Note: we operate object ids, not disk ids } } /** * */ private static final class Index implements TopologyEventListener { final Map<UUID, Datacenter> dataCenters = new HashMap<>(); final Map<String, Datacenter> dataCentersByName = new HashMap<>(); final Map<UUID, Rack> racks = new HashMap<>(); final Map<UUID, StorageNode> storageNodes = new HashMap<>(); final Map<UUID, Disk> disks = new HashMap<>(); // Events @Override public void onDatacenterAdded(Datacenter dc) { dataCenters.put(dc.id, dc); dataCentersByName.put(dc.name, dc); } @Override public void onDatacenterRemoved(Datacenter dc) { dataCenters.remove(dc.id); dataCentersByName.remove(dc.name); } @Override public void onRackAdded(Rack rack) { racks.put(rack.id, rack); } @Override public void onRackRemoved(Rack rack) { racks.remove(rack.id); } @Override public void onStorageNodeAdded(StorageNode node) { storageNodes.put(node.id, node); } @Override public void onStorageNodeRemoved(StorageNode node) { storageNodes.remove(node.id); } @Override public void onDiskAdded(Disk disk) { disks.put(disk.id, disk); } @Override public void onDiskRemoved(Disk disk) { disks.remove(disk.id); } // Find methods public Datacenter findDatacenter(UUID id) { return dataCenters.get(id); } public Datacenter findDatacenter(String name) { return dataCentersByName.get(name); } public Rack findRack(UUID id) { return racks.get(id); } public Rack findRack(final Datacenter dc, final String name) { Collection<Rack> result = CollectionUtils.select(findAllRacksInDatacenter(dc), new Predicate<Rack>() { @Override public boolean evaluate(Rack rack) { return name == rack.name; } }); return result.iterator().hasNext() ? result.iterator().next() : null; } public Rack findRack(final Datacenter dc, final UUID id) { Collection<Rack> result = CollectionUtils.select(findAllRacksInDatacenter(dc), new Predicate<Rack>() { @Override public boolean evaluate(Rack rack) { return id == rack.id; } }); return result.iterator().hasNext() ? result.iterator().next() : null; } public StorageNode findStorageNode(UUID id) { return storageNodes.get(id); } public Disk findDisk(UUID id) { return disks.get(id); } public List<Datacenter> findAllDataCenters() { return Lists.newArrayList(dataCenters.values()); } public List<Rack> findAllRacks() { return Lists.newArrayList(racks.values()); } public List<Rack> findAllRacksInDatacenter(final Datacenter dc) { return Lists.newArrayList(CollectionUtils.select(findAllRacks(), new Predicate<Rack>() { @Override public boolean evaluate(Rack rack) { return dc == rack.datacenter; } })); } public List<StorageNode> findAllStorageNodes() { return Lists.newArrayList(storageNodes.values()); } public List<StorageNode> findAllStorageNodesInDatacenter(UUID id) { final Datacenter dc = dataCenters.get(id); assert null != dc; return Lists.newArrayList(CollectionUtils.select(storageNodes.values(), new Predicate<StorageNode>() { @Override public boolean evaluate(StorageNode node) { return node.datacenter().equals(dc); } })); } public List<Disk> findAllDisks() { return Lists.newArrayList(disks.values()); } public List<Disk> findAllDisksInDatacenter(UUID id) { final Datacenter dc = dataCenters.get(id); assert null != dc; return Lists.newArrayList(CollectionUtils.select(disks.values(), new Predicate<Disk>() { @Override public boolean evaluate(Disk disk) { return disk.datacenter().equals(dc); } })); } public List<Disk> findAllDisksInRack(UUID id) { final Rack rack = racks.get(id); assert null != rack; return Lists.newArrayList(CollectionUtils.select(disks.values(), new Predicate<Disk>() { @Override public boolean evaluate(Disk disk) { return disk.rack().equals(rack); } })); } } public Datacenter findDatacenter(UUID id) { return cache.findDatacenter(id); } public Datacenter findDatacenter(String name) { return cache.findDatacenter(name); } public Rack findRack(UUID id) { return cache.findRack(id); } public Rack findRack(Datacenter datacenter, UUID id) { return cache.findRack(datacenter, id); } public Rack findRack(Datacenter datacenter, String name) { return cache.findRack(datacenter, name); } public StorageNode findStorageNode(UUID id) { return cache.findStorageNode(id); } public Disk findDisk(UUID id) { return cache.findDisk(id); } public Collection<StorageNode> getStorageNodes() { return cache.findAllStorageNodes(); } }