org.dasein.cloud.cloudstack.CSCloud Maven / Gradle / Ivy
Go to download
Show more of this group Show more artifacts with this name
Show all versions of dasein-cloud-cloudstack Show documentation
Show all versions of dasein-cloud-cloudstack Show documentation
Implements the Dasein Cloud API for Cloud.com Cloudstack-based public and private clouds.
/**
* Copyright (C) 2009-2015 Dell, Inc.
*
* ====================================================================
* 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.dasein.cloud.cloudstack;
import java.io.UnsupportedEncodingException;
import java.text.DateFormat;
import java.text.ParseException;
import java.text.SimpleDateFormat;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.List;
import java.util.Properties;
import org.apache.log4j.Logger;
import org.dasein.cloud.AbstractCloud;
import org.dasein.cloud.CloudException;
import org.dasein.cloud.ContextRequirements;
import org.dasein.cloud.InternalException;
import org.dasein.cloud.ProviderContext;
import org.dasein.cloud.Tag;
import org.dasein.cloud.cloudstack.compute.CSComputeServices;
import org.dasein.cloud.cloudstack.identity.CSIdentityServices;
import org.dasein.cloud.cloudstack.network.CSNetworkServices;
import org.dasein.cloud.storage.BlobStoreSupport;
import org.dasein.cloud.storage.StorageServices;
import org.dasein.cloud.util.APITrace;
import org.dasein.cloud.util.Cache;
import org.dasein.cloud.util.CacheLevel;
import org.dasein.util.uom.time.Day;
import org.dasein.util.uom.time.TimePeriod;
import org.dasein.util.uom.time.TimePeriodUnit;
import org.w3c.dom.Document;
import org.w3c.dom.Node;
import org.w3c.dom.NodeList;
import javax.annotation.Nonnegative;
import javax.annotation.Nonnull;
import javax.annotation.Nullable;
import javax.servlet.http.HttpServletResponse;
public class CSCloud extends AbstractCloud {
static private final Logger logger = getLogger(CSCloud.class, "std");
static public final String LIST_ACCOUNTS = "listAccounts";
static private final String LIST_HYPERVISORS = "listHypervisors";
static private final String LIST_TAGS = "listTags";
static private final String CREATE_TAGS = "createTags";
static private final String DELETE_TAGS = "deleteTags";
static private @Nonnull String getLastItem(@Nonnull String name) {
int idx = name.lastIndexOf('.');
if( idx < 0 ) {
return name;
}
else if( idx == (name.length()-1) ) {
return "";
}
return name.substring(idx+1);
}
static public @Nonnull Logger getLogger(@Nonnull Class> cls, @Nonnull String type) {
String pkg = getLastItem(cls.getPackage().getName());
if( pkg.equals("cloudstack") ) {
pkg = "";
}
else {
pkg = pkg + ".";
}
return Logger.getLogger("dasein.cloud.cloudstack." + type + "." + pkg + getLastItem(cls.getName()));
}
public CSCloud() { }
@Override
public @Nonnull String getCloudName() {
ProviderContext ctx = getContext();
if( ctx == null ) {
return "CloudStack";
}
String name = ctx.getCloudName();
if( name == null ) {
return "CloudStack";
}
return name;
}
@Override
public @Nonnull ContextRequirements getContextRequirements() {
return new ContextRequirements(
new ContextRequirements.Field("apiKey", "The API Keypair", ContextRequirements.FieldType.KEYPAIR, ContextRequirements.Field.ACCESS_KEYS, true)
);
}
@Override
public @Nonnull CSComputeServices getComputeServices() {
return new CSComputeServices(this);
}
@Override
public @Nonnull CSTopology getDataCenterServices() {
return new CSTopology(this);
}
@Override
public @Nullable CSIdentityServices getIdentityServices() {
if( getVersion().greaterThan(CSVersion.CS21) ) {
return new CSIdentityServices(this);
}
return null;
}
@Override
public @Nonnull CSNetworkServices getNetworkServices() {
return new CSNetworkServices(this);
}
@Override
public @Nonnull String getProviderName() {
ProviderContext ctx = getContext();
if( ctx == null ) {
return "Citrix";
}
String name = ctx.getProviderName();
if( name == null ) {
return "Citrix";
}
return name;
}
private transient CSServiceProvider serviceProvider;
public CSServiceProvider getServiceProvider() {
if( serviceProvider == null ) {
String pn = getProviderName();
if( "kt".equalsIgnoreCase(pn) ) {
serviceProvider = CSServiceProvider.KT;
}
else if( "datapipe".equalsIgnoreCase(pn) ) {
serviceProvider = CSServiceProvider.DATAPIPE;
}
else if( "tata".equalsIgnoreCase(pn) ) {
serviceProvider = CSServiceProvider.TATA;
} else if( "democloud".equalsIgnoreCase(pn) ) {
serviceProvider = CSServiceProvider.DEMOCLOUD;
}
else {
serviceProvider = CSServiceProvider.INTERNAL;
}
}
return serviceProvider;
}
private transient String versionString;
public @Nonnull String getVersionString() throws CloudException {
APITrace.begin(this, "CSCloud.getVersionString");
if( versionString == null ) {
//run list zone query to check whether this might be v4
try {
CSMethod method = new CSMethod(this);
Document doc = method.get("listZones", new Param("available", "true"));
NodeList meta = doc.getElementsByTagName("listzonesresponse");
for (int item = 0; item fields = getContextRequirements().getConfigurableValues();
for(ContextRequirements.Field f : fields ) {
if(f.type.equals(ContextRequirements.FieldType.KEYPAIR)){
byte[][] keyPair = (byte[][])getContext().getConfigurationValue(f);
ctxKey = new String(keyPair[0], "utf-8");
}
}
}
catch( UnsupportedEncodingException e ) {
e.printStackTrace();
throw new RuntimeException("This cannot happen: " + e.getMessage(), e);
}
for( int i=0; i 0 ) {
value = attribute.getFirstChild().getNodeValue();
}
else {
value = null;
}
if (name.equals("apikey")) {
if (value.equals(ctxKey)) {
found = true;
continue;
}
}
// i think we should match with username, not account name since
// the keys belong to user, not the account
else if (name.equals("account")) {
account = value;
}
}
if (found) {
// not sure we need to match this
if (!getContext().getAccountNumber().equals(account)) {
getContext().setAccountNumber(account);
}
return true;
}
}
logger.debug("No match to api key found");
return false;
}
catch( CSException e ) {
int code = e.getHttpCode();
if( code == HttpServletResponse.SC_FORBIDDEN || code == 401 || code == 531 ) {
return false;
}
throw e;
}
catch( CloudException e ) {
int code = e.getHttpCode();
if( code == HttpServletResponse.SC_FORBIDDEN || code == HttpServletResponse.SC_UNAUTHORIZED ) {
return false;
}
throw e;
}
}
finally {
APITrace.end();
}
}
public @Nonnegative long parseTime(@Nonnull String timestamp) {
DateFormat df = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ssZ"); //2009-02-03T05:26:32.612278
try {
return df.parse(timestamp).getTime();
}
catch( ParseException e ) {
df = new SimpleDateFormat("EEE MMM dd HH:mm:ss z yyyy"); //Sun Jul 04 02:18:02 EST 2010
try {
return df.parse(timestamp).getTime();
}
catch( ParseException another ) {
return 0L;
}
}
}
@Override
public @Nullable String testContext() {
APITrace.begin(this, "testContext");
try {
try {
ProviderContext ctx = getContext();
if( ctx == null ) {
return null;
}
if( logger.isDebugEnabled() ) {
logger.debug("testContext(): Checking CSCloud compute credentials");
}
if( !isSubscribed() ) {
logger.warn("testContext(): CSCloud compute credentials are not subscribed for VM services");
return null;
}
if( hasStorageServices() ) {
if( logger.isDebugEnabled() ) {
logger.debug("testContext(): Checking " + ctx.getStorage() + " storage credentials");
}
StorageServices services = getStorageServices();
if( services != null && services.hasOnlineStorageSupport() ) {
BlobStoreSupport support = services.getOnlineStorageSupport();
if( support != null ) {
try {
support.list(null).iterator().hasNext();
}
catch( Throwable t ) {
logger.warn("testContext(): Storage credentials failed: " + t.getMessage());
if( logger.isDebugEnabled() ) {
logger.debug("textContext(): Storage credentials failed: ", t);
}
return null;
}
}
}
}
if( logger.isInfoEnabled() ) {
logger.info("testContext(): Credentials validated");
}
return ctx.getAccountNumber();
}
catch( Throwable t ) {
logger.warn("testContext(): Failed to test cloudstack context: " + t.getMessage());
t.printStackTrace();
return null;
}
}
finally {
APITrace.end();
}
}
public Document waitForJob(Document doc, String jobName) throws CloudException, InternalException {
NodeList matches = doc.getElementsByTagName("jobid");
if( matches.getLength() > 0 ) {
return waitForJob(matches.item(0).getFirstChild().getNodeValue(), jobName);
}
return null;
}
public Document waitForJob(String jobId, String jobName) throws CloudException, InternalException {
APITrace.begin(this, "waitForJob");
try {
CSMethod method = new CSMethod(this);
while( true ) {
try { Thread.sleep(5000L); }
catch( InterruptedException e ) { /* ignore */ }
Document doc = method.get("queryAsyncJobResult", new Param("jobId", jobId));
NodeList matches = doc.getElementsByTagName("jobstatus");
int status = 0;
if( matches.getLength() > 0 ) {
status = Integer.parseInt(matches.item(0).getFirstChild().getNodeValue());
}
if( status > 0 ) {
int code = status;
if( status == 1 ) {
return doc;
}
if( status == 2 ) {
matches = doc.getElementsByTagName("jobresult");
if( matches.getLength() > 0 ) {
String str = matches.item(0).getFirstChild().getNodeValue();
if( str == null || str.trim().length() < 1 ) {
NodeList nodes = matches.item(0).getChildNodes();
String message = null;
for( int i=0; i cache = Cache.getInstance(this, "api."+callName, Boolean.class, CacheLevel.CLOUD_ACCOUNT, new TimePeriod(1, TimePeriod.DAY));
Iterable cachedValues = cache.get(getContext());
if( cachedValues != null && cachedValues.iterator().hasNext() ) {
return cachedValues.iterator().next();
}
APITrace.begin(this, "getApis");
try {
new CSMethod(this).get("listApis", new Param("name", callName));
cache.put(getContext(), Collections.singleton(Boolean.TRUE));
return true;
} catch( CSException e ) {
if( e.getHttpCode() == 530 ) {
cache.put(getContext(), Collections.singleton(Boolean.FALSE));
return false;
}
throw e;
}
finally {
APITrace.end();
}
}
private @Nonnull AccountData getUserAccountData() throws CloudException, InternalException {
AccountData data = null;
Cache cache = Cache.getInstance(this, "account", AccountData.class, CacheLevel.CLOUD_ACCOUNT, new TimePeriod(1, TimePeriod.DAY));
Iterable cachedValues = cache.get(getContext());
if( cachedValues != null && cachedValues.iterator().hasNext() ) {
data = cachedValues.iterator().next();
}
if( data != null ) {
return data;
}
APITrace.begin(this, "getUserAccountData");
try {
Document doc = new CSMethod(this).get("listAccounts");
String ctxKey = null;
List fields = getContextRequirements().getConfigurableValues();
for( ContextRequirements.Field f : fields ) {
if( f.type.equals(ContextRequirements.FieldType.KEYPAIR) ) {
byte[][] keyPair = ( byte[][] ) getContext().getConfigurationValue(f);
ctxKey = new String(keyPair[0], "utf-8");
}
}
NodeList matches = doc.getElementsByTagName("user");
for( int i = 0; i < matches.getLength(); i++ ) {
boolean userAccountFound = false;
String accountName = null;
String domainId = null;
String accountId = null;
String username = null;
int accountType = 0;
NodeList attributes = matches.item(i).getChildNodes();
for( int j = 0; j < attributes.getLength(); j++ ) {
Node attribute = attributes.item(j);
String name = attribute.getNodeName().toLowerCase();
String value;
if( attribute.hasChildNodes() && attribute.getChildNodes().getLength() > 0 ) {
value = attribute.getFirstChild().getNodeValue();
}
else {
value = null;
}
if( name.equalsIgnoreCase("username") ) {
username = value;
}
else if( name.equalsIgnoreCase("apikey") && ctxKey.equals(value) ) {
userAccountFound = true; // user record matched by the context api key
}
else if( name.equalsIgnoreCase("account") ) {
accountName = value;
}
else if( name.equalsIgnoreCase("domainid") ) {
domainId = value;
}
else if( "accountid".equalsIgnoreCase(name) ) {
accountId = value;
}
else if( "accounttype".equalsIgnoreCase(name) ) {
accountType = Integer.parseInt(value); // 0-user, 1-domain admin, 2-root admin
}
}
if( userAccountFound ) {
data = new AccountData(username, accountId, accountName, domainId, accountType > 0);
break;
}
}
}
catch( UnsupportedEncodingException e ) {
throw new RuntimeException("This cannot happen: " + e.getMessage(), e);
}
finally {
APITrace.end();
}
if( data != null ) {
cache.put(getContext(), Arrays.asList(data));
}
else {
throw new InternalException("Unable to find user account for name " + getContext().getAccountNumber());
}
return data;
}
public class AccountData {
private String accountId;
private String parentAccount;
private String username;
private String domainId;
private boolean admin;
public AccountData(String username, String accountId, String parentAccount, String domainId, boolean admin) {
this.username = username;
this.accountId = accountId;
this.parentAccount = parentAccount;
this.domainId = domainId;
this.admin = admin;
}
public String getAccountId() {
return accountId;
}
public String getParentAccount() {
return parentAccount;
}
public String getDomainId() {
return domainId;
}
public boolean isAdmin() {
return admin;
}
public String getUsername() {
return username;
}
}
/**
* Returns the text from the given node.
*
* @param node the node to extract the value from
* @return the text from the node
*/
static public String getTextValue(Node node) {
if( node.getChildNodes().getLength() == 0 ) {
return null;
}
return node.getFirstChild().getNodeValue();
}
/**
* Returns the boolean value of the given node.
*
* @param node the node to extract the value from
* @return the boolean value of the node
*/
static public boolean getBooleanValue(Node node) {
return Boolean.valueOf(getTextValue(node));
}
public @Nonnull List getZoneHypervisors(String regionId) throws CloudException, InternalException {
ProviderContext ctx = getContext();
if( ctx == null ) {
throw new CloudException("No context was set for this request");
}
String cacheName = "hypervisorCache";
Cache hypervisorCache = Cache.getInstance(this, cacheName, String.class, CacheLevel.REGION_ACCOUNT, new TimePeriod(1, TimePeriod.DAY));
List zoneHypervisors = Iterables.toList(hypervisorCache.get(ctx));
if( zoneHypervisors != null ) {
return zoneHypervisors;
}
try {
Document doc = new CSMethod(this).get(LIST_HYPERVISORS, new Param("zoneid", ctx.getRegionId()));
NodeList nodes = doc.getElementsByTagName("name");
zoneHypervisors = new ArrayList();
for( int i = 0; i < nodes.getLength(); i++ ) {
Node item = nodes.item(i);
zoneHypervisors.add(item.getFirstChild().getNodeValue().trim());
}
hypervisorCache.put(ctx, zoneHypervisors);
return zoneHypervisors;
}
finally {
}
}
public @Nullable void createTags(@Nonnull String[] resIds, @Nonnull String resourceType, Tag... keyValuePairs) throws InternalException, CloudException {
APITrace.begin(this, "Cloud.createTags");
try {
try {
String resourceIds = "";
for( String resId : resIds ) {
resourceIds += resId + ",";
}
if( resourceIds.endsWith(",") ) {
resourceIds = resourceIds.substring(0, resourceIds.length() - 1);
}
List params = new ArrayList();
params.add(new Param("resourceids", resourceIds));
params.add(new Param("resourcetype", resourceType));
for( int i = 0; i < keyValuePairs.length; i++ ) {
// Tag value can't be null or ""
if( keyValuePairs[i].getValue() != null && !keyValuePairs[i].getValue().equals("") ) {
params.add(new Param("tags[" + i + "].key", keyValuePairs[i].getKey()));
params.add(new Param("tags[" + i + "].value", keyValuePairs[i].getValue()));
}
}
Document doc = new CSMethod(this).get(CREATE_TAGS, params);
waitForJob(doc, "Create Tags");
}
catch( CloudException e ) {
logger.error("Error while creating tags for " + resourceType + " - ", e);
}
}
finally {
APITrace.end();
}
}
public @Nullable void updateTags(@Nonnull String[] resIds, String resourceType, Tag... keyValuePairs) throws InternalException, CloudException {
APITrace.begin(this, "Cloud.updateTags");
try {
try {
// List and remove existing tags to update the values
for( String resId : resIds ) {
Tag[] tagList = getTags(resId);
if( tagList.length > 0 ) {
List tags = new ArrayList();
// New tags to update
for( int i = 0; i < keyValuePairs.length; i++ ) {
// Existing tags
for( int k = 0; k < tagList.length; k++ ) {
if( keyValuePairs[i].getKey().equals(tagList[k].getKey()) ) {
if( tagList[k].getValue() != null ) {
tags.add(new Tag(tagList[k].getKey(), tagList[k].getValue()));
}
}
}
}
if( tags.size() > 0 ) {
removeTags(new String[]{ resId }, resourceType, tags.toArray(new Tag[tags.size()]));
}
}
}
// Update tags
createTags(resIds, resourceType, keyValuePairs);
}
catch( CloudException e ) {
logger.error("Error while updating tags for " + resourceType + " - ", e);
}
}
finally {
APITrace.end();
}
}
public @Nullable void removeTags(@Nonnull String[] vmIds, String resourceType, Tag... keyValuePairs) throws InternalException, CloudException {
APITrace.begin(this, "Cloud.removeTags");
try {
String resourceIds = "";
try {
for( String vmId : vmIds ) {
resourceIds += vmId + ",";
}
if( resourceIds.endsWith(",") ) {
resourceIds = resourceIds.substring(0, resourceIds.length() - 1);
}
List params = new ArrayList();
params.add(new Param("resourceids", resourceIds));
params.add(new Param("resourcetype", resourceType));
for( int i = 0; i < keyValuePairs.length; i++ ) {
// Tag value can't be null or ""
if( keyValuePairs[i].getValue() != null && !keyValuePairs[i].getValue().equals("") ) {
params.add(new Param("tags[" + i + "].key", keyValuePairs[i].getKey()));
params.add(new Param("tags[" + i + "].value", keyValuePairs[i].getValue()));
}
}
Document doc = new CSMethod(this).get(DELETE_TAGS, params);
waitForJob(doc, "Delete Tags");
}
catch( CloudException e ) {
logger.error("Error while removing tags for " + resourceType + " - ", e);
}
}
finally {
APITrace.end();
}
}
public @Nullable Tag[] getTags(@Nonnull String resourceId) throws InternalException, CloudException {
APITrace.begin(this, "Cloud.listTags");
try {
List tags = new ArrayList();
try {
Document doc = new CSMethod(this).get(LIST_TAGS, new Param("resourceid", resourceId));
NodeList matches = doc.getElementsByTagName("tag");
if( matches.getLength() > 0 ) {
for( int i = 0; i < matches.getLength(); i++ ) {
// Child 0 has the key, child 1 has the value
tags.add(new Tag(matches.item(i).getChildNodes().item(0).getTextContent(), matches.item(i).getChildNodes().item(1).getTextContent()));
}
}
}
catch( CloudException e ) {
logger.error("Error while listing the tags for - " + resourceId + ".", e);
}
return tags.toArray(new Tag[tags.size()]);
}
finally {
APITrace.end();
}
}
}