/* * Claudia Project * http://claudia.morfeo-project.org * * (C) Copyright 2010 Telefonica Investigacion y Desarrollo * S.A.Unipersonal (Telefonica I+D) * * See CREDITS file for info about members and contributors. * * This program is free software; you can redistribute it and/or modify * it under the terms of the Affero GNU General Public License (AGPL) as * published by the Free Software Foundation; either version 3 of the License, * or (at your option) any later version. * * This program 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 Affero GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. * * If you want to use this software an plan to distribute a * proprietary application in any way, and you are not licensing and * distributing your source code under AGPL, you probably need to * purchase a commercial license of the product. Please contact * claudia-support@lists.morfeo-project.org for more information. */ package com.telefonica.claudia.slm.monitoring; import java.io.IOException; import java.util.HashMap; import java.util.HashSet; import java.util.Iterator; import java.util.Set; import org.apache.log4j.Logger; import org.json.JSONException; import com.telefonica.claudia.slm.deployment.Customer; import com.telefonica.claudia.slm.deployment.ServiceApplication; import com.telefonica.claudia.slm.deployment.ServiceKPI; import com.telefonica.claudia.slm.deployment.VEE; import com.telefonica.claudia.slm.deployment.VEEReplica; import com.telefonica.claudia.slm.deployment.hwItems.CPU; import com.telefonica.claudia.slm.deployment.hwItems.Disk; import com.telefonica.claudia.slm.deployment.hwItems.Memory; import com.telefonica.claudia.slm.deployment.hwItems.NIC; import com.telefonica.claudia.slm.deployment.hwItems.Network; import com.telefonica.claudia.slm.naming.DirectoryEntry; import com.telefonica.claudia.slm.naming.FQN; public class WasupHierarchy { private static Logger log = Logger.getLogger("Monitoring"); /** * The Hierachy is implemented with a singleton pattern. This attribute * holds the actual instance. */ private static WasupHierarchy actualHierarchy; /** * Map containing all the nodes indexed by its data FQN. */ private HashMap<FQN, WasupNode> wasupIds= new HashMap<FQN, WasupNode> (); /** * Root of the actual Wasup hierarchy. */ private WasupNode wasupRoot; /** * Wasup Rest client to make the actual calls. */ private static WasupClient client; /** * Element type in wasup. The element types are fixed for the Service Manager, * but they need to be recreated each time a new SM is deployed. * */ private static class WasupType { String id; String name; String description; String unit=""; String[] range=new String[] {null, null}; public WasupType(String name, String description) { this.name=name; this.description=description; } public WasupType(String name, String description, String unit, String lowerBound, String upperBound) { this(name, description); this.unit= unit; this.range[0] = lowerBound; this.range[1] = upperBound; } } /** * Wasup element types. There should be one type per class in the deployment model. Its key * is the fully qualified class name of each class and the vale an array with two strings: * the first being the name of the element type and the second being the element description. */ private static HashMap<String, WasupType> wasupTypes = new HashMap<String, WasupType> (); /** * Wasup id of each measurement type hold by the hierarchy. Each measurment type wasup id (value) * is associated with an element FQN (key). */ private HashMap<String, String> measureTypeIds = new HashMap<String, String> (); /** * Collection that maps class names of each element to the String identifier in wasup of its * measurement type. */ private static HashMap<String, String> measurementTypeStrings = new HashMap<String, String>(); static { measurementTypeStrings.put(NIC.class.getName(), "Network Interface"); measurementTypeStrings.put(Disk.class.getName(), "Disk"); measurementTypeStrings.put(CPU.class.getName(), "CPU"); measurementTypeStrings.put(ServiceKPI.class.getName(), "ServiceKPI"); measurementTypeStrings.put(Memory.class.getName(), "Memory"); } /** * Array containing the measure types that are pending of being created. During element creation, * some of the elements may have a measurement type associated. For those element, an entry in this * array is created, with its FQN. When the publishMeasureTypes() method is invoked, all of them are * created in wasup and the array is emptied. */ private Set<DirectoryEntry> pendingMeasureTypes = new HashSet <DirectoryEntry>(); private static String CUSTOMER_TYPE_NAME= null; private static String SERVICE_TYPE_NAME= null; private static String VEE_TYPE_NAME= null; private static String VEEREPLICA_TYPE_NAME= null; private static String NETWORK_TYPE_NAME= null; private static String HW_TYPE_NAME= null; public static void setParameters(String customer, String service, String vee, String veeReplica, String network, String hardware) { CUSTOMER_TYPE_NAME= customer; SERVICE_TYPE_NAME= service; VEE_TYPE_NAME= vee; VEEREPLICA_TYPE_NAME= veeReplica; NETWORK_TYPE_NAME= network; HW_TYPE_NAME= hardware; wasupTypes.put(Customer.class.getCanonicalName(), new WasupType(CUSTOMER_TYPE_NAME, "Customer")); WasupType serviceType = new WasupType(SERVICE_TYPE_NAME, "Service Application", " ", null, null); wasupTypes.put(ServiceApplication.class.getCanonicalName(), serviceType); wasupTypes.put(ServiceKPI.class.getCanonicalName(), serviceType); wasupTypes.put(VEE.class.getCanonicalName(), new WasupType(VEE_TYPE_NAME, "Virtual Execution Environment")); wasupTypes.put(VEEReplica.class.getCanonicalName(), new WasupType(VEEREPLICA_TYPE_NAME, "VEE Replica")); wasupTypes.put(Network.class.getCanonicalName(), new WasupType(NETWORK_TYPE_NAME, "Network Interface", " ", null, null)); WasupType hwType = new WasupType(HW_TYPE_NAME, "Hardware Item", " ", "0.0", null); wasupTypes.put(CPU.class.getCanonicalName(), hwType); wasupTypes.put(Disk.class.getCanonicalName(), hwType); wasupTypes.put(Memory.class.getCanonicalName(), hwType); wasupTypes.put(NIC.class.getCanonicalName(), hwType); } public static void loadWasupTypes() throws IOException, JSONException { client = WasupClient.getWasupClient(); for (WasupType type: wasupTypes.values()) { int typeId=client.getElementTypeId(type.name); if (typeId==-1) { client.createElementType(type.name, type.description); log.debug("Type: " + type.name + " created in Wasup with id: " + type.id); } else { type.id = String.valueOf(typeId); log.debug("Type: " + type.name + " found in Wasup with id: " + type.id); } } } /** * Node of the actual hierarchy. Each node holds a Wasup element representing an entity in the Reservoir * directory. It also contains a reference to its Element type. A reference to the real Reservoir object * is hold in the <i>data</i> field. * * Each node contains a list of its descendant and a reference to its parent for navegability purposes. * */ private class WasupNode { DirectoryEntry data; WasupNode parent; WasupType type; String wasupId; String parentString; Set<WasupNode> children = new HashSet<WasupNode>(); String[] range=new String[] {"0", "100"}; public WasupNode(DirectoryEntry data) { this.data = data; WasupHierarchy.this.wasupIds.put(data.getFQN(), this); type = WasupHierarchy.wasupTypes.get(data.getClass().getCanonicalName()); range[0] = type.range[0]; range[1] = type.range[1]; } public void add(WasupNode child) { children.add(child); child.parent = this; } public boolean equals(Object otherNode) { if (!(otherNode instanceof WasupNode)) return false; return data.getFQN().equals( ((WasupNode)otherNode).data.getFQN() ); } public int hashCode() { return data.getFQN().hashCode(); } } /** * Returns the actual working instance of the hierarchy class, creating a new * when necessary. * * @return * The actual working instance * * @throws Exception */ public static WasupHierarchy getWasupHierarchy() throws Exception { if (actualHierarchy==null) { actualHierarchy = new WasupHierarchy(); } return actualHierarchy; } private WasupHierarchy() throws Exception { client = WasupClient.getWasupClient(); } /** * Creates the wasup element hierarchy based upon the information of the given * ServiceApplication object. The hierarchy is built as a tree composed of WasupNodes. * A HashMap of WasapNodes is also created to efficiently update the hierarchy if * there are any changes in one point in the tree. * * Once the tree is created in memory, the method make all the Rest Calls needed in order to * create it in Wasup. * * @param sp * ServiceApplication object which is to be deployed on the Wasup * * @throws IOException * @throws JSONException * */ public void createWasupHierarchy(ServiceApplication sp) throws IOException, JSONException { log.info("Creating Wasup hierarchy for Service Application " + sp.getFQN().toString()); // The customer will be the root of the hierarchy, followed by the service if (wasupRoot==null) { Customer customer = sp.getCustomer(); wasupRoot = new WasupNode(customer); } WasupNode serviceNode = new WasupNode(sp); wasupRoot.add(serviceNode); // Down the service, all the VEES are created. Below each VEE, the VEEReplicasn are placed // and, under them, all the hardware items present in each VEE. for (VEE vee: sp.getVEEs()) { log.info("\tCreating node for VEE " + vee.getFQN().toString()); addNode(vee, sp); } // Create the network nodes for (Network net: sp.getNetworks()) { log.info("\tCreating node for Network " + net.getFQN().toString()); addNode(net, sp); } // Create the KPIs also for (ServiceKPI kpi: sp.getServiceKPIs()) { log.info("\tCreating node for VEE " + kpi.getFQN().toString()); pendingMeasureTypes.add(kpi); addNode(kpi, sp); } // Check if the Customer is already created int idCustomer; int customerType; try { customerType = Integer.parseInt(wasupTypes.get(wasupRoot.data.getClass().getCanonicalName()).id); } catch (NumberFormatException nfe) { log.error("WASUP hierarchy could not be created: wrong customer type ID"); return; } if ((idCustomer = client.getElementId(wasupRoot.data.getFQN().toString(), customerType))!= WasupClient.NO_ELEMENT_FOUND) { wasupRoot.wasupId = String.valueOf(idCustomer); publishNode(serviceNode); } else { // The hierarchy is now created, send it to Wasup publishNode(wasupRoot); } publishMeasureTypes(); } /** * Make all the Rest calls needed to create the actual node and all of its descendants in WASUP. * * @param node * Node to publis in the wasup. * * @param parentString * Path between the root of the wasup Rest server and the node to be deloyed. * @throws IOException * @throws JSONException */ private void publishNode(WasupNode node) throws IOException, JSONException { // Create the actual node if (node.parent==null) node.parentString=""; else node.parentString = node.parent.parentString + "/" + node.parent.wasupId; // Make the rest call node.wasupId = String.valueOf(client.createElement((node.parent!=null)?node.parent.wasupId:"", node.data.getFQN().toString(), wasupTypes.get(node.data.getClass().getCanonicalName()).id)); // Publish each descendant of the actual node for (WasupNode child: node.children) publishNode(child); } /** * Remove the directory entry passed as a parameter from the local hierarchy and from the * wasup. * * @param node * Directory entry to be removed. * * @throws IOException */ public void removeNode(DirectoryEntry node) throws IOException { // First of all, remove the node from the hierarchy WasupNode nodeToRemove = wasupIds.get(node.getFQN()); removeNode(nodeToRemove); if (nodeToRemove.parent!= null) { nodeToRemove.parent.children.remove(nodeToRemove); } } private void removeNode(WasupNode nodeToRemove) throws IOException { if (nodeToRemove == null) { log.error("Null object realeasing the hierarchy"); return; } // First of all, remove all its children synchronized(nodeToRemove.children) { Iterator<WasupNode> iter = nodeToRemove.children.iterator(); while (iter.hasNext()) { removeNode(iter.next()); iter.remove(); } } wasupIds.remove(nodeToRemove.data.getFQN()); // Call the wasup to actually delete the element. client.removeElement(nodeToRemove.wasupId); } /** * Add the directory entry node to the wasup hierarchy under the node of the * directory entry parent, and update the wasup hierarchy accordingly. * * @param parent * Parent directory entry under which the new node will be added. * * @param node * Directory entry to be added. * @throws IOException * @throws JSONException */ private WasupNode addNode(DirectoryEntry data, DirectoryEntry parent) throws IOException, JSONException { WasupNode parentNode = wasupIds.get(parent.getFQN()); WasupNode node = new WasupNode(data); parentNode.add(node); return node; } private WasupNode addNode(VEE vee, DirectoryEntry parent) throws IOException, JSONException { WasupNode serviceNode = wasupIds.get(parent.getFQN()); WasupNode veeNode = new WasupNode(vee); serviceNode.add(veeNode); for (VEEReplica replica: vee.getVEEReplicas()) { addNode(replica, vee); } return veeNode; } private WasupNode addNode(VEEReplica replica, DirectoryEntry parent) throws IOException, JSONException { log.debug("Adding replica [" + replica.getFQN() + "] to the Wasup hierarchy"); WasupNode veeNode = wasupIds.get(parent.getFQN()); WasupNode veeReplicaNode = new WasupNode(replica); veeNode.add(veeReplicaNode); for (NIC nic: replica.getNICs()) { WasupNode nicNode = new WasupNode(nic); veeReplicaNode.add(nicNode); pendingMeasureTypes.add(nic); } for (CPU cpu: replica.getCPUs()) { WasupNode cpuNode = new WasupNode(cpu); veeReplicaNode.add(cpuNode); pendingMeasureTypes.add(cpu); } for (Disk disk: replica.getDisks()) { WasupNode diskNode = new WasupNode(disk); veeReplicaNode.add(diskNode); diskNode.range[1] = String.valueOf(disk.getDiskConf().getCapacity()); pendingMeasureTypes.add(disk); } WasupNode memoryNode = new WasupNode(replica.getMemory()); veeReplicaNode.add(memoryNode); memoryNode.range[1] = String.valueOf(replica.getMemory().getMemoryConf().getCapacity()); pendingMeasureTypes.add(replica.getMemory()); log.debug("Replica added"); return veeReplicaNode; } public void createVEE(VEE vee, DirectoryEntry parent) throws IOException, JSONException { publishNode(addNode(vee, parent)); } public void createVEEReplica(VEEReplica replica, DirectoryEntry parent) throws IOException, JSONException { publishNode(addNode(replica, parent)); publishMeasureTypes(); } /** * Publish the pending measure types to the Wasup. Measure types may be created during element creation * (no every element is associated to a measure type). Measure types to be created are stored in the * pendingMeasureTypes array. * @throws JSONException * @throws IOException * */ public void publishMeasureTypes() throws IOException, JSONException { for (DirectoryEntry node: pendingMeasureTypes) { createMeasureType(node); } pendingMeasureTypes.clear(); } /** * Create a measure typa in the wasup, associated to the given element, and store its wasup id. * * @param element * Fully qualified name of the element associated to the measure. * @throws JSONException * @throws IOException */ private void createMeasureType(DirectoryEntry element) throws IOException, JSONException { WasupNode node = wasupIds.get(element.getFQN()); log.info("Create a measurement type for element " + element.getFQN().toString()); String name=element.getFQN().toString(); if (name.contains("replicas")) { name=name.substring(name.indexOf("replicas")); name=name.substring(name.indexOf(".", name.indexOf("."))+1); name=name.substring(name.indexOf(".")+1); if (name==null || name.equals("")) name=element.getFQN().toString(); } else if (name.contains("kpis")) { name=name.substring(name.indexOf("kpis")); } String itemName = element.getFQN().toString(); measureTypeIds.put(element.getFQN().toString(), String.valueOf(client.createMeasurementType(name, itemName, node.parent.wasupId, node.type.unit, measurementTypeStrings.get(element.getClass().getName()), node.range[0], node.range[1]))); log.info("Creating measurement range for measure type " + measureTypeIds.get(element.getFQN().toString()) + " and values in [" + node.range[0] +"," + node.range[1] + "]"); client.createMeasurementRange(measureTypeIds.get(element.getFQN().toString()), node.range[0], node.range[1]); } /** * Post a measure value to the indicated measure type. The measure type must be checked in the * measureTypesList in order to get the actual Wasup id to make the real call. * * @param measureType * Fully qualified name of the measure type. * * @param value * String representation of the measure value. * @throws JSONException * @throws IOException * */ public void postMeasure(DirectoryEntry measureType, String value) throws IOException, JSONException { if (measureType==null) { log.error("A null measureType has been found."); return; } log.info("Measure[" + measureType.getFQN() + "]: " + value); client.postMeasure(measureType.getFQN().toString(), value); } }