/** * Copyright (C) 2009-2015 Dell, Inc * See annotations for authorship information * * ==================================================================== * 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.joyent.compute; import org.apache.log4j.Logger; import org.dasein.cloud.*; import org.dasein.cloud.compute.*; import org.dasein.cloud.identity.ServiceAction; import org.dasein.cloud.joyent.JoyentException; import org.dasein.cloud.joyent.JoyentMethod; import org.dasein.cloud.joyent.SmartDataCenter; import org.dasein.cloud.util.APITrace; import org.dasein.cloud.util.Cache; import org.dasein.cloud.util.CacheLevel; import org.dasein.util.CalendarWrapper; import org.dasein.util.uom.storage.Megabyte; import org.dasein.util.uom.storage.Storage; import org.dasein.util.uom.time.Day; import org.dasein.util.uom.time.TimePeriod; import org.dasein.util.uom.time.TimePeriodUnit; import org.json.JSONArray; import org.json.JSONException; import org.json.JSONObject; import javax.annotation.Nonnull; import javax.annotation.Nullable; import java.util.ArrayList; import java.util.Collections; import java.util.HashMap; import java.util.List; import java.util.Map; public class Machine extends AbstractVMSupport<SmartDataCenter> { Logger logger = SmartDataCenter.getLogger(Machine.class, "std"); private SmartDataCenter provider; private transient volatile MachineCapabilities capabilities; Machine(SmartDataCenter sdc) { super(sdc); provider = sdc; } @Override /** * Only resizing of type=smartmachine is supported. */ public VirtualMachine alterVirtualMachineProduct( @Nonnull String virtualMachineId, @Nonnull String productId ) throws InternalException, CloudException { JoyentMethod method = new JoyentMethod(provider); method.doPostString(provider.getEndpoint(), "machines/" + virtualMachineId, "action=resize&package="+productId); return getVirtualMachine(virtualMachineId); } @Override public void start(@Nonnull String vmId) throws InternalException, CloudException { JoyentMethod method = new JoyentMethod(provider); method.doPostString(provider.getEndpoint(), "machines/" + vmId, "action=start"); } static private class MiData { public Architecture architecture; public Platform platform; } static private HashMap<String,MiData> miCache = new HashMap<String,MiData>(); private void discover(@Nonnull VirtualMachine vm) throws InternalException, CloudException { String miId = vm.getProviderMachineImageId(); if( miCache.containsKey(miId) ) { MiData d = miCache.get(miId); vm.setArchitecture(d.architecture); vm.setPlatform(d.platform); } else { if( !provider.getComputeServices().hasImageSupport() ) { vm.setArchitecture(Architecture.I64); vm.setPlatform(Platform.UNKNOWN); return; } MachineImage img = provider.getComputeServices().getImageSupport().getImage(miId); if( img == null ) { vm.setArchitecture(Architecture.I64); vm.setPlatform(Platform.UNKNOWN); } else { if( img.getName().contains("64") ) { vm.setArchitecture(Architecture.I64); } else if( img.getName().contains("32") ) { vm.setArchitecture(Architecture.I32); } else { vm.setArchitecture(Architecture.I64); } vm.setPlatform(Platform.guess(img.getName() + " " + img.getDescription())); MiData d = new MiData(); d.architecture = vm.getArchitecture(); d.platform = vm.getPlatform(); miCache.put(miId, d); } } } static private HashMap<String,VirtualMachineProduct> productCache = new HashMap<String,VirtualMachineProduct>(); @Override public @Nonnull VirtualMachineCapabilities getCapabilities() throws InternalException, CloudException { if( capabilities == null ) { capabilities = new MachineCapabilities(provider); } return capabilities; } @Override public @Nullable VirtualMachineProduct getProduct(@Nonnull String productId) throws InternalException, CloudException { if( productCache.containsKey(productId) ) { return productCache.get(productId); } for( VirtualMachineProduct prd : listProducts(Architecture.I64) ) { if( prd.getProviderProductId().equals(productId) ) { productCache.put(productId, prd); return prd; } } return null; } @Override public @Nullable VirtualMachine getVirtualMachine(@Nonnull String vmId) throws InternalException, CloudException { JoyentMethod method = new JoyentMethod(provider); try { String json = method.doGetJson(provider.getEndpoint(), "machines/" + vmId); if( json == null ) { return null; } return toVirtualMachine(new JSONObject(json)); } catch( JSONException e ) { throw new CloudException(e); } } @Override public boolean isSubscribed() throws CloudException, InternalException { org.dasein.cloud.util.Cache<Boolean> cache = org.dasein.cloud.util.Cache.getInstance(getProvider(), "isSubscribedVirtualMachine", Boolean.class, CacheLevel.REGION_ACCOUNT); final Iterable<Boolean> cachedIsSubscribed = cache.get(getContext()); if (cachedIsSubscribed != null && cachedIsSubscribed.iterator().hasNext()) { final Boolean isSubscribed = cachedIsSubscribed.iterator().next(); if (isSubscribed != null) { return isSubscribed; } } JoyentMethod method = new JoyentMethod(provider); try { method.doGetJson(provider.getEndpoint(), "packages"); } catch (JoyentException e) { if (e.getErrorType().equals(CloudErrorType.AUTHENTICATION)) { cache.put(getContext(), Collections.singleton(false)); return false; } throw e; } cache.put(getContext(), Collections.singleton(true)); return true; } @Override public @Nonnull VirtualMachine launch(@Nonnull VMLaunchOptions withLaunchOptions) throws CloudException, InternalException { JoyentMethod method = new JoyentMethod(provider); Map<String, Object> post = new HashMap<String,Object>(); if( withLaunchOptions.getUserData() != null ) { post.put("metadata.user-script", withLaunchOptions.getUserData()); } if( withLaunchOptions.getHostName() != null ) { String name = validateName(withLaunchOptions.getHostName()); post.put("name", name); } // Joyent will use datacenter default package/dataset if missing if( withLaunchOptions.getStandardProductId() != null ) { post.put("package", withLaunchOptions.getStandardProductId()); } if( withLaunchOptions.getMachineImageId() != null ) { post.put("dataset", withLaunchOptions.getMachineImageId()); } Map<String, Object> meta = withLaunchOptions.getMetaData(); if( meta.size() > 0 ) { for( Map.Entry<String,Object> entry : meta.entrySet() ) { post.put("metadata." + entry.getKey(), String.valueOf(entry.getValue())); } } post.put("metadata.dsnTrueImage", withLaunchOptions.getMachineImageId()); post.put("metadata.dsnTrueProduct", withLaunchOptions.getStandardProductId()); post.put("metadata.dsnDescription", withLaunchOptions.getDescription()); String json = method.doPostString(provider.getEndpoint(), "machines", new JSONObject(post).toString()); if( json == null ) { throw new CloudException("No machine was created"); } try { return toVirtualMachine(new JSONObject(json)); } catch( JSONException e ) { throw new CloudException(e); } } @SuppressWarnings("deprecation") @Override public @Nonnull VirtualMachine launch(@Nonnull String fromMachineImageId, @Nonnull VirtualMachineProduct product, @Nonnull String dataCenterId, @Nonnull String name, @Nonnull String description, @Nullable String withKeypairId, @Nullable String inVlanId, boolean withAnalytics, boolean asSandbox, @Nullable String[] firewallIds, @Nullable Tag... tags) throws InternalException, CloudException { VMLaunchOptions options; if( inVlanId == null ) { options = VMLaunchOptions.getInstance(product.getProviderProductId(), fromMachineImageId, name, description).inDataCenter(dataCenterId); } else { options = VMLaunchOptions.getInstance(product.getProviderProductId(), fromMachineImageId, name, description).inVlan(null, dataCenterId, inVlanId); } if( withKeypairId != null ) { options = options.withBoostrapKey(withKeypairId); } if( tags != null ) { for( Tag t : tags ) { options = options.withMetaData(t.getKey(), t.getValue()); } } if( firewallIds != null ) { options = options.behindFirewalls(firewallIds); } // this is backwards compat for some old enStratus stuff in Joyent // you really should be using the launch(VMLaunchOptions) and you won't be burdened // by this legacy stuff options = options.withUserData("#!/usr/bin/env sh\n" +"\n" +"export PATH=/opt/local/bin:/opt/local/sbin:/usr/bin:/usr/sbin\n" +"\n" +"if [[ $EUID -ne 0 ]] ; then\n" +" echo \"Must be root to install enstratus package\"\n" +" exit 1\n" +"fi\n" +"\n" +"echo \"==> Updating package lists...\"\n" +"out=$(pkgin update)\n" +"if [[ $? -ne 0 ]] ; then\n" +" echo \"error updating package lists\"\n" +" exit 1\n" +"fi\n" +"\n" +"echo \"==> Installing Sun-jdk6...\"\n" +"out=$(yes | pkgin in sun-jdk6 sun-jre6)\n" +"if [[ $? -ne 0 ]] ; then\n" +" echo \"error installing java\"\n" +" exit 1\n" +"fi\n" +"\n" +"echo \"==> Installing enstratus package...\"\n" +"out=$(pkgin -y in enstratus-agent)\n" +"if [[ $? -ne 0 ]] ; then\n" +" echo \"error installing enstratus agent\"\n" +" exit 1\n" +"fi\n"); return launch(options); } @Override public @Nonnull Iterable<String> listFirewalls(@Nonnull String vmId) throws InternalException, CloudException { // TODO return Collections.emptyList(); } @Override public @Nonnull Iterable<VirtualMachineProduct> listProducts(@Nonnull String machineImageId) throws InternalException, CloudException { MachineImage image = getProvider().getComputeServices().getImageSupport().getImage(machineImageId); Iterable<VirtualMachineProduct> allProducts = listProducts(image.getArchitecture()); List<VirtualMachineProduct> products = new ArrayList<VirtualMachineProduct>(); long minRamSize = 0L; String value = image.getProviderMetadata().get("min_ram"); if( value != null ) { minRamSize = Long.parseLong(value); } for( VirtualMachineProduct product : allProducts ) { boolean includeProduct = true; if( product.getRamSize().longValue() < minRamSize ) { includeProduct = false; } else if( product.getRootVolumeSize() != null && product.getRootVolumeSize().longValue() < image.getMinimumDiskSizeGb() ) { includeProduct = false; } if( includeProduct ) { products.add(product); } } return products; } @Override public @Nonnull Iterable<VirtualMachineProduct> listProducts(VirtualMachineProductFilterOptions options, Architecture architecture) throws InternalException, CloudException { Cache<VirtualMachineProduct> cache = Cache.getInstance(getProvider(), "VM.listProducts", VirtualMachineProduct.class, CacheLevel.REGION_ACCOUNT, TimePeriod.valueOf(1, "day")); final Iterable<VirtualMachineProduct> cachedProducts = cache.get(getContext()); if( cachedProducts != null && cachedProducts.iterator().hasNext() ) { return cachedProducts; } JoyentMethod method = new JoyentMethod(provider); String json = method.doGetJson(provider.getEndpoint(), "packages"); if( json == null ) { return Collections.emptyList(); } try { ArrayList<VirtualMachineProduct> products = new ArrayList<VirtualMachineProduct>(); JSONArray list = new JSONArray(json); for( int i=0; i<list.length(); i++ ) { JSONObject ob = list.getJSONObject(i); VirtualMachineProduct prd = new VirtualMachineProduct(); if( ob.has("name") ) { prd.setName(ob.getString("name")); } if( ob.has("memory") ) { prd.setRamSize(new Storage<Megabyte>(ob.getInt("memory"), Storage.MEGABYTE)); } if( ob.has("disk") ) { prd.setRootVolumeSize(new Storage<Megabyte>(ob.getInt("disk"), Storage.MEGABYTE)); } if( ob.has("vcpus") ) { prd.setCpuCount(ob.getInt("vcpus")); } // SmartOS products are returned with 0 vCPUs as this metric doesn't apply // to them according to Joyent. In SmartOS you get some burstable capacity. // We will set them to 1 CPU anyway, as zero CPU is no CPU. if( prd.getCpuCount() == 0 ) { prd.setCpuCount(1); } if( ob.has("description") ) { prd.setDescription(ob.getString("description")); } else { prd.setDescription(prd.getName()); } prd.setProviderProductId(ob.getString("id")); if( options != null) { if( options.matches(prd) ) products.add(prd); } else { products.add(prd); } } cache.put(getContext(), products); return products; } catch( JSONException e ) { throw new CloudException(e); } } @Override public @Nonnull Iterable<ResourceStatus> listVirtualMachineStatus() throws InternalException, CloudException { JoyentMethod method = new JoyentMethod(provider); try { JSONArray machines = new JSONArray(method.doGetJson(provider.getEndpoint(), "machines")); ArrayList<ResourceStatus> vms = new ArrayList<ResourceStatus>(); for( int i=0; i<machines.length(); i++ ) { ResourceStatus vm = toStatus(machines.getJSONObject(i)); if( vm != null ) { vms.add(vm); } } return vms; } catch( JSONException e ) { throw new CloudException(e); } } @Override public @Nonnull Iterable<VirtualMachine> listVirtualMachines() throws InternalException, CloudException { JoyentMethod method = new JoyentMethod(provider); try { JSONArray machines = new JSONArray(method.doGetJson(provider.getEndpoint(), "machines")); ArrayList<VirtualMachine> vms = new ArrayList<VirtualMachine>(); for( int i=0; i<machines.length(); i++ ) { VirtualMachine vm = toVirtualMachine(machines.getJSONObject(i)); if( vm != null ) { vms.add(vm); } } return vms; } catch( JSONException e ) { throw new CloudException(e); } } @Override public @Nonnull String[] mapServiceAction(@Nonnull ServiceAction action) { return new String[0]; } @Override public void stop(@Nonnull String vmId, boolean force) throws InternalException, CloudException { JoyentMethod method = new JoyentMethod(provider); method.doPostString(provider.getEndpoint(), "machines/" + vmId, "action=stop"); } @Override public void reboot(@Nonnull String vmId) throws CloudException, InternalException { JoyentMethod method = new JoyentMethod(provider); method.doPostString(provider.getEndpoint(), "machines/" + vmId, "action=reboot"); } @Override public void terminate(@Nonnull String vmId, @Nullable String explanation) throws InternalException, CloudException { long timeout = System.currentTimeMillis() + (CalendarWrapper.MINUTE * 20); JoyentMethod method = new JoyentMethod(provider); VirtualMachine vm = getVirtualMachine(vmId); if( vm == null ) { throw new CloudException("No such server: " + vmId); } VmState currentState = vm.getCurrentState(); while( VmState.PENDING.equals(currentState) && System.currentTimeMillis() < timeout ) { try { Thread.sleep(10000); } catch( InterruptedException e ) { /* ignore */ } vm = getVirtualMachine(vmId); if( vm == null ) { return; } currentState = vm.getCurrentState(); } if( VmState.RUNNING.equals(currentState) ) { method.doPostString(provider.getEndpoint(), "machines/" + vmId, "action=stop"); } while( !VmState.STOPPED.equals(currentState) && !VmState.TERMINATED.equals(currentState) && System.currentTimeMillis() < timeout ) { try { Thread.sleep(10000); } catch( InterruptedException e ) { /* ignore */ } vm = getVirtualMachine(vmId); if( vm == null ) { return; } currentState = vm.getCurrentState(); } method.doDelete(provider.getEndpoint(), "machines/" + vmId); if( timeout < CalendarWrapper.MINUTE * 5 ) { timeout = System.currentTimeMillis() + (CalendarWrapper.MINUTE * 5); } while( System.currentTimeMillis() < timeout ) { if( vm == null || VmState.TERMINATED.equals(vm.getCurrentState()) ) { return; } try { Thread.sleep(10000L); } catch( InterruptedException ignore ) { } vm = getVirtualMachine(vmId); } logger.warn("System timed out waiting for VM termination"); } @Override public @Nullable String getPassword( @Nonnull String vmId ) throws InternalException, CloudException { final String[] adminUsernames = {"root", "administrator", "admin"}; JoyentMethod method = new JoyentMethod(provider); try { JSONObject ob = new JSONObject(method.doGetJson(provider.getEndpoint(), "machines/"+vmId+"/metadata?credentials=true")); if( ob.has("credentials") ) { JSONObject credentials = ob.getJSONObject("credentials"); for( String username : adminUsernames ) { if( ob.has(username) ) { return ob.getString(username); } } } } catch( JSONException e ) { throw new InternalException("Unable to parse the response", e); } return null; } @Override public @Nullable String getUserData( @Nonnull String vmId ) throws InternalException, CloudException { JoyentMethod method = new JoyentMethod(provider); try { JSONObject ob = new JSONObject(method.doGetJson(provider.getEndpoint(), "machines/"+vmId+"/metadata")); if( ob.has("user-script") ) { return ob.getString("user-script"); } } catch( JSONException e ) { throw new InternalException("Unable to parse the response", e); } return null; } private VirtualMachine toVirtualMachine(JSONObject ob) throws CloudException, InternalException { if( ob == null ) { return null; } try { VirtualMachine vm = new VirtualMachine(); vm.setClonable(false); vm.setImagable(false); vm.setLastPauseTimestamp(-1L); vm.setPersistent(true); vm.setProviderDataCenterId(provider.getContext().getRegionId() + "a"); vm.setProviderOwnerId(provider.getContext().getAccountNumber()); vm.setProviderRegionId(provider.getContext().getRegionId()); vm.setTerminationTimestamp(-1L); if(ob.has("id") ) { vm.setProviderVirtualMachineId(ob.getString("id")); } if( ob.has("name") ) { vm.setName(ob.getString("name")); } if( ob.has("package") ) { vm.setProductId(ob.getString("package")); } if( ob.has("ips") ) { JSONArray ips = ob.getJSONArray("ips"); ArrayList<String> pubIp = new ArrayList<String>(); ArrayList<String> privIp = new ArrayList<String>(); for( int i=0; i<ips.length(); i++ ) { String addr = ips.getString(i); boolean pub = false; if( !addr.startsWith("10.") && !addr.startsWith("192.168.") ) { if( addr.startsWith("172.") ) { String[] nums = addr.split("\\."); if( nums.length != 4 ) { pub = true; } else { try { int x = Integer.parseInt(nums[1]); if( x < 16 || x > 31 ) { pub = true; } } catch( NumberFormatException ignore ) { // ignore } } } else { pub = true; } } if( pub ) { pubIp.add(addr); } else { privIp.add(addr); } } if( !pubIp.isEmpty() ) { vm.setPublicIpAddresses(pubIp.toArray(new String[pubIp.size()])); } if( !privIp.isEmpty() ) { vm.setPrivateIpAddresses(privIp.toArray(new String[privIp.size()])); } } if( ob.has("metadata") ) { JSONObject md = ob.getJSONObject("metadata"); JSONArray names = md.names(); if( names != null ) { for( int i=0; i<names.length(); i++ ) { String name = names.getString(i); if( name.equals("dsnDescription") ) { vm.setDescription(md.getString(name)); } else if( name.equals("dsnTrueImage") ) { vm.setProviderMachineImageId(md.getString(name)); } // else if( name.equals("dsnTrueProduct") ) { // vm.setProductId(md.getString(name)); // } else { vm.addTag(name, md.getString(name)); } } } } if( vm.getProviderMachineImageId() == null && ob.has("dataset") ) { vm.setProviderMachineImageId(getImageIdFromUrn(ob.getString("dataset"))); } if( ob.has("created") ) { vm.setCreationTimestamp(provider.parseTimestamp(ob.getString("created"))); } vm.setPausable(false); // can't ever pause/resume joyent vms vm.setRebootable(false); if( ob.has("state") ) { vm.setCurrentState(toState(ob.getString("state"))); if( VmState.RUNNING.equals(vm.getCurrentState()) ) { vm.setRebootable(true); } else if( VmState.STOPPED.equals(vm.getCurrentState())) { vm.setImagable(true); } } vm.setLastBootTimestamp(vm.getCreationTimestamp()); if( vm.getName() == null ) { vm.setName(vm.getProviderVirtualMachineId()); } if( vm.getDescription() == null ) { vm.setDescription(vm.getName()); } discover(vm); boolean isVMSmartOs = (vm.getPlatform().equals(Platform.SMARTOS)); if( vm.getProductId() == null ) { VirtualMachineProduct d = null; int disk, ram; disk = ob.getInt("disk"); ram = ob.getInt("memory"); for( VirtualMachineProduct prd : listProducts(vm.getArchitecture()) ) { d = prd; boolean isProductSmartOs = prd.getName().contains("smartos"); if( prd.getRootVolumeSize().convertTo(Storage.MEGABYTE).intValue() == disk && prd.getRamSize().intValue() == ram ) { if (isVMSmartOs && !isProductSmartOs){ continue; } if (!isVMSmartOs && isProductSmartOs){ continue; } vm.setProductId(prd.getProviderProductId()); break; } } // if( vm.getProductId() == null && d != null ) { // vm.setProductId(d.getProviderProductId()); // } } return vm; } catch( JSONException e ) { throw new CloudException(e); } } private @Nonnull VmState toState(@Nonnull String s) { if( s.equalsIgnoreCase("running") ) { return VmState.RUNNING; } else if( s.equalsIgnoreCase("provisioning") ) { return VmState.PENDING; } else if( s.equalsIgnoreCase("stopping") ) { return VmState.STOPPING; } else if( s.equalsIgnoreCase("stopped") ) { return VmState.STOPPED; } else if( s.equalsIgnoreCase("deleted") ) { return VmState.TERMINATED; } else { logger.warn("DEBUG: Unknown Joyent VM state: " + s); return VmState.PENDING; } } private @Nullable ResourceStatus toStatus(@Nullable JSONObject ob) throws CloudException, InternalException { if( ob == null ) { return null; } try { VmState state = VmState.PENDING; String vmId = null; if(ob.has("id") ) { vmId = ob.getString("id"); } if( ob.has("state") ) { state = toState(ob.getString("state")); } if( vmId == null ) { return null; } return new ResourceStatus(vmId, state); } catch( JSONException e ) { throw new CloudException(e); } } static private HashMap<String,String> urnMapping = new HashMap<String,String>(); private String getImageIdFromUrn(String urn) throws CloudException, InternalException { if( logger.isTraceEnabled() ) { logger.trace("ENTER: " + Machine.class.getName() + ".getImageIdFromUrn(" + urn + ")"); } try { if( urnMapping.containsKey(urn) ) { return urnMapping.get(urn); } MachineImage img = provider.getComputeServices().getImageSupport().getImage(urn); if( img != null ) { String id = img.getProviderMachineImageId(); if( id != null ) { urnMapping.put(urn, id); return id; } } return urn; } finally { if( logger.isTraceEnabled() ) { logger.trace("EXIT: " + Machine.class.getName() + ".getImageIdFromUrn()"); } } } /* TODO: fix @see org.dasein.cloud.joyent.compute.MachineCapabilities#getVirtualMachineNamingConstraints */ private String validateName(String originalName) { StringBuilder name = new StringBuilder(); for( int i=0; i<originalName.length(); i++ ) { char c = originalName.charAt(i); if( (c >= 'a' && c <= 'z') || (c >= 'A' && c <= 'Z') ) { name.append(c); } else if( ((c >= '0' && c <= '9') || c == '-' || c == '_' || c == ' ') && name.length() > 0 ) { if( c == ' ' ) { c = '-'; } name.append(c); } } if( name.length() < 1 ) { return "unnamed-" + System.currentTimeMillis(); } if ( name.charAt(name.length()-1)=='-' || name.charAt(name.length()-1)=='_') { // check for trailing - or _ name.deleteCharAt(name.length()-1); } return name.toString(); } @Override public void setTags(@Nonnull String vmId, @Nonnull Tag... tags) throws CloudException, InternalException { APITrace.begin(getProvider(), "Server.createTags"); try { try{ JoyentMethod method = new JoyentMethod(provider); method.doDelete(provider.getEndpoint(), "machines/" + vmId + "/tags"); // Delete tags takes time to reflect do { try { Thread.sleep(4000); } catch( InterruptedException e ) { /* ignore */ } } while (!method.doGetJson(provider.getEndpoint(), "machines/" + vmId + "/tags").equals("{}")); } catch(CloudException e){ logger.error("Error while deleting all tags for - " + vmId + ".", e); } updateTags(vmId, tags); } finally { APITrace.end(); } } @Override public void setTags(@Nonnull String[] vmIds, @Nonnull Tag... tags) throws CloudException, InternalException { for (String vmId : vmIds) { setTags(vmId , tags); } } @Override public void updateTags(@Nonnull String vmId, @Nonnull Tag... tags) throws CloudException, InternalException { APITrace.begin(getProvider(), "Server.updateTags"); try { try{ JoyentMethod method = new JoyentMethod(provider); Map<String, Object> post = new HashMap<String,Object>(); for (int i = 0; i < tags.length; i++){ post.put(tags[i].getKey(), tags[i].getValue() == null ? "" : tags[i].getValue()); } method.doPostString(provider.getEndpoint(), "machines/"+ vmId +"/tags", new JSONObject(post).toString()); } catch(CloudException e){ logger.error("Error while creating the tags for - " + vmId + ".", e); } } finally { APITrace.end(); } } @Override public void updateTags(@Nonnull String[] vmIds, @Nonnull Tag... tags) throws CloudException, InternalException { for( String vmId : vmIds ) { updateTags(vmId, tags); } } @Override public void removeTags(@Nonnull String vmId, @Nonnull Tag... tags) throws CloudException, InternalException { APITrace.begin(getProvider(), "Server.removeTags"); try { JoyentMethod method = new JoyentMethod(provider); for (int i = 0; i < tags.length; i++) { try{ method.doDelete(provider.getEndpoint(), "machines/" + vmId + "/tags/" + tags[i].getKey()); // Delete tags takes time to reflect while (method.doGetJson(provider.getEndpoint(), "machines/" + vmId + "/tags/" + tags[i].getKey()) != null) { try { Thread.sleep(4000); } catch( InterruptedException e ) { /* ignore */ } } } catch(CloudException e){ logger.error("Error while deleting the tags for - " + vmId + ".", e); } } } finally { APITrace.end(); } } @Override public void removeTags(@Nonnull String[] vmIds, @Nonnull Tag... tags) throws CloudException, InternalException { for (String vmId : vmIds) { removeTags(vmId , tags); } } }