/** * Copyright 2014 SeaClouds * Contact: SeaClouds * * 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 eu.seaclouds.platform.discoverer.crawler; import eu.seaclouds.platform.discoverer.core.Offering; import org.apache.http.HttpEntity; import org.apache.http.client.methods.CloseableHttpResponse; import org.apache.http.client.methods.HttpGet; import org.apache.http.impl.client.CloseableHttpClient; import org.apache.http.impl.client.HttpClients; import org.json.simple.JSONArray; import org.json.simple.JSONObject; import org.json.simple.parser.JSONParser; import org.json.simple.parser.ParseException; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import java.io.IOException; import java.text.DecimalFormat; import java.util.*; /* cloud type enumeration */ enum CloudTypes { IAAS, PAAS } public class CloudHarmonyCrawler extends SCCrawler { /* consts */ private static final String API_KEY = "3y5A9MYhtEPFaq16CLdUvfH7TgK0zjc2"; /* vars */ private JSONParser jsonParser; private Hashtable<String, String> booleans; private Hashtable<String, String> numbers; private Hashtable<String, String> strings; private DecimalFormat slaFormat = new DecimalFormat("0.00000"); CloseableHttpClient httpclient; static Logger log = LoggerFactory.getLogger(CloudHarmonyCrawler.class); public static String Name = "CloudHarmonyCrawler"; /* c.tor */ public CloudHarmonyCrawler() { /* json parser */ this.jsonParser = new JSONParser(); /* client init */ this.httpclient = HttpClients.createDefault(); /* mapping booleans */ this.booleans = new Hashtable<String, String>(); this.booleans.put("autoScaling", "auto_scaling"); this.booleans.put("selfHostable", "self_hostable"); this.booleans.put("apiRestricted", "api_restricted"); this.booleans.put("autoFailover", "auto_failover"); this.booleans.put("processPricing", "process_pricing"); this.booleans.put("vmBased", "vm_based"); /* mapping numbers */ this.numbers = new Hashtable<String, String>(); this.numbers.put("cpuCores", "num_cpus"); this.numbers.put("localDisks", "num_disks"); this.numbers.put("memory", "ram"); this.numbers.put("localStorage", "disk_size"); /* mapping strings */ this.strings = new Hashtable<String, String>(); this.strings.put("localDiskType", "disk_type"); } private JSONArray iaas_getServiceFeatures(String serviceId) { /* computing the query string */ String getServiceFeaturesQuery = "https://cloudharmony.com/api/properties/compute/" + serviceId + "?" + "api-key=" + API_KEY + "&"; return (JSONArray) query(getServiceFeaturesQuery); } private JSONObject paas_getServiceFeatures(String serviceId) { /* computing the query string */ String getServiceFeaturesQuery = "https://cloudharmony.com/api/properties/paas/" + serviceId + "?" + "api-key=" + API_KEY + "&"; return (JSONObject) query(getServiceFeaturesQuery); } private Object query(String query) { HttpGet httpGet = new HttpGet(query); CloseableHttpResponse response = null; try { response = httpclient.execute(httpGet); HttpEntity entity = response.getEntity(); String content = new Scanner(entity.getContent()).useDelimiter("\\Z").next(); return jsonParser.parse(content); } catch (ParseException | NullPointerException | IOException | NoSuchElementException e) { // TODO: investigate why it raises NoSuchElementException, sometimes. // It looks like a CloudHarmony bug. return null; } } private JSONObject getComputeInstanceType(String serviceId, String instanceTypeId) { /* query string */ String queryStr = "https://cloudharmony.com/api/compute/" + serviceId + "/" + instanceTypeId + "?" + "api-key=" + API_KEY + "&"; return (JSONObject) query(queryStr); } private CloudHarmonyService getService(String serviceId, CloudTypes t) { /* computing the query string */ String getServiceQuery = "https://cloudharmony.com/api/service/" + serviceId + "?" + "api-key=" + API_KEY + "&"; JSONObject service = null; try { service = (JSONObject) query(getServiceQuery); } catch(Exception ex) { // 'no content' possible. dunno why } if(service == null) return null; /* name */ String name = "unknown_name"; if(service.containsKey("name")) name = (String) service.get("name"); /* sla */ Double sla = null; if (service.containsKey("sla")) { Object slaValue = service.get("sla"); if (slaValue.getClass().equals(Long.class)) { sla = ((Long) slaValue).doubleValue(); } else { // Double sla = (Double) slaValue; } } /* locations */ JSONArray locations = null; if (service.containsKey("regions")) locations = (JSONArray) service.get("regions"); /* service features */ Object serviceFeatures; ArrayList<JSONObject> computeInstanceTypes = null; if (t == CloudTypes.IAAS) { serviceFeatures = iaas_getServiceFeatures(serviceId); /* in the service features we can find * all the instance types to use to retrieve * the compute instance types. */ if( serviceFeatures != null && ((JSONArray)(serviceFeatures)).size() != 0 ) { /* retrieving instance types * up to now only Amazon AWS:EC2 and Aruba ARUBA:COMPUTE * return more than one properties object */ JSONObject oneFeature = (JSONObject) ((JSONArray) (serviceFeatures)).get(0); /* It's actually returned a list of service features apparently equal * (except for id which is -> aws:ec2, aws:ec2-..-..) */ JSONArray instanceTypes = (JSONArray) oneFeature.get("instanceTypes"); Iterator<String> instanceTypesIDs = instanceTypes.iterator(); /* querying each instance type */ computeInstanceTypes = new ArrayList<JSONObject>(); while (instanceTypesIDs.hasNext()) { /* current instance id */ String instanceTypeID = instanceTypesIDs.next(); JSONObject cit = getComputeInstanceType(serviceId, instanceTypeID); computeInstanceTypes.add(cit); } } } else { // t == CloudTypes.PAAS serviceFeatures = paas_getServiceFeatures(serviceId); } /* returning everything */ return new CloudHarmonyService(t, // cloud type serviceId, // service id name, // cloud name sla, // availability locations, // geographics null, // bandwidth pricing (unused) serviceFeatures, // cloudharmony service features computeInstanceTypes); // compute instance types } private void generateOfferings(CloudHarmonyService chs) { if(chs == null || chs.computeInstanceTypes == null || chs.locations == null) return; /* for each location... */ Iterator<JSONObject> locations = chs.locations.iterator(); while (locations.hasNext()) { JSONObject location_i = locations.next(); Offering newOffer; if(chs.cloudType == CloudTypes.IAAS) { /* ...and for each compute instance type */ for (JSONObject cit_i : chs.computeInstanceTypes) { /* generate the offering */ newOffer = generateIAASOffering(chs, location_i, cit_i); /* collect */ if (newOffer != null) addOffering(newOffer); } } else { // chs.cloudType == CloudTypes.PAAS /* generate the offering */ newOffer = generatePAASOffering(chs, location_i); /* collect */ if(newOffer != null) addOffering(newOffer); } } } private String expandCountryCode(String cc) { Locale l = new Locale("", cc); return l.getDisplayCountry(); } private Offering generateIAASOffering(CloudHarmonyService chs, JSONObject locationInformation, JSONObject computeInstanceType) { /* tosca lines into ArrayList */ ArrayList<String> gt = new ArrayList<>(); String providerName = chs.name; /* name not sanitized (used to access SPECint table) */ String providerOriginalName = chs.originalName; String instanceId = (String) computeInstanceType.get("instanceId"); String locationCode = (String) locationInformation.get("providerCode"); /* name, taken as 'cloud service name'_'instance id'_'city where is located' */ String name = Offering.sanitizeName(providerName + "_" + instanceId + "_" + locationCode); Offering offering = new Offering(name); offering.setType("seaclouds.nodes.Compute." + Offering.sanitizeName(providerName)); offering.addProperty("resource_type", "compute"); offering.addProperty("hardwareId", instanceId); offering.addProperty("location", chs.serviceId); offering.addProperty("region", locationCode); /* Performance */ Integer performance = CloudHarmonySPECint.getSPECint(providerOriginalName, instanceId); if (performance != null) { offering.addProperty("performance", performance.toString()); } /* sla */ if(chs.sla != null) { offering.addProperty("availability", this.slaFormat.format(chs.sla/100.0)); } /* location */ offering.addProperty("country", expandCountryCode((String) (locationInformation.get("country")))); offering.addProperty("city", expandCountryCode((String) (locationInformation.get("city")))); JSONArray pricings = (JSONArray) computeInstanceType.get("pricing"); if (pricings.size() > 0) { JSONObject pricing = ((JSONObject) ((JSONArray) computeInstanceType.get("pricing")).get(0)); Object price = pricing.get("price"); Object currency = pricing.get("currency"); Object priceInterval = pricing.get("priceInterval"); offering.addProperty("cost", price.toString() + " " + currency.toString() + "/" + priceInterval); } /* compute instance type - numbers */ for(String k : this.numbers.keySet()) { if( computeInstanceType.containsKey(k) ) { Object vv = computeInstanceType.get(k); offering.addProperty(this.numbers.get(k), vv.toString()); } } /* compute instance type - strings */ if( computeInstanceType != null ) { for (String k : this.strings.keySet()) { if (computeInstanceType.containsKey(k)) { String v = (String) (computeInstanceType.get(k)); offering.addProperty(this.strings.get(k), v); } } } return offering; } private Offering generatePAASOffering(CloudHarmonyService chs, JSONObject location) { /* tosca lines into ArrayList */ /* name */ String name = Offering.sanitizeName(chs.name + "_" + location.get("city")); Offering offering = new Offering(name); offering.setType("seaclouds.nodes.Platform." + Offering.sanitizeName(chs.name)); /* resource type */ offering.addProperty("resource_type", "platform"); /* sla */ if(chs.sla != null) { offering.addProperty("availability", this.slaFormat.format(chs.sla / 100.0)); } /* location */ offering.addProperty("country", expandCountryCode((String) (location.get("country")))); offering.addProperty("city", expandCountryCode((String)(location.get("city")))); /* features */ JSONObject feat = (JSONObject) chs.serviceFeatures; /* booleans */ for(String k : this.booleans.keySet()) { if( feat.containsKey(k) ) { boolean v = Boolean.parseBoolean((String)(feat.get(k))); offering.addProperty(this.booleans.get(k), (v ? "true" : "false")); } } /* supported databases and languages */ supports("supportedDatabases", feat, offering); supports("supportedLanguages", feat, offering); /* building the tosca */ return offering; } private void supports(String keyName, JSONObject feat, Offering offering) { if( feat.containsKey(keyName) == false ) return; JSONArray dbs = (JSONArray) feat.get(keyName); for(int i = 0; i < dbs.size(); i++) { String cloudHarmonySupportName = (String) dbs.get(i); String seaCloudsSupportName = externaltoSCTag.get(cloudHarmonySupportName); if (seaCloudsSupportName != null) offering.addProperty(seaCloudsSupportName + "_support", "true"); } } private void crawlComputeOfferings() { /* iaas section */ String computeQuery = "https://cloudharmony.com/api/services?" + "api-key=" + API_KEY + "&" + "serviceTypes=compute"; JSONObject resp = (JSONObject) query(computeQuery); if(resp == null) { return; } JSONArray computes = (JSONArray) resp.get("ids"); Iterator<String> it = computes.iterator(); while (it.hasNext()) { try { String serviceId = it.next(); CloudHarmonyService chService = getService(serviceId, CloudTypes.IAAS); if (chService != null) generateOfferings(chService); } catch(Exception ex) { log.warn(ex.getMessage()); } } } private void crawlPaasOfferings() { JSONObject resp; Iterator<String> it; /* paas section */ String paasQuery = "https://cloudharmony.com/api/services?" + "api-key=" + API_KEY + "&" + "serviceTypes=paas"; resp = (JSONObject) query(paasQuery); JSONArray paases = (JSONArray) resp.get("ids"); it = paases.iterator(); while (it.hasNext()) { try { String serviceId = it.next(); CloudHarmonyService chService = getService(serviceId, CloudTypes.PAAS); generateOfferings(chService); } catch(Exception e) { log.warn(e.getMessage()); } } } @Override public void crawl() { /* crawl Compute and PaaS offerings */ this.crawlComputeOfferings(); this.crawlPaasOfferings(); } } class CloudHarmonyService { /* members */ public CloudTypes cloudType; public String name; public String originalName; public String serviceId; public Double sla; public JSONArray locations; public JSONArray bandwidthPricing; /** * In case of <code>cloudType</code> is equal to <code>CloudTypes.IAAS</code>, * this field must be cast as <code>JSONArray</code>, while in case of * <code>cloudType</code> is equal to <code>CloudTypes.PAAS</code>, this * field must be cast as <code>JSONObject</code>. */ public Object serviceFeatures; public ArrayList<JSONObject> computeInstanceTypes; /* c.tor */ public CloudHarmonyService(CloudTypes cloudType, String serviceId, String name, Double sla, JSONArray locations, JSONArray bandwidthPricing, Object serviceFeatures, ArrayList<JSONObject> computeInstanceTypes) { this.serviceId = serviceId; this.cloudType = cloudType; this.name = Offering.sanitizeName(name); this.originalName = name; this.sla = sla; this.locations = locations; this.bandwidthPricing = bandwidthPricing; this.serviceFeatures = serviceFeatures; this.computeInstanceTypes = computeInstanceTypes; } }